├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.md │ ├── feature_request.yml │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── auto-release.yml ├── mergify.yml ├── renovate.json └── workflows │ ├── feature-branch-chatops.yml │ ├── feature-branch.yml │ ├── release-branch.yml │ ├── release-published.yml │ └── scheduled.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README.yaml ├── context.tf ├── docs ├── example.png ├── github-repo-scopes.png ├── logo.png ├── targets.md └── terraform.md ├── examples ├── complete │ ├── context.tf │ ├── fixtures.us-west-2.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── with_cognito_authentication │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── with_google_oidc_authentication │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── without_authentication │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── main.tf ├── outputs.tf ├── test ├── .gitignore ├── Makefile ├── Makefile.alpine └── src │ ├── .gitignore │ ├── Makefile │ ├── examples_complete_test.go │ ├── go.mod │ └── go.sum ├── variables.tf └── versions.tf /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Use this file to define individuals or teams that are responsible for code in a repository. 2 | # Read more: 3 | # 4 | # Order is important: the last matching pattern has the highest precedence 5 | 6 | # These owners will be the default owners for everything 7 | * @cloudposse/engineering @cloudposse/contributors 8 | 9 | # Cloud Posse must review any changes to Makefiles 10 | **/Makefile @cloudposse/engineering 11 | **/Makefile.* @cloudposse/engineering 12 | 13 | # Cloud Posse must review any changes to GitHub actions 14 | .github/* @cloudposse/engineering 15 | 16 | # Cloud Posse must review any changes to standard context definition, 17 | # but some changes can be rubber-stamped. 18 | **/*.tf @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 19 | README.yaml @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 20 | README.md @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 21 | docs/*.md @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 22 | 23 | # Cloud Posse Admins must review all changes to CODEOWNERS or the mergify configuration 24 | .github/mergify.yml @cloudposse/admins 25 | .github/CODEOWNERS @cloudposse/admins 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Found a bug? Maybe our [Slack Community](https://slack.cloudposse.com) can help. 11 | 12 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 13 | 14 | ## Describe the Bug 15 | A clear and concise description of what the bug is. 16 | 17 | ## Expected Behavior 18 | A clear and concise description of what you expected to happen. 19 | 20 | ## Steps to Reproduce 21 | Steps to reproduce the behavior: 22 | 1. Go to '...' 23 | 2. Run '....' 24 | 3. Enter '....' 25 | 4. See error 26 | 27 | ## Screenshots 28 | If applicable, add screenshots or logs to help explain your problem. 29 | 30 | ## Environment (please complete the following information): 31 | 32 | Anything that will help us triage the bug will help. Here are some ideas: 33 | - OS: [e.g. Linux, OSX, WSL, etc] 34 | - Version [e.g. 10.15] 35 | 36 | ## Additional Context 37 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | description: Create a report to help us improve 4 | labels: ["bug"] 5 | assignees: [""] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Found a bug? 11 | 12 | Please checkout our [Slack Community](https://slack.cloudposse.com) 13 | or visit our [Slack Archive](https://archive.sweetops.com/). 14 | 15 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 16 | 17 | - type: textarea 18 | id: concise-description 19 | attributes: 20 | label: Describe the Bug 21 | description: A clear and concise description of what the bug is. 22 | placeholder: What is the bug about? 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: expected 28 | attributes: 29 | label: Expected Behavior 30 | description: A clear and concise description of what you expected. 31 | placeholder: What happened? 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: reproduction-steps 37 | attributes: 38 | label: Steps to Reproduce 39 | description: Steps to reproduce the behavior. 40 | placeholder: How do we reproduce it? 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: screenshots 46 | attributes: 47 | label: Screenshots 48 | description: If applicable, add screenshots or logs to help explain. 49 | validations: 50 | required: false 51 | 52 | - type: textarea 53 | id: environment 54 | attributes: 55 | label: Environment 56 | description: Anything that will help us triage the bug. 57 | placeholder: | 58 | - OS: [e.g. Linux, OSX, WSL, etc] 59 | - Version [e.g. 10.15] 60 | - Module version 61 | - Terraform version 62 | validations: 63 | required: false 64 | 65 | - type: textarea 66 | id: additional 67 | attributes: 68 | label: Additional Context 69 | description: | 70 | Add any other context about the problem here. 71 | validations: 72 | required: false 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | 5 | - name: Community Slack Team 6 | url: https://cloudposse.com/slack/ 7 | about: |- 8 | Please ask and answer questions here. 9 | 10 | - name: Office Hours 11 | url: https://cloudposse.com/office-hours/ 12 | about: |- 13 | Join us every Wednesday for FREE Office Hours (lunch & learn). 14 | 15 | - name: DevOps Accelerator Program 16 | url: https://cloudposse.com/accelerate/ 17 | about: |- 18 | Own your infrastructure in record time. We build it. You drive it. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Have a question? Please checkout our [Slack Community](https://slack.cloudposse.com) or visit our [Slack Archive](https://archive.sweetops.com/). 11 | 12 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 13 | 14 | ## Describe the Feature 15 | 16 | A clear and concise description of what the bug is. 17 | 18 | ## Expected Behavior 19 | 20 | A clear and concise description of what you expected to happen. 21 | 22 | ## Use Case 23 | 24 | Is your feature request related to a problem/challenge you are trying to solve? Please provide some additional context of why this feature or capability will be valuable. 25 | 26 | ## Describe Ideal Solution 27 | 28 | A clear and concise description of what you want to happen. If you don't know, that's okay. 29 | 30 | ## Alternatives Considered 31 | 32 | Explain what alternative solutions or features you've considered. 33 | 34 | ## Additional Context 35 | 36 | Add any other context or screenshots about the feature request here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | description: Suggest an idea for this project 4 | labels: ["feature request"] 5 | assignees: [""] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Have a question? 11 | 12 | Please checkout our [Slack Community](https://slack.cloudposse.com) 13 | or visit our [Slack Archive](https://archive.sweetops.com/). 14 | 15 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 16 | 17 | - type: textarea 18 | id: concise-description 19 | attributes: 20 | label: Describe the Feature 21 | description: A clear and concise description of what the feature is. 22 | placeholder: What is the feature about? 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: expected 28 | attributes: 29 | label: Expected Behavior 30 | description: A clear and concise description of what you expected. 31 | placeholder: What happened? 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: use-case 37 | attributes: 38 | label: Use Case 39 | description: | 40 | Is your feature request related to a problem/challenge you are trying 41 | to solve? 42 | 43 | Please provide some additional context of why this feature or 44 | capability will be valuable. 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | id: ideal-solution 50 | attributes: 51 | label: Describe Ideal Solution 52 | description: A clear and concise description of what you want to happen. 53 | validations: 54 | required: true 55 | 56 | - type: textarea 57 | id: alternatives-considered 58 | attributes: 59 | label: Alternatives Considered 60 | description: Explain alternative solutions or features considered. 61 | validations: 62 | required: false 63 | 64 | - type: textarea 65 | id: additional 66 | attributes: 67 | label: Additional Context 68 | description: | 69 | Add any other context about the problem here. 70 | validations: 71 | required: false 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudposse-archives/terraform-aws-ecs-atlantis/c4e95fed76876ab4e7eac391cd5cfed980bfc3de/.github/ISSUE_TEMPLATE/question.md -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## what 2 | 3 | 7 | 8 | ## why 9 | 10 | 15 | 16 | ## references 17 | 18 | 22 | -------------------------------------------------------------------------------- /.github/auto-release.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: '$RESOLVED_VERSION' 3 | version-template: '$MAJOR.$MINOR.$PATCH' 4 | version-resolver: 5 | major: 6 | labels: 7 | - 'major' 8 | minor: 9 | labels: 10 | - 'minor' 11 | - 'enhancement' 12 | patch: 13 | labels: 14 | - 'auto-update' 15 | - 'patch' 16 | - 'fix' 17 | - 'bugfix' 18 | - 'bug' 19 | - 'hotfix' 20 | default: 'minor' 21 | filter-by-commitish: true 22 | 23 | categories: 24 | - title: '🚀 Enhancements' 25 | labels: 26 | - 'enhancement' 27 | - 'patch' 28 | - title: '🐛 Bug Fixes' 29 | labels: 30 | - 'fix' 31 | - 'bugfix' 32 | - 'bug' 33 | - 'hotfix' 34 | - title: '🤖 Automatic Updates' 35 | labels: 36 | - 'auto-update' 37 | 38 | change-template: | 39 |
40 | $TITLE @$AUTHOR (#$NUMBER) 41 | 42 | $BODY 43 |
44 | 45 | template: | 46 | $CHANGES 47 | 48 | replacers: 49 | # Remove irrelevant information from Renovate bot 50 | - search: '/(?<=---\s)\s*^#.*(Renovate configuration|Configuration)(?:.|\n)*?This PR has been generated .*/gm' 51 | replace: '' 52 | # Remove Renovate bot banner image 53 | - search: '/\[!\[[^\]]*Renovate\][^\]]*\](\([^)]*\))?\s*\n+/gm' 54 | replace: '' 55 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | # https://docs.mergify.io/conditions.html 2 | # https://docs.mergify.io/actions.html 3 | pull_request_rules: 4 | - name: "approve automated PRs that have passed checks" 5 | conditions: 6 | - "author~=^(cloudpossebot|renovate\\[bot\\])$" 7 | - "-closed" 8 | - "head~=^(auto-update|renovate)/.*" 9 | - "check-success=test/bats" 10 | - "check-success=test/readme" 11 | - "check-success=test/terratest" 12 | - "check-success=validate-codeowners" 13 | - or: 14 | - "base=master" 15 | - "base=main" 16 | - "base~=^release/v\\d{1,2}$" 17 | 18 | actions: 19 | review: 20 | type: "APPROVE" 21 | bot_account: "cloudposse-mergebot" 22 | message: "We've automatically approved this PR because the checks from the automated Pull Request have passed." 23 | 24 | - name: "merge automated PRs when approved and tests pass" 25 | conditions: 26 | - "author~=^(cloudpossebot|renovate\\[bot\\])$" 27 | - "-closed" 28 | - "head~=^(auto-update|renovate)/.*" 29 | - "check-success=test/bats" 30 | - "check-success=test/readme" 31 | - "check-success=test/terratest" 32 | - "check-success=validate-codeowners" 33 | - "#approved-reviews-by>=1" 34 | - "#changes-requested-reviews-by=0" 35 | - "#commented-reviews-by=0" 36 | - or: 37 | - "base=master" 38 | - "base=main" 39 | - "base~=^release/v\\d{1,2}$" 40 | 41 | actions: 42 | merge: 43 | method: "squash" 44 | 45 | - name: "delete the head branch after merge" 46 | conditions: 47 | - "merged" 48 | actions: 49 | delete_head_branch: {} 50 | 51 | - name: "ask to resolve conflict" 52 | conditions: 53 | - "conflict" 54 | - "-closed" 55 | actions: 56 | comment: 57 | message: "This pull request is now in conflict. Could you fix it @{{author}}? 🙏" 58 | 59 | - name: "remove outdated reviews" 60 | conditions: 61 | - or: 62 | - "base=master" 63 | - "base=main" 64 | - "base~=^release/v\\d{1,2}$" 65 | actions: 66 | dismiss_reviews: 67 | changes_requested: true 68 | approved: true 69 | message: "This Pull Request has been updated, so we're dismissing all reviews." 70 | 71 | - name: "close Pull Requests without files changed" 72 | conditions: 73 | - "#files=0" 74 | actions: 75 | close: 76 | message: "This pull request has been automatically closed by Mergify because there are no longer any changes." 77 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges" 5 | ], 6 | "baseBranches": ["main", "master", "/^release\\/v\\d{1,2}$/"], 7 | "labels": ["auto-update"], 8 | "dependencyDashboardAutoclose": true, 9 | "enabledManagers": ["terraform"], 10 | "terraform": { 11 | "ignorePaths": ["**/context.tf", "examples/**"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/feature-branch-chatops.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: feature-branch-chatops 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | permissions: 8 | pull-requests: write 9 | id-token: write 10 | contents: write 11 | 12 | jobs: 13 | terraform-module: 14 | uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/feature-branch-chatops.yml@main 15 | secrets: 16 | github_access_token: ${{ secrets.REPO_ACCESS_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/feature-branch.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: feature-branch 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - release/** 8 | types: [opened, synchronize, reopened, labeled, unlabeled] 9 | 10 | permissions: 11 | pull-requests: write 12 | id-token: write 13 | contents: write 14 | 15 | jobs: 16 | terraform-module: 17 | uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/feature-branch.yml@main 18 | secrets: 19 | github_access_token: ${{ secrets.REPO_ACCESS_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/release-branch.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release-branch 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/** 8 | paths-ignore: 9 | - '.github/**' 10 | - 'docs/**' 11 | - 'examples/**' 12 | - 'test/**' 13 | 14 | permissions: 15 | contents: write 16 | id-token: write 17 | 18 | jobs: 19 | terraform-module: 20 | uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/release-branch.yml@main 21 | secrets: 22 | github_access_token: ${{ secrets.REPO_ACCESS_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/release-published.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release-published 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | permissions: 9 | contents: write 10 | id-token: write 11 | 12 | jobs: 13 | terraform-module: 14 | uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/release.yml@main 15 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled 3 | on: 4 | workflow_dispatch: { } # Allows manually trigger this workflow 5 | schedule: 6 | - cron: "0 3 * * *" 7 | 8 | permissions: 9 | pull-requests: write 10 | id-token: write 11 | contents: write 12 | 13 | jobs: 14 | scheduled: 15 | uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/scheduled.yml@main 16 | secrets: 17 | github_access_token: ${{ secrets.REPO_ACCESS_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | .terraform.tfstate.lock.info 5 | **/.terraform.lock.hcl 6 | **/test.log 7 | 8 | # Module directory 9 | .terraform/ 10 | .idea 11 | *.iml 12 | 13 | # Build Harness 14 | .build-harness 15 | build-harness/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018-2020 Cloud Posse, LLC 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | # List of targets the `readme` target should call before generating the readme 4 | export README_DEPS ?= docs/targets.md docs/terraform.md 5 | 6 | -include $(shell curl -sSL -o .build-harness "https://cloudposse.tools/build-harness"; echo .build-harness) 7 | 8 | ## Lint terraform code 9 | lint: 10 | $(SELF) terraform/install terraform/get-modules terraform/get-plugins terraform/lint terraform/validate -------------------------------------------------------------------------------- /README.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # This is the canonical configuration for the `README.md` 4 | # Run `make readme` to rebuild the `README.md` 5 | # 6 | 7 | # Name of this project 8 | name: terraform-aws-ecs-atlantis 9 | 10 | # Logo for this project 11 | logo: docs/logo.png 12 | 13 | # License of this project 14 | license: "APACHE2" 15 | 16 | # Canonical GitHub repo 17 | github_repo: cloudposse/terraform-aws-ecs-atlantis 18 | 19 | # Screenshots 20 | screenshots: 21 | - name: "demo" 22 | description: "Example of a Pull Request comment from running `terraform plan` using `atlantis`" 23 | url: "docs/example.png" 24 | 25 | # Badges to display 26 | badges: 27 | - name: "Latest Release" 28 | image: "https://img.shields.io/github/release/cloudposse/terraform-aws-ecs-atlantis.svg" 29 | url: "https://github.com/cloudposse/terraform-aws-ecs-atlantis/releases/latest" 30 | - name: "Slack Community" 31 | image: "https://slack.cloudposse.com/badge.svg" 32 | url: "https://slack.cloudposse.com" 33 | 34 | related: 35 | - name: "terraform-aws-ecs-web-app" 36 | description: "Terraform module that implements a web app on ECS and supports autoscaling, CI/CD, monitoring, ALB integration, and much more" 37 | url: "https://github.com/cloudposse/terraform-aws-ecs-web-app" 38 | - name: "terraform-aws-alb" 39 | description: "Terraform module to provision a standard ALB for HTTP/HTTP traffic" 40 | url: "https://github.com/cloudposse/terraform-aws-alb" 41 | - name: "terraform-aws-alb-ingress" 42 | description: "Terraform module to provision an HTTP style ingress rule based on hostname and path for an ALB" 43 | url: "https://github.com/cloudposse/terraform-aws-alb-ingress" 44 | - name: "terraform-aws-codebuild" 45 | description: "Terraform Module to easily leverage AWS CodeBuild for Continuous Integration" 46 | url: "https://github.com/cloudposse/terraform-aws-codebuild" 47 | - name: "terraform-aws-ecr" 48 | description: "Terraform Module to manage Docker Container Registries on AWS ECR" 49 | url: "https://github.com/cloudposse/terraform-aws-ecr" 50 | - name: "terraform-aws-ecs-alb-service-task" 51 | description: "Terraform module which implements an ECS service which exposes a web service via ALB." 52 | url: "https://github.com/cloudposse/terraform-aws-ecs-alb-service-task" 53 | - name: "terraform-aws-ecs-codepipeline" 54 | description: "Terraform Module for CI/CD with AWS Code Pipeline and Code Build for ECS" 55 | url: "https://github.com/cloudposse/terraform-aws-ecs-codepipeline" 56 | - name: "terraform-aws-ecs-container-definition" 57 | description: "Terraform module to generate well-formed JSON documents that are passed to the aws_ecs_task_definition Terraform resource" 58 | url: "https://github.com/cloudposse/terraform-aws-ecs-container-definition" 59 | - name: "terraform-aws-lb-s3-bucket" 60 | description: "Terraform module to provision an S3 bucket with built in IAM policy to allow AWS Load Balancers to ship access logs." 61 | url: "https://github.com/cloudposse/terraform-aws-lb-s3-bucket" 62 | 63 | 64 | # Short description of this project 65 | description: |- 66 | 67 | A Terraform module for deploying [Atlantis](https://runatlantis.io) to an AWS ECS cluster. 68 | 69 | introduction: |- 70 | 71 | Atlantis enables GitOps workflows so that teams can collaborate on operations using Pull Requests. 72 | 73 | Under the hood, it's a small self-hosted daemon (`#golang`) that listens for Pull Request webhook events from GitHub. 74 | 75 | With Atlantis, engineers can run `terraform plan` and `terraform apply` using "chat ops" type comments on the Pull Request. 76 | 77 | ### Features 78 | 79 | This module provisions the following resources: 80 | 81 | - ECS Atlantis web application, which includes: 82 | - ECR Docker registry 83 | - ALB target group, listener rule and alarms 84 | - ECS container definition (using a default backend) 85 | - ECS task definition and IAM role 86 | - ECS service and IAM role 87 | - ECS task autoscaling 88 | - ECS SNS based alarms 89 | - ECS Codepipeline to build our Atlantis image on GitHub release 90 | - ECS Codedeploy to deploy our ECS Atlantis web app 91 | - SSH key pair for Atlantis to pull private Github repositories, which are written to SSM for reading with [chamber](https://github.com/segmentio/chamber) 92 | - Route53 alias for Atlantis 93 | - GitHub webhook to trigger Atlantis for a given repository 94 | 95 | What this module does not provision: 96 | 97 | - ECS Cluster (BYOC) 98 | - ALB 99 | - ACM certificate 100 | - VPC 101 | - Subnets 102 | 103 | ## Caveats 104 | 105 | - This project assumes that the repo being deployed defines a `Dockerfile` which runs `atlantis`. It might not work with the official version of atlantis. We use [`geodesic`](https://github.com/cloudposse/geodesic) as our docker base image. 106 | - This project defines parameters which are not available in the *official version* of `atlantis`. Our [fork](https://github.com/cloudposse/atlantis) implements the ability to restrict `plan` and `apply` to GitHub teams. 107 | 108 | 109 | ### GitHub Repo Scopes 110 | 111 | This module accepts two GitHub OAuth tokens: 112 | 113 | 1. `github_oauth_token` with permissions to pull private repos. Used by CodePipeline to clone repos before the build, and by the atlantis server to clone repos and comment on Pull Requests. 114 | 115 | The token needs the following OAuth scopes: 116 | 117 | - `repo` 118 | * `repo:status` 119 | * `repo_deployment` 120 | * `public_repo` 121 | * `repo:invite` 122 | 123 | 2. `github_webhooks_token` with permissions to create GitHub webhooks. 124 | Only used by [Terraform GitHub Provider](https://www.terraform.io/docs/providers/github/index.html) when provisioning the module. 125 | 126 | The token needs the following OAuth scopes: 127 | 128 | - `admin:repo_hook` 129 | * `write:repo_hook` 130 | * `read:repo_hook` 131 | 132 | We suggest the following steps when creating the tokens and provisioning the module: 133 | 134 | 1. Create a GitHub bot user 135 | 2. Create the two Personal Access Tokens and add them to the bot 136 | 3. In `github.com///settings/collaboration`, create a Team for the bot and add the bot user to it 137 | 4. Give `Admin` permissions to the Team (select it from the dropdown). We need it temporalily to provision GitHub webhooks on the repo 138 | 5. Provision the module with Terraform. 139 | [Terraform GitHub Provider](https://www.terraform.io/docs/providers/github/index.html) will use the `github_webhooks_token` to create webhooks on the repo 140 | 6. Go to `github.com///settings/hooks` and make sure that two webhooks have been created: one for the CodePipeline with `Releases` events, 141 | the other is for the `atlantis` server with `Issue comments`, `Pull request reviews`, `Pull requests`, `Pull request review comments` and `Pushes` events 142 | 7. **IMPORTANT:** Remove the `Admin` permissions and add `Read` permissions for the bot Team. 143 | The CodePipeline and `atlantis` server will use the `github_oauth_token` to clone repos, which does not require escalated privileges 144 | 145 | **IMPORTANT:** Do not commit the tokens to source control (_e.g._ via `terraform.tvfars`). 146 | 147 | **NOTE:** If the two tokens are not provided (left empty), they will be looked up from SSM Parameter Store. 148 | You can write `atlantis atlantis_gh` and `github_webhooks_token` to SSM Parameter Store before provisioning the module. 149 | For example, by using [chamber](https://github.com/segmentio/chamber): 150 | 151 | ```sh 152 | chamber write atlantis atlantis_gh_token "....." 153 | chamber write atlantis github_webhooks_token "....." 154 | ``` 155 | 156 | # How to use this project 157 | usage: |- 158 | 159 | For a complete example, see [examples/complete](examples/complete). 160 | 161 | For automated tests of the complete example using [bats](https://github.com/bats-core/bats-core) and [Terratest](https://github.com/gruntwork-io/terratest) (which tests and deploys the example on AWS), see [test](test). 162 | 163 | Other examples: 164 | 165 | - [without authentication](examples/without_authentication) - example without authentication 166 | - [with Google OIDC authentication](examples/with_google_oidc_authentication) - example with Google OIDC authentication 167 | - [with Cognito authentication](examples/with_cognito_authentication) - example with Cognito authentication 168 | 169 | 170 | **NOTE:** 171 | 172 | If no `github_oauth_token` is set, the module attempts to look one up from SSM. 173 | 174 | If no `github_webhooks_token` is set, [Terraform GitHub Provider](https://www.terraform.io/docs/providers/github/index.html) attempts to look one up in the `GITHUB_TOKEN` environment variable. 175 | 176 | ```hcl 177 | provider "aws" { 178 | region = var.region 179 | } 180 | 181 | module "label" { 182 | source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.16.0" 183 | namespace = var.namespace 184 | name = var.name 185 | stage = var.stage 186 | delimiter = var.delimiter 187 | attributes = var.attributes 188 | tags = var.tags 189 | } 190 | 191 | module "vpc" { 192 | source = "git::https://github.com/cloudposse/terraform-aws-vpc.git?ref=tags/0.8.1" 193 | namespace = var.namespace 194 | stage = var.stage 195 | name = var.name 196 | delimiter = var.delimiter 197 | attributes = var.attributes 198 | cidr_block = var.vpc_cidr_block 199 | tags = var.tags 200 | } 201 | 202 | module "subnets" { 203 | source = "git::https://github.com/cloudposse/terraform-aws-dynamic-subnets.git?ref=tags/0.16.1" 204 | availability_zones = var.availability_zones 205 | namespace = var.namespace 206 | stage = var.stage 207 | name = var.name 208 | attributes = var.attributes 209 | delimiter = var.delimiter 210 | vpc_id = module.vpc.vpc_id 211 | igw_id = module.vpc.igw_id 212 | cidr_block = module.vpc.vpc_cidr_block 213 | nat_gateway_enabled = true 214 | nat_instance_enabled = false 215 | tags = var.tags 216 | } 217 | 218 | module "alb" { 219 | source = "git::https://github.com/cloudposse/terraform-aws-alb.git?ref=tags/0.7.0" 220 | namespace = var.namespace 221 | stage = var.stage 222 | name = var.name 223 | attributes = var.attributes 224 | delimiter = var.delimiter 225 | vpc_id = module.vpc.vpc_id 226 | security_group_ids = [module.vpc.vpc_default_security_group_id] 227 | subnet_ids = module.subnets.public_subnet_ids 228 | internal = false 229 | http_enabled = true 230 | access_logs_enabled = false 231 | alb_access_logs_s3_bucket_force_destroy = true 232 | access_logs_region = var.region 233 | cross_zone_load_balancing_enabled = true 234 | http2_enabled = true 235 | deletion_protection_enabled = false 236 | tags = var.tags 237 | } 238 | 239 | resource "aws_ecs_cluster" "default" { 240 | name = module.label.id 241 | tags = module.label.tags 242 | } 243 | 244 | resource "aws_sns_topic" "sns_topic" { 245 | name = module.label.id 246 | display_name = "Test terraform-aws-ecs-atlantis" 247 | tags = module.label.tags 248 | } 249 | 250 | module "kms_key" { 251 | source = "git::https://github.com/cloudposse/terraform-aws-kms-key.git?ref=tags/0.3.0" 252 | enabled = var.enabled 253 | namespace = var.namespace 254 | stage = var.stage 255 | name = var.name 256 | attributes = var.attributes 257 | delimiter = var.delimiter 258 | tags = var.tags 259 | description = "Test terraform-aws-ecs-atlantis KMS key" 260 | deletion_window_in_days = 7 261 | enable_key_rotation = false 262 | } 263 | 264 | module "atlantis" { 265 | source = "cloudposse/ecs-atlantis/aws" 266 | # Cloud Posse recommends pinning every module to a specific version 267 | # version = "x.x.x" 268 | enabled = var.enabled 269 | namespace = var.namespace 270 | stage = var.stage 271 | name = var.name 272 | attributes = var.attributes 273 | delimiter = var.delimiter 274 | tags = var.tags 275 | 276 | region = var.region 277 | vpc_id = module.vpc.vpc_id 278 | policy_arn = var.policy_arn 279 | ssh_private_key_name = var.ssh_private_key_name 280 | ssh_public_key_name = var.ssh_public_key_name 281 | kms_key_id = module.kms_key.key_id 282 | 283 | atlantis_gh_user = var.atlantis_gh_user 284 | atlantis_gh_team_whitelist = var.atlantis_gh_team_whitelist 285 | atlantis_gh_webhook_secret = var.atlantis_gh_webhook_secret 286 | atlantis_log_level = var.atlantis_log_level 287 | atlantis_repo_config = var.atlantis_repo_config 288 | atlantis_repo_whitelist = var.atlantis_repo_whitelist 289 | atlantis_port = var.atlantis_port 290 | atlantis_webhook_format = var.atlantis_webhook_format 291 | atlantis_url_format = var.atlantis_url_format 292 | 293 | default_backend_image = var.default_backend_image 294 | healthcheck_path = var.healthcheck_path 295 | short_name = var.short_name 296 | hostname = var.hostname 297 | parent_zone_id = var.parent_zone_id 298 | 299 | // Container 300 | container_cpu = var.container_cpu 301 | container_memory = var.container_memory 302 | 303 | // Authentication 304 | authentication_type = var.authentication_type 305 | alb_ingress_listener_unauthenticated_priority = var.alb_ingress_listener_unauthenticated_priority 306 | alb_ingress_listener_authenticated_priority = var.alb_ingress_listener_authenticated_priority 307 | alb_ingress_unauthenticated_hosts = var.alb_ingress_unauthenticated_hosts 308 | alb_ingress_authenticated_hosts = var.alb_ingress_authenticated_hosts 309 | alb_ingress_unauthenticated_paths = var.alb_ingress_unauthenticated_paths 310 | alb_ingress_authenticated_paths = var.alb_ingress_authenticated_paths 311 | authentication_cognito_user_pool_arn = var.authentication_cognito_user_pool_arn 312 | authentication_cognito_user_pool_client_id = var.authentication_cognito_user_pool_client_id 313 | authentication_cognito_user_pool_domain = var.authentication_cognito_user_pool_domain 314 | authentication_oidc_client_id = var.authentication_oidc_client_id 315 | authentication_oidc_client_secret = var.authentication_oidc_client_secret 316 | authentication_oidc_issuer = var.authentication_oidc_issuer 317 | authentication_oidc_authorization_endpoint = var.authentication_oidc_authorization_endpoint 318 | authentication_oidc_token_endpoint = var.authentication_oidc_token_endpoint 319 | authentication_oidc_user_info_endpoint = var.authentication_oidc_user_info_endpoint 320 | 321 | // ECS 322 | private_subnet_ids = module.subnets.private_subnet_ids 323 | ecs_cluster_arn = aws_ecs_cluster.default.arn 324 | ecs_cluster_name = aws_ecs_cluster.default.name 325 | security_group_ids = var.security_group_ids 326 | desired_count = var.desired_count 327 | launch_type = var.launch_type 328 | 329 | // ALB 330 | alb_zone_id = module.alb.alb_zone_id 331 | alb_arn_suffix = module.alb.alb_arn_suffix 332 | alb_dns_name = module.alb.alb_dns_name 333 | alb_security_group = module.alb.security_group_id 334 | alb_ingress_unauthenticated_listener_arns = [module.alb.http_listener_arn] 335 | alb_ingress_unauthenticated_listener_arns_count = 1 336 | 337 | // CodePipeline 338 | codepipeline_enabled = var.codepipeline_enabled 339 | github_oauth_token = var.github_oauth_token 340 | github_webhooks_token = var.github_webhooks_token 341 | repo_owner = var.repo_owner 342 | repo_name = var.repo_name 343 | branch = var.branch 344 | build_timeout = var.build_timeout 345 | webhook_enabled = var.webhook_enabled 346 | webhook_secret_length = var.webhook_secret_length 347 | webhook_events = var.webhook_events 348 | codepipeline_s3_bucket_force_destroy = var.codepipeline_s3_bucket_force_destroy 349 | 350 | // Autoscaling 351 | autoscaling_enabled = var.autoscaling_enabled 352 | autoscaling_min_capacity = var.autoscaling_min_capacity 353 | autoscaling_max_capacity = var.autoscaling_max_capacity 354 | 355 | // Alarms 356 | alb_target_group_alarms_enabled = var.alb_target_group_alarms_enabled 357 | ecs_alarms_enabled = var.ecs_alarms_enabled 358 | alb_target_group_alarms_alarm_actions = [aws_sns_topic.sns_topic.arn] 359 | alb_target_group_alarms_ok_actions = [aws_sns_topic.sns_topic.arn] 360 | alb_target_group_alarms_insufficient_data_actions = [aws_sns_topic.sns_topic.arn] 361 | ecs_alarms_cpu_utilization_high_alarm_actions = [aws_sns_topic.sns_topic.arn] 362 | ecs_alarms_cpu_utilization_high_ok_actions = [aws_sns_topic.sns_topic.arn] 363 | ecs_alarms_cpu_utilization_low_alarm_actions = [aws_sns_topic.sns_topic.arn] 364 | ecs_alarms_cpu_utilization_low_ok_actions = [aws_sns_topic.sns_topic.arn] 365 | ecs_alarms_memory_utilization_high_alarm_actions = [aws_sns_topic.sns_topic.arn] 366 | ecs_alarms_memory_utilization_high_ok_actions = [aws_sns_topic.sns_topic.arn] 367 | ecs_alarms_memory_utilization_low_alarm_actions = [aws_sns_topic.sns_topic.arn] 368 | ecs_alarms_memory_utilization_low_ok_actions = [aws_sns_topic.sns_topic.arn] 369 | } 370 | ``` 371 | 372 | # Example usage 373 | #examples: |- 374 | # Example goes here... 375 | 376 | # How to get started quickly 377 | #quickstart: |- 378 | # Here's how to get started... 379 | 380 | # Other files to include in this README from the project folder 381 | include: 382 | - "docs/targets.md" 383 | - "docs/terraform.md" 384 | 385 | references: 386 | - name: "atlantis" 387 | description: "Official home of the Atlantis project" 388 | url: "https://runatlantis.io" 389 | 390 | # Contributors to this project 391 | contributors: 392 | - name: "Josh Myers" 393 | github: "joshmyers" 394 | - name: "Erik Osterman" 395 | github: "osterman" 396 | - name: "Andriy Knysh" 397 | github: "aknysh" 398 | - name: "Igor Rodionov" 399 | github: "goruha" 400 | -------------------------------------------------------------------------------- /context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudposse-archives/terraform-aws-ecs-atlantis/c4e95fed76876ab4e7eac391cd5cfed980bfc3de/docs/example.png -------------------------------------------------------------------------------- /docs/github-repo-scopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudposse-archives/terraform-aws-ecs-atlantis/c4e95fed76876ab4e7eac391cd5cfed980bfc3de/docs/github-repo-scopes.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudposse-archives/terraform-aws-ecs-atlantis/c4e95fed76876ab4e7eac391cd5cfed980bfc3de/docs/logo.png -------------------------------------------------------------------------------- /docs/targets.md: -------------------------------------------------------------------------------- 1 | 2 | ## Makefile Targets 3 | ```text 4 | Available targets: 5 | 6 | help Help screen 7 | help/all Display help for all targets 8 | help/short This help short screen 9 | lint Lint terraform code 10 | 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /examples/complete/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /examples/complete/fixtures.us-west-2.tfvars: -------------------------------------------------------------------------------- 1 | enabled = true 2 | 3 | region = "us-west-2" 4 | 5 | availability_zones = ["us-west-2a", "us-west-2b"] 6 | 7 | namespace = "eg" 8 | 9 | stage = "test" 10 | 11 | name = "ecs-atlantis" 12 | 13 | vpc_cidr_block = "172.16.0.0/16" 14 | 15 | container_cpu = 256 16 | 17 | container_memory = 512 18 | 19 | desired_count = 1 20 | 21 | launch_type = "FARGATE" 22 | 23 | authentication_type = "" 24 | 25 | alb_ingress_listener_unauthenticated_priority = 1000 26 | 27 | alb_ingress_unauthenticated_paths = ["/"] 28 | 29 | autoscaling_enabled = true 30 | 31 | autoscaling_min_capacity = 1 32 | 33 | autoscaling_max_capacity = 2 34 | 35 | webhook_enabled = false 36 | 37 | github_oauth_token = "test" 38 | 39 | atlantis_gh_user = "test" 40 | 41 | atlantis_gh_team_whitelist = "dev:plan,ops:*" 42 | 43 | atlantis_repo_whitelist = ["cloudposse/terraform-aws-ecs-atlantis"] 44 | 45 | codepipeline_enabled = true 46 | 47 | codepipeline_s3_bucket_force_destroy = true 48 | 49 | build_timeout = 20 50 | 51 | repo_name = "atlantis" 52 | 53 | repo_owner = "cloudposse" 54 | 55 | branch = "master" 56 | 57 | alb_target_group_alarms_enabled = true 58 | 59 | ecs_alarms_enabled = true 60 | 61 | parent_zone_id = "Z3SO0TKDDQ0RGG" 62 | 63 | short_name = "ecs-atlantis-test" 64 | 65 | default_backend_image = "cloudposse/default-backend:0.1.2" 66 | 67 | chamber_service = "ecs-atlantis-test" 68 | 69 | kms_key_id = "alias/cpco-testing-chamber" 70 | -------------------------------------------------------------------------------- /examples/complete/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | module "vpc" { 6 | source = "cloudposse/vpc/aws" 7 | version = "0.18.1" 8 | cidr_block = var.vpc_cidr_block 9 | 10 | context = module.this.context 11 | } 12 | 13 | module "subnets" { 14 | source = "cloudposse/dynamic-subnets/aws" 15 | version = "0.33.0" 16 | availability_zones = var.availability_zones 17 | vpc_id = module.vpc.vpc_id 18 | igw_id = module.vpc.igw_id 19 | cidr_block = module.vpc.vpc_cidr_block 20 | nat_gateway_enabled = true 21 | nat_instance_enabled = false 22 | 23 | context = module.this.context 24 | } 25 | 26 | module "alb" { 27 | source = "cloudposse/alb/aws" 28 | version = "0.27.0" 29 | vpc_id = module.vpc.vpc_id 30 | security_group_ids = [module.vpc.vpc_default_security_group_id] 31 | subnet_ids = module.subnets.public_subnet_ids 32 | internal = false 33 | http_enabled = true 34 | alb_access_logs_s3_bucket_force_destroy = true 35 | access_logs_enabled = true 36 | cross_zone_load_balancing_enabled = true 37 | http2_enabled = true 38 | deletion_protection_enabled = false 39 | 40 | context = module.this.context 41 | } 42 | 43 | resource "aws_ecs_cluster" "default" { 44 | name = module.this.id 45 | tags = module.this.tags 46 | setting { 47 | name = "containerInsights" 48 | value = "enabled" 49 | } 50 | } 51 | 52 | resource "aws_sns_topic" "sns_topic" { 53 | #bridgecrew:skip=BC_AWS_GENERAL_15:Skipping `Encrypt SNS Topic Data` in example/test modules 54 | name = module.this.id 55 | display_name = "Test terraform-aws-ecs-atlantis" 56 | tags = module.this.tags 57 | } 58 | 59 | module "kms_key" { 60 | source = "cloudposse/kms-key/aws" 61 | version = "0.9.0" 62 | enabled = var.kms_key_id == "" ? false : true 63 | description = "Test terraform-aws-ecs-atlantis KMS key" 64 | deletion_window_in_days = 7 65 | enable_key_rotation = false 66 | 67 | context = module.this.context 68 | } 69 | 70 | module "atlantis_dns_name" { 71 | source = "cloudposse/label/null" 72 | version = "0.22.1" 73 | 74 | name = var.short_name 75 | attributes = module.this.attributes 76 | 77 | # Omission of context = module.this.context is intentional 78 | } 79 | 80 | module "atlantis" { 81 | source = "../.." 82 | 83 | region = var.region 84 | vpc_id = module.vpc.vpc_id 85 | policy_arn = var.policy_arn 86 | ssh_private_key_name = var.ssh_private_key_name 87 | ssh_public_key_name = var.ssh_public_key_name 88 | kms_key_id = var.kms_key_id == "" ? module.kms_key.key_id : var.kms_key_id 89 | chamber_service = var.chamber_service 90 | 91 | atlantis_gh_user = var.atlantis_gh_user 92 | atlantis_gh_team_whitelist = var.atlantis_gh_team_whitelist 93 | atlantis_gh_webhook_secret = var.atlantis_gh_webhook_secret 94 | atlantis_log_level = var.atlantis_log_level 95 | atlantis_repo_config = var.atlantis_repo_config 96 | atlantis_repo_whitelist = var.atlantis_repo_whitelist 97 | atlantis_port = var.atlantis_port 98 | atlantis_webhook_format = var.atlantis_webhook_format 99 | atlantis_url_format = var.atlantis_url_format 100 | 101 | default_backend_image = var.default_backend_image 102 | healthcheck_path = var.healthcheck_path 103 | short_name = module.atlantis_dns_name.id 104 | hostname = var.hostname 105 | parent_zone_id = var.parent_zone_id 106 | 107 | // Container 108 | container_cpu = var.container_cpu 109 | container_memory = var.container_memory 110 | 111 | // Authentication 112 | authentication_type = var.authentication_type 113 | alb_ingress_listener_unauthenticated_priority = var.alb_ingress_listener_unauthenticated_priority 114 | alb_ingress_listener_authenticated_priority = var.alb_ingress_listener_authenticated_priority 115 | alb_ingress_unauthenticated_hosts = var.alb_ingress_unauthenticated_hosts 116 | alb_ingress_authenticated_hosts = var.alb_ingress_authenticated_hosts 117 | alb_ingress_unauthenticated_paths = var.alb_ingress_unauthenticated_paths 118 | alb_ingress_authenticated_paths = var.alb_ingress_authenticated_paths 119 | authentication_cognito_user_pool_arn = var.authentication_cognito_user_pool_arn 120 | authentication_cognito_user_pool_client_id = var.authentication_cognito_user_pool_client_id 121 | authentication_cognito_user_pool_domain = var.authentication_cognito_user_pool_domain 122 | authentication_oidc_client_id = var.authentication_oidc_client_id 123 | authentication_oidc_client_secret = var.authentication_oidc_client_secret 124 | authentication_oidc_issuer = var.authentication_oidc_issuer 125 | authentication_oidc_authorization_endpoint = var.authentication_oidc_authorization_endpoint 126 | authentication_oidc_token_endpoint = var.authentication_oidc_token_endpoint 127 | authentication_oidc_user_info_endpoint = var.authentication_oidc_user_info_endpoint 128 | 129 | // ECS 130 | private_subnet_ids = module.subnets.private_subnet_ids 131 | ecs_cluster_arn = aws_ecs_cluster.default.arn 132 | ecs_cluster_name = aws_ecs_cluster.default.name 133 | security_group_ids = var.security_group_ids 134 | desired_count = var.desired_count 135 | launch_type = var.launch_type 136 | 137 | // ALB 138 | alb_zone_id = module.alb.alb_zone_id 139 | alb_arn_suffix = module.alb.alb_arn_suffix 140 | alb_dns_name = module.alb.alb_dns_name 141 | alb_security_group = module.alb.security_group_id 142 | alb_ingress_unauthenticated_listener_arns = [module.alb.http_listener_arn] 143 | alb_ingress_unauthenticated_listener_arns_count = 1 144 | 145 | // CodePipeline 146 | codepipeline_enabled = var.codepipeline_enabled 147 | github_oauth_token = var.github_oauth_token 148 | github_webhooks_token = var.github_webhooks_token 149 | repo_owner = var.repo_owner 150 | repo_name = var.repo_name 151 | branch = var.branch 152 | build_timeout = var.build_timeout 153 | webhook_enabled = var.webhook_enabled 154 | webhook_secret_length = var.webhook_secret_length 155 | webhook_events = var.webhook_events 156 | codepipeline_s3_bucket_force_destroy = var.codepipeline_s3_bucket_force_destroy 157 | 158 | // Autoscaling 159 | autoscaling_enabled = var.autoscaling_enabled 160 | autoscaling_min_capacity = var.autoscaling_min_capacity 161 | autoscaling_max_capacity = var.autoscaling_max_capacity 162 | 163 | // Alarms 164 | alb_target_group_alarms_enabled = var.alb_target_group_alarms_enabled 165 | ecs_alarms_enabled = var.ecs_alarms_enabled 166 | alb_target_group_alarms_alarm_actions = [aws_sns_topic.sns_topic.arn] 167 | alb_target_group_alarms_ok_actions = [aws_sns_topic.sns_topic.arn] 168 | alb_target_group_alarms_insufficient_data_actions = [aws_sns_topic.sns_topic.arn] 169 | ecs_alarms_cpu_utilization_high_alarm_actions = [aws_sns_topic.sns_topic.arn] 170 | ecs_alarms_cpu_utilization_high_ok_actions = [aws_sns_topic.sns_topic.arn] 171 | ecs_alarms_cpu_utilization_low_alarm_actions = [aws_sns_topic.sns_topic.arn] 172 | ecs_alarms_cpu_utilization_low_ok_actions = [aws_sns_topic.sns_topic.arn] 173 | ecs_alarms_memory_utilization_high_alarm_actions = [aws_sns_topic.sns_topic.arn] 174 | ecs_alarms_memory_utilization_high_ok_actions = [aws_sns_topic.sns_topic.arn] 175 | ecs_alarms_memory_utilization_low_alarm_actions = [aws_sns_topic.sns_topic.arn] 176 | ecs_alarms_memory_utilization_low_ok_actions = [aws_sns_topic.sns_topic.arn] 177 | 178 | context = module.this.context 179 | } 180 | -------------------------------------------------------------------------------- /examples/complete/outputs.tf: -------------------------------------------------------------------------------- 1 | output "public_subnet_cidrs" { 2 | value = module.subnets.public_subnet_cidrs 3 | description = "Public subnet CIDRs" 4 | } 5 | 6 | output "private_subnet_cidrs" { 7 | value = module.subnets.private_subnet_cidrs 8 | description = "Private subnet CIDRs" 9 | } 10 | 11 | output "vpc_cidr" { 12 | value = module.vpc.vpc_cidr_block 13 | description = "VPC ID" 14 | } 15 | 16 | output "atlantis_ssh_public_key" { 17 | description = "Atlantis SSH Public Key" 18 | value = module.atlantis.atlantis_ssh_public_key 19 | } 20 | 21 | output "atlantis_url" { 22 | description = "The URL endpoint for the atlantis server" 23 | value = module.atlantis.atlantis_url 24 | } 25 | 26 | output "atlantis_webhook_url" { 27 | description = "atlantis webhook URL" 28 | value = module.atlantis.atlantis_webhook_url 29 | } 30 | 31 | output "alb_name" { 32 | description = "The ARN suffix of the ALB" 33 | value = module.alb.alb_name 34 | } 35 | 36 | output "alb_arn" { 37 | description = "The ARN of the ALB" 38 | value = module.alb.alb_arn 39 | } 40 | 41 | output "alb_arn_suffix" { 42 | description = "The ARN suffix of the ALB" 43 | value = module.alb.alb_arn_suffix 44 | } 45 | 46 | output "alb_dns_name" { 47 | description = "DNS name of ALB" 48 | value = module.alb.alb_dns_name 49 | } 50 | 51 | output "alb_zone_id" { 52 | description = "The ID of the zone which ALB is provisioned" 53 | value = module.alb.alb_zone_id 54 | } 55 | 56 | output "alb_security_group_id" { 57 | description = "The security group ID of the ALB" 58 | value = module.alb.security_group_id 59 | } 60 | 61 | output "alb_default_target_group_arn" { 62 | description = "The default target group ARN" 63 | value = module.alb.default_target_group_arn 64 | } 65 | 66 | output "alb_http_listener_arn" { 67 | description = "The ARN of the HTTP listener" 68 | value = module.alb.http_listener_arn 69 | } 70 | 71 | output "alb_listener_arns" { 72 | description = "A list of all the listener ARNs" 73 | value = module.alb.listener_arns 74 | } 75 | 76 | output "alb_access_logs_bucket_id" { 77 | description = "The S3 bucket ID for access logs" 78 | value = module.alb.access_logs_bucket_id 79 | } 80 | 81 | output "ecr_registry_id" { 82 | value = module.atlantis.ecr_registry_id 83 | description = "Registry ID" 84 | } 85 | 86 | output "ecr_repository_url" { 87 | value = module.atlantis.ecr_repository_url 88 | description = "Repository URL" 89 | } 90 | 91 | output "ecr_repository_name" { 92 | value = module.atlantis.ecr_repository_name 93 | description = "Repository name" 94 | } 95 | 96 | output "alb_ingress_target_group_name" { 97 | description = "ALB Target Group name" 98 | value = module.atlantis.alb_ingress_target_group_name 99 | } 100 | 101 | output "alb_ingress_target_group_arn" { 102 | description = "ALB Target Group ARN" 103 | value = module.atlantis.alb_ingress_target_group_arn 104 | } 105 | 106 | output "alb_ingress_target_group_arn_suffix" { 107 | description = "ALB Target Group ARN suffix" 108 | value = module.atlantis.alb_ingress_target_group_arn_suffix 109 | } 110 | 111 | output "container_definition_json" { 112 | description = "JSON encoded list of container definitions for use with other terraform resources such as aws_ecs_task_definition" 113 | value = module.atlantis.container_definition_json 114 | } 115 | 116 | output "container_definition_json_map" { 117 | description = "JSON encoded container definitions for use with other terraform resources such as aws_ecs_task_definition" 118 | value = module.atlantis.container_definition_json_map 119 | } 120 | 121 | output "ecs_exec_role_policy_id" { 122 | description = "The ECS service role policy ID, in the form of `role_name:role_policy_name`" 123 | value = module.atlantis.ecs_exec_role_policy_id 124 | } 125 | 126 | output "ecs_exec_role_policy_name" { 127 | description = "ECS service role name" 128 | value = module.atlantis.ecs_exec_role_policy_name 129 | } 130 | 131 | output "ecs_service_name" { 132 | description = "ECS Service name" 133 | value = module.atlantis.ecs_service_name 134 | } 135 | 136 | output "ecs_service_role_arn" { 137 | description = "ECS Service role ARN" 138 | value = module.atlantis.ecs_service_role_arn 139 | } 140 | 141 | output "ecs_task_exec_role_name" { 142 | description = "ECS Task role name" 143 | value = module.atlantis.ecs_task_exec_role_name 144 | } 145 | 146 | output "ecs_task_exec_role_arn" { 147 | description = "ECS Task exec role ARN" 148 | value = module.atlantis.ecs_task_exec_role_arn 149 | } 150 | 151 | output "ecs_task_role_name" { 152 | description = "ECS Task role name" 153 | value = module.atlantis.ecs_task_role_name 154 | } 155 | 156 | output "ecs_task_role_arn" { 157 | description = "ECS Task role ARN" 158 | value = module.atlantis.ecs_task_role_arn 159 | } 160 | 161 | output "ecs_task_role_id" { 162 | description = "ECS Task role id" 163 | value = module.atlantis.ecs_task_role_id 164 | } 165 | 166 | output "ecs_service_security_group_id" { 167 | description = "Security Group ID of the ECS task" 168 | value = module.atlantis.ecs_service_security_group_id 169 | } 170 | 171 | output "ecs_task_definition_family" { 172 | description = "ECS task definition family" 173 | value = module.atlantis.ecs_task_definition_family 174 | } 175 | 176 | output "ecs_task_definition_revision" { 177 | description = "ECS task definition revision" 178 | value = module.atlantis.ecs_task_definition_revision 179 | } 180 | 181 | output "codebuild_project_name" { 182 | description = "CodeBuild project name" 183 | value = module.atlantis.codebuild_project_name 184 | } 185 | 186 | output "codebuild_project_id" { 187 | description = "CodeBuild project ID" 188 | value = module.atlantis.codebuild_project_id 189 | } 190 | 191 | output "codebuild_role_id" { 192 | description = "CodeBuild IAM Role ID" 193 | value = module.atlantis.codebuild_role_id 194 | } 195 | 196 | output "codebuild_role_arn" { 197 | description = "CodeBuild IAM Role ARN" 198 | value = module.atlantis.codebuild_role_arn 199 | } 200 | 201 | output "codebuild_cache_bucket_name" { 202 | description = "CodeBuild cache S3 bucket name" 203 | value = module.atlantis.codebuild_cache_bucket_name 204 | } 205 | 206 | output "codebuild_cache_bucket_arn" { 207 | description = "CodeBuild cache S3 bucket ARN" 208 | value = module.atlantis.codebuild_cache_bucket_arn 209 | } 210 | 211 | output "codebuild_badge_url" { 212 | description = "The URL of the build badge when badge_enabled is enabled" 213 | value = module.atlantis.codebuild_badge_url 214 | } 215 | 216 | output "codepipeline_id" { 217 | description = "CodePipeline ID" 218 | value = module.atlantis.codepipeline_id 219 | } 220 | 221 | output "codepipeline_arn" { 222 | description = "CodePipeline ARN" 223 | value = module.atlantis.codepipeline_arn 224 | } 225 | 226 | output "codepipeline_webhook_id" { 227 | description = "The CodePipeline webhook's ID" 228 | value = module.atlantis.codepipeline_webhook_id 229 | } 230 | 231 | output "codepipeline_webhook_url" { 232 | description = "The CodePipeline webhook's URL. POST events to this endpoint to trigger the target" 233 | value = module.atlantis.codepipeline_webhook_url 234 | sensitive = true 235 | } 236 | 237 | output "ecs_cloudwatch_autoscaling_scale_up_policy_arn" { 238 | description = "ARN of the scale up policy" 239 | value = module.atlantis.ecs_cloudwatch_autoscaling_scale_up_policy_arn 240 | } 241 | 242 | output "ecs_cloudwatch_autoscaling_scale_down_policy_arn" { 243 | description = "ARN of the scale down policy" 244 | value = module.atlantis.ecs_cloudwatch_autoscaling_scale_down_policy_arn 245 | } 246 | 247 | output "ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_id" { 248 | value = module.atlantis.ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_id 249 | description = "ECS CPU utilization high CloudWatch metric alarm ID" 250 | } 251 | 252 | output "ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_arn" { 253 | value = module.atlantis.ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_arn 254 | description = "ECS CPU utilization high CloudWatch metric alarm ARN" 255 | } 256 | 257 | output "ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_id" { 258 | value = module.atlantis.ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_id 259 | description = "ECS CPU utilization low CloudWatch metric alarm ID" 260 | } 261 | 262 | output "ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_arn" { 263 | value = module.atlantis.ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_arn 264 | description = "ECS CPU utilization low CloudWatch metric alarm ARN" 265 | } 266 | 267 | output "ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_id" { 268 | value = module.atlantis.ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_id 269 | description = "ECS Memory utilization high CloudWatch metric alarm ID" 270 | } 271 | 272 | output "ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_arn" { 273 | value = module.atlantis.ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_arn 274 | description = "ECS Memory utilization high CloudWatch metric alarm ARN" 275 | } 276 | 277 | output "ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_id" { 278 | value = module.atlantis.ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_id 279 | description = "ECS Memory utilization low CloudWatch metric alarm ID" 280 | } 281 | 282 | output "ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_arn" { 283 | value = module.atlantis.ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_arn 284 | description = "ECS Memory utilization low CloudWatch metric alarm ARN" 285 | } 286 | 287 | output "httpcode_target_3xx_count_cloudwatch_metric_alarm_id" { 288 | value = module.atlantis.httpcode_target_3xx_count_cloudwatch_metric_alarm_id 289 | description = "ALB Target Group 3xx count CloudWatch metric alarm ID" 290 | } 291 | 292 | output "httpcode_target_3xx_count_cloudwatch_metric_alarm_arn" { 293 | value = module.atlantis.httpcode_target_3xx_count_cloudwatch_metric_alarm_arn 294 | description = "ALB Target Group 3xx count CloudWatch metric alarm ARN" 295 | } 296 | 297 | output "httpcode_target_4xx_count_cloudwatch_metric_alarm_id" { 298 | value = module.atlantis.httpcode_target_4xx_count_cloudwatch_metric_alarm_id 299 | description = "ALB Target Group 4xx count CloudWatch metric alarm ID" 300 | } 301 | 302 | output "httpcode_target_4xx_count_cloudwatch_metric_alarm_arn" { 303 | value = module.atlantis.httpcode_target_4xx_count_cloudwatch_metric_alarm_arn 304 | description = "ALB Target Group 4xx count CloudWatch metric alarm ARN" 305 | } 306 | 307 | output "httpcode_target_5xx_count_cloudwatch_metric_alarm_id" { 308 | value = module.atlantis.httpcode_target_5xx_count_cloudwatch_metric_alarm_id 309 | description = "ALB Target Group 5xx count CloudWatch metric alarm ID" 310 | } 311 | 312 | output "httpcode_target_5xx_count_cloudwatch_metric_alarm_arn" { 313 | value = module.atlantis.httpcode_target_5xx_count_cloudwatch_metric_alarm_arn 314 | description = "ALB Target Group 5xx count CloudWatch metric alarm ARN" 315 | } 316 | 317 | output "httpcode_elb_5xx_count_cloudwatch_metric_alarm_id" { 318 | value = module.atlantis.httpcode_elb_5xx_count_cloudwatch_metric_alarm_id 319 | description = "ALB 5xx count CloudWatch metric alarm ID" 320 | } 321 | 322 | output "httpcode_elb_5xx_count_cloudwatch_metric_alarm_arn" { 323 | value = module.atlantis.httpcode_elb_5xx_count_cloudwatch_metric_alarm_arn 324 | description = "ALB 5xx count CloudWatch metric alarm ARN" 325 | } 326 | 327 | output "target_response_time_average_cloudwatch_metric_alarm_id" { 328 | value = module.atlantis.target_response_time_average_cloudwatch_metric_alarm_id 329 | description = "ALB Target Group response time average CloudWatch metric alarm ID" 330 | } 331 | 332 | output "target_response_time_average_cloudwatch_metric_alarm_arn" { 333 | value = module.atlantis.target_response_time_average_cloudwatch_metric_alarm_arn 334 | description = "ALB Target Group response time average CloudWatch metric alarm ARN" 335 | } 336 | -------------------------------------------------------------------------------- /examples/complete/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "AWS Region for S3 bucket" 4 | } 5 | 6 | variable "availability_zones" { 7 | type = list(string) 8 | description = "List of availability zones" 9 | } 10 | 11 | variable "vpc_cidr_block" { 12 | type = string 13 | description = "VPC CIDR block" 14 | } 15 | 16 | variable "launch_type" { 17 | type = string 18 | description = "The ECS launch type (valid options: FARGATE or EC2)" 19 | default = "FARGATE" 20 | } 21 | 22 | variable "default_backend_image" { 23 | type = string 24 | default = "cloudposse/default-backend:0.1.2" 25 | description = "ECS default (bootstrap) image" 26 | } 27 | 28 | variable "github_oauth_token" { 29 | type = string 30 | description = "GitHub OAuth token. If not provided the token is looked up from SSM" 31 | } 32 | 33 | variable "github_webhooks_token" { 34 | type = string 35 | description = "GitHub OAuth Token with permissions to create webhooks. If not provided the token is looked up from SSM" 36 | default = "" 37 | } 38 | 39 | variable "codepipeline_enabled" { 40 | type = bool 41 | description = "A boolean to enable/disable AWS Codepipeline and ECR" 42 | default = true 43 | } 44 | 45 | variable "codepipeline_s3_bucket_force_destroy" { 46 | type = bool 47 | description = "A boolean that indicates all objects should be deleted from the CodePipeline artifact store S3 bucket so that the bucket can be destroyed without error" 48 | default = false 49 | } 50 | 51 | variable "chamber_service" { 52 | type = string 53 | default = "atlantis" 54 | description = "SSM parameter service name for use with chamber. This is used in chamber_format where /$chamber_service/$parameter would be the default." 55 | } 56 | 57 | variable "autoscaling_enabled" { 58 | type = bool 59 | description = "A boolean to enable/disable Autoscaling policy for ECS Service" 60 | default = true 61 | } 62 | 63 | variable "build_timeout" { 64 | type = number 65 | default = 20 66 | description = "How long in minutes, from 5 to 480 (8 hours), for AWS CodeBuild to wait until timing out any related build that does not get marked as completed." 67 | } 68 | 69 | variable "branch" { 70 | type = string 71 | default = "master" 72 | description = "Atlantis branch of the GitHub repository, _e.g._ `master`" 73 | } 74 | 75 | variable "repo_name" { 76 | type = string 77 | description = "GitHub repository name of the atlantis to be built and deployed to ECS." 78 | } 79 | 80 | variable "repo_owner" { 81 | type = string 82 | description = "GitHub organization containing the Atlantis repository" 83 | } 84 | 85 | variable "atlantis_repo_config" { 86 | type = string 87 | description = "Path to atlantis server-side repo config file (https://www.runatlantis.io/docs/server-side-repo-config.html)" 88 | default = "atlantis-repo-config.yaml" 89 | } 90 | 91 | variable "atlantis_repo_whitelist" { 92 | type = list(string) 93 | description = "Whitelist of repositories Atlantis will accept webhooks from" 94 | default = [] 95 | } 96 | 97 | variable "healthcheck_path" { 98 | type = string 99 | description = "Healthcheck path" 100 | default = "/healthz" 101 | } 102 | 103 | variable "desired_count" { 104 | type = number 105 | description = "Atlantis desired number of tasks" 106 | default = 1 107 | } 108 | 109 | variable "short_name" { 110 | type = string 111 | description = "Alantis short DNS name (e.g. `atlantis`)" 112 | default = "atlantis" 113 | } 114 | 115 | variable "hostname" { 116 | type = string 117 | description = "Atlantis URL" 118 | default = "" 119 | } 120 | 121 | variable "atlantis_gh_user" { 122 | type = string 123 | description = "Atlantis GitHub user" 124 | } 125 | 126 | variable "atlantis_gh_team_whitelist" { 127 | type = string 128 | description = "Atlantis GitHub team whitelist" 129 | default = "" 130 | } 131 | 132 | variable "atlantis_gh_webhook_secret" { 133 | type = string 134 | description = "Atlantis GitHub webhook secret" 135 | default = "" 136 | } 137 | 138 | variable "atlantis_log_level" { 139 | type = string 140 | description = "Atlantis log level" 141 | default = "info" 142 | } 143 | 144 | variable "atlantis_port" { 145 | type = number 146 | description = "Atlantis container port" 147 | default = 4141 148 | } 149 | 150 | variable "atlantis_webhook_format" { 151 | type = string 152 | default = "https://%s/events" 153 | description = "Template for the Atlantis webhook URL which is populated with the hostname" 154 | } 155 | 156 | variable "atlantis_url_format" { 157 | type = string 158 | default = "https://%s" 159 | description = "Template for the Atlantis URL which is populated with the hostname" 160 | } 161 | 162 | variable "autoscaling_min_capacity" { 163 | type = number 164 | description = "Atlantis minimum tasks to run" 165 | default = 1 166 | } 167 | 168 | variable "autoscaling_max_capacity" { 169 | type = number 170 | description = "Atlantis maximum tasks to run" 171 | default = 1 172 | } 173 | 174 | variable "container_cpu" { 175 | type = number 176 | description = "Atlantis CPUs per task" 177 | default = 256 178 | } 179 | 180 | variable "container_memory" { 181 | type = number 182 | description = "Atlantis memory per task" 183 | default = 512 184 | } 185 | 186 | variable "policy_arn" { 187 | type = string 188 | default = "arn:aws:iam::aws:policy/AdministratorAccess" 189 | description = "Permission to grant to atlantis server" 190 | } 191 | 192 | variable "webhook_enabled" { 193 | type = bool 194 | description = "Set to false to prevent the module from creating any webhook resources" 195 | default = false 196 | } 197 | 198 | variable "webhook_secret_length" { 199 | type = number 200 | default = 32 201 | description = "GitHub webhook secret length" 202 | } 203 | 204 | variable "webhook_events" { 205 | type = list(string) 206 | description = "A list of events which should trigger the webhook." 207 | 208 | default = [ 209 | "issue_comment", 210 | "pull_request", 211 | "pull_request_review", 212 | "pull_request_review_comment", 213 | "push", 214 | ] 215 | } 216 | 217 | variable "ssh_private_key_name" { 218 | type = string 219 | default = "atlantis_ssh_private_key" 220 | description = "Atlantis SSH private key name" 221 | } 222 | 223 | variable "ssh_public_key_name" { 224 | type = string 225 | default = "atlantis_ssh_public_key" 226 | description = "Atlantis SSH public key name" 227 | } 228 | 229 | variable "security_group_ids" { 230 | type = list(string) 231 | default = [] 232 | description = "Additional Security Group IDs to allow into ECS Service." 233 | } 234 | 235 | variable "parent_zone_id" { 236 | type = string 237 | description = "The zone ID where the DNS record for the `short_name` will be written" 238 | default = "" 239 | } 240 | 241 | variable "alb_ingress_listener_unauthenticated_priority" { 242 | type = number 243 | default = 50 244 | description = "The priority for the rules without authentication, between 1 and 50000 (1 being highest priority). Must be different from `alb_ingress_listener_authenticated_priority` since a listener can't have multiple rules with the same priority" 245 | } 246 | 247 | variable "alb_ingress_listener_authenticated_priority" { 248 | type = number 249 | default = 100 250 | description = "The priority for the rules with authentication, between 1 and 50000 (1 being highest priority). Must be different from `alb_ingress_listener_unauthenticated_priority` since a listener can't have multiple rules with the same priority" 251 | } 252 | 253 | variable "alb_ingress_unauthenticated_hosts" { 254 | type = list(string) 255 | default = [] 256 | description = "Unauthenticated hosts to match in Hosts header (a maximum of 1 can be defined)" 257 | } 258 | 259 | variable "alb_ingress_authenticated_hosts" { 260 | type = list(string) 261 | default = [] 262 | description = "Authenticated hosts to match in Hosts header (a maximum of 1 can be defined)" 263 | } 264 | 265 | variable "alb_ingress_unauthenticated_paths" { 266 | type = list(string) 267 | default = ["/events"] 268 | description = "Unauthenticated path pattern to match (a maximum of 1 can be defined)" 269 | } 270 | 271 | variable "alb_ingress_authenticated_paths" { 272 | type = list(string) 273 | default = ["/*"] 274 | description = "Authenticated path pattern to match (a maximum of 1 can be defined)" 275 | } 276 | 277 | variable "authentication_type" { 278 | type = string 279 | default = "" 280 | description = "Authentication type. Supported values are `COGNITO` and `OIDC`" 281 | } 282 | 283 | variable "authentication_cognito_user_pool_arn" { 284 | type = string 285 | description = "Cognito User Pool ARN" 286 | default = "" 287 | } 288 | 289 | variable "authentication_cognito_user_pool_client_id" { 290 | type = string 291 | description = "Cognito User Pool Client ID" 292 | default = "" 293 | } 294 | 295 | variable "authentication_cognito_user_pool_domain" { 296 | type = string 297 | description = "Cognito User Pool Domain. The User Pool Domain should be set to the domain prefix (`xxx`) instead of full domain (https://xxx.auth.us-west-2.amazoncognito.com)" 298 | default = "" 299 | } 300 | 301 | variable "authentication_oidc_client_id" { 302 | type = string 303 | description = "OIDC Client ID" 304 | default = "" 305 | } 306 | 307 | variable "authentication_oidc_client_secret" { 308 | type = string 309 | description = "OIDC Client Secret" 310 | default = "" 311 | } 312 | 313 | variable "authentication_oidc_issuer" { 314 | type = string 315 | description = "OIDC Issuer" 316 | default = "" 317 | } 318 | 319 | variable "authentication_oidc_authorization_endpoint" { 320 | type = string 321 | description = "OIDC Authorization Endpoint" 322 | default = "" 323 | } 324 | 325 | variable "authentication_oidc_token_endpoint" { 326 | type = string 327 | description = "OIDC Token Endpoint" 328 | default = "" 329 | } 330 | 331 | variable "authentication_oidc_user_info_endpoint" { 332 | type = string 333 | description = "OIDC User Info Endpoint" 334 | default = "" 335 | } 336 | 337 | variable "alb_target_group_alarms_enabled" { 338 | type = bool 339 | description = "A boolean to enable/disable CloudWatch Alarms for ALB Target metrics" 340 | default = true 341 | } 342 | 343 | variable "ecs_alarms_enabled" { 344 | type = bool 345 | description = "A boolean to enable/disable CloudWatch Alarms for ECS Service metrics" 346 | default = false 347 | } 348 | 349 | variable "kms_key_id" { 350 | type = string 351 | default = "" 352 | description = "KMS key ID used to encrypt SSM SecureString parameters" 353 | } 354 | -------------------------------------------------------------------------------- /examples/complete/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.26" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.0" 8 | } 9 | template = { 10 | source = "hashicorp/template" 11 | version = ">= 2.0" 12 | } 13 | null = { 14 | source = "hashicorp/null" 15 | version = ">= 2.0" 16 | } 17 | local = { 18 | source = "hashicorp/local" 19 | version = ">= 1.3" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/with_cognito_authentication/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # Modules should access the whole context as `module.this.context` 12 | # to get the input variables with nulls for defaults, 13 | # for example `context = module.this.context`, 14 | # and access individual variables as `module.this.`, 15 | # with final values filled in. 16 | # 17 | # For example, when using defaults, `module.this.context.delimiter` 18 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 19 | # 20 | 21 | module "this" { 22 | source = "cloudposse/label/null" 23 | version = "0.22.1" // requires Terraform >= 0.12.26 24 | 25 | enabled = var.enabled 26 | namespace = var.namespace 27 | environment = var.environment 28 | stage = var.stage 29 | name = var.name 30 | delimiter = var.delimiter 31 | attributes = var.attributes 32 | tags = var.tags 33 | additional_tag_map = var.additional_tag_map 34 | label_order = var.label_order 35 | regex_replace_chars = var.regex_replace_chars 36 | id_length_limit = var.id_length_limit 37 | 38 | context = var.context 39 | } 40 | 41 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 42 | 43 | variable "context" { 44 | type = object({ 45 | enabled = bool 46 | namespace = string 47 | environment = string 48 | stage = string 49 | name = string 50 | delimiter = string 51 | attributes = list(string) 52 | tags = map(string) 53 | additional_tag_map = map(string) 54 | regex_replace_chars = string 55 | label_order = list(string) 56 | id_length_limit = number 57 | }) 58 | default = { 59 | enabled = true 60 | namespace = null 61 | environment = null 62 | stage = null 63 | name = null 64 | delimiter = null 65 | attributes = [] 66 | tags = {} 67 | additional_tag_map = {} 68 | regex_replace_chars = null 69 | label_order = [] 70 | id_length_limit = null 71 | } 72 | description = <<-EOT 73 | Single object for setting entire context at once. 74 | See description of individual variables for details. 75 | Leave string and numeric variables as `null` to use default value. 76 | Individual variable settings (non-null) override settings in context object, 77 | except for attributes, tags, and additional_tag_map, which are merged. 78 | EOT 79 | } 80 | 81 | variable "enabled" { 82 | type = bool 83 | default = null 84 | description = "Set to false to prevent the module from creating any resources" 85 | } 86 | 87 | variable "namespace" { 88 | type = string 89 | default = null 90 | description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'" 91 | } 92 | 93 | variable "environment" { 94 | type = string 95 | default = null 96 | description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'" 97 | } 98 | 99 | variable "stage" { 100 | type = string 101 | default = null 102 | description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'" 103 | } 104 | 105 | variable "name" { 106 | type = string 107 | default = null 108 | description = "Solution name, e.g. 'app' or 'jenkins'" 109 | } 110 | 111 | variable "delimiter" { 112 | type = string 113 | default = null 114 | description = <<-EOT 115 | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`. 116 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 117 | EOT 118 | } 119 | 120 | variable "attributes" { 121 | type = list(string) 122 | default = [] 123 | description = "Additional attributes (e.g. `1`)" 124 | } 125 | 126 | variable "tags" { 127 | type = map(string) 128 | default = {} 129 | description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`" 130 | } 131 | 132 | variable "additional_tag_map" { 133 | type = map(string) 134 | default = {} 135 | description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`." 136 | } 137 | 138 | variable "label_order" { 139 | type = list(string) 140 | default = null 141 | description = <<-EOT 142 | The naming order of the id output and Name tag. 143 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 144 | You can omit any of the 5 elements, but at least one must be present. 145 | EOT 146 | } 147 | 148 | variable "regex_replace_chars" { 149 | type = string 150 | default = null 151 | description = <<-EOT 152 | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`. 153 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 154 | EOT 155 | } 156 | 157 | variable "id_length_limit" { 158 | type = number 159 | default = null 160 | description = <<-EOT 161 | Limit `id` to this many characters. 162 | Set to `0` for unlimited length. 163 | Set to `null` for default, which is `0`. 164 | Does not affect `id_full`. 165 | EOT 166 | } 167 | 168 | #### End of copy of cloudposse/terraform-null-label/variables.tf 169 | -------------------------------------------------------------------------------- /examples/with_cognito_authentication/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | data "aws_availability_zones" "available" { 6 | } 7 | 8 | locals { 9 | availability_zones = slice(data.aws_availability_zones.available.names, 0, 2) 10 | } 11 | 12 | module "vpc" { 13 | source = "cloudposse/vpc/aws" 14 | version = "0.18.1" 15 | cidr_block = "172.16.0.0/16" 16 | 17 | context = module.this.context 18 | } 19 | 20 | module "subnets" { 21 | source = "cloudposse/dynamic-subnets/aws" 22 | version = "0.33.0" 23 | availability_zones = local.availability_zones 24 | vpc_id = module.vpc.vpc_id 25 | igw_id = module.vpc.igw_id 26 | cidr_block = module.vpc.vpc_cidr_block 27 | nat_gateway_enabled = true 28 | nat_instance_enabled = false 29 | 30 | context = module.this.context 31 | } 32 | 33 | module "alb" { 34 | source = "cloudposse/alb/aws" 35 | version = "0.24.0" 36 | vpc_id = module.vpc.vpc_id 37 | security_group_ids = [module.vpc.vpc_default_security_group_id] 38 | subnet_ids = module.subnets.public_subnet_ids 39 | internal = false 40 | http_enabled = true 41 | alb_access_logs_s3_bucket_force_destroy = true 42 | access_logs_enabled = true 43 | cross_zone_load_balancing_enabled = true 44 | http2_enabled = true 45 | deletion_protection_enabled = false 46 | 47 | context = module.this.context 48 | } 49 | 50 | # ECS Cluster (needed even if using FARGATE launch type) 51 | resource "aws_ecs_cluster" "default" { 52 | name = module.this.id 53 | tags = module.this.tags 54 | setting { 55 | name = "containerInsights" 56 | value = "enabled" 57 | } 58 | } 59 | 60 | module "atlantis" { 61 | source = "../.." 62 | enabled = true 63 | 64 | atlantis_gh_team_whitelist = var.atlantis_gh_team_whitelist 65 | atlantis_gh_user = var.atlantis_gh_user 66 | atlantis_repo_whitelist = [var.atlantis_repo_whitelist] 67 | 68 | alb_arn_suffix = module.alb.alb_arn_suffix 69 | alb_dns_name = module.alb.alb_dns_name 70 | alb_name = module.alb.alb_name 71 | alb_zone_id = module.alb.alb_zone_id 72 | alb_security_group = module.alb.security_group_id 73 | 74 | container_cpu = var.atlantis_container_cpu 75 | container_memory = var.atlantis_container_memory 76 | 77 | branch = var.atlantis_branch 78 | parent_zone_id = var.parent_zone_id 79 | ecs_cluster_arn = aws_ecs_cluster.default.arn 80 | ecs_cluster_name = aws_ecs_cluster.default.name 81 | repo_name = var.atlantis_repo_name 82 | repo_owner = var.atlantis_repo_owner 83 | private_subnet_ids = [module.subnets.private_subnet_ids] 84 | security_group_ids = [module.vpc.vpc_default_security_group_id] 85 | vpc_id = module.vpc.vpc_id 86 | 87 | alb_ingress_authenticated_listener_arns = [module.alb.https_listener_arn] 88 | alb_ingress_authenticated_listener_arns_count = 1 89 | 90 | alb_ingress_unauthenticated_listener_arns = [module.alb.listener_arns] 91 | alb_ingress_unauthenticated_listener_arns_count = 2 92 | 93 | # Unauthenticated paths (with higher priority than the authenticated paths) 94 | alb_ingress_unauthenticated_paths = ["/events"] 95 | alb_ingress_listener_unauthenticated_priority = 50 96 | 97 | # Authenticated paths 98 | alb_ingress_authenticated_paths = ["/*"] 99 | alb_ingress_listener_authenticated_priority = 100 100 | 101 | authentication_type = "COGNITO" 102 | authentication_cognito_user_pool_arn = var.cognito_user_pool_arn 103 | authentication_cognito_user_pool_client_id = var.cognito_user_pool_client_id 104 | authentication_cognito_user_pool_domain = var.cognito_user_pool_domain 105 | 106 | context = module.this.context 107 | } 108 | -------------------------------------------------------------------------------- /examples/with_cognito_authentication/outputs.tf: -------------------------------------------------------------------------------- 1 | output "atlantis_url" { 2 | value = module.atlantis.atlantis_url 3 | } 4 | -------------------------------------------------------------------------------- /examples/with_cognito_authentication/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "AWS Region" 4 | default = "us-east-2" 5 | } 6 | 7 | variable "certificate_arn" { 8 | type = string 9 | description = "SSL certificate ARN for ALB HTTPS endpoints" 10 | } 11 | 12 | variable "cognito_user_pool_arn" { 13 | type = string 14 | description = "Cognito User Pool ARN" 15 | } 16 | 17 | variable "cognito_user_pool_client_id" { 18 | type = string 19 | description = "Cognito User Pool Client ID" 20 | } 21 | 22 | variable "cognito_user_pool_domain" { 23 | type = string 24 | description = "Cognito User Pool Domain. The User Pool Domain should be set to the domain prefix (`xxx`) instead of full domain (https://xxx.auth.us-west-2.amazoncognito.com)" 25 | } 26 | 27 | variable "atlantis_gh_team_whitelist" { 28 | type = string 29 | description = "Atlantis GitHub team whitelist" 30 | default = "engineering:plan,devops:*" 31 | } 32 | 33 | variable "atlantis_gh_user" { 34 | type = string 35 | description = "Atlantis GitHub user" 36 | default = "examplebot" 37 | } 38 | 39 | variable "atlantis_repo_whitelist" { 40 | type = list(string) 41 | description = "Whitelist of repositories Atlantis will accept webhooks from" 42 | default = ["github.com/example/*"] 43 | } 44 | 45 | variable "atlantis_repo_name" { 46 | type = string 47 | description = "GitHub repository name of the atlantis to be built and deployed to ECS" 48 | default = "atlantis" 49 | } 50 | 51 | variable "atlantis_repo_owner" { 52 | type = string 53 | description = "GitHub organization containing the Atlantis repository" 54 | default = "cloudposse" 55 | } 56 | 57 | variable "atlantis_branch" { 58 | type = string 59 | description = "Atlantis branch of the GitHub repository, _e.g._ `master`" 60 | default = "master" 61 | } 62 | 63 | variable "atlantis_container_cpu" { 64 | type = number 65 | description = "The vCPU setting to control cpu limits of container. (If FARGATE launch type is used below, this must be a supported vCPU size from the table here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html)" 66 | default = 256 67 | } 68 | 69 | variable "atlantis_container_memory" { 70 | type = number 71 | description = "The amount of RAM to allow container to use in MB. (If FARGATE launch type is used below, this must be a supported Memory size from the table here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html)" 72 | default = 512 73 | } 74 | 75 | variable "parent_zone_id" { 76 | type = string 77 | description = "The zone ID where the DNS record for the atlantis `short_name` will be written" 78 | } 79 | -------------------------------------------------------------------------------- /examples/with_cognito_authentication/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.26" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.0" 8 | } 9 | template = { 10 | source = "hashicorp/template" 11 | version = ">= 2.0" 12 | } 13 | null = { 14 | source = "hashicorp/null" 15 | version = ">= 2.0" 16 | } 17 | local = { 18 | source = "hashicorp/local" 19 | version = ">= 1.3" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/with_google_oidc_authentication/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # Modules should access the whole context as `module.this.context` 12 | # to get the input variables with nulls for defaults, 13 | # for example `context = module.this.context`, 14 | # and access individual variables as `module.this.`, 15 | # with final values filled in. 16 | # 17 | # For example, when using defaults, `module.this.context.delimiter` 18 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 19 | # 20 | 21 | module "this" { 22 | source = "cloudposse/label/null" 23 | version = "0.22.1" // requires Terraform >= 0.12.26 24 | 25 | enabled = var.enabled 26 | namespace = var.namespace 27 | environment = var.environment 28 | stage = var.stage 29 | name = var.name 30 | delimiter = var.delimiter 31 | attributes = var.attributes 32 | tags = var.tags 33 | additional_tag_map = var.additional_tag_map 34 | label_order = var.label_order 35 | regex_replace_chars = var.regex_replace_chars 36 | id_length_limit = var.id_length_limit 37 | 38 | context = var.context 39 | } 40 | 41 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 42 | 43 | variable "context" { 44 | type = object({ 45 | enabled = bool 46 | namespace = string 47 | environment = string 48 | stage = string 49 | name = string 50 | delimiter = string 51 | attributes = list(string) 52 | tags = map(string) 53 | additional_tag_map = map(string) 54 | regex_replace_chars = string 55 | label_order = list(string) 56 | id_length_limit = number 57 | }) 58 | default = { 59 | enabled = true 60 | namespace = null 61 | environment = null 62 | stage = null 63 | name = null 64 | delimiter = null 65 | attributes = [] 66 | tags = {} 67 | additional_tag_map = {} 68 | regex_replace_chars = null 69 | label_order = [] 70 | id_length_limit = null 71 | } 72 | description = <<-EOT 73 | Single object for setting entire context at once. 74 | See description of individual variables for details. 75 | Leave string and numeric variables as `null` to use default value. 76 | Individual variable settings (non-null) override settings in context object, 77 | except for attributes, tags, and additional_tag_map, which are merged. 78 | EOT 79 | } 80 | 81 | variable "enabled" { 82 | type = bool 83 | default = null 84 | description = "Set to false to prevent the module from creating any resources" 85 | } 86 | 87 | variable "namespace" { 88 | type = string 89 | default = null 90 | description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'" 91 | } 92 | 93 | variable "environment" { 94 | type = string 95 | default = null 96 | description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'" 97 | } 98 | 99 | variable "stage" { 100 | type = string 101 | default = null 102 | description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'" 103 | } 104 | 105 | variable "name" { 106 | type = string 107 | default = null 108 | description = "Solution name, e.g. 'app' or 'jenkins'" 109 | } 110 | 111 | variable "delimiter" { 112 | type = string 113 | default = null 114 | description = <<-EOT 115 | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`. 116 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 117 | EOT 118 | } 119 | 120 | variable "attributes" { 121 | type = list(string) 122 | default = [] 123 | description = "Additional attributes (e.g. `1`)" 124 | } 125 | 126 | variable "tags" { 127 | type = map(string) 128 | default = {} 129 | description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`" 130 | } 131 | 132 | variable "additional_tag_map" { 133 | type = map(string) 134 | default = {} 135 | description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`." 136 | } 137 | 138 | variable "label_order" { 139 | type = list(string) 140 | default = null 141 | description = <<-EOT 142 | The naming order of the id output and Name tag. 143 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 144 | You can omit any of the 5 elements, but at least one must be present. 145 | EOT 146 | } 147 | 148 | variable "regex_replace_chars" { 149 | type = string 150 | default = null 151 | description = <<-EOT 152 | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`. 153 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 154 | EOT 155 | } 156 | 157 | variable "id_length_limit" { 158 | type = number 159 | default = null 160 | description = <<-EOT 161 | Limit `id` to this many characters. 162 | Set to `0` for unlimited length. 163 | Set to `null` for default, which is `0`. 164 | Does not affect `id_full`. 165 | EOT 166 | } 167 | 168 | #### End of copy of cloudposse/terraform-null-label/variables.tf 169 | -------------------------------------------------------------------------------- /examples/with_google_oidc_authentication/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | data "aws_availability_zones" "available" {} 6 | 7 | locals { 8 | availability_zones = slice(data.aws_availability_zones.available.names, 0, 2) 9 | } 10 | 11 | module "vpc" { 12 | source = "cloudposse/vpc/aws" 13 | version = "0.18.1" 14 | cidr_block = "172.16.0.0/16" 15 | 16 | context = module.this.context 17 | } 18 | 19 | module "subnets" { 20 | source = "cloudposse/dynamic-subnets/aws" 21 | version = "0.33.0" 22 | availability_zones = local.availability_zones 23 | vpc_id = module.vpc.vpc_id 24 | igw_id = module.vpc.igw_id 25 | cidr_block = module.vpc.vpc_cidr_block 26 | nat_gateway_enabled = true 27 | nat_instance_enabled = false 28 | 29 | context = module.this.context 30 | } 31 | 32 | module "alb" { 33 | source = "cloudposse/alb/aws" 34 | version = "0.24.0" 35 | vpc_id = module.vpc.vpc_id 36 | security_group_ids = [module.vpc.vpc_default_security_group_id] 37 | subnet_ids = module.subnets.public_subnet_ids 38 | internal = false 39 | http_enabled = true 40 | alb_access_logs_s3_bucket_force_destroy = true 41 | access_logs_enabled = true 42 | cross_zone_load_balancing_enabled = true 43 | http2_enabled = true 44 | deletion_protection_enabled = false 45 | 46 | context = module.this.context 47 | } 48 | 49 | # ECS Cluster (needed even if using FARGATE launch type) 50 | resource "aws_ecs_cluster" "default" { 51 | name = module.this.id 52 | tags = module.this.tags 53 | setting { 54 | name = "containerInsights" 55 | value = "enabled" 56 | } 57 | } 58 | 59 | module "atlantis" { 60 | source = "../.." 61 | enabled = true 62 | 63 | atlantis_gh_team_whitelist = var.atlantis_gh_team_whitelist 64 | atlantis_gh_user = var.atlantis_gh_user 65 | atlantis_repo_whitelist = var.atlantis_repo_whitelist 66 | 67 | alb_arn_suffix = module.alb.alb_arn_suffix 68 | alb_dns_name = module.alb.alb_dns_name 69 | alb_name = module.alb.alb_name 70 | alb_zone_id = module.alb.alb_zone_id 71 | alb_security_group = module.alb.security_group_id 72 | 73 | container_cpu = var.atlantis_container_cpu 74 | container_memory = var.atlantis_container_memory 75 | 76 | branch = var.atlantis_branch 77 | parent_zone_id = var.parent_zone_id 78 | ecs_cluster_arn = aws_ecs_cluster.default.arn 79 | ecs_cluster_name = aws_ecs_cluster.default.name 80 | repo_name = var.atlantis_repo_name 81 | repo_owner = var.atlantis_repo_owner 82 | private_subnet_ids = module.subnets.private_subnet_ids 83 | security_group_ids = [module.vpc.vpc_default_security_group_id] 84 | vpc_id = module.vpc.vpc_id 85 | 86 | alb_ingress_authenticated_listener_arns = [module.alb.https_listener_arn] 87 | alb_ingress_authenticated_listener_arns_count = 1 88 | 89 | alb_ingress_unauthenticated_listener_arns = module.alb.listener_arns 90 | alb_ingress_unauthenticated_listener_arns_count = 2 91 | 92 | # Unauthenticated paths (with higher priority than the authenticated paths) 93 | alb_ingress_unauthenticated_paths = ["/events"] 94 | alb_ingress_listener_unauthenticated_priority = 50 95 | 96 | # Authenticated paths 97 | alb_ingress_authenticated_paths = ["/*"] 98 | alb_ingress_listener_authenticated_priority = 100 99 | 100 | authentication_type = "OIDC" 101 | authentication_oidc_client_id = var.google_oidc_client_id 102 | authentication_oidc_client_secret = var.google_oidc_client_secret 103 | authentication_oidc_issuer = "https://accounts.google.com" 104 | authentication_oidc_authorization_endpoint = "https://accounts.google.com/o/oauth2/v2/auth" 105 | authentication_oidc_token_endpoint = "https://oauth2.googleapis.com/token" 106 | authentication_oidc_user_info_endpoint = "https://openidconnect.googleapis.com/v1/userinfo" 107 | 108 | context = module.this.context 109 | } 110 | -------------------------------------------------------------------------------- /examples/with_google_oidc_authentication/outputs.tf: -------------------------------------------------------------------------------- 1 | output "atlantis_url" { 2 | value = module.atlantis.atlantis_url 3 | } 4 | -------------------------------------------------------------------------------- /examples/with_google_oidc_authentication/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "AWS Region" 4 | default = "us-east-2" 5 | } 6 | 7 | variable "certificate_arn" { 8 | type = string 9 | description = "SSL certificate ARN for ALB HTTPS endpoints" 10 | } 11 | 12 | variable "google_oidc_client_id" { 13 | type = string 14 | description = "Google OIDC Client ID. Use this URL to create a Google OAuth 2.0 Client and obtain the Client ID and Client Secret: https://console.developers.google.com/apis/credentials" 15 | } 16 | 17 | variable "google_oidc_client_secret" { 18 | type = string 19 | description = "Google OIDC Client Secret. Use this URL to create a Google OAuth 2.0 Client and obtain the Client ID and Client Secret: https://console.developers.google.com/apis/credentials" 20 | } 21 | 22 | variable "atlantis_gh_team_whitelist" { 23 | type = string 24 | description = "Atlantis GitHub team whitelist" 25 | default = "engineering:plan,devops:*" 26 | } 27 | 28 | variable "atlantis_gh_user" { 29 | type = string 30 | description = "Atlantis GitHub user" 31 | default = "examplebot" 32 | } 33 | 34 | variable "atlantis_repo_whitelist" { 35 | type = list(string) 36 | description = "Whitelist of repositories Atlantis will accept webhooks from" 37 | default = ["github.com/example/*"] 38 | } 39 | 40 | variable "atlantis_repo_name" { 41 | type = string 42 | description = "GitHub repository name of the atlantis to be built and deployed to ECS" 43 | default = "atlantis" 44 | } 45 | 46 | variable "atlantis_repo_owner" { 47 | type = string 48 | description = "GitHub organization containing the Atlantis repository" 49 | default = "cloudposse" 50 | } 51 | 52 | variable "atlantis_branch" { 53 | type = string 54 | description = "Atlantis branch of the GitHub repository, _e.g._ `master`" 55 | default = "master" 56 | } 57 | 58 | variable "atlantis_container_cpu" { 59 | type = number 60 | description = "The vCPU setting to control cpu limits of container. (If FARGATE launch type is used below, this must be a supported vCPU size from the table here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html)" 61 | default = 256 62 | } 63 | 64 | variable "atlantis_container_memory" { 65 | type = number 66 | description = "The amount of RAM to allow container to use in MB. (If FARGATE launch type is used below, this must be a supported Memory size from the table here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html)" 67 | default = 512 68 | } 69 | 70 | variable "parent_zone_id" { 71 | type = string 72 | description = "The zone ID where the DNS record for the atlantis `short_name` will be written" 73 | } 74 | -------------------------------------------------------------------------------- /examples/with_google_oidc_authentication/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.26" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.0" 8 | } 9 | template = { 10 | source = "hashicorp/template" 11 | version = ">= 2.0" 12 | } 13 | null = { 14 | source = "hashicorp/null" 15 | version = ">= 2.0" 16 | } 17 | local = { 18 | source = "hashicorp/local" 19 | version = ">= 1.3" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/without_authentication/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # Modules should access the whole context as `module.this.context` 12 | # to get the input variables with nulls for defaults, 13 | # for example `context = module.this.context`, 14 | # and access individual variables as `module.this.`, 15 | # with final values filled in. 16 | # 17 | # For example, when using defaults, `module.this.context.delimiter` 18 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 19 | # 20 | 21 | module "this" { 22 | source = "cloudposse/label/null" 23 | version = "0.22.1" // requires Terraform >= 0.12.26 24 | 25 | enabled = var.enabled 26 | namespace = var.namespace 27 | environment = var.environment 28 | stage = var.stage 29 | name = var.name 30 | delimiter = var.delimiter 31 | attributes = var.attributes 32 | tags = var.tags 33 | additional_tag_map = var.additional_tag_map 34 | label_order = var.label_order 35 | regex_replace_chars = var.regex_replace_chars 36 | id_length_limit = var.id_length_limit 37 | 38 | context = var.context 39 | } 40 | 41 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 42 | 43 | variable "context" { 44 | type = object({ 45 | enabled = bool 46 | namespace = string 47 | environment = string 48 | stage = string 49 | name = string 50 | delimiter = string 51 | attributes = list(string) 52 | tags = map(string) 53 | additional_tag_map = map(string) 54 | regex_replace_chars = string 55 | label_order = list(string) 56 | id_length_limit = number 57 | }) 58 | default = { 59 | enabled = true 60 | namespace = null 61 | environment = null 62 | stage = null 63 | name = null 64 | delimiter = null 65 | attributes = [] 66 | tags = {} 67 | additional_tag_map = {} 68 | regex_replace_chars = null 69 | label_order = [] 70 | id_length_limit = null 71 | } 72 | description = <<-EOT 73 | Single object for setting entire context at once. 74 | See description of individual variables for details. 75 | Leave string and numeric variables as `null` to use default value. 76 | Individual variable settings (non-null) override settings in context object, 77 | except for attributes, tags, and additional_tag_map, which are merged. 78 | EOT 79 | } 80 | 81 | variable "enabled" { 82 | type = bool 83 | default = null 84 | description = "Set to false to prevent the module from creating any resources" 85 | } 86 | 87 | variable "namespace" { 88 | type = string 89 | default = null 90 | description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'" 91 | } 92 | 93 | variable "environment" { 94 | type = string 95 | default = null 96 | description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'" 97 | } 98 | 99 | variable "stage" { 100 | type = string 101 | default = null 102 | description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'" 103 | } 104 | 105 | variable "name" { 106 | type = string 107 | default = null 108 | description = "Solution name, e.g. 'app' or 'jenkins'" 109 | } 110 | 111 | variable "delimiter" { 112 | type = string 113 | default = null 114 | description = <<-EOT 115 | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`. 116 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 117 | EOT 118 | } 119 | 120 | variable "attributes" { 121 | type = list(string) 122 | default = [] 123 | description = "Additional attributes (e.g. `1`)" 124 | } 125 | 126 | variable "tags" { 127 | type = map(string) 128 | default = {} 129 | description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`" 130 | } 131 | 132 | variable "additional_tag_map" { 133 | type = map(string) 134 | default = {} 135 | description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`." 136 | } 137 | 138 | variable "label_order" { 139 | type = list(string) 140 | default = null 141 | description = <<-EOT 142 | The naming order of the id output and Name tag. 143 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 144 | You can omit any of the 5 elements, but at least one must be present. 145 | EOT 146 | } 147 | 148 | variable "regex_replace_chars" { 149 | type = string 150 | default = null 151 | description = <<-EOT 152 | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`. 153 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 154 | EOT 155 | } 156 | 157 | variable "id_length_limit" { 158 | type = number 159 | default = null 160 | description = <<-EOT 161 | Limit `id` to this many characters. 162 | Set to `0` for unlimited length. 163 | Set to `null` for default, which is `0`. 164 | Does not affect `id_full`. 165 | EOT 166 | } 167 | 168 | #### End of copy of cloudposse/terraform-null-label/variables.tf 169 | -------------------------------------------------------------------------------- /examples/without_authentication/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | data "aws_availability_zones" "available" {} 6 | 7 | locals { 8 | availability_zones = slice(data.aws_availability_zones.available.names, 0, 2) 9 | } 10 | 11 | module "vpc" { 12 | source = "cloudposse/vpc/aws" 13 | version = "0.18.1" 14 | cidr_block = "172.16.0.0/16" 15 | 16 | context = module.this.context 17 | } 18 | 19 | module "subnets" { 20 | source = "cloudposse/dynamic-subnets/aws" 21 | version = "0.33.0" 22 | availability_zones = local.availability_zones 23 | vpc_id = module.vpc.vpc_id 24 | igw_id = module.vpc.igw_id 25 | cidr_block = module.vpc.vpc_cidr_block 26 | nat_gateway_enabled = true 27 | nat_instance_enabled = false 28 | 29 | context = module.this.context 30 | } 31 | 32 | module "alb" { 33 | source = "cloudposse/alb/aws" 34 | version = "0.24.0" 35 | vpc_id = module.vpc.vpc_id 36 | security_group_ids = [module.vpc.vpc_default_security_group_id] 37 | subnet_ids = module.subnets.public_subnet_ids 38 | internal = false 39 | http_enabled = true 40 | alb_access_logs_s3_bucket_force_destroy = true 41 | access_logs_enabled = true 42 | cross_zone_load_balancing_enabled = true 43 | http2_enabled = true 44 | deletion_protection_enabled = false 45 | 46 | context = module.this.context 47 | } 48 | 49 | # ECS Cluster (needed even if using FARGATE launch type) 50 | resource "aws_ecs_cluster" "default" { 51 | name = module.this.id 52 | tags = module.this.tags 53 | setting { 54 | name = "containerInsights" 55 | value = "enabled" 56 | } 57 | } 58 | 59 | module "atlantis" { 60 | source = "../.." 61 | enabled = true 62 | 63 | atlantis_gh_team_whitelist = var.atlantis_gh_team_whitelist 64 | atlantis_gh_user = var.atlantis_gh_user 65 | atlantis_repo_whitelist = var.atlantis_repo_whitelist 66 | 67 | alb_arn_suffix = module.alb.alb_arn_suffix 68 | alb_dns_name = module.alb.alb_dns_name 69 | alb_name = module.alb.alb_name 70 | alb_zone_id = module.alb.alb_zone_id 71 | alb_security_group = module.alb.security_group_id 72 | 73 | container_cpu = var.atlantis_container_cpu 74 | container_memory = var.atlantis_container_memory 75 | 76 | branch = var.atlantis_branch 77 | parent_zone_id = var.parent_zone_id 78 | ecs_cluster_arn = aws_ecs_cluster.default.arn 79 | ecs_cluster_name = aws_ecs_cluster.default.name 80 | repo_name = var.atlantis_repo_name 81 | repo_owner = var.atlantis_repo_owner 82 | private_subnet_ids = module.subnets.private_subnet_ids 83 | security_group_ids = [module.vpc.vpc_default_security_group_id] 84 | vpc_id = module.vpc.vpc_id 85 | 86 | # Without authentication, both HTTP and HTTPS endpoints are supported 87 | alb_ingress_unauthenticated_listener_arns = [module.alb.listener_arns] 88 | alb_ingress_unauthenticated_listener_arns_count = 2 89 | 90 | # All paths are unauthenticated 91 | alb_ingress_unauthenticated_paths = ["/*"] 92 | alb_ingress_listener_unauthenticated_priority = 100 93 | alb_ingress_authenticated_paths = [] 94 | 95 | context = module.this.context 96 | } 97 | -------------------------------------------------------------------------------- /examples/without_authentication/outputs.tf: -------------------------------------------------------------------------------- 1 | output "atlantis_url" { 2 | value = module.atlantis.atlantis_url 3 | } 4 | -------------------------------------------------------------------------------- /examples/without_authentication/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "AWS Region" 4 | default = "us-west-2" 5 | } 6 | 7 | variable "certificate_arn" { 8 | type = string 9 | description = "SSL certificate ARN for ALB HTTPS endpoints" 10 | } 11 | 12 | variable "atlantis_gh_team_whitelist" { 13 | type = string 14 | description = "Atlantis GitHub team whitelist" 15 | default = "engineering:plan,devops:*" 16 | } 17 | 18 | variable "atlantis_gh_user" { 19 | type = string 20 | description = "Atlantis GitHub user" 21 | default = "examplebot" 22 | } 23 | 24 | variable "atlantis_repo_whitelist" { 25 | type = list(string) 26 | description = "Whitelist of repositories Atlantis will accept webhooks from" 27 | default = ["github.com/example/*"] 28 | } 29 | 30 | variable "atlantis_repo_name" { 31 | type = string 32 | description = "GitHub repository name of the atlantis to be built and deployed to ECS" 33 | default = "atlantis" 34 | } 35 | 36 | variable "atlantis_repo_owner" { 37 | type = string 38 | description = "GitHub organization containing the Atlantis repository" 39 | default = "cloudposse" 40 | } 41 | 42 | variable "atlantis_branch" { 43 | type = string 44 | description = "Atlantis branch of the GitHub repository, _e.g._ `master`" 45 | default = "master" 46 | } 47 | 48 | variable "atlantis_container_cpu" { 49 | type = number 50 | description = "The vCPU setting to control cpu limits of container. (If FARGATE launch type is used below, this must be a supported vCPU size from the table here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html)" 51 | default = 256 52 | } 53 | 54 | variable "atlantis_container_memory" { 55 | type = number 56 | description = "The amount of RAM to allow container to use in MB. (If FARGATE launch type is used below, this must be a supported Memory size from the table here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html)" 57 | default = 512 58 | } 59 | 60 | variable "parent_zone_id" { 61 | type = string 62 | description = "The zone ID where the DNS record for the atlantis `short_name` will be written" 63 | } 64 | -------------------------------------------------------------------------------- /examples/without_authentication/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.26" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.0" 8 | } 9 | template = { 10 | source = "hashicorp/template" 11 | version = ">= 2.0" 12 | } 13 | null = { 14 | source = "hashicorp/null" 15 | version = ">= 2.0" 16 | } 17 | local = { 18 | source = "hashicorp/local" 19 | version = ">= 1.3" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # Data 2 | #-------------------------------------------------------------- 3 | data "aws_ssm_parameter" "atlantis_gh_token" { 4 | count = local.enabled && var.github_oauth_token == "" ? 1 : 0 5 | name = local.github_oauth_token_ssm_name 6 | } 7 | 8 | data "aws_ssm_parameter" "github_webhooks_token" { 9 | count = local.enabled && var.github_webhooks_token == "" ? 1 : 0 10 | name = local.github_webhooks_token_ssm_name 11 | } 12 | 13 | data "aws_kms_key" "chamber_kms_key" { 14 | count = local.enabled ? 1 : 0 15 | key_id = local.kms_key_id 16 | } 17 | 18 | # Locals 19 | #-------------------------------------------------------------- 20 | locals { 21 | enabled = module.this.enabled 22 | hostname = var.hostname != "" ? var.hostname : local.default_hostname 23 | atlantis_webhook_url = format(var.atlantis_webhook_format, local.hostname) 24 | atlantis_url = format(var.atlantis_url_format, local.hostname) 25 | atlantis_gh_webhook_secret = var.atlantis_gh_webhook_secret != "" ? var.atlantis_gh_webhook_secret : join("", random_string.atlantis_gh_webhook_secret.*.result) 26 | default_hostname = join("", aws_route53_record.default.*.fqdn) 27 | kms_key_id = var.kms_key_id != "" ? var.kms_key_id : format("alias/%s-%s-chamber", module.this.namespace, module.this.stage) 28 | } 29 | 30 | # GitHub tokens 31 | locals { 32 | github_oauth_token = var.github_oauth_token != "" ? var.github_oauth_token : join("", data.aws_ssm_parameter.atlantis_gh_token.*.value) 33 | github_oauth_token_ssm_name = var.github_oauth_token_ssm_name != "" ? var.github_oauth_token_ssm_name : format(var.chamber_format, var.chamber_service, "atlantis_gh_token") 34 | 35 | github_webhooks_token = var.github_webhooks_token != "" ? var.github_webhooks_token : join("", data.aws_ssm_parameter.github_webhooks_token.*.value) 36 | github_webhooks_token_ssm_name = var.github_webhooks_token_ssm_name != "" ? var.github_webhooks_token_ssm_name : format(var.chamber_format, var.chamber_service, "github_webhooks_token") 37 | } 38 | 39 | # Modules 40 | #-------------------------------------------------------------- 41 | module "ssh_key_pair" { 42 | source = "cloudposse/ssm-tls-ssh-key-pair/aws" 43 | version = "0.10.0" 44 | ssh_private_key_name = var.ssh_private_key_name 45 | ssh_public_key_name = var.ssh_public_key_name 46 | ssm_path_prefix = var.chamber_service 47 | kms_key_id = local.kms_key_id 48 | 49 | context = module.this.context 50 | } 51 | 52 | module "github_webhooks" { 53 | source = "cloudposse/repository-webhooks/github" 54 | version = "0.12.0" 55 | enabled = local.enabled && var.webhook_enabled ? true : false 56 | github_organization = var.repo_owner 57 | github_repositories = [var.repo_name] 58 | github_token = local.github_webhooks_token 59 | webhook_secret = local.atlantis_gh_webhook_secret 60 | webhook_url = local.atlantis_webhook_url 61 | webhook_content_type = "json" 62 | events = var.webhook_events 63 | 64 | context = module.this.context 65 | } 66 | 67 | module "ecs_web_app" { 68 | source = "cloudposse/ecs-web-app/aws" 69 | version = "0.60.0" 70 | 71 | region = var.region 72 | vpc_id = var.vpc_id 73 | launch_type = var.launch_type 74 | 75 | container_environment = [ 76 | { 77 | name = "ATLANTIS_ENABLED" 78 | value = local.enabled 79 | } 80 | ] 81 | 82 | container_image = var.default_backend_image 83 | container_cpu = var.container_cpu 84 | container_memory = var.container_memory 85 | 86 | container_repo_credentials = var.container_repo_credentials 87 | 88 | container_port = var.atlantis_port 89 | 90 | port_mappings = [ 91 | { 92 | containerPort = var.atlantis_port 93 | hostPort = var.atlantis_port 94 | protocol = "tcp" 95 | } 96 | ] 97 | 98 | desired_count = var.desired_count 99 | 100 | autoscaling_enabled = var.autoscaling_enabled 101 | autoscaling_dimension = "cpu" 102 | autoscaling_min_capacity = var.autoscaling_min_capacity 103 | autoscaling_max_capacity = var.autoscaling_max_capacity 104 | autoscaling_scale_up_adjustment = 1 105 | autoscaling_scale_up_cooldown = 60 106 | autoscaling_scale_down_adjustment = -1 107 | autoscaling_scale_down_cooldown = 300 108 | 109 | aws_logs_region = var.region 110 | ecs_cluster_arn = var.ecs_cluster_arn 111 | ecs_cluster_name = var.ecs_cluster_name 112 | ecs_security_group_ids = var.security_group_ids 113 | ecs_private_subnet_ids = var.private_subnet_ids 114 | 115 | alb_ingress_healthcheck_path = var.healthcheck_path 116 | 117 | webhook_enabled = var.webhook_enabled 118 | github_webhook_events = ["release"] 119 | webhook_filter_json_path = "$.action" 120 | webhook_filter_match_equals = "published" 121 | 122 | github_oauth_token = local.github_oauth_token 123 | github_webhooks_token = local.github_webhooks_token 124 | 125 | repo_owner = var.repo_owner 126 | repo_name = var.repo_name 127 | branch = var.branch 128 | build_timeout = var.build_timeout 129 | badge_enabled = false 130 | 131 | codepipeline_enabled = var.codepipeline_enabled 132 | codepipeline_s3_bucket_force_destroy = var.codepipeline_s3_bucket_force_destroy 133 | 134 | ecs_alarms_enabled = var.ecs_alarms_enabled 135 | alb_target_group_alarms_enabled = var.alb_target_group_alarms_enabled 136 | alb_target_group_alarms_3xx_threshold = 25 137 | alb_target_group_alarms_4xx_threshold = 25 138 | alb_target_group_alarms_5xx_threshold = 25 139 | alb_target_group_alarms_response_time_threshold = 0.5 140 | alb_target_group_alarms_period = 300 141 | alb_target_group_alarms_evaluation_periods = 1 142 | alb_arn_suffix = var.alb_arn_suffix 143 | use_alb_security_group = var.use_alb_security_group 144 | alb_security_group = var.alb_security_group 145 | 146 | alb_target_group_alarms_alarm_actions = var.alb_target_group_alarms_alarm_actions 147 | alb_target_group_alarms_ok_actions = var.alb_target_group_alarms_ok_actions 148 | alb_target_group_alarms_insufficient_data_actions = var.alb_target_group_alarms_insufficient_data_actions 149 | 150 | alb_ingress_authenticated_paths = var.alb_ingress_authenticated_paths 151 | alb_ingress_unauthenticated_paths = var.alb_ingress_unauthenticated_paths 152 | alb_ingress_authenticated_hosts = var.alb_ingress_authenticated_hosts 153 | alb_ingress_unauthenticated_hosts = var.alb_ingress_unauthenticated_hosts 154 | 155 | alb_ingress_listener_authenticated_priority = var.alb_ingress_listener_authenticated_priority 156 | alb_ingress_listener_unauthenticated_priority = var.alb_ingress_listener_unauthenticated_priority 157 | 158 | alb_ingress_unauthenticated_listener_arns = var.alb_ingress_unauthenticated_listener_arns 159 | alb_ingress_unauthenticated_listener_arns_count = var.alb_ingress_unauthenticated_listener_arns_count 160 | alb_ingress_authenticated_listener_arns = var.alb_ingress_authenticated_listener_arns 161 | alb_ingress_authenticated_listener_arns_count = var.alb_ingress_authenticated_listener_arns_count 162 | 163 | authentication_type = var.authentication_type 164 | authentication_cognito_user_pool_arn = local.authentication_cognito_user_pool_arn 165 | authentication_cognito_user_pool_client_id = local.authentication_cognito_user_pool_client_id 166 | authentication_cognito_user_pool_domain = local.authentication_cognito_user_pool_domain 167 | authentication_oidc_client_id = local.authentication_oidc_client_id 168 | authentication_oidc_client_secret = local.authentication_oidc_client_secret 169 | authentication_oidc_issuer = var.authentication_oidc_issuer 170 | authentication_oidc_authorization_endpoint = var.authentication_oidc_authorization_endpoint 171 | authentication_oidc_token_endpoint = var.authentication_oidc_token_endpoint 172 | authentication_oidc_user_info_endpoint = var.authentication_oidc_user_info_endpoint 173 | 174 | context = module.this.context 175 | } 176 | 177 | # Resources 178 | #-------------------------------------------------------------- 179 | 180 | resource "aws_route53_record" "default" { 181 | count = local.enabled ? 1 : 0 182 | zone_id = var.parent_zone_id 183 | name = var.short_name 184 | type = "A" 185 | 186 | alias { 187 | name = var.alb_dns_name 188 | zone_id = var.alb_zone_id 189 | evaluate_target_health = false 190 | } 191 | } 192 | 193 | resource "random_string" "atlantis_gh_webhook_secret" { 194 | count = local.enabled ? 1 : 0 195 | length = var.webhook_secret_length 196 | special = true 197 | } 198 | 199 | resource "aws_ssm_parameter" "atlantis_port" { 200 | count = local.enabled ? 1 : 0 201 | description = "Atlantis server port" 202 | name = format(var.chamber_format, var.chamber_service, "atlantis_port") 203 | overwrite = var.overwrite_ssm_parameter 204 | type = "String" 205 | value = var.atlantis_port 206 | tags = module.this.tags 207 | } 208 | 209 | resource "aws_ssm_parameter" "atlantis_atlantis_url" { 210 | count = local.enabled ? 1 : 0 211 | description = "Atlantis URL" 212 | name = format(var.chamber_format, var.chamber_service, "atlantis_atlantis_url") 213 | overwrite = var.overwrite_ssm_parameter 214 | type = "String" 215 | value = local.atlantis_url 216 | tags = module.this.tags 217 | } 218 | 219 | resource "aws_ssm_parameter" "atlantis_gh_user" { 220 | count = local.enabled ? 1 : 0 221 | description = "Atlantis GitHub user" 222 | name = format(var.chamber_format, var.chamber_service, "atlantis_gh_user") 223 | overwrite = var.overwrite_ssm_parameter 224 | type = "String" 225 | value = var.atlantis_gh_user 226 | tags = module.this.tags 227 | } 228 | 229 | resource "aws_ssm_parameter" "atlantis_gh_team_whitelist" { 230 | count = local.enabled ? 1 : 0 231 | description = "Atlantis GitHub team whitelist" 232 | name = format(var.chamber_format, var.chamber_service, "atlantis_gh_team_whitelist") 233 | overwrite = var.overwrite_ssm_parameter 234 | type = "String" 235 | value = var.atlantis_gh_team_whitelist 236 | tags = module.this.tags 237 | } 238 | 239 | resource "aws_ssm_parameter" "atlantis_gh_webhook_secret" { 240 | count = local.enabled ? 1 : 0 241 | description = "Atlantis GitHub webhook secret" 242 | key_id = join("", data.aws_kms_key.chamber_kms_key.*.id) 243 | name = format(var.chamber_format, var.chamber_service, "atlantis_gh_webhook_secret") 244 | overwrite = var.overwrite_ssm_parameter 245 | type = "SecureString" 246 | value = local.atlantis_gh_webhook_secret 247 | tags = module.this.tags 248 | } 249 | 250 | resource "aws_ssm_parameter" "atlantis_iam_role_arn" { 251 | count = local.enabled ? 1 : 0 252 | description = "Atlantis IAM role ARN" 253 | name = format(var.chamber_format, var.chamber_service, "atlantis_iam_role_arn") 254 | overwrite = var.overwrite_ssm_parameter 255 | type = "String" 256 | value = module.ecs_web_app.ecs_task_role_arn 257 | tags = module.this.tags 258 | } 259 | 260 | resource "aws_ssm_parameter" "atlantis_log_level" { 261 | count = local.enabled ? 1 : 0 262 | description = "Atlantis log level" 263 | name = format(var.chamber_format, var.chamber_service, "atlantis_log_level") 264 | overwrite = var.overwrite_ssm_parameter 265 | type = "String" 266 | value = var.atlantis_log_level 267 | tags = module.this.tags 268 | } 269 | 270 | resource "aws_ssm_parameter" "atlantis_repo_config" { 271 | count = local.enabled ? 1 : 0 272 | description = "Path to atlantis config file" 273 | name = format(var.chamber_format, var.chamber_service, "atlantis_repo_config") 274 | overwrite = var.overwrite_ssm_parameter 275 | type = "String" 276 | value = var.atlantis_repo_config 277 | tags = module.this.tags 278 | } 279 | 280 | resource "aws_ssm_parameter" "atlantis_repo_whitelist" { 281 | count = local.enabled ? 1 : 0 282 | description = "Whitelist of repositories Atlantis will accept webhooks from" 283 | name = format(var.chamber_format, var.chamber_service, "atlantis_repo_whitelist") 284 | overwrite = var.overwrite_ssm_parameter 285 | type = "String" 286 | value = join(",", var.atlantis_repo_whitelist) 287 | tags = module.this.tags 288 | } 289 | 290 | resource "aws_ssm_parameter" "atlantis_wake_word" { 291 | count = local.enabled ? 1 : 0 292 | description = "Wake world for Atlantis" 293 | name = format(var.chamber_format, var.chamber_service, "atlantis_wake_word") 294 | overwrite = var.overwrite_ssm_parameter 295 | type = "String" 296 | value = var.atlantis_wake_word 297 | tags = module.this.tags 298 | } 299 | 300 | resource "aws_ssm_parameter" "atlantis_gh_token" { 301 | count = local.enabled && var.github_oauth_token != "" ? 1 : 0 302 | description = "Atlantis GitHub OAuth token" 303 | key_id = join("", data.aws_kms_key.chamber_kms_key.*.id) 304 | name = local.github_oauth_token_ssm_name 305 | overwrite = var.overwrite_ssm_parameter 306 | type = "SecureString" 307 | value = local.github_oauth_token 308 | tags = module.this.tags 309 | } 310 | 311 | resource "aws_ssm_parameter" "github_webhooks_token" { 312 | count = local.enabled && var.github_webhooks_token != "" ? 1 : 0 313 | description = "GitHub OAuth token with permission to create webhooks" 314 | key_id = join("", data.aws_kms_key.chamber_kms_key.*.id) 315 | name = local.github_webhooks_token_ssm_name 316 | overwrite = var.overwrite_ssm_parameter 317 | type = "SecureString" 318 | value = local.github_webhooks_token 319 | tags = module.this.tags 320 | } 321 | 322 | resource "aws_security_group_rule" "egress_http" { 323 | count = local.enabled ? 1 : 0 324 | cidr_blocks = ["0.0.0.0/0"] 325 | from_port = 80 326 | protocol = "tcp" 327 | security_group_id = module.ecs_web_app.ecs_service_security_group_id 328 | to_port = 80 329 | type = "egress" 330 | } 331 | 332 | resource "aws_security_group_rule" "egress_https" { 333 | count = local.enabled ? 1 : 0 334 | cidr_blocks = ["0.0.0.0/0"] 335 | from_port = 443 336 | protocol = "tcp" 337 | security_group_id = module.ecs_web_app.ecs_service_security_group_id 338 | to_port = 443 339 | type = "egress" 340 | } 341 | 342 | resource "aws_security_group_rule" "egress_udp_dns" { 343 | count = local.enabled ? 1 : 0 344 | cidr_blocks = ["0.0.0.0/0"] 345 | from_port = 53 346 | protocol = "udp" 347 | security_group_id = module.ecs_web_app.ecs_service_security_group_id 348 | to_port = 53 349 | type = "egress" 350 | } 351 | 352 | resource "aws_security_group_rule" "egress_tcp_dns" { 353 | count = local.enabled ? 1 : 0 354 | cidr_blocks = ["0.0.0.0/0"] 355 | from_port = 53 356 | protocol = "tcp" 357 | security_group_id = module.ecs_web_app.ecs_service_security_group_id 358 | to_port = 53 359 | type = "egress" 360 | } 361 | 362 | resource "aws_iam_role_policy_attachment" "default" { 363 | count = local.enabled ? 1 : 0 364 | role = module.ecs_web_app.ecs_task_role_name 365 | policy_arn = var.policy_arn 366 | 367 | lifecycle { 368 | create_before_destroy = true 369 | } 370 | } 371 | 372 | locals { 373 | authentication_cognito_user_pool_arn = var.authentication_cognito_user_pool_arn != "" ? var.authentication_cognito_user_pool_arn : join("", data.aws_ssm_parameter.atlantis_cognito_user_pool_arn.*.value) 374 | 375 | authentication_cognito_user_pool_arn_ssm_name = var.authentication_cognito_user_pool_arn_ssm_name != "" ? var.authentication_cognito_user_pool_arn_ssm_name : format( 376 | var.chamber_format, 377 | var.chamber_service, 378 | "atlantis_cognito_user_pool_arn" 379 | ) 380 | 381 | authentication_cognito_user_pool_client_id = var.authentication_cognito_user_pool_client_id != "" ? var.authentication_cognito_user_pool_client_id : join("", data.aws_ssm_parameter.atlantis_cognito_user_pool_client_id.*.value) 382 | 383 | authentication_cognito_user_pool_client_id_ssm_name = var.authentication_cognito_user_pool_client_id_ssm_name != "" ? var.authentication_cognito_user_pool_client_id_ssm_name : format( 384 | var.chamber_format, 385 | var.chamber_service, 386 | "atlantis_cognito_user_pool_client_id" 387 | ) 388 | 389 | authentication_cognito_user_pool_domain = var.authentication_cognito_user_pool_domain != "" ? var.authentication_cognito_user_pool_domain : join("", data.aws_ssm_parameter.atlantis_cognito_user_pool_domain.*.value) 390 | 391 | authentication_cognito_user_pool_domain_ssm_name = var.authentication_cognito_user_pool_domain_ssm_name != "" ? var.authentication_cognito_user_pool_domain_ssm_name : format( 392 | var.chamber_format, 393 | var.chamber_service, 394 | "atlantis_cognito_user_pool_domain" 395 | ) 396 | 397 | authentication_oidc_client_id = var.authentication_oidc_client_id != "" ? var.authentication_oidc_client_id : join("", data.aws_ssm_parameter.atlantis_oidc_client_id.*.value) 398 | 399 | authentication_oidc_client_id_ssm_name = var.authentication_oidc_client_id_ssm_name != "" ? var.authentication_oidc_client_id_ssm_name : format( 400 | var.chamber_format, 401 | var.chamber_service, 402 | "atlantis_oidc_client_id" 403 | ) 404 | 405 | authentication_oidc_client_secret = var.authentication_oidc_client_secret != "" ? var.authentication_oidc_client_secret : join("", data.aws_ssm_parameter.atlantis_oidc_client_secret.*.value) 406 | 407 | authentication_oidc_client_secret_ssm_name = var.authentication_oidc_client_secret_ssm_name != "" ? var.authentication_oidc_client_secret_ssm_name : format( 408 | var.chamber_format, 409 | var.chamber_service, 410 | "atlantis_oidc_client_secret" 411 | ) 412 | } 413 | 414 | data "aws_ssm_parameter" "atlantis_cognito_user_pool_arn" { 415 | count = local.enabled && var.authentication_type == "COGNITO" && var.authentication_cognito_user_pool_arn == "" ? 1 : 0 416 | name = local.authentication_cognito_user_pool_arn_ssm_name 417 | } 418 | 419 | data "aws_ssm_parameter" "atlantis_cognito_user_pool_client_id" { 420 | count = local.enabled && var.authentication_type == "COGNITO" && var.authentication_cognito_user_pool_client_id == "" ? 1 : 0 421 | name = local.authentication_cognito_user_pool_client_id_ssm_name 422 | } 423 | 424 | data "aws_ssm_parameter" "atlantis_cognito_user_pool_domain" { 425 | count = local.enabled && var.authentication_type == "COGNITO" && var.authentication_cognito_user_pool_domain == "" ? 1 : 0 426 | name = local.authentication_cognito_user_pool_domain_ssm_name 427 | } 428 | 429 | data "aws_ssm_parameter" "atlantis_oidc_client_id" { 430 | count = local.enabled && var.authentication_type == "OIDC" && var.authentication_oidc_client_id == "" ? 1 : 0 431 | name = local.authentication_oidc_client_id_ssm_name 432 | } 433 | 434 | data "aws_ssm_parameter" "atlantis_oidc_client_secret" { 435 | count = local.enabled && var.authentication_type == "OIDC" && var.authentication_oidc_client_secret == "" ? 1 : 0 436 | name = local.authentication_oidc_client_secret_ssm_name 437 | } 438 | 439 | resource "aws_ssm_parameter" "atlantis_cognito_user_pool_arn" { 440 | count = local.enabled && var.authentication_type == "COGNITO" && var.authentication_cognito_user_pool_arn != "" ? 1 : 0 441 | overwrite = var.overwrite_ssm_parameter 442 | type = "SecureString" 443 | description = "Atlantis Cognito User Pool ARN" 444 | key_id = local.kms_key_id 445 | name = local.authentication_cognito_user_pool_arn_ssm_name 446 | value = local.authentication_cognito_user_pool_arn 447 | tags = module.this.tags 448 | } 449 | 450 | resource "aws_ssm_parameter" "atlantis_cognito_user_pool_client_id" { 451 | count = local.enabled && var.authentication_type == "COGNITO" && var.authentication_cognito_user_pool_client_id != "" ? 1 : 0 452 | overwrite = var.overwrite_ssm_parameter 453 | type = "SecureString" 454 | description = "Atlantis Cognito User Pool Client ID" 455 | key_id = local.kms_key_id 456 | name = local.authentication_cognito_user_pool_client_id_ssm_name 457 | value = local.authentication_cognito_user_pool_client_id 458 | tags = module.this.tags 459 | } 460 | 461 | resource "aws_ssm_parameter" "atlantis_cognito_user_pool_domain" { 462 | count = local.enabled && var.authentication_type == "COGNITO" && var.authentication_cognito_user_pool_domain != "" ? 1 : 0 463 | overwrite = var.overwrite_ssm_parameter 464 | type = "SecureString" 465 | description = "Atlantis Cognito User Pool Domain" 466 | key_id = local.kms_key_id 467 | name = local.authentication_cognito_user_pool_domain_ssm_name 468 | value = local.authentication_cognito_user_pool_domain 469 | tags = module.this.tags 470 | } 471 | 472 | resource "aws_ssm_parameter" "atlantis_oidc_client_id" { 473 | count = local.enabled && var.authentication_type == "OIDC" && var.authentication_oidc_client_id != "" ? 1 : 0 474 | overwrite = var.overwrite_ssm_parameter 475 | type = "SecureString" 476 | description = "Atlantis OIDC Client ID" 477 | key_id = local.kms_key_id 478 | name = local.authentication_oidc_client_id_ssm_name 479 | value = local.authentication_oidc_client_id 480 | tags = module.this.tags 481 | } 482 | 483 | resource "aws_ssm_parameter" "atlantis_oidc_client_secret" { 484 | count = local.enabled && var.authentication_type == "OIDC" && var.authentication_oidc_client_secret != "" ? 1 : 0 485 | overwrite = var.overwrite_ssm_parameter 486 | type = "SecureString" 487 | description = "Atlantis OIDC Client Secret" 488 | key_id = local.kms_key_id 489 | name = local.authentication_oidc_client_secret_ssm_name 490 | value = local.authentication_oidc_client_secret 491 | tags = module.this.tags 492 | } 493 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "atlantis_ssh_public_key" { 2 | description = "Atlantis SSH Public Key" 3 | value = module.ssh_key_pair.public_key 4 | } 5 | 6 | output "atlantis_url" { 7 | description = "The URL endpoint for the atlantis server" 8 | value = local.atlantis_url 9 | } 10 | 11 | output "atlantis_webhook_url" { 12 | description = "atlantis webhook URL" 13 | value = local.atlantis_webhook_url 14 | } 15 | 16 | output "ecr_registry_id" { 17 | value = module.ecs_web_app.ecr_registry_id 18 | description = "Registry ID" 19 | } 20 | 21 | output "ecr_repository_url" { 22 | value = module.ecs_web_app.ecr_repository_url 23 | description = "Repository URL" 24 | } 25 | 26 | output "ecr_repository_name" { 27 | value = module.ecs_web_app.ecr_repository_name 28 | description = "Repository name" 29 | } 30 | 31 | output "alb_ingress_target_group_name" { 32 | description = "ALB Target Group name" 33 | value = module.ecs_web_app.alb_ingress_target_group_name 34 | } 35 | 36 | output "alb_ingress_target_group_arn" { 37 | description = "ALB Target Group ARN" 38 | value = module.ecs_web_app.alb_ingress_target_group_arn 39 | } 40 | 41 | output "alb_ingress_target_group_arn_suffix" { 42 | description = "ALB Target Group ARN suffix" 43 | value = module.ecs_web_app.alb_ingress_target_group_arn_suffix 44 | } 45 | 46 | output "container_definition_json" { 47 | description = "JSON encoded list of container definitions for use with other terraform resources such as aws_ecs_task_definition" 48 | value = module.ecs_web_app.container_definition_json 49 | sensitive = true 50 | } 51 | 52 | output "container_definition_json_map" { 53 | description = "JSON encoded container definitions for use with other terraform resources such as aws_ecs_task_definition" 54 | value = module.ecs_web_app.container_definition_json_map 55 | sensitive = true 56 | } 57 | 58 | output "ecs_exec_role_policy_id" { 59 | description = "The ECS service role policy ID, in the form of `role_name:role_policy_name`" 60 | value = module.ecs_web_app.ecs_exec_role_policy_id 61 | } 62 | 63 | output "ecs_exec_role_policy_name" { 64 | description = "ECS service role name" 65 | value = module.ecs_web_app.ecs_exec_role_policy_name 66 | } 67 | 68 | output "ecs_service_name" { 69 | description = "ECS Service name" 70 | value = module.ecs_web_app.ecs_service_name 71 | } 72 | 73 | output "ecs_service_role_arn" { 74 | description = "ECS Service role ARN" 75 | value = module.ecs_web_app.ecs_service_role_arn 76 | } 77 | 78 | output "ecs_task_exec_role_name" { 79 | description = "ECS Task role name" 80 | value = module.ecs_web_app.ecs_task_exec_role_name 81 | } 82 | 83 | output "ecs_task_exec_role_arn" { 84 | description = "ECS Task exec role ARN" 85 | value = module.ecs_web_app.ecs_task_exec_role_arn 86 | } 87 | 88 | output "ecs_task_role_name" { 89 | description = "ECS Task role name" 90 | value = module.ecs_web_app.ecs_task_role_name 91 | } 92 | 93 | output "ecs_task_role_arn" { 94 | description = "ECS Task role ARN" 95 | value = module.ecs_web_app.ecs_task_role_arn 96 | } 97 | 98 | output "ecs_task_role_id" { 99 | description = "ECS Task role id" 100 | value = module.ecs_web_app.ecs_task_role_id 101 | } 102 | 103 | output "ecs_service_security_group_id" { 104 | description = "Security Group ID of the ECS task" 105 | value = module.ecs_web_app.ecs_service_security_group_id 106 | } 107 | 108 | output "ecs_task_definition_family" { 109 | description = "ECS task definition family" 110 | value = module.ecs_web_app.ecs_task_definition_family 111 | } 112 | 113 | output "ecs_task_definition_revision" { 114 | description = "ECS task definition revision" 115 | value = module.ecs_web_app.ecs_task_definition_revision 116 | } 117 | 118 | output "codebuild_project_name" { 119 | description = "CodeBuild project name" 120 | value = module.ecs_web_app.codebuild_project_name 121 | } 122 | 123 | output "codebuild_project_id" { 124 | description = "CodeBuild project ID" 125 | value = module.ecs_web_app.codebuild_project_id 126 | } 127 | 128 | output "codebuild_role_id" { 129 | description = "CodeBuild IAM Role ID" 130 | value = module.ecs_web_app.codebuild_role_id 131 | } 132 | 133 | output "codebuild_role_arn" { 134 | description = "CodeBuild IAM Role ARN" 135 | value = module.ecs_web_app.codebuild_role_arn 136 | } 137 | 138 | output "codebuild_cache_bucket_name" { 139 | description = "CodeBuild cache S3 bucket name" 140 | value = module.ecs_web_app.codebuild_cache_bucket_name 141 | } 142 | 143 | output "codebuild_cache_bucket_arn" { 144 | description = "CodeBuild cache S3 bucket ARN" 145 | value = module.ecs_web_app.codebuild_cache_bucket_arn 146 | } 147 | 148 | output "codebuild_badge_url" { 149 | description = "The URL of the build badge when badge_enabled is enabled" 150 | value = module.ecs_web_app.codebuild_badge_url 151 | } 152 | 153 | output "codepipeline_id" { 154 | description = "CodePipeline ID" 155 | value = module.ecs_web_app.codepipeline_id 156 | } 157 | 158 | output "codepipeline_arn" { 159 | description = "CodePipeline ARN" 160 | value = module.ecs_web_app.codepipeline_arn 161 | } 162 | 163 | output "codepipeline_webhook_id" { 164 | description = "The CodePipeline webhook's ID" 165 | value = module.ecs_web_app.codepipeline_webhook_id 166 | } 167 | 168 | output "codepipeline_webhook_url" { 169 | description = "The CodePipeline webhook's URL. POST events to this endpoint to trigger the target" 170 | value = module.ecs_web_app.codepipeline_webhook_url 171 | sensitive = true 172 | } 173 | 174 | output "ecs_cloudwatch_autoscaling_scale_up_policy_arn" { 175 | description = "ARN of the scale up policy" 176 | value = module.ecs_web_app.ecs_cloudwatch_autoscaling_scale_up_policy_arn 177 | } 178 | 179 | output "ecs_cloudwatch_autoscaling_scale_down_policy_arn" { 180 | description = "ARN of the scale down policy" 181 | value = module.ecs_web_app.ecs_cloudwatch_autoscaling_scale_down_policy_arn 182 | } 183 | 184 | output "ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_id" { 185 | value = module.ecs_web_app.ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_id 186 | description = "ECS CPU utilization high CloudWatch metric alarm ID" 187 | } 188 | 189 | output "ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_arn" { 190 | value = module.ecs_web_app.ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_arn 191 | description = "ECS CPU utilization high CloudWatch metric alarm ARN" 192 | } 193 | 194 | output "ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_id" { 195 | value = module.ecs_web_app.ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_id 196 | description = "ECS CPU utilization low CloudWatch metric alarm ID" 197 | } 198 | 199 | output "ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_arn" { 200 | value = module.ecs_web_app.ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_arn 201 | description = "ECS CPU utilization low CloudWatch metric alarm ARN" 202 | } 203 | 204 | output "ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_id" { 205 | value = module.ecs_web_app.ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_id 206 | description = "ECS Memory utilization high CloudWatch metric alarm ID" 207 | } 208 | 209 | output "ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_arn" { 210 | value = module.ecs_web_app.ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_arn 211 | description = "ECS Memory utilization high CloudWatch metric alarm ARN" 212 | } 213 | 214 | output "ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_id" { 215 | value = module.ecs_web_app.ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_id 216 | description = "ECS Memory utilization low CloudWatch metric alarm ID" 217 | } 218 | 219 | output "ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_arn" { 220 | value = module.ecs_web_app.ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_arn 221 | description = "ECS Memory utilization low CloudWatch metric alarm ARN" 222 | } 223 | 224 | output "httpcode_target_3xx_count_cloudwatch_metric_alarm_id" { 225 | value = module.ecs_web_app.httpcode_target_3xx_count_cloudwatch_metric_alarm_id 226 | description = "ALB Target Group 3xx count CloudWatch metric alarm ID" 227 | } 228 | 229 | output "httpcode_target_3xx_count_cloudwatch_metric_alarm_arn" { 230 | value = module.ecs_web_app.httpcode_target_3xx_count_cloudwatch_metric_alarm_arn 231 | description = "ALB Target Group 3xx count CloudWatch metric alarm ARN" 232 | } 233 | 234 | output "httpcode_target_4xx_count_cloudwatch_metric_alarm_id" { 235 | value = module.ecs_web_app.httpcode_target_4xx_count_cloudwatch_metric_alarm_id 236 | description = "ALB Target Group 4xx count CloudWatch metric alarm ID" 237 | } 238 | 239 | output "httpcode_target_4xx_count_cloudwatch_metric_alarm_arn" { 240 | value = module.ecs_web_app.httpcode_target_4xx_count_cloudwatch_metric_alarm_arn 241 | description = "ALB Target Group 4xx count CloudWatch metric alarm ARN" 242 | } 243 | 244 | output "httpcode_target_5xx_count_cloudwatch_metric_alarm_id" { 245 | value = module.ecs_web_app.httpcode_target_5xx_count_cloudwatch_metric_alarm_id 246 | description = "ALB Target Group 5xx count CloudWatch metric alarm ID" 247 | } 248 | 249 | output "httpcode_target_5xx_count_cloudwatch_metric_alarm_arn" { 250 | value = module.ecs_web_app.httpcode_target_5xx_count_cloudwatch_metric_alarm_arn 251 | description = "ALB Target Group 5xx count CloudWatch metric alarm ARN" 252 | } 253 | 254 | output "httpcode_elb_5xx_count_cloudwatch_metric_alarm_id" { 255 | value = module.ecs_web_app.httpcode_elb_5xx_count_cloudwatch_metric_alarm_id 256 | description = "ALB 5xx count CloudWatch metric alarm ID" 257 | } 258 | 259 | output "httpcode_elb_5xx_count_cloudwatch_metric_alarm_arn" { 260 | value = module.ecs_web_app.httpcode_elb_5xx_count_cloudwatch_metric_alarm_arn 261 | description = "ALB 5xx count CloudWatch metric alarm ARN" 262 | } 263 | 264 | output "target_response_time_average_cloudwatch_metric_alarm_id" { 265 | value = module.ecs_web_app.target_response_time_average_cloudwatch_metric_alarm_id 266 | description = "ALB Target Group response time average CloudWatch metric alarm ID" 267 | } 268 | 269 | output "target_response_time_average_cloudwatch_metric_alarm_arn" { 270 | value = module.ecs_web_app.target_response_time_average_cloudwatch_metric_alarm_arn 271 | description = "ALB Target Group response time average CloudWatch metric alarm ARN" 272 | } 273 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | .test-harness 2 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | TEST_HARNESS ?= https://github.com/cloudposse/test-harness.git 2 | TEST_HARNESS_BRANCH ?= master 3 | TEST_HARNESS_PATH = $(realpath .test-harness) 4 | BATS_ARGS ?= --tap 5 | BATS_LOG ?= test.log 6 | 7 | # Define a macro to run the tests 8 | define RUN_TESTS 9 | @echo "Running tests in $(1)" 10 | @cd $(1) && bats $(BATS_ARGS) $(addsuffix .bats,$(addprefix $(TEST_HARNESS_PATH)/test/terraform/,$(TESTS))) 11 | endef 12 | 13 | default: all 14 | 15 | -include Makefile.* 16 | 17 | ## Provision the test-harnesss 18 | .test-harness: 19 | [ -d $@ ] || git clone --depth=1 -b $(TEST_HARNESS_BRANCH) $(TEST_HARNESS) $@ 20 | 21 | ## Initialize the tests 22 | init: .test-harness 23 | 24 | ## Install all dependencies (OS specific) 25 | deps:: 26 | @exit 0 27 | 28 | ## Clean up the test harness 29 | clean: 30 | [ "$(TEST_HARNESS_PATH)" == "/" ] || rm -rf $(TEST_HARNESS_PATH) 31 | 32 | ## Run all tests 33 | all: module examples/complete 34 | 35 | ## Run basic sanity checks against the module itself 36 | module: export TESTS ?= installed lint get-modules module-pinning get-plugins provider-pinning validate terraform-docs input-descriptions output-descriptions 37 | module: deps 38 | $(call RUN_TESTS, ../) 39 | 40 | ## Run tests against example 41 | examples/complete: export TESTS ?= installed lint get-modules get-plugins validate 42 | examples/complete: deps 43 | $(call RUN_TESTS, ../$@) 44 | -------------------------------------------------------------------------------- /test/Makefile.alpine: -------------------------------------------------------------------------------- 1 | ifneq (,$(wildcard /sbin/apk)) 2 | ## Install all dependencies for alpine 3 | deps:: init 4 | @apk add --update terraform-docs@cloudposse json2hcl@cloudposse 5 | endif 6 | -------------------------------------------------------------------------------- /test/src/.gitignore: -------------------------------------------------------------------------------- 1 | .gopath 2 | vendor/ 3 | -------------------------------------------------------------------------------- /test/src/Makefile: -------------------------------------------------------------------------------- 1 | export TF_CLI_ARGS_init ?= -get-plugins=true 2 | export TERRAFORM_VERSION ?= $(shell curl -s https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r -M '.current_version' | cut -d. -f1-2) 3 | 4 | .DEFAULT_GOAL : all 5 | 6 | .PHONY: all 7 | ## Default target 8 | all: test 9 | 10 | .PHONY : init 11 | ## Initialize tests 12 | init: 13 | @exit 0 14 | 15 | .PHONY : test 16 | ## Run tests 17 | test: init 18 | go mod download 19 | go test -v -timeout 60m -run TestExamplesComplete 20 | 21 | ## Run tests in docker container 22 | docker/test: 23 | docker run --name terratest --rm -it -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e GITHUB_TOKEN \ 24 | -e PATH="/usr/local/terraform/$(TERRAFORM_VERSION)/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ 25 | -v $(CURDIR)/../../:/module/ cloudposse/test-harness:latest -C /module/test/src test 26 | 27 | .PHONY : clean 28 | ## Clean up files 29 | clean: 30 | rm -rf ../../examples/complete/*.tfstate* -------------------------------------------------------------------------------- /test/src/examples_complete_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/gruntwork-io/terratest/modules/terraform" 9 | "github.com/stretchr/testify/assert" 10 | "math/rand" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | // Test the Terraform module in examples/complete using Terratest. 16 | func TestExamplesComplete(t *testing.T) { 17 | t.Parallel() 18 | 19 | targets := []string{"module.label", "module.vpc", "module.subnets", "module.alb"} 20 | 21 | rand.Seed(time.Now().UnixNano()) 22 | 23 | attributes := []string{strconv.Itoa(rand.Intn(1000))} 24 | 25 | // We need to create the ALB first because terraform does not wwait for it to be in the ready state before creating ECS target group 26 | terraformOptions := &terraform.Options{ 27 | // The path to where our Terraform code is located 28 | TerraformDir: "../../examples/complete", 29 | Upgrade: true, 30 | // Variables to pass to our Terraform code using -var-file options 31 | VarFiles: []string{"fixtures.us-west-2.tfvars"}, 32 | Vars: map[string]interface{}{ 33 | "attributes": attributes, 34 | }, 35 | Targets: targets, 36 | } 37 | 38 | // At the end of the test, run `terraform destroy` to clean up any resources that were created 39 | defer func() { 40 | if r := recover(); r != nil { 41 | terraformOptions.Targets = targets 42 | terraform.Destroy(t, terraformOptions) 43 | terraformOptions.Targets = nil 44 | terraform.Destroy(t, terraformOptions) 45 | assert.Fail(t, fmt.Sprintf("Panicked: %v", r)) 46 | } else { 47 | terraformOptions.Targets = nil 48 | terraform.Destroy(t, terraformOptions) 49 | } 50 | }() 51 | 52 | // This will run `terraform init` and `terraform apply` and fail the test if there are any errors 53 | terraform.InitAndApply(t, terraformOptions) 54 | 55 | terraformOptions.Targets = nil 56 | 57 | // This will run `terraform init` and `terraform apply` and fail the test if there are any errors 58 | terraform.Apply(t, terraformOptions) 59 | 60 | // Run `terraform output` to get the value of an output variable 61 | vpcCidr := terraform.Output(t, terraformOptions, "vpc_cidr") 62 | // Verify we're getting back the outputs we expect 63 | assert.Equal(t, "172.16.0.0/16", vpcCidr) 64 | 65 | // Run `terraform output` to get the value of an output variable 66 | privateSubnetCidrs := terraform.OutputList(t, terraformOptions, "private_subnet_cidrs") 67 | // Verify we're getting back the outputs we expect 68 | assert.Equal(t, []string{"172.16.0.0/19", "172.16.32.0/19"}, privateSubnetCidrs) 69 | 70 | // Run `terraform output` to get the value of an output variable 71 | publicSubnetCidrs := terraform.OutputList(t, terraformOptions, "public_subnet_cidrs") 72 | // Verify we're getting back the outputs we expect 73 | assert.Equal(t, []string{"172.16.128.0/19", "172.16.160.0/19"}, publicSubnetCidrs) 74 | 75 | // Run `terraform output` to get the value of an output variable 76 | albName := terraform.Output(t, terraformOptions, "alb_name") 77 | // Verify we're getting back the outputs we expect 78 | expectedAlbName := "eg-test-ecs-atlantis-" + attributes[0] 79 | assert.Equal(t, expectedAlbName, albName) 80 | 81 | // Run `terraform output` to get the value of an output variable 82 | albHttpListenerArn := terraform.Output(t, terraformOptions, "alb_http_listener_arn") 83 | // Verify we're getting back the outputs we expect 84 | assert.Contains(t, albHttpListenerArn, "listener/app/eg-test-ecs-atlantis") 85 | 86 | // Run `terraform output` to get the value of an output variable 87 | albIngressTargetGroupName := terraform.Output(t, terraformOptions, "alb_ingress_target_group_name") 88 | // Verify we're getting back the outputs we expect 89 | expectedAlbIngressTargetGroupName := "eg-test-ecs-atlantis-" + attributes[0] 90 | assert.Equal(t, expectedAlbIngressTargetGroupName, albIngressTargetGroupName) 91 | 92 | // Run `terraform output` to get the value of an output variable 93 | albAccessLogsBucketId := terraform.Output(t, terraformOptions, "alb_access_logs_bucket_id") 94 | // Verify we're getting back the outputs we expect 95 | expectedAlbAccessLogsBucketId := "eg-test-ecs-atlantis-" + attributes[0] + "-alb-access-logs" 96 | assert.Equal(t, expectedAlbAccessLogsBucketId, albAccessLogsBucketId) 97 | 98 | // Run `terraform output` to get the value of an output variable 99 | containerDefinitionJsonMap := terraform.OutputRequired(t, terraformOptions, "container_definition_json_map") 100 | // Verify we're getting back the outputs we expect 101 | var jsonObject map[string]interface{} 102 | err := json.Unmarshal([]byte(containerDefinitionJsonMap), &jsonObject) 103 | assert.NoError(t, err) 104 | expectedContainerDefinitionName := "eg-test-ecs-atlantis-" + attributes[0] 105 | assert.Equal(t, expectedContainerDefinitionName, jsonObject["name"]) 106 | assert.Equal(t, "cloudposse/default-backend:0.1.2", jsonObject["image"]) 107 | assert.Equal(t, 512, int((jsonObject["memory"]).(float64))) 108 | assert.Equal(t, 128, int((jsonObject["memoryReservation"]).(float64))) 109 | assert.Equal(t, 256, int((jsonObject["cpu"]).(float64))) 110 | assert.Equal(t, true, jsonObject["essential"]) 111 | assert.Equal(t, false, jsonObject["readonlyRootFilesystem"]) 112 | 113 | // Run `terraform output` to get the value of an output variable 114 | codebuildCacheBucketName := terraform.Output(t, terraformOptions, "codebuild_cache_bucket_name") 115 | // Verify we're getting back the outputs we expect 116 | expectedCodebuildCacheBucketName := "eg-test-ecs-atlantis-" + attributes[0] + "-build" 117 | assert.Contains(t, codebuildCacheBucketName, expectedCodebuildCacheBucketName) 118 | 119 | // Run `terraform output` to get the value of an output variable 120 | codebuildProjectName := terraform.Output(t, terraformOptions, "codebuild_project_name") 121 | // Verify we're getting back the outputs we expect 122 | expectedCodebuildProjectName := "eg-test-ecs-atlantis-" + attributes[0] + "-build" 123 | assert.Equal(t, expectedCodebuildProjectName, codebuildProjectName) 124 | 125 | // Run `terraform output` to get the value of an output variable 126 | codebuildRoleId := terraform.Output(t, terraformOptions, "codebuild_role_id") 127 | // Verify we're getting back the outputs we expect 128 | expectedCodebuildRoleId := "eg-test-ecs-atlantis-" + attributes[0] + "-build" 129 | assert.Equal(t, expectedCodebuildRoleId, codebuildRoleId) 130 | 131 | // Run `terraform output` to get the value of an output variable 132 | codepipelineId := terraform.Output(t, terraformOptions, "codepipeline_id") 133 | // Verify we're getting back the outputs we expect 134 | expectedCodepipelineId := "eg-test-ecs-atlantis-" + attributes[0] + "-codepipeline" 135 | assert.Equal(t, expectedCodepipelineId, codepipelineId) 136 | 137 | // Run `terraform output` to get the value of an output variable 138 | ecrRepositoryName := terraform.Output(t, terraformOptions, "ecr_repository_name") 139 | // Verify we're getting back the outputs we expect 140 | expectedEcrRepositoryName := "eg-test-ecs-atlantis-" + attributes[0] + "-ecr" 141 | assert.Equal(t, expectedEcrRepositoryName, ecrRepositoryName) 142 | 143 | // Run `terraform output` to get the value of an output variable 144 | ecsTaskRoleName := terraform.Output(t, terraformOptions, "ecs_task_role_name") 145 | // Verify we're getting back the outputs we expect 146 | expectedEcsTaskRoleName := "eg-test-ecs-atlantis-" + attributes[0] + "-task" 147 | assert.Equal(t, expectedEcsTaskRoleName, ecsTaskRoleName) 148 | 149 | // Run `terraform output` to get the value of an output variable 150 | ecsTaskExecRoleName := terraform.Output(t, terraformOptions, "ecs_task_exec_role_name") 151 | // Verify we're getting back the outputs we expect 152 | expectedEcsTaskExecRoleName := "eg-test-ecs-atlantis-" + attributes[0] + "-exec" 153 | assert.Equal(t, expectedEcsTaskExecRoleName, ecsTaskExecRoleName) 154 | 155 | // Run `terraform output` to get the value of an output variable 156 | ecsServiceName := terraform.Output(t, terraformOptions, "ecs_service_name") 157 | // Verify we're getting back the outputs we expect 158 | expectedEcsServiceName := "eg-test-ecs-atlantis-" + attributes[0] 159 | assert.Equal(t, expectedEcsServiceName, ecsServiceName) 160 | 161 | // Run `terraform output` to get the value of an output variable 162 | ecsExecRolePolicyName := terraform.Output(t, terraformOptions, "ecs_exec_role_policy_name") 163 | // Verify we're getting back the outputs we expect 164 | expectedEcsExecRolePolicyName := "eg-test-ecs-atlantis-" + attributes[0] + "-exec" 165 | assert.Equal(t, expectedEcsExecRolePolicyName, ecsExecRolePolicyName) 166 | 167 | // Run `terraform output` to get the value of an output variable 168 | ecsCloudwatchAutoscalingScaleDownPolicyArn := terraform.Output(t, terraformOptions, "ecs_cloudwatch_autoscaling_scale_down_policy_arn") 169 | // Verify we're getting back the outputs we expect 170 | expectedEcsCloudwatchAutoscalingScaleDownPolicyArn := "eg-test-ecs-atlantis-" + attributes[0] + ":policyName/down" 171 | assert.Contains(t, ecsCloudwatchAutoscalingScaleDownPolicyArn, expectedEcsCloudwatchAutoscalingScaleDownPolicyArn) 172 | 173 | // Run `terraform output` to get the value of an output variable 174 | ecsCloudwatchAutoscalingScaleUpPolicyArn := terraform.Output(t, terraformOptions, "ecs_cloudwatch_autoscaling_scale_up_policy_arn") 175 | // Verify we're getting back the outputs we expect 176 | expectedEcsCloudwatchAutoscalingScaleUpPolicyArn := "eg-test-ecs-atlantis-" + attributes[0] + ":policyName/up" 177 | assert.Contains(t, ecsCloudwatchAutoscalingScaleUpPolicyArn, expectedEcsCloudwatchAutoscalingScaleUpPolicyArn) 178 | 179 | // Run `terraform output` to get the value of an output variable 180 | ecsAlarmsCpuUtilizationHighCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "ecs_alarms_cpu_utilization_high_cloudwatch_metric_alarm_id") 181 | // Verify we're getting back the outputs we expect 182 | expectedEcsAlarmsCpuUtilizationHighCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-cpu-utilization-high" 183 | assert.Equal(t, expectedEcsAlarmsCpuUtilizationHighCloudwatchMetricAlarmId, ecsAlarmsCpuUtilizationHighCloudwatchMetricAlarmId) 184 | 185 | // Run `terraform output` to get the value of an output variable 186 | ecsAlarmsCpuUtilizationLowCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "ecs_alarms_cpu_utilization_low_cloudwatch_metric_alarm_id") 187 | // Verify we're getting back the outputs we expect 188 | expectedEcsAlarmsCpuUtilizationLowCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-cpu-utilization-low" 189 | assert.Equal(t, expectedEcsAlarmsCpuUtilizationLowCloudwatchMetricAlarmId, ecsAlarmsCpuUtilizationLowCloudwatchMetricAlarmId) 190 | 191 | // Run `terraform output` to get the value of an output variable 192 | ecsAlarmsMemoryUtilizationHighCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "ecs_alarms_memory_utilization_high_cloudwatch_metric_alarm_id") 193 | // Verify we're getting back the outputs we expect 194 | expectedEcsAlarmsMemoryUtilizationHighCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-memory-utilization-high" 195 | assert.Equal(t, expectedEcsAlarmsMemoryUtilizationHighCloudwatchMetricAlarmId, ecsAlarmsMemoryUtilizationHighCloudwatchMetricAlarmId) 196 | 197 | // Run `terraform output` to get the value of an output variable 198 | ecsAlarmsMemoryUtilizationLowCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "ecs_alarms_memory_utilization_low_cloudwatch_metric_alarm_id") 199 | // Verify we're getting back the outputs we expect 200 | expectedEcsAlarmsMemoryUtilizationLowCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-memory-utilization-low" 201 | assert.Equal(t, expectedEcsAlarmsMemoryUtilizationLowCloudwatchMetricAlarmId, ecsAlarmsMemoryUtilizationLowCloudwatchMetricAlarmId) 202 | 203 | // Run `terraform output` to get the value of an output variable 204 | httpcodeElb5xxCountCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "httpcode_elb_5xx_count_cloudwatch_metric_alarm_id") 205 | // Verify we're getting back the outputs we expect 206 | expectedHttpcodeElb5xxCountCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-elb-5xx-count-high" 207 | assert.Equal(t, expectedHttpcodeElb5xxCountCloudwatchMetricAlarmId, httpcodeElb5xxCountCloudwatchMetricAlarmId) 208 | 209 | // Run `terraform output` to get the value of an output variable 210 | httpcodeTarget3xxCountCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "httpcode_target_3xx_count_cloudwatch_metric_alarm_id") 211 | // Verify we're getting back the outputs we expect 212 | expectedHttpcodeTarget3xxCountCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-3xx-count-high" 213 | assert.Equal(t, expectedHttpcodeTarget3xxCountCloudwatchMetricAlarmId, httpcodeTarget3xxCountCloudwatchMetricAlarmId) 214 | 215 | // Run `terraform output` to get the value of an output variable 216 | httpcodeTarget4xxCountCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "httpcode_target_4xx_count_cloudwatch_metric_alarm_id") 217 | // Verify we're getting back the outputs we expect 218 | expectedHttpcodeTarget4xxCountCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-4xx-count-high" 219 | assert.Equal(t, expectedHttpcodeTarget4xxCountCloudwatchMetricAlarmId, httpcodeTarget4xxCountCloudwatchMetricAlarmId) 220 | 221 | // Run `terraform output` to get the value of an output variable 222 | httpcodeTarget5xxCountCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "httpcode_target_5xx_count_cloudwatch_metric_alarm_id") 223 | // Verify we're getting back the outputs we expect 224 | expectedHttpcodeTarget5xxCountCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-5xx-count-high" 225 | assert.Equal(t, expectedHttpcodeTarget5xxCountCloudwatchMetricAlarmId, httpcodeTarget5xxCountCloudwatchMetricAlarmId) 226 | 227 | // Run `terraform output` to get the value of an output variable 228 | targetResponseTimeAverageCloudwatchMetricAlarmId := terraform.Output(t, terraformOptions, "target_response_time_average_cloudwatch_metric_alarm_id") 229 | // Verify we're getting back the outputs we 230 | expectedTargetResponseTimeAverageCloudwatchMetricAlarmId := "eg-test-ecs-atlantis-" + attributes[0] + "-target-response-high" 231 | assert.Equal(t, expectedTargetResponseTimeAverageCloudwatchMetricAlarmId, targetResponseTimeAverageCloudwatchMetricAlarmId) 232 | 233 | // Run `terraform output` to get the value of an output variable 234 | atlantisUrl := terraform.Output(t, terraformOptions, "atlantis_url") 235 | // Verify we're getting back the outputs we expect 236 | assert.Equal(t, "https://ecs-atlantis-test-" + attributes[0] +".testing.cloudposse.co", atlantisUrl) 237 | 238 | // Run `terraform output` to get the value of an output variable 239 | atlantisWebhookUrl := terraform.Output(t, terraformOptions, "atlantis_webhook_url") 240 | // Verify we're getting back the outputs we expect 241 | assert.Equal(t, "https://ecs-atlantis-test-" + attributes[0] +".testing.cloudposse.co/events", atlantisWebhookUrl) 242 | } 243 | -------------------------------------------------------------------------------- /test/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudposse/terraform-aws-ecs-atlantis 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gruntwork-io/terratest v0.31.4 7 | github.com/stretchr/testify v1.6.1 8 | ) 9 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "AWS Region for S3 bucket" 4 | } 5 | 6 | variable "launch_type" { 7 | type = string 8 | description = "The ECS launch type (valid options: FARGATE or EC2)" 9 | default = "FARGATE" 10 | } 11 | 12 | variable "default_backend_image" { 13 | type = string 14 | default = "cloudposse/default-backend:0.1.2" 15 | description = "ECS default (bootstrap) image" 16 | } 17 | 18 | variable "github_oauth_token" { 19 | type = string 20 | description = "GitHub OAuth token. If not provided the token is looked up from SSM" 21 | default = "" 22 | } 23 | 24 | variable "github_webhooks_token" { 25 | type = string 26 | description = "GitHub OAuth Token with permissions to create webhooks. If not provided the token is looked up from SSM" 27 | default = "" 28 | } 29 | 30 | variable "github_oauth_token_ssm_name" { 31 | type = string 32 | description = "SSM param name to lookup `github_oauth_token` if not provided" 33 | default = "" 34 | } 35 | 36 | variable "github_webhooks_token_ssm_name" { 37 | type = string 38 | description = "SSM param name to lookup `github_webhooks_token` if not provided" 39 | default = "" 40 | } 41 | 42 | variable "codepipeline_s3_bucket_force_destroy" { 43 | type = bool 44 | description = "A boolean that indicates all objects should be deleted from the CodePipeline artifact store S3 bucket so that the bucket can be destroyed without error" 45 | default = false 46 | } 47 | 48 | variable "codepipeline_enabled" { 49 | type = bool 50 | description = "A boolean to enable/disable AWS Codepipeline and ECR" 51 | default = false 52 | } 53 | 54 | variable "build_timeout" { 55 | type = number 56 | default = 10 57 | description = "How long in minutes, from 5 to 480 (8 hours), for AWS CodeBuild to wait until timing out any related build that does not get marked as completed." 58 | } 59 | 60 | variable "branch" { 61 | type = string 62 | default = "master" 63 | description = "Atlantis branch of the GitHub repository, _e.g._ `master`" 64 | } 65 | 66 | variable "repo_name" { 67 | type = string 68 | description = "GitHub repository name of the atlantis to be built and deployed to ECS." 69 | } 70 | 71 | variable "repo_owner" { 72 | type = string 73 | description = "GitHub organization containing the Atlantis repository" 74 | } 75 | 76 | variable "atlantis_repo_config" { 77 | type = string 78 | description = "Path to atlantis server-side repo config file (https://www.runatlantis.io/docs/server-side-repo-config.html)" 79 | default = "atlantis-repo-config.yaml" 80 | } 81 | 82 | variable "atlantis_repo_whitelist" { 83 | type = list(string) 84 | description = "Whitelist of repositories Atlantis will accept webhooks from" 85 | default = [] 86 | } 87 | 88 | variable "autoscaling_enabled" { 89 | type = bool 90 | description = "A boolean to enable/disable Autoscaling policy for ECS Service" 91 | default = false 92 | } 93 | 94 | variable "healthcheck_path" { 95 | type = string 96 | description = "Healthcheck path" 97 | default = "/healthz" 98 | } 99 | 100 | variable "chamber_format" { 101 | type = string 102 | default = "/%s/%s" 103 | description = "Format to store parameters in SSM, for consumption with chamber" 104 | } 105 | 106 | variable "chamber_service" { 107 | type = string 108 | default = "atlantis" 109 | description = "SSM parameter service name for use with chamber. This is used in chamber_format where /$chamber_service/$parameter would be the default." 110 | } 111 | 112 | variable "desired_count" { 113 | type = number 114 | description = "Atlantis desired number of tasks" 115 | default = 1 116 | } 117 | 118 | variable "short_name" { 119 | type = string 120 | description = "Alantis short DNS name (e.g. `atlantis`)" 121 | default = "atlantis" 122 | } 123 | 124 | variable "hostname" { 125 | type = string 126 | description = "Atlantis URL" 127 | default = "" 128 | } 129 | 130 | variable "atlantis_gh_user" { 131 | type = string 132 | description = "Atlantis GitHub user" 133 | } 134 | 135 | variable "atlantis_gh_team_whitelist" { 136 | type = string 137 | description = "Atlantis GitHub team whitelist" 138 | default = "" 139 | } 140 | 141 | variable "atlantis_gh_webhook_secret" { 142 | type = string 143 | description = "Atlantis GitHub webhook secret" 144 | default = "" 145 | } 146 | 147 | variable "atlantis_log_level" { 148 | type = string 149 | description = "Atlantis log level" 150 | default = "info" 151 | } 152 | 153 | variable "atlantis_port" { 154 | type = number 155 | description = "Atlantis container port" 156 | default = 4141 157 | } 158 | 159 | variable "atlantis_wake_word" { 160 | type = string 161 | description = "Wake world for atlantis" 162 | default = "atlantis" 163 | } 164 | 165 | variable "atlantis_webhook_format" { 166 | type = string 167 | default = "https://%s/events" 168 | description = "Template for the Atlantis webhook URL which is populated with the hostname" 169 | } 170 | 171 | variable "atlantis_url_format" { 172 | type = string 173 | default = "https://%s" 174 | description = "Template for the Atlantis URL which is populated with the hostname" 175 | } 176 | 177 | variable "autoscaling_min_capacity" { 178 | type = number 179 | description = "Atlantis minimum tasks to run" 180 | default = 1 181 | } 182 | 183 | variable "autoscaling_max_capacity" { 184 | type = number 185 | description = "Atlantis maximum tasks to run" 186 | default = 1 187 | } 188 | 189 | variable "container_repo_credentials" { 190 | type = map(string) 191 | default = null 192 | description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" 193 | } 194 | 195 | variable "container_cpu" { 196 | type = number 197 | description = "Atlantis CPUs per task" 198 | default = 256 199 | } 200 | 201 | variable "container_memory" { 202 | type = number 203 | description = "Atlantis memory per task" 204 | default = 512 205 | } 206 | 207 | variable "policy_arn" { 208 | type = string 209 | default = "arn:aws:iam::aws:policy/AdministratorAccess" 210 | description = "Permission to grant to atlantis server" 211 | } 212 | 213 | variable "kms_key_id" { 214 | type = string 215 | default = "" 216 | description = "KMS key ID used to encrypt SSM SecureString parameters" 217 | } 218 | 219 | variable "webhook_enabled" { 220 | type = bool 221 | description = "Set to false to prevent the module from creating any webhook resources" 222 | default = true 223 | } 224 | 225 | variable "webhook_secret_length" { 226 | type = number 227 | default = 32 228 | description = "GitHub webhook secret length" 229 | } 230 | 231 | variable "webhook_events" { 232 | type = list(string) 233 | description = "A list of events which should trigger the webhook." 234 | 235 | default = [ 236 | "issue_comment", 237 | "pull_request", 238 | "pull_request_review", 239 | "pull_request_review_comment", 240 | "push", 241 | ] 242 | } 243 | 244 | variable "ssh_private_key_name" { 245 | type = string 246 | default = "atlantis_ssh_private_key" 247 | description = "Atlantis SSH private key name" 248 | } 249 | 250 | variable "ssh_public_key_name" { 251 | type = string 252 | default = "atlantis_ssh_public_key" 253 | description = "Atlantis SSH public key name" 254 | } 255 | 256 | variable "vpc_id" { 257 | type = string 258 | description = "VPC ID for the ECS Cluster" 259 | } 260 | 261 | variable "alb_arn_suffix" { 262 | type = string 263 | description = "The ARN suffix of the ALB" 264 | } 265 | 266 | variable "use_alb_security_group" { 267 | type = bool 268 | description = "A flag to enable/disable adding the ingress rule to the ALB security group" 269 | default = true 270 | } 271 | 272 | variable "alb_security_group" { 273 | type = string 274 | description = "Security group of the ALB" 275 | } 276 | 277 | variable "alb_target_group_alarms_alarm_actions" { 278 | type = list(string) 279 | description = "A list of ARNs (i.e. SNS Topic ARN) to execute when ALB Target Group alarms transition into an ALARM state from any other state." 280 | default = [] 281 | } 282 | 283 | variable "alb_target_group_alarms_ok_actions" { 284 | type = list(string) 285 | description = "A list of ARNs (i.e. SNS Topic ARN) to execute when ALB Target Group alarms transition into an OK state from any other state." 286 | default = [] 287 | } 288 | 289 | variable "alb_target_group_alarms_insufficient_data_actions" { 290 | type = list(string) 291 | description = "A list of ARNs (i.e. SNS Topic ARN) to execute when ALB Target Group alarms transition into an INSUFFICIENT_DATA state from any other state." 292 | default = [] 293 | } 294 | 295 | variable "ecs_alarms_cpu_utilization_high_alarm_actions" { 296 | type = list(string) 297 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on CPU Utilization High Alarm action" 298 | default = [] 299 | } 300 | 301 | variable "ecs_alarms_cpu_utilization_high_ok_actions" { 302 | type = list(string) 303 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on CPU Utilization High OK action" 304 | default = [] 305 | } 306 | 307 | variable "ecs_alarms_cpu_utilization_low_alarm_actions" { 308 | type = list(string) 309 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on CPU Utilization Low Alarm action" 310 | default = [] 311 | } 312 | 313 | variable "ecs_alarms_cpu_utilization_low_ok_actions" { 314 | type = list(string) 315 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on CPU Utilization Low OK action" 316 | default = [] 317 | } 318 | 319 | variable "ecs_alarms_memory_utilization_high_alarm_actions" { 320 | type = list(string) 321 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on Memory Utilization High Alarm action" 322 | default = [] 323 | } 324 | 325 | variable "ecs_alarms_memory_utilization_high_ok_actions" { 326 | type = list(string) 327 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on Memory Utilization High OK action" 328 | default = [] 329 | } 330 | 331 | variable "ecs_alarms_memory_utilization_low_alarm_actions" { 332 | type = list(string) 333 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on Memory Utilization Low Alarm action" 334 | default = [] 335 | } 336 | 337 | variable "ecs_alarms_memory_utilization_low_ok_actions" { 338 | type = list(string) 339 | description = "A list of ARNs (i.e. SNS Topic ARN) to notify on Memory Utilization Low OK action" 340 | default = [] 341 | } 342 | 343 | variable "alb_dns_name" { 344 | type = string 345 | description = "DNS name of ALB" 346 | } 347 | 348 | variable "alb_zone_id" { 349 | type = string 350 | description = "The ID of the zone in which ALB is provisioned" 351 | } 352 | 353 | variable "ecs_cluster_name" { 354 | type = string 355 | description = "Name of the ECS cluster to deploy Atlantis" 356 | } 357 | 358 | variable "ecs_cluster_arn" { 359 | type = string 360 | description = "ARN of the ECS cluster to deploy Atlantis" 361 | } 362 | 363 | variable "security_group_ids" { 364 | type = list(string) 365 | default = [] 366 | description = "Additional Security Group IDs to allow into ECS Service." 367 | } 368 | 369 | variable "private_subnet_ids" { 370 | type = list(string) 371 | default = [] 372 | description = "The private subnet IDs" 373 | } 374 | 375 | variable "parent_zone_id" { 376 | type = string 377 | description = "The zone ID where the DNS record for the `short_name` will be written" 378 | default = "" 379 | } 380 | 381 | variable "overwrite_ssm_parameter" { 382 | type = bool 383 | default = true 384 | description = "Whether to overwrite an existing SSM parameter" 385 | } 386 | 387 | variable "alb_ingress_listener_unauthenticated_priority" { 388 | type = number 389 | default = 50 390 | description = "The priority for the rules without authentication, between 1 and 50000 (1 being highest priority). Must be different from `alb_ingress_listener_authenticated_priority` since a listener can't have multiple rules with the same priority" 391 | } 392 | 393 | variable "alb_ingress_listener_authenticated_priority" { 394 | type = number 395 | default = 100 396 | description = "The priority for the rules with authentication, between 1 and 50000 (1 being highest priority). Must be different from `alb_ingress_listener_unauthenticated_priority` since a listener can't have multiple rules with the same priority" 397 | } 398 | 399 | variable "alb_ingress_unauthenticated_hosts" { 400 | type = list(string) 401 | default = [] 402 | description = "Unauthenticated hosts to match in Hosts header (a maximum of 1 can be defined)" 403 | } 404 | 405 | variable "alb_ingress_authenticated_hosts" { 406 | type = list(string) 407 | default = [] 408 | description = "Authenticated hosts to match in Hosts header (a maximum of 1 can be defined)" 409 | } 410 | 411 | variable "alb_ingress_unauthenticated_paths" { 412 | type = list(string) 413 | default = ["/events"] 414 | description = "Unauthenticated path pattern to match (a maximum of 1 can be defined)" 415 | } 416 | 417 | variable "alb_ingress_authenticated_paths" { 418 | type = list(string) 419 | default = ["/*"] 420 | description = "Authenticated path pattern to match (a maximum of 1 can be defined)" 421 | } 422 | 423 | variable "alb_ingress_unauthenticated_listener_arns" { 424 | type = list(string) 425 | default = [] 426 | description = "A list of unauthenticated ALB listener ARNs to attach ALB listener rules to" 427 | } 428 | 429 | variable "alb_ingress_unauthenticated_listener_arns_count" { 430 | type = number 431 | default = 0 432 | description = "The number of unauthenticated ARNs in `alb_ingress_unauthenticated_listener_arns`. This is necessary to work around a limitation in Terraform where counts cannot be computed" 433 | } 434 | 435 | variable "alb_ingress_authenticated_listener_arns" { 436 | type = list(string) 437 | default = [] 438 | description = "A list of authenticated ALB listener ARNs to attach ALB listener rules to" 439 | } 440 | 441 | variable "alb_ingress_authenticated_listener_arns_count" { 442 | type = number 443 | default = 0 444 | description = "The number of authenticated ARNs in `alb_ingress_authenticated_listener_arns`. This is necessary to work around a limitation in Terraform where counts cannot be computed" 445 | } 446 | 447 | variable "authentication_type" { 448 | type = string 449 | default = "" 450 | description = "Authentication type. Supported values are `COGNITO` and `OIDC`" 451 | } 452 | 453 | variable "authentication_cognito_user_pool_arn" { 454 | type = string 455 | description = "Cognito User Pool ARN" 456 | default = "" 457 | } 458 | 459 | variable "authentication_cognito_user_pool_client_id" { 460 | type = string 461 | description = "Cognito User Pool Client ID" 462 | default = "" 463 | } 464 | 465 | variable "authentication_cognito_user_pool_domain" { 466 | type = string 467 | description = "Cognito User Pool Domain. The User Pool Domain should be set to the domain prefix (`xxx`) instead of full domain (https://xxx.auth.us-west-2.amazoncognito.com)" 468 | default = "" 469 | } 470 | 471 | variable "authentication_oidc_client_id" { 472 | type = string 473 | description = "OIDC Client ID" 474 | default = "" 475 | } 476 | 477 | variable "authentication_oidc_client_secret" { 478 | type = string 479 | description = "OIDC Client Secret" 480 | default = "" 481 | } 482 | 483 | variable "authentication_oidc_issuer" { 484 | type = string 485 | description = "OIDC Issuer" 486 | default = "" 487 | } 488 | 489 | variable "authentication_oidc_authorization_endpoint" { 490 | type = string 491 | description = "OIDC Authorization Endpoint" 492 | default = "" 493 | } 494 | 495 | variable "authentication_oidc_token_endpoint" { 496 | type = string 497 | description = "OIDC Token Endpoint" 498 | default = "" 499 | } 500 | 501 | variable "authentication_oidc_user_info_endpoint" { 502 | type = string 503 | description = "OIDC User Info Endpoint" 504 | default = "" 505 | } 506 | 507 | variable "authentication_cognito_user_pool_arn_ssm_name" { 508 | type = string 509 | description = "SSM param name to lookup `authentication_cognito_user_pool_arn` if not provided" 510 | default = "" 511 | } 512 | 513 | variable "authentication_cognito_user_pool_client_id_ssm_name" { 514 | type = string 515 | description = "SSM param name to lookup `authentication_cognito_user_pool_client_id` if not provided" 516 | default = "" 517 | } 518 | 519 | variable "authentication_cognito_user_pool_domain_ssm_name" { 520 | type = string 521 | description = "SSM param name to lookup `authentication_cognito_user_pool_domain` if not provided" 522 | default = "" 523 | } 524 | 525 | variable "authentication_oidc_client_id_ssm_name" { 526 | type = string 527 | description = "SSM param name to lookup `authentication_oidc_client_id` if not provided" 528 | default = "" 529 | } 530 | 531 | variable "authentication_oidc_client_secret_ssm_name" { 532 | type = string 533 | description = "SSM param name to lookup `authentication_oidc_client_secret` if not provided" 534 | default = "" 535 | } 536 | 537 | variable "alb_target_group_alarms_enabled" { 538 | type = bool 539 | description = "A boolean to enable/disable CloudWatch Alarms for ALB Target metrics" 540 | default = false 541 | } 542 | 543 | variable "ecs_alarms_enabled" { 544 | type = bool 545 | description = "A boolean to enable/disable CloudWatch Alarms for ECS Service metrics" 546 | default = false 547 | } 548 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.13.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 2.0" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = ">= 2.0" 12 | } 13 | } 14 | } 15 | --------------------------------------------------------------------------------