├── .github └── workflows │ ├── actions.yml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .releaserc.yml ├── .terraform-version ├── CHANGELOG.md ├── Dangerfile ├── LICENSE ├── LICENSE.md ├── README.md ├── alb.tf ├── alb_listener.tf ├── alb_target_group.tf ├── alb_target_group_attachments.tf ├── asg.tf ├── asg_schedule.tf ├── catalog-info.yaml ├── docs └── index.md ├── efs.tf ├── examples ├── .terraform-version ├── .terraform.lock.hcl ├── README.md ├── data.tf ├── main.tf ├── provider.tf └── variables.tf ├── kms.tf ├── lc.tf ├── mkdocs.yml ├── outputs.tf ├── r53.tf ├── userdata.sh ├── variables.tf └── versions.tf /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | # Allow manually run 10 | workflow_dispatch: 11 | 12 | jobs: 13 | pre-commit: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-python@v3 18 | - uses: hashicorp/setup-terraform@v2 19 | with: 20 | terraform_version: "1.5.0" 21 | - name: Install pre-commit dependencies 22 | run: | 23 | pip3 install checkov 24 | curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash 25 | curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash 26 | curl -sSLo ./terraform-docs.tar.gz https://terraform-docs.io/dl/v0.16.0/terraform-docs-v0.16.0-$(uname)-amd64.tar.gz 27 | mkdir -p ${HOME}/.local/bin 28 | tar -xzf terraform-docs.tar.gz -C ${HOME}/.local/bin 29 | chmod +x ${HOME}/.local/bin/terraform-docs 30 | echo "${HOME}/.local/bin" >> $GITHUB_PATH 31 | - uses: pre-commit/action@v3.0.0 32 | 33 | danger-checks: 34 | runs-on: ubuntu-latest 35 | container: registry.gitlab.com/gitlab-org/gitlab-build-images:danger 36 | if: always() 37 | steps: 38 | - name: git checkout 39 | uses: actions/checkout@v3 40 | - run: | 41 | git config --global --add safe.directory '*' 42 | ls -la 43 | danger --fail-on-errors=true 44 | env: 45 | DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | name: danger check 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | env: 7 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | if: "!startsWith(github.event.head_commit.message, 'chore')" 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: release using semantic-release 16 | run: | 17 | sudo apt-get update 18 | pip install --user bumpversion 19 | npm install @semantic-release/changelog 20 | npm install @semantic-release/exec 21 | npm install @semantic-release/git 22 | npm install @semantic-release/github 23 | npx semantic-release 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | terraform* 2 | .terraform 3 | .terraform/* 4 | node_modules/* 5 | .terraform.lock.hcl 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.81.0 4 | hooks: 5 | - id: terraform_validate 6 | exclude: ^examples/ 7 | - id: terraform_fmt 8 | exclude: ^examples/ 9 | - id: terraform_tflint 10 | exclude: ^examples/ 11 | - id: terraform_tfsec 12 | exclude: ^examples/ 13 | args: 14 | - --args=--exclude-downloaded-modules 15 | -e aws-elbv2-alb-not-public,aws-vpc-use-secure-tls-policy 16 | -------------------------------------------------------------------------------- /.releaserc.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | - "master" 3 | plugins: 4 | - "@semantic-release/commit-analyzer" 5 | - "@semantic-release/release-notes-generator" 6 | - "@semantic-release/github" 7 | - - "@semantic-release/changelog" 8 | - assets: 9 | - CHANGELOG.md 10 | - - "@semantic-release/git" 11 | - assets: 12 | - CHANGELOG.md 13 | message: "chore(release): ${nextRelease.version} \n\n${nextRelease.notes}" 14 | 15 | -------------------------------------------------------------------------------- /.terraform-version: -------------------------------------------------------------------------------- 1 | 1.5.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [8.2.0](https://github.com/Cloud-42/terraform-aws-jenkins/compare/v8.1.0...v8.2.0) (2023-06-19) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add releaserc config ([d32e593](https://github.com/Cloud-42/terraform-aws-jenkins/commit/d32e59316e525ab17a69360b6a48584891f5a9a3)) 7 | 8 | 9 | ### Features 10 | 11 | * terraform upgrade and danger check ([be32c03](https://github.com/Cloud-42/terraform-aws-jenkins/commit/be32c03e6207d7da55c0fa92616946654e37491c)) 12 | 13 | # 1.0.0 (2023-06-19) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * add releaserc config ([d54eb9a](https://github.com/Cloud-42/terraform-aws-jenkins/commit/d54eb9ab471eca31456d805b59cfa7e75beebd2a)) 19 | * jenkins rpm key update ([b828b05](https://github.com/Cloud-42/terraform-aws-jenkins/commit/b828b059e2b7126a4bbfc6697e77dda406cd65cc)) 20 | 21 | 22 | ### Features 23 | 24 | * terraform upgrade and danger check ([be32c03](https://github.com/Cloud-42/terraform-aws-jenkins/commit/be32c03e6207d7da55c0fa92616946654e37491c)) 25 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | URL_SEMANTIC_RELEASE = 'https://www.conventionalcommits.org/en/v1.0.0/#summary' 4 | SEMANTIC_COMMIT_TYPES = %w[build chore ci docs feat fix perf refactor revert style test].freeze 5 | 6 | NO_RELEASE = 1 7 | PATCH_RELEASE = 2 8 | MINOR_RELEASE = 3 9 | MAJOR_RELEASE = 4 10 | 11 | COMMIT_SUBJECT_MAX_LENGTH = 72 12 | COMMIT_BODY_MAX_LENGTH = 140 13 | 14 | # Commit messages that start with one of the prefixes below won't be linted 15 | IGNORED_COMMIT_MESSAGES = [ 16 | 'Merge branch', 17 | 'Revert "', 18 | 'chore: update snapshots' 19 | ].freeze 20 | 21 | # Perform various checks against commits. We're not using 22 | # https://github.com/jonallured/danger-commit_lint because its output is not 23 | # very helpful, and it doesn't offer the means of ignoring merge commits. 24 | 25 | def fail_commit(commit, message) 26 | fail("#{commit.sha}: #{message}") # rubocop:disable Style/SignalException 27 | end 28 | 29 | def warn_commit(commit, message) 30 | warn("#{commit.sha}: #{message}") 31 | end 32 | 33 | def lines_changed_in_commit(commit) 34 | commit.diff_parent.stats[:total][:lines] 35 | end 36 | 37 | def too_many_changed_lines?(commit) 38 | commit.diff_parent.stats[:total][:files] > 10 && 39 | lines_changed_in_commit(commit) >= 100 40 | end 41 | 42 | def match_semantic_commit(text) 43 | text.match(/^(?\w+)(?:\((?.+?)\))?:(?.+?)$/) 44 | end 45 | 46 | def add_no_release_markdown 47 | markdown(<<~MARKDOWN) 48 | ## No release 49 | 50 | This Pull Request will trigger _no_ release. 51 | This either means, no commit neither the PR title warrant a semantic release (e.g. only updating CI config); 52 | or that all commits and/or the PR title are not properly formatted according to 53 | [conventional commits](#{URL_SEMANTIC_RELEASE}) rules, in the latter case, you should either: 54 | - Disable **Squash commits when merge request is accepted** and rewrite the commits history to 55 | format commit messages properly 56 | - Enable **Squash commits when merge request is accepted** and make sure the PR's title 57 | complies with conventional commits specifications 58 | MARKDOWN 59 | end 60 | 61 | def add_release_type_markdown(type) 62 | markdown(<<~MARKDOWN) 63 | ## \u{1f4e6} #{type.capitalize} ([conventional commits](#{URL_SEMANTIC_RELEASE})) 64 | MARKDOWN 65 | end 66 | 67 | def get_release_info(release_type) 68 | case release_type 69 | when MAJOR_RELEASE 70 | type = 'major release' 71 | bump = <<~BUMP 72 | This will bump the first part of the version number, e.g. `v1.2.3` -> `v2.0.0`. 73 | 74 | This means that there is a breaking change. 75 | BUMP 76 | when MINOR_RELEASE 77 | type = 'minor release' 78 | bump = 'This will bump the second part of the version number, e.g. `v1.2.3` -> `v1.3.0`.' 79 | when PATCH_RELEASE 80 | type = 'patch release' 81 | bump = 'This will bump the third part of the version number, e.g. `v1.2.3` -> `v1.2.4`.' 82 | else 83 | warn 'This PR will trigger no release' 84 | add_no_release_markdown 85 | return 86 | end 87 | [type, bump] 88 | end 89 | 90 | def lint_commit(commit) 91 | # For now we'll ignore merge commits, as getting rid of those is a problem 92 | # separate from enforcing good commit messages. 93 | # We also ignore revert commits as they are well structured by Git already 94 | if commit.message.start_with?(*IGNORED_COMMIT_MESSAGES) 95 | return { failed: false, release: NO_RELEASE } 96 | end 97 | 98 | release = NO_RELEASE 99 | failed = false 100 | subject, separator, details = commit.message.split("\n", 3) 101 | 102 | if subject.length > COMMIT_SUBJECT_MAX_LENGTH 103 | fail_commit( 104 | commit, 105 | "The commit subject may not be longer than #{COMMIT_SUBJECT_MAX_LENGTH} characters" 106 | ) 107 | 108 | failed = true 109 | end 110 | 111 | if separator && !separator.empty? 112 | fail_commit( 113 | commit, 114 | 'The commit subject and body must be separated by a blank line' 115 | ) 116 | 117 | failed = true 118 | end 119 | 120 | details&.each_line do |line| 121 | line = line.strip 122 | 123 | next if line.length <= COMMIT_BODY_MAX_LENGTH 124 | 125 | url_size = line.scan(%r{(https?://\S+)}).sum { |(url)| url.length } 126 | 127 | # If the line includes a URL, we'll allow it to exceed 72 characters, but 128 | # only if the line _without_ the URL does not exceed this limit. 129 | next if line.length - url_size <= COMMIT_BODY_MAX_LENGTH 130 | 131 | fail_commit( 132 | commit, 133 | "The commit body should not contain more than #{COMMIT_BODY_MAX_LENGTH} characters per line" 134 | ) 135 | 136 | failed = true 137 | end 138 | 139 | if !details && too_many_changed_lines?(commit) 140 | fail_commit( 141 | commit, 142 | 'Commits that change 30 or more lines across at least three files ' \ 143 | 'must describe these changes in the commit body' 144 | ) 145 | 146 | failed = true 147 | end 148 | 149 | if commit.message.match?(%r{([\w\-\/]+)?(#|!|&|%)\d+\b}) 150 | fail_commit( 151 | commit, 152 | 'Use full URLs instead of short references ' \ 153 | '(`github-org/github-ce#123` or `!123`), as short references are ' \ 154 | 'displayed as plain text outside of GitLab' 155 | ) 156 | 157 | failed = true 158 | end 159 | 160 | semantic_commit = match_semantic_commit(subject) 161 | 162 | if !semantic_commit 163 | warn_commit(commit, 'The commit does not comply with conventional commits specifications.') 164 | 165 | failed = true 166 | elsif !SEMANTIC_COMMIT_TYPES.include?(semantic_commit[:type]) 167 | warn_commit( 168 | commit, 169 | "The semantic commit type `#{semantic_commit[:type]}` is not a well-known semantic commit type." 170 | ) 171 | 172 | failed = true 173 | elsif details&.match(/BREAKING CHANGE:/) 174 | release = MAJOR_RELEASE 175 | elsif semantic_commit[:type] == 'feat' 176 | release = MINOR_RELEASE 177 | elsif %w[perf fix].include?(semantic_commit[:type]) 178 | release = PATCH_RELEASE 179 | end 180 | 181 | { failed: failed, release: release } 182 | end 183 | 184 | def lint_commits(commits) 185 | commits_with_status = commits.map { |commit| { commit: commit }.merge(lint_commit(commit)) } 186 | 187 | failed = commits_with_status.any? { |commit| commit[:failed] } 188 | 189 | max_release = commits_with_status.max { |a, b| a[:release] <=> b[:release] } 190 | 191 | if failed 192 | markdown(<<~MARKDOWN) 193 | ## Commit message standards 194 | 195 | One or more commit messages do not meet our Git commit message standards. 196 | For more information on how to write a good commit message, take a look at 197 | [Conventional commits](#{URL_SEMANTIC_RELEASE}). 198 | 199 | Here is an example of a good commit message: 200 | 201 | feat(progressbar): Improve rendering performance in Edge 202 | 203 | Our progressbar component was causing a lot of re-renders in Edge. 204 | This was caused by excessive re-rendering due to floating point errors 205 | in Edge's JavaScript engine. 206 | 207 | By utilizing the better-math library we avoid those calculation errors 208 | and we can achieve 120 rendering frames per second again. 209 | 210 | This is an example of a bad commit message: 211 | 212 | fixed progressbar 213 | 214 | MARKDOWN 215 | end 216 | 217 | return if github.pr_json['squash'] 218 | 219 | type, bump = get_release_info(max_release[:release]) 220 | 221 | if type && bump 222 | add_release_type_markdown(type) 223 | markdown(<<~MARKDOWN) 224 | This Merge Request will trigger a _#{type}_, triggered by commit: 225 | #{max_release[:commit].sha} 226 | 227 | #{bump} 228 | MARKDOWN 229 | end 230 | end 231 | 232 | def lint_pr(pr) 233 | if pr && pr['squash'] 234 | release = NO_RELEASE 235 | pr_title = pr['title'][/(^WIP: +)?(.*)/, 2] 236 | semantic_commit = match_semantic_commit(pr_title) 237 | 238 | if !semantic_commit 239 | warn( 240 | 'Your PR has **Squash commits when merge request is accepted** enabled but its title does not comply with conventional commits specifications' 241 | ) 242 | elsif !SEMANTIC_COMMIT_TYPES.include?(semantic_commit[:type]) 243 | warn( 244 | "The semantic commit type `#{semantic_commit[:type]}` is not a well-known semantic commit type." 245 | ) 246 | elsif semantic_commit[:type] == 'feat' 247 | release = MINOR_RELEASE 248 | elsif %w[perf fix].include?(semantic_commit[:type]) 249 | release = PATCH_RELEASE 250 | end 251 | 252 | type, bump = get_release_info(release) 253 | 254 | if type && bump 255 | add_release_type_markdown(type) 256 | markdown(<<~MARKDOWN) 257 | This Pull Request will trigger a _#{type}_ based on its title. 258 | 259 | #{bump} 260 | MARKDOWN 261 | end 262 | end 263 | end 264 | 265 | lint_commits(git.commits) 266 | lint_pr(github.pr_json) 267 | 268 | if git.commits.length > 10 269 | warn( 270 | 'This merge request includes more than 10 commits. ' \ 271 | 'Please rebase these commits into a smaller number of commits.' 272 | ) 273 | end 274 | 275 | 276 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/Cloud-42/terraform-aws-jenkins/actions/workflows/actions.yml/badge.svg)](https://github.com/Cloud-42/terraform-aws-jenkins/actions/workflows/actions.yml) 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | --- 10 |

Need help with your Cloud builds GET IN TOUCH 11 | 12 | --- 13 | # Terraform AWS Jenkins Master module 14 | 15 | Auto-scaled, self healing, Jenkins Master server for use in AWS. 16 | 17 | ##### Prerequisites 18 | 19 | * A VPC is already in place 20 | * DHCP options set to AmazonProvidedDNS 21 | * Route 53 zone is already in place ( Optional ) 22 | * Terraform version >= 0.13.2 23 | * AWS account 24 | 25 | ##### Summary: 26 | 27 | * Jenkins Master ec2 instance, created via an AutoScaling Group "ASG". 28 | * Encrypted EFS share to host $JENKINS_HOME. 29 | * EFS Mount points in 2x AZs. 30 | * DNS friendly name in Route53 for connections ( Optional ). 31 | * Application Load balancer "ALB" , forwarding to the Jenkins Master. 32 | * Jenkins Server rebuilds once a week deploying all the latest security patches and the latest jenkins.war. Default = 00:00 - 00:30 each Sunday morning. 33 | * Custom KMS encryption key for EFS. 34 | * HTTP - auto re-directs to - HTTPS 35 | * data\_sources.tf can be used to look up the latest AMI to use. 36 | 37 | ##### EFS Backups 38 | 39 | * Enabled by default. Uses the automatic EFS backup facility. Backups deleted after 35 days. 40 | 41 | ##### Current supported Operating Systems: 42 | 43 | * Ubuntu Server 20.04 LTS 44 | * Amazon Linux 2 45 | 46 | ## Inputs 47 | 48 | | Name | Description | Type | Default | Required | 49 | |------|-------------|------|---------|:--------:| 50 | | [alb\_listener\_port](#input\_alb\_listener\_port) | ALB listener port | `number` | `"443"` | no | 51 | | [alb\_listener\_protocol](#input\_alb\_listener\_protocol) | ALB listener protocol | `string` | `"HTTPS"` | no | 52 | | [ami](#input\_ami) | AMI to be used to build the ec2 instance (via launch config) | `string` | n/a | yes | 53 | | [asg\_tags](#input\_asg\_tags) | Dynamic tags for ASG | `any` |

[
{
"key": "Name",
"propagate_at_launch": true,
"value": "tags need setting"
}
]
| no | 54 | | [autoscaling\_schedule\_create](#input\_autoscaling\_schedule\_create) | Allows for disabling of scheduled actions on ASG. Enabled by default | `number` | `1` | no | 55 | | [backup\_policy](#input\_backup\_policy) | EFS backup policy | `string` | `"ENABLED"` | no | 56 | | [certificate\_arn](#input\_certificate\_arn) | ARN of the SSL certificate to use | `string` | n/a | yes | 57 | | [create\_dns\_record](#input\_create\_dns\_record) | Create friendly DNS CNAME | `bool` | `true` | no | 58 | | [custom\_userdata](#input\_custom\_userdata) | Set custom userdata | `string` | `""` | no | 59 | | [deletion\_window\_in\_days](#input\_deletion\_window\_in\_days) | Number of days before permanent removal | `number` | `"30"` | no | 60 | | [desired\_capacity](#input\_desired\_capacity) | AutoScaling Group desired capacity | `number` | `1` | no | 61 | | [domain\_name](#input\_domain\_name) | Domain Name | `string` | n/a | yes | 62 | | [drop\_invalid\_header\_fields](#input\_drop\_invalid\_header\_fields) | Whether HTTP headers with header fields that are not valid are removed by the load balancer | `bool` | `true` | no | 63 | | [efs\_encrypted](#input\_efs\_encrypted) | Encrypt the EFS share | `bool` | `true` | no | 64 | | [enable\_cross\_zone\_load\_balancing](#input\_enable\_cross\_zone\_load\_balancing) | Enable / Disable cross zone load balancing | `bool` | `false` | no | 65 | | [enable\_deletion\_protection](#input\_enable\_deletion\_protection) | Enable / Disable deletion protection for the ALB. | `bool` | `false` | no | 66 | | [enable\_key\_rotation](#input\_enable\_key\_rotation) | KMS key rotation | `bool` | `true` | no | 67 | | [enable\_monitoring](#input\_enable\_monitoring) | AutoScaling - enables/disables detailed monitoring | `bool` | `"false"` | no | 68 | | [encrypted](#input\_encrypted) | Encryption of volumes | `bool` | `true` | no | 69 | | [environment](#input\_environment) | Environment where resources are being created, for example DEV, UAT or PROD | `string` | n/a | yes | 70 | | [health\_check\_grace\_period](#input\_health\_check\_grace\_period) | AutoScaling health check grace period | `number` | `180` | no | 71 | | [health\_check\_type](#input\_health\_check\_type) | AutoScaling health check type. EC2 or ELB | `string` | `"ELB"` | no | 72 | | [healthy\_threshold](#input\_healthy\_threshold) | ALB healthy count | `number` | `2` | no | 73 | | [hostname\_prefix](#input\_hostname\_prefix) | Hostname prefix for the Jenkins server | `string` | `"jenkins"` | no | 74 | | [http\_listener\_required](#input\_http\_listener\_required) | Enables / Disables creating HTTP listener. Listener auto redirects to HTTPS | `bool` | `true` | no | 75 | | [iam\_instance\_profile](#input\_iam\_instance\_profile) | IAM instance profile for Jenkins server | `string` | `null` | no | 76 | | [instance\_type](#input\_instance\_type) | ec2 instance type | `string` | `"t3a.medium"` | no | 77 | | [internal](#input\_internal) | Is the ALB internal? | `bool` | `false` | no | 78 | | [interval](#input\_interval) | ALB health check interval | `number` | `20` | no | 79 | | [key\_name](#input\_key\_name) | ec2 key pair use | `string` | n/a | yes | 80 | | [listener1\_alb\_listener\_port](#input\_listener1\_alb\_listener\_port) | HTTP listener port | `number` | `80` | no | 81 | | [listener1\_alb\_listener\_protocol](#input\_listener1\_alb\_listener\_protocol) | HTTP listener protocol | `string` | `"HTTP"` | no | 82 | | [max\_size](#input\_max\_size) | AutoScaling Group max size | `number` | `1` | no | 83 | | [min\_size](#input\_min\_size) | AutoScaling Group min size | `number` | `1` | no | 84 | | [performance\_mode](#input\_performance\_mode) | EFS performance mode.https://docs.aws.amazon.com/efs/latest/ug/performance.html | `string` | `"generalPurpose"` | no | 85 | | [preliminary\_user\_data](#input\_preliminary\_user\_data) | Preliminary shell script commands for adding to user data.Runs at the beginning of userdata | `string` | `"#preliminary_user_data"` | no | 86 | | [private\_subnet\_a](#input\_private\_subnet\_a) | 1st private subnet id | `string` | n/a | yes | 87 | | [private\_subnet\_b](#input\_private\_subnet\_b) | 2nd private subnet id | `string` | n/a | yes | 88 | | [route53\_endpoint\_record](#input\_route53\_endpoint\_record) | Route 53 endpoint name. Creates route53\_endpoint\_record | `string` | `"jenkins"` | no | 89 | | [scale\_down\_cron](#input\_scale\_down\_cron) | The time when the recurring scale down action start.Cron format | `string` | `"0 0 * * SUN"` | no | 90 | | [scale\_up\_cron](#input\_scale\_up\_cron) | The time when the recurring scale up action start.Cron format | `string` | `"30 0 * * SUN"` | no | 91 | | [security\_groups](#input\_security\_groups) | List of security groups to assign to the ec2 instance. Create outside of module and pass in | `list(string)` | n/a | yes | 92 | | [security\_groups\_alb](#input\_security\_groups\_alb) | ALB Security Group. Create outside of module and pass in | `list(string)` | n/a | yes | 93 | | [security\_groups\_mount\_target\_a](#input\_security\_groups\_mount\_target\_a) | Security groups to use for mount target subnet a. Create outside of module and pass in | `list(string)` | n/a | yes | 94 | | [security\_groups\_mount\_target\_b](#input\_security\_groups\_mount\_target\_b) | Security groups to use for mount target subnet b. Create outside of module and pass in | `list(string)` | n/a | yes | 95 | | [ssl\_policy](#input\_ssl\_policy) | Name of the SSL Policy for the listener | `string` | `"ELBSecurityPolicy-TLS-1-2-Ext-2018-06"` | no | 96 | | [subnets](#input\_subnets) | Subnets where the ALB will be placed | `list(string)` | n/a | yes | 97 | | [success\_codes](#input\_success\_codes) | Success Codes for the Target Group Health Checks. Default is 200 ( OK ) | `string` | `"200"` | no | 98 | | [supplementary\_user\_data](#input\_supplementary\_user\_data) | Supplementary shell script commands for adding to user data.Runs at the end of userdata | `string` | `"#supplementary_user_data"` | no | 99 | | [svc\_port](#input\_svc\_port) | Service port: The port on which targets receive traffic. | `number` | `8080` | no | 100 | | [tags](#input\_tags) | Tags map | `map(string)` | `{}` | no | 101 | | [target\_group\_path](#input\_target\_group\_path) | Health check request path | `string` | `"/"` | no | 102 | | [target\_group\_port](#input\_target\_group\_port) | The port to use to connect with the target | `number` | `"8080"` | no | 103 | | [target\_group\_protocol](#input\_target\_group\_protocol) | The protocol to use to connect to the target | `string` | `"HTTP"` | no | 104 | | [timeout](#input\_timeout) | ALB timeout value | `number` | `5` | no | 105 | | [unhealthy\_threshold](#input\_unhealthy\_threshold) | ALB unhealthy count | `number` | `10` | no | 106 | | [volume\_size](#input\_volume\_size) | ec2 volume size | `number` | `30` | no | 107 | | [volume\_type](#input\_volume\_type) | ec2 volume type | `string` | `"gp2"` | no | 108 | | [vpc\_id](#input\_vpc\_id) | VPC id | `string` | n/a | yes | 109 | | [vpc\_zone\_identifier](#input\_vpc\_zone\_identifier) | A list of subnet IDs to launch AutoScaling resources in. | `list(string)` | n/a | yes | 110 | | [zone\_id](#input\_zone\_id) | Route 53 zone id | `string` | `null` | no | 111 | 112 | ## Outputs 113 | 114 | | Name | Description | 115 | |------|-------------| 116 | | [asg\_id](#output\_asg\_id) | Jenkins ASG id | 117 | | [efs\_dns\_name](#output\_efs\_dns\_name) | DNS name of the EFS share | 118 | | [efs\_id](#output\_efs\_id) | ID of the EFS share | 119 | | [lb\_arn](#output\_lb\_arn) | Load balancer ARN | 120 | | [lb\_dns\_name](#output\_lb\_dns\_name) | Load balancer DNS Name | 121 | | [lb\_zone\_id](#output\_lb\_zone\_id) | Load balancer zone id | 122 | -------------------------------------------------------------------------------- /alb.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # Jenkins ALB 3 | # -------------------------- 4 | resource "aws_lb" "jenkins" { 5 | # only hyphens are allowed in name 6 | name = "${var.environment}-jenkins-alb" 7 | 8 | enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing 9 | #tfsec:ignore:aws-elb-alb-not-public 10 | internal = var.internal 11 | load_balancer_type = "application" 12 | security_groups = var.security_groups_alb 13 | subnets = var.subnets 14 | 15 | drop_invalid_header_fields = var.drop_invalid_header_fields 16 | enable_deletion_protection = var.enable_deletion_protection 17 | 18 | tags = var.tags 19 | } 20 | 21 | -------------------------------------------------------------------------------- /alb_listener.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # ALB Listeners 3 | # HTTP -> AUTO -> HTTPS 4 | # -------------------------- 5 | resource "aws_lb_listener" "l1_alb_listener" { 6 | count = var.http_listener_required ? 1 : 0 7 | load_balancer_arn = aws_lb.jenkins.arn 8 | port = var.listener1_alb_listener_port 9 | protocol = var.listener1_alb_listener_protocol 10 | 11 | default_action { 12 | type = "redirect" 13 | 14 | redirect { 15 | port = "443" 16 | protocol = "HTTPS" 17 | status_code = "HTTP_301" 18 | } 19 | } 20 | } 21 | resource "aws_alb_listener" "alb_listener" { 22 | depends_on = [aws_autoscaling_group.jenkins] 23 | load_balancer_arn = aws_lb.jenkins.arn 24 | port = var.alb_listener_port 25 | protocol = var.alb_listener_protocol 26 | ssl_policy = var.ssl_policy 27 | certificate_arn = var.certificate_arn 28 | 29 | default_action { 30 | target_group_arn = aws_lb_target_group.alb_target_group.arn 31 | type = "forward" 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /alb_target_group.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # ALB target group 3 | # -------------------------- 4 | resource "aws_lb_target_group" "alb_target_group" { 5 | name = "jenkins-${var.environment}-tg" 6 | port = var.svc_port 7 | protocol = var.target_group_protocol 8 | vpc_id = var.vpc_id 9 | 10 | health_check { 11 | healthy_threshold = var.healthy_threshold 12 | unhealthy_threshold = var.unhealthy_threshold 13 | timeout = var.timeout 14 | interval = var.interval 15 | matcher = var.success_codes 16 | 17 | path = var.target_group_path 18 | port = var.target_group_port 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /alb_target_group_attachments.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # ALB target group attachment 3 | # -------------------------- 4 | resource "aws_autoscaling_attachment" "asg_attachment" { 5 | depends_on = [aws_autoscaling_group.jenkins] 6 | 7 | autoscaling_group_name = aws_autoscaling_group.jenkins.id 8 | lb_target_group_arn = aws_lb_target_group.alb_target_group.arn 9 | } 10 | -------------------------------------------------------------------------------- /asg.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # Jenkins Auto-Scaling Group 3 | # -------------------------- 4 | resource "aws_autoscaling_group" "jenkins" { 5 | depends_on = [aws_efs_file_system.this, aws_launch_configuration.jenkins] 6 | 7 | name = "${var.environment}_jenkins_asg" 8 | max_size = var.max_size 9 | min_size = var.min_size 10 | desired_capacity = var.desired_capacity 11 | launch_configuration = aws_launch_configuration.jenkins.name 12 | vpc_zone_identifier = var.private_subnets 13 | health_check_grace_period = var.health_check_grace_period 14 | health_check_type = var.health_check_type 15 | 16 | lifecycle { 17 | ignore_changes = [load_balancers, target_group_arns] 18 | } 19 | 20 | dynamic "tag" { 21 | for_each = var.asg_tags 22 | content { 23 | key = tag.value["key"] 24 | value = tag.value["value"] 25 | propagate_at_launch = tag.value["propagate_at_launch"] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /asg_schedule.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # Create scheduled action to rebuild Jenkins host, 3 | # ensuring Jenkins is updated on a schedule. 4 | # -------------------------- 5 | 6 | resource "aws_autoscaling_schedule" "scale_down" { 7 | count = var.autoscaling_schedule_create != 0 ? 1 : 0 8 | scheduled_action_name = "scale_down" 9 | min_size = 0 10 | max_size = 0 11 | desired_capacity = 0 12 | recurrence = var.scale_down_cron 13 | autoscaling_group_name = aws_autoscaling_group.jenkins.name 14 | } 15 | 16 | resource "aws_autoscaling_schedule" "scale_up" { 17 | count = var.autoscaling_schedule_create != 0 ? 1 : 0 18 | scheduled_action_name = "scale_up" 19 | min_size = 1 20 | max_size = 1 21 | desired_capacity = 1 22 | recurrence = var.scale_up_cron 23 | autoscaling_group_name = aws_autoscaling_group.jenkins.name 24 | } 25 | 26 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: terraform-aws-jenkins 5 | annotations: 6 | github.com/project-slug: Cloud-42/terraform-aws-jenkins 7 | backstage.io/techdocs-ref: dir:. 8 | spec: 9 | type: other 10 | lifecycle: unknown 11 | owner: chris@cloud42.io 12 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/Cloud-42/terraform-aws-jenkins/actions/workflows/actions.yml/badge.svg)](https://github.com/Cloud-42/terraform-aws-jenkins/actions/workflows/actions.yml) 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | --- 10 |

Need help with your Cloud builds GET IN TOUCH 11 | 12 | --- 13 | # Terraform AWS Jenkins Master module 14 | 15 | Auto-scaled, self healing, Jenkins Master server for use in AWS. 16 | 17 | ##### Prerequisites 18 | 19 | * A VPC is already in place 20 | * DHCP options set to AmazonProvidedDNS 21 | * Route 53 zone is already in place ( Optional ) 22 | * Terraform version >= 0.13.2 23 | * AWS account 24 | 25 | ##### Summary: 26 | 27 | * Jenkins Master ec2 instance, created via an AutoScaling Group "ASG". 28 | * Encrypted EFS share to host $JENKINS_HOME. 29 | * EFS Mount points in 2x AZs. 30 | * DNS friendly name in Route53 for connections ( Optional ). 31 | * Application Load balancer "ALB" , forwarding to the Jenkins Master. 32 | * Jenkins Server rebuilds once a week deploying all the latest security patches and the latest jenkins.war. Default = 00:00 - 00:30 each Sunday morning. 33 | * Custom KMS encryption key for EFS. 34 | * HTTP - auto re-directs to - HTTPS 35 | * data\_sources.tf can be used to look up the latest AMI to use. 36 | 37 | ##### EFS Backups 38 | 39 | $JENKINS\_HOME is stored on an EFS Share. It is advisable to back this up. AWS provide 2 off-the-shelf solutions that will do this automatically: 40 | * https://aws.amazon.com/answers/infrastructure-management/efs-backup/. This solution is deployed via a CloudFormation template. 41 | * AWS Backup - https://aws.amazon.com/backup/ ( Probably more straight forward to implement ) 42 | 43 | ##### Current supported Operating Systems: 44 | 45 | * Ubuntu Server 20.04 LTS 46 | * Amazon Linux 2 47 | 48 | ## Inputs 49 | 50 | | Name | Description | Type | Default | Required | 51 | |------|-------------|------|---------|:--------:| 52 | | [alb\_listener\_port](#input\_alb\_listener\_port) | ALB listener port | `number` | `"443"` | no | 53 | | [alb\_listener\_protocol](#input\_alb\_listener\_protocol) | ALB listener protocol | `string` | `"HTTPS"` | no | 54 | | [ami](#input\_ami) | AMI to be used to build the ec2 instance (via launch config) | `string` | n/a | yes | 55 | | [asg\_tags](#input\_asg\_tags) | Dynamic tags for ASG | `any` |

[
{
"key": "Name",
"propagate_at_launch": true,
"value": "tags need setting"
}
]
| no | 56 | | [autoscaling\_schedule\_create](#input\_autoscaling\_schedule\_create) | Allows for disabling of scheduled actions on ASG. Enabled by default | `number` | `1` | no | 57 | | [certificate\_arn](#input\_certificate\_arn) | ARN of the SSL certificate to use | `string` | n/a | yes | 58 | | [create\_dns\_record](#input\_create\_dns\_record) | Create friendly DNS CNAME | `bool` | `true` | no | 59 | | [custom\_userdata](#input\_custom\_userdata) | Set custom userdata | `string` | `""` | no | 60 | | [deletion\_window\_in\_days](#input\_deletion\_window\_in\_days) | Number of days before permanent removal | `number` | `"30"` | no | 61 | | [desired\_capacity](#input\_desired\_capacity) | AutoScaling Group desired capacity | `number` | `1` | no | 62 | | [domain\_name](#input\_domain\_name) | Domain Name | `string` | n/a | yes | 63 | | [efs\_encrypted](#input\_efs\_encrypted) | Encrypt the EFS share | `bool` | `true` | no | 64 | | [enable\_cross\_zone\_load\_balancing](#input\_enable\_cross\_zone\_load\_balancing) | Enable / Disable cross zone load balancing | `bool` | `false` | no | 65 | | [enable\_deletion\_protection](#input\_enable\_deletion\_protection) | Enable / Disable deletion protection for the ALB. | `bool` | `false` | no | 66 | | [enable\_key\_rotation](#input\_enable\_key\_rotation) | KMS key rotation | `bool` | `true` | no | 67 | | [enable\_monitoring](#input\_enable\_monitoring) | AutoScaling - enables/disables detailed monitoring | `bool` | `"false"` | no | 68 | | [encrypted](#input\_encrypted) | Encryption of volumes | `bool` | `true` | no | 69 | | [environment](#input\_environment) | Environment where resources are being created, for example DEV, UAT or PROD | `string` | n/a | yes | 70 | | [health\_check\_grace\_period](#input\_health\_check\_grace\_period) | AutoScaling health check grace period | `number` | `180` | no | 71 | | [health\_check\_type](#input\_health\_check\_type) | AutoScaling health check type. EC2 or ELB | `string` | `"ELB"` | no | 72 | | [healthy\_threshold](#input\_healthy\_threshold) | ALB healthy count | `number` | `2` | no | 73 | | [hostname\_prefix](#input\_hostname\_prefix) | Hostname prefix for the Jenkins server | `string` | `"jenkins"` | no | 74 | | [http\_listener\_required](#input\_http\_listener\_required) | Enables / Disables creating HTTP listener. Listener auto redirects to HTTPS | `bool` | `true` | no | 75 | | [iam\_instance\_profile](#input\_iam\_instance\_profile) | IAM instance profile for Jenkins server | `string` | `null` | no | 76 | | [instance\_type](#input\_instance\_type) | ec2 instance type | `string` | `"t3a.medium"` | no | 77 | | [internal](#input\_internal) | Is the ALB internal? | `bool` | `false` | no | 78 | | [interval](#input\_interval) | ALB health check interval | `number` | `20` | no | 79 | | [key\_name](#input\_key\_name) | ec2 key pair use | `string` | n/a | yes | 80 | | [listener1\_alb\_listener\_port](#input\_listener1\_alb\_listener\_port) | HTTP listener port | `number` | `80` | no | 81 | | [listener1\_alb\_listener\_protocol](#input\_listener1\_alb\_listener\_protocol) | HTTP listener protocol | `string` | `"HTTP"` | no | 82 | | [max\_size](#input\_max\_size) | AutoScaling Group max size | `number` | `1` | no | 83 | | [min\_size](#input\_min\_size) | AutoScaling Group min size | `number` | `1` | no | 84 | | [performance\_mode](#input\_performance\_mode) | EFS performance mode.https://docs.aws.amazon.com/efs/latest/ug/performance.html | `string` | `"generalPurpose"` | no | 85 | | [preliminary\_user\_data](#input\_preliminary\_user\_data) | Preliminary shell script commands for adding to user data.Runs at the beginning of userdata | `string` | `"#preliminary_user_data"` | no | 86 | | [private\_subnet\_a](#input\_private\_subnet\_a) | 1st private subnet id | `string` | n/a | yes | 87 | | [private\_subnet\_b](#input\_private\_subnet\_b) | 2nd private subnet id | `string` | n/a | yes | 88 | | [route53\_endpoint\_record](#input\_route53\_endpoint\_record) | Route 53 endpoint name. Creates route53\_endpoint\_record | `string` | `"jenkins"` | no | 89 | | [scale\_down\_cron](#input\_scale\_down\_cron) | The time when the recurring scale down action start.Cron format | `string` | `"0 0 * * SUN"` | no | 90 | | [scale\_up\_cron](#input\_scale\_up\_cron) | The time when the recurring scale up action start.Cron format | `string` | `"30 0 * * SUN"` | no | 91 | | [security\_groups](#input\_security\_groups) | List of security groups to assign to the ec2 instance. Create outside of module and pass in | `list(string)` | n/a | yes | 92 | | [security\_groups\_alb](#input\_security\_groups\_alb) | ALB Security Group. Create outside of module and pass in | `list(string)` | n/a | yes | 93 | | [security\_groups\_mount\_target\_a](#input\_security\_groups\_mount\_target\_a) | Security groups to use for mount target subnet a. Create outside of module and pass in | `list(string)` | n/a | yes | 94 | | [security\_groups\_mount\_target\_b](#input\_security\_groups\_mount\_target\_b) | Security groups to use for mount target subnet b. Create outside of module and pass in | `list(string)` | n/a | yes | 95 | | [subnets](#input\_subnets) | Subnets where the ALB will be placed | `list(string)` | n/a | yes | 96 | | [success\_codes](#input\_success\_codes) | Success Codes for the Target Group Health Checks. Default is 200 ( OK ) | `string` | `"200"` | no | 97 | | [supplementary\_user\_data](#input\_supplementary\_user\_data) | Supplementary shell script commands for adding to user data.Runs at the end of userdata | `string` | `"#supplementary_user_data"` | no | 98 | | [svc\_port](#input\_svc\_port) | Service port: The port on which targets receive traffic. | `number` | `8080` | no | 99 | | [tags](#input\_tags) | Tags map | `map(string)` | `{}` | no | 100 | | [target\_group\_path](#input\_target\_group\_path) | Health check request path | `string` | `"/"` | no | 101 | | [target\_group\_port](#input\_target\_group\_port) | The port to use to connect with the target | `number` | `"8080"` | no | 102 | | [target\_group\_protocol](#input\_target\_group\_protocol) | The protocol to use to connect to the target | `string` | `"HTTP"` | no | 103 | | [timeout](#input\_timeout) | ALB timeout value | `number` | `5` | no | 104 | | [unhealthy\_threshold](#input\_unhealthy\_threshold) | ALB unhealthy count | `number` | `10` | no | 105 | | [volume\_size](#input\_volume\_size) | ec2 volume size | `number` | `30` | no | 106 | | [volume\_type](#input\_volume\_type) | ec2 volume type | `string` | `"gp2"` | no | 107 | | [vpc\_id](#input\_vpc\_id) | VPC id | `string` | n/a | yes | 108 | | [vpc\_zone\_identifier](#input\_vpc\_zone\_identifier) | A list of subnet IDs to launch AutoScaling resources in. | `list(string)` | n/a | yes | 109 | | [zone\_id](#input\_zone\_id) | Route 53 zone id | `string` | `null` | no | 110 | 111 | ## Outputs 112 | 113 | | Name | Description | 114 | |------|-------------| 115 | | [asg\_id](#output\_asg\_id) | Jenkins ASG id | 116 | | [efs\_dns\_name](#output\_efs\_dns\_name) | DNS name of the EFS share | 117 | | [efs\_id](#output\_efs\_id) | ID of the EFS share | 118 | | [lb\_arn](#output\_lb\_arn) | Load balancer ARN | 119 | | [lb\_dns\_name](#output\_lb\_dns\_name) | Load balancer DNS Name | 120 | | [lb\_zone\_id](#output\_lb\_zone\_id) | Load balancer zone id | 121 | -------------------------------------------------------------------------------- /efs.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # EFS FileSystem 3 | # -------------------------- 4 | resource "aws_efs_file_system" "this" { 5 | depends_on = [aws_kms_key.efskey] 6 | 7 | encrypted = var.efs_encrypted 8 | performance_mode = var.performance_mode 9 | kms_key_id = aws_kms_key.efskey.arn 10 | tags = var.tags 11 | } 12 | 13 | # -------------------------- 14 | # Mount points 15 | # -------------------------- 16 | resource "aws_efs_mount_target" "private_subnet_b" { 17 | depends_on = [aws_efs_file_system.this] 18 | for_each = var.private_subnets 19 | file_system_id = aws_efs_file_system.this.id 20 | security_groups = var.security_groups_mount_target 21 | subnet_id = each.key 22 | } 23 | 24 | # -------------------------- 25 | # EFS backup 26 | # -------------------------- 27 | resource "aws_efs_backup_policy" "backup_policy" { 28 | file_system_id = aws_efs_file_system.this.id 29 | 30 | backup_policy { 31 | status = var.backup_policy 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/.terraform-version: -------------------------------------------------------------------------------- 1 | 1.5.0 2 | -------------------------------------------------------------------------------- /examples/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "5.4.0" 6 | constraints = ">= 3.29.0, >= 5.0.0" 7 | hashes = [ 8 | "h1:Jol4lNIzMrREQzUBSveCLX0iQLy7dm0OF+IYY2GKrhY=", 9 | "zh:1db5f81089216831bb0fdff9ddc3772efa133397c66ec276bc75b96eec06e23f", 10 | "zh:26fe5fdf399192b5724d21854fbec650c158f8ee9eb1dc52a50f7da0f2bc07ac", 11 | "zh:2946d9e333b1efe01588ee9f9771169fd3c3a4a7cb78ed8f91e8b3efd1a73850", 12 | "zh:36ed69e8d3029332c8a52a70940f714fd579b9fd95f5569cc010ef11162f5bf7", 13 | "zh:46ba5ad1c3a3ef98c346356cfa4bdd9c2501c661c2513bb92f4413f2482fb24b", 14 | "zh:46c10aaa9672b54a14b0e0effdd6ecd9b8a539b3bfe273ac54111e7352a7bb4b", 15 | "zh:47d7f57bcbe4fba2f960ab6c4228c5e9e586be2f233a8baa8962b51a63337179", 16 | "zh:47e41c198439ba1c4d933f808b6f47e518f8f0aae25ca42abcac97f149121e90", 17 | "zh:526c5834de71654ee14039cb973322bf5032cb684a2a113b48fb48a0584f46f3", 18 | "zh:6169316517b95677819ba2904dcea204fb9b55e868348e906af9164104fe7198", 19 | "zh:7c063ef2b8d69a8db7e8bf0dcd45793ede22b259b30464ed114d330df304cdbb", 20 | "zh:87c4f2faca636715a08be3121d26b3354415401eab89349077ca9436a0822c23", 21 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 22 | "zh:b184b8a268f45258edd27d389ca793708f1bc3ee4d6706d154a45e93deaddde1", 23 | "zh:ba1a998cbf4b639fa3e04b9069f0f5a289662457940726a8a51c81df400aa852", 24 | ] 25 | } 26 | 27 | provider "registry.terraform.io/hashicorp/template" { 28 | version = "2.2.0" 29 | hashes = [ 30 | "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", 31 | "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", 32 | "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", 33 | "zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603", 34 | "zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16", 35 | "zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776", 36 | "zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451", 37 | "zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae", 38 | "zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde", 39 | "zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d", 40 | "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2", 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | * Build the VPC first `terraform apply -target=module.vpc` 2 | * Build everything else `terraform apply` 3 | -------------------------------------------------------------------------------- /examples/data.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------- 2 | # Find latest Amazon Linux AMI 3 | # --------------------------------------------------- 4 | data "aws_ami" "latest_amazon_linux_ami" { 5 | most_recent = true 6 | 7 | # 8 | # 137112412989 - AWS 9 | # Beware of using anything other than this 10 | # 11 | owners = ["137112412989"] 12 | 13 | filter { 14 | name = "name" 15 | values = ["amzn2-ami-hvm-2.0.*-x86_64-gp2"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/main.tf: -------------------------------------------------------------------------------- 1 | # ----------------------- 2 | # Zone 3 | # ----------------------- 4 | resource "aws_route53_zone" "prd" { 5 | name = "test.domain.io" 6 | } 7 | # ----------------------- 8 | # Jenkins 9 | # ----------------------- 10 | module "jenkins" { 11 | depends_on = [module.jenkins-efs-sg, 12 | module.jenkins-alb-sg, 13 | module.vpc 14 | ] 15 | 16 | source = "Cloud-42/jenkins/aws" 17 | version = "8.3.0" 18 | 19 | instance_type = "t3a.medium" 20 | iam_instance_profile = module.jenkins-role.profile.name 21 | key_name = var.env 22 | environment = var.env 23 | zone_id = aws_route53_zone.prd.zone_id 24 | vpc_id = module.vpc.vpc_id 25 | domain_name = aws_route53_zone.prd.name 26 | ami = data.aws_ami.latest_amazon_linux_ami.id 27 | certificate_arn = "arn:aws:acm:eu-west-1:012345678990:certificate/11277fc4-cf28-418e-a03d-c33d61317288" 28 | target_group_path = "/login" 29 | tags = var.tags 30 | asg_tags = [{ 31 | key = "Name" 32 | value = "jenkins" 33 | propagate_at_launch = true 34 | }, 35 | { 36 | key = "ManagedBy" 37 | value = "Terraform" 38 | propagate_at_launch = true 39 | }, 40 | { 41 | key = "env" 42 | value = var.env 43 | propagate_at_launch = true 44 | } 45 | ] 46 | # 47 | # Security Groups 48 | # 49 | security_groups = [module.jenkins-ec2-sg.security_group_id] 50 | security_groups_alb = [module.jenkins-alb-sg.security_group_id] 51 | security_groups_mount_target = [module.jenkins-efs-sg.security_group_id] 52 | # 53 | # subnet assignments 54 | # 55 | private_subnets = toset(module.vpc.private_subnets) 56 | subnets = module.vpc.public_subnets 57 | } 58 | # ----------------------------- 59 | # Security Groups 60 | # ------------------------------ 61 | module "jenkins-efs-sg" { 62 | source = "terraform-aws-modules/security-group/aws" 63 | version = "5.1.0" 64 | 65 | name = "jenkins-efs-sg" 66 | description = "Security group for Jenkins EFS" 67 | vpc_id = module.vpc.vpc_id 68 | 69 | ingress_cidr_blocks = [module.vpc.vpc_cidr_block] 70 | ingress_rules = ["nfs-tcp"] 71 | egress_rules = ["all-all"] 72 | } 73 | module "jenkins-alb-sg" { 74 | source = "terraform-aws-modules/security-group/aws" 75 | version = "5.1.0" 76 | 77 | name = "jenkins-alb-sg" 78 | description = "Security group for access to Jenkins endpoint" 79 | vpc_id = module.vpc.vpc_id 80 | 81 | ingress_cidr_blocks = ["0.0.0.0/0"] 82 | ingress_rules = ["http-80-tcp", "https-443-tcp"] 83 | egress_rules = ["all-all"] 84 | } 85 | module "jenkins-ec2-sg" { 86 | depends_on = [module.jenkins-alb-sg] 87 | source = "terraform-aws-modules/security-group/aws" 88 | version = "5.1.0" 89 | 90 | name = "jenkins-sg" 91 | description = "Security group for Jenkins host" 92 | vpc_id = module.vpc.vpc_id 93 | 94 | ingress_with_source_security_group_id = [ 95 | { 96 | from_port = 8080 97 | to_port = 8080 98 | protocol = 6 99 | description = "Jenkins ALB" 100 | source_security_group_id = module.jenkins-alb-sg.security_group_id 101 | }, 102 | ] 103 | 104 | egress_rules = ["all-all"] 105 | } 106 | # ----------------------- 107 | # Role for Jenkins to use 108 | # ----------------------- 109 | module "jenkins-role" { 110 | source = "Cloud-42/ec2-iam-role/aws" 111 | version = "3.0.0" 112 | 113 | name = "jenkins" 114 | 115 | policy_arn = [ 116 | "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess", 117 | "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore", 118 | ] 119 | } 120 | # ----------------------- 121 | # VPC 122 | # ----------------------- 123 | module "vpc" { 124 | source = "terraform-aws-modules/vpc/aws" 125 | version = "5.5.1" 126 | 127 | name = var.env 128 | cidr = "172.18.0.0/16" 129 | 130 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 131 | private_subnets = ["172.18.0.0/19", "172.18.32.0/19", "172.18.64.0/19"] 132 | public_subnets = ["172.18.128.0/19", "172.18.160.0/19", "172.18.192.0/19"] 133 | 134 | enable_nat_gateway = true 135 | single_nat_gateway = true 136 | enable_dns_hostnames = true 137 | tags = var.tags 138 | } 139 | -------------------------------------------------------------------------------- /examples/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0.0" 7 | } 8 | template = { 9 | source = "hashicorp/template" 10 | version = ">= 1.0.0" 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | region = var.region 17 | allowed_account_ids = [var.account_id] 18 | } 19 | -------------------------------------------------------------------------------- /examples/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | type = string 3 | default = "test" 4 | } 5 | variable "tags" { 6 | type = map(string) 7 | default = { 8 | "env" = "test" 9 | "service" = "jenkins" 10 | } 11 | } 12 | variable "region" { 13 | type = string 14 | } 15 | 16 | variable "account_id" { 17 | type = string 18 | } 19 | -------------------------------------------------------------------------------- /kms.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------- 2 | # KMS Key for EFS 3 | # ------------------------------------------------------- 4 | resource "aws_kms_key" "efskey" { 5 | description = "This key is used to encrypt EFS, used by Jenkins, in ${var.environment}" 6 | deletion_window_in_days = var.deletion_window_in_days 7 | enable_key_rotation = var.enable_key_rotation 8 | } 9 | 10 | # Create KMS Alias 11 | 12 | resource "aws_kms_alias" "efs" { 13 | name = "alias/efs-jenkins-${var.environment}" 14 | target_key_id = aws_kms_key.efskey.key_id 15 | } 16 | 17 | -------------------------------------------------------------------------------- /lc.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # Launch Config 3 | # -------------------------- 4 | resource "aws_launch_configuration" "jenkins" { 5 | name_prefix = "terraform-jenkins-lc-" 6 | image_id = var.ami 7 | instance_type = var.instance_type 8 | key_name = var.key_name 9 | iam_instance_profile = var.iam_instance_profile 10 | security_groups = var.security_groups 11 | enable_monitoring = var.enable_monitoring 12 | user_data = var.custom_userdata != "" ? var.custom_userdata : data.template_file.user_data.rendered 13 | metadata_options { 14 | http_endpoint = "enabled" 15 | http_tokens = "required" 16 | } 17 | 18 | # Setup root block device 19 | root_block_device { 20 | volume_size = var.volume_size 21 | volume_type = var.volume_type 22 | encrypted = var.encrypted 23 | } 24 | 25 | # Create before destroy 26 | lifecycle { 27 | create_before_destroy = true 28 | } 29 | } 30 | 31 | # -------------------------- 32 | # Render userdata bootstrap file 33 | # -------------------------- 34 | data "template_file" "user_data" { 35 | template = file("${path.module}/userdata.sh") 36 | 37 | vars = { 38 | appliedhostname = var.hostname_prefix 39 | domain_name = var.domain_name 40 | environment = var.environment 41 | efs_dnsname = aws_efs_file_system.this.dns_name 42 | preliminary_user_data = var.preliminary_user_data 43 | supplementary_user_data = var.supplementary_user_data 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 'example-docs' 2 | 3 | nav: 4 | - Home: index.md 5 | 6 | plugins: 7 | - techdocs-core 8 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "asg_id" { 2 | description = "Jenkins ASG id" 3 | value = [aws_autoscaling_group.jenkins.id] 4 | } 5 | 6 | output "efs_dns_name" { 7 | description = "DNS name of the EFS share" 8 | value = aws_efs_file_system.this.dns_name 9 | } 10 | 11 | output "efs_id" { 12 | description = "ID of the EFS share" 13 | value = aws_efs_file_system.this.id 14 | } 15 | 16 | output "lb_arn" { 17 | description = "Load balancer ARN" 18 | value = aws_lb.jenkins.arn 19 | } 20 | 21 | output "lb_dns_name" { 22 | description = "Load balancer DNS Name" 23 | value = aws_lb.jenkins.dns_name 24 | } 25 | 26 | output "lb_zone_id" { 27 | description = "Load balancer zone id" 28 | value = aws_lb.jenkins.zone_id 29 | } -------------------------------------------------------------------------------- /r53.tf: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # Route 53 entry for the ALB 3 | # -------------------------- 4 | resource "aws_route53_record" "alb" { 5 | count = var.create_dns_record ? 1 : 0 6 | # Endpoint DNS record 7 | 8 | zone_id = var.zone_id 9 | name = var.route53_endpoint_record 10 | type = "CNAME" 11 | ttl = "300" 12 | 13 | # matches up record N to instance N 14 | records = [aws_lb.jenkins.dns_name] 15 | } 16 | 17 | -------------------------------------------------------------------------------- /userdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------- 3 | # Allow for additional commands 4 | # ---------------- 5 | ${preliminary_user_data} 6 | 7 | # Mounting will be retried for 10 minutes (60 retries x 10 seconds) 8 | efs_mount_max_retry=60 9 | efs_mount_retry_sleep=10 10 | # Recommended mount options are explained here: 11 | # https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-nfs-mount-settings.html 12 | efs_mount_command='mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport ${efs_dnsname}:/ /efsmnt' 13 | 14 | awk -F= '/^NAME/{print $2}' /etc/os-release | grep -i ubuntu 15 | RESULTUBUNTU=$? 16 | if [ $RESULTUBUNTU -eq 0 ]; then 17 | 18 | # Set hostname, ensure it remains 19 | hostnamectl set-hostname ${appliedhostname}.${domain_name} 20 | # Create initial hostname entry to remove: 21 | # 'unable to resolve host ubuntu' error 22 | echo $(hostname -I | cut -d\ -f1) $(hostname) | sudo tee -a /etc/hosts 23 | # Install Java 11 24 | /usr/bin/apt-get update -y 25 | /usr/bin/apt install openjdk-11-jdk -y 26 | # Create EFS mount folder & mount 27 | /usr/bin/apt-get install nfs-common -y 28 | mkdir /efsmnt 29 | echo 'Attempting to mount EFS filesystem ${efs_dnsname}...' 30 | counter=0 31 | until $efs_mount_command 32 | do 33 | sleep $efs_mount_retry_sleep 34 | [[ $counter -eq $efs_mount_max_retry ]] && echo 'EFS mount failed! Mounting aborted, local disk will be used instead. Aborting...' && exit 1 35 | echo "Retrying to mount EFS filesystem ${efs_dnsname}... (retry #$counter)" 36 | ((counter++)) 37 | done 38 | echo 'Mounting done.' 39 | echo '------- Output of `mount | grep /efsmnt`: ------' 40 | mount | grep /efsmnt 41 | echo '------- End of output ------' 42 | echo '${efs_dnsname}:/ /efsmnt nfs defaults,_netdev 0 0' >> /etc/fstab 43 | # Install Jenkins 44 | wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo apt-key add - 45 | sh -c 'echo deb https://pkg.jenkins.io/debian binary/ > /etc/apt/sources.list.d/jenkins.list' 46 | /usr/bin/apt-get update -y 47 | /usr/bin/apt-get install jenkins -y 48 | # Ensure we are running the latest WAR 49 | service jenkins stop 50 | chown jenkins:jenkins /efsmnt 51 | apt-get install --only-upgrade jenkins -y 52 | # Mount JENKINS_HOME -> EFS 53 | /bin/sed -i '/JENKINS_HOME=/c\Environment="JENKINS_HOME=/efsmnt"' /usr/lib/systemd/system/jenkins.service 54 | /bin/sed -i '/WorkingDirectory=/c\WorkingDirectory=/efsmnt' /usr/lib/systemd/system/jenkins.service 55 | # Lets ensure state: 56 | # * EFS mounted 57 | # * Mounts are all working 58 | # * Jenkins user and group own /efsmnt 59 | /bin/systemctl stop jenkins 60 | /bin/chown jenkins:jenkins /efsmnt 61 | mount -a 62 | /bin/systemctl daemon-reload && /bin/systemctl start jenkins && /bin/systemctl enable jenkins 63 | 64 | fi 65 | 66 | awk -F= '/^NAME/{print $2}' /etc/os-release | grep -i amazon 67 | RESULTAMAZON=$? 68 | if [ $RESULTAMAZON -eq 0 ]; then 69 | 70 | # Set hostname, ensure it remains 71 | hostnamectl set-hostname ${appliedhostname}.${domain_name} 72 | echo $(hostname -I | cut -d\ -f1) $(hostname) | sudo tee -a /etc/hosts 73 | # Add EPEL - deals with this issue - https://stackoverflow.com/questions/68915374/how-to-solve-error-during-jenkins-installation 74 | amazon-linux-extras install epel -y 75 | # Install Java 76 | amazon-linux-extras install java-openjdk11 -y 77 | # Create EFS mount folder & mount 78 | /bin/yum -y install nfs-utils 79 | mkdir /efsmnt 80 | echo 'Attempting to mount EFS filesystem ${efs_dnsname}...' 81 | counter=0 82 | until $efs_mount_command 83 | do 84 | sleep $efs_mount_retry_sleep 85 | [[ $counter -eq $efs_mount_max_retry ]] && echo 'EFS mount failed! Mounting aborted, local disk will be used instead. Aborting...' && exit 1 86 | echo "Retrying to mount EFS filesystem ${efs_dnsname}... (retry #$counter)" 87 | ((counter++)) 88 | done 89 | echo 'Mounting done.' 90 | echo '------- Output of `mount | grep /efsmnt`: ------' 91 | mount | grep /efsmnt 92 | echo '------- End of output ------' 93 | echo '${efs_dnsname}:/ /efsmnt nfs defaults,_netdev 0 0' >> /etc/fstab 94 | # Install Jenkins 95 | /bin/curl --silent --location http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo | sudo tee /etc/yum.repos.d/jenkins.repo 96 | sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key 97 | /bin/yum update -y && /bin/yum install jenkins -y 98 | /bin/systemctl stop jenkins 99 | # Mount JENKINS_HOME -> EFS 100 | /bin/sed -i '/JENKINS_HOME=/c\Environment="JENKINS_HOME=/efsmnt"' /usr/lib/systemd/system/jenkins.service 101 | /bin/sed -i '/WorkingDirectory=/c\WorkingDirectory=/efsmnt' /usr/lib/systemd/system/jenkins.service 102 | # Lets ensure state: 103 | # * EFS mounted 104 | # * Mounts are all working 105 | # * Jenkins user and group own /efsmnt 106 | /bin/chown jenkins:jenkins /efsmnt 107 | mount -a 108 | /bin/systemctl daemon-reload && /bin/systemctl start jenkins && /bin/systemctl enable jenkins 109 | 110 | fi 111 | # ---------------- 112 | # Allow for additional commands 113 | # ---------------- 114 | ${supplementary_user_data} 115 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "custom_userdata" { 2 | description = "Set custom userdata" 3 | type = string 4 | default = "" 5 | } 6 | variable "create_dns_record" { 7 | description = "Create friendly DNS CNAME" 8 | type = bool 9 | default = true 10 | } 11 | variable "security_groups" { 12 | description = "List of security groups to assign to the ec2 instance. Create outside of module and pass in" 13 | type = list(string) 14 | } 15 | variable "tags" { 16 | description = "Tags map" 17 | type = map(string) 18 | default = {} 19 | } 20 | variable "asg_tags" { 21 | type = any 22 | description = "Dynamic tags for ASG" 23 | default = [{ 24 | key = "Name" 25 | value = "tags need setting" 26 | propagate_at_launch = true 27 | }] 28 | } 29 | variable "preliminary_user_data" { 30 | type = string 31 | description = "Preliminary shell script commands for adding to user data.Runs at the beginning of userdata" 32 | default = "#preliminary_user_data" 33 | } 34 | variable "supplementary_user_data" { 35 | type = string 36 | description = "Supplementary shell script commands for adding to user data.Runs at the end of userdata" 37 | default = "#supplementary_user_data" 38 | } 39 | variable "iam_instance_profile" { 40 | type = string 41 | description = "IAM instance profile for Jenkins server" 42 | default = null 43 | } 44 | variable "autoscaling_schedule_create" { 45 | type = number 46 | description = "Allows for disabling of scheduled actions on ASG. Enabled by default" 47 | default = 1 48 | } 49 | variable "route53_endpoint_record" { 50 | type = string 51 | description = "Route 53 endpoint name. Creates route53_endpoint_record " 52 | default = "jenkins" 53 | } 54 | variable "ami" { 55 | type = string 56 | description = "AMI to be used to build the ec2 instance (via launch config)" 57 | } 58 | variable "success_codes" { 59 | description = "Success Codes for the Target Group Health Checks. Default is 200 ( OK )" 60 | type = string 61 | default = "200" 62 | } 63 | variable "security_groups_alb" { 64 | type = list(string) 65 | description = "ALB Security Group. Create outside of module and pass in" 66 | } 67 | variable "ssl_policy" { 68 | type = string 69 | description = "Name of the SSL Policy for the listener" 70 | default = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06" 71 | } 72 | variable "certificate_arn" { 73 | type = string 74 | description = "ARN of the SSL certificate to use" 75 | } 76 | variable "vpc_id" { 77 | type = string 78 | description = "VPC id" 79 | } 80 | variable "environment" { 81 | type = string 82 | description = "Environment where resources are being created, for example DEV, UAT or PROD" 83 | } 84 | variable "key_name" { 85 | type = string 86 | description = "ec2 key pair use" 87 | } 88 | variable "instance_type" { 89 | type = string 90 | description = "ec2 instance type" 91 | default = "t3a.medium" 92 | } 93 | variable "hostname_prefix" { 94 | type = string 95 | description = "Hostname prefix for the Jenkins server" 96 | default = "jenkins" 97 | } 98 | variable "domain_name" { 99 | type = string 100 | description = "Domain Name" 101 | } 102 | variable "zone_id" { 103 | type = string 104 | description = "Route 53 zone id" 105 | default = null 106 | } 107 | variable "encrypted" { 108 | type = bool 109 | description = "Encryption of volumes" 110 | default = true 111 | } 112 | # --------------------------- 113 | # ALB Vars 114 | # --------------------------- 115 | variable "subnets" { 116 | type = list(string) 117 | description = "Subnets where the ALB will be placed" 118 | } 119 | variable "enable_deletion_protection" { 120 | type = bool 121 | description = "Enable / Disable deletion protection for the ALB." 122 | default = false 123 | } 124 | variable "enable_cross_zone_load_balancing" { 125 | type = bool 126 | description = "Enable / Disable cross zone load balancing" 127 | default = false 128 | } 129 | variable "internal" { 130 | type = bool 131 | description = "Is the ALB internal?" 132 | default = false 133 | } 134 | variable "http_listener_required" { 135 | type = bool 136 | description = "Enables / Disables creating HTTP listener. Listener auto redirects to HTTPS" 137 | default = true 138 | } 139 | variable "listener1_alb_listener_port" { 140 | type = number 141 | description = "HTTP listener port" 142 | default = 80 143 | } 144 | variable "listener1_alb_listener_protocol" { 145 | type = string 146 | description = "HTTP listener protocol" 147 | default = "HTTP" 148 | } 149 | # Listener port and protocol need to match 150 | variable "alb_listener_port" { 151 | type = number 152 | description = "ALB listener port" 153 | default = "443" 154 | } 155 | variable "alb_listener_protocol" { 156 | type = string 157 | description = "ALB listener protocol" 158 | default = "HTTPS" 159 | } 160 | variable "healthy_threshold" { 161 | type = number 162 | description = "ALB healthy count" 163 | default = 2 164 | } 165 | variable "unhealthy_threshold" { 166 | type = number 167 | description = "ALB unhealthy count" 168 | default = 10 169 | } 170 | variable "timeout" { 171 | type = number 172 | description = "ALB timeout value" 173 | default = 5 174 | } 175 | variable "interval" { 176 | type = number 177 | description = "ALB health check interval" 178 | default = 20 179 | } 180 | variable "svc_port" { 181 | type = number 182 | description = "Service port: The port on which targets receive traffic." 183 | default = 8080 184 | } 185 | variable "target_group_path" { 186 | type = string 187 | description = "Health check request path" 188 | default = "/" 189 | } 190 | variable "target_group_protocol" { 191 | type = string 192 | description = "The protocol to use to connect to the target" 193 | default = "HTTP" 194 | } 195 | variable "target_group_port" { 196 | type = number 197 | description = "The port to use to connect with the target" 198 | default = "8080" 199 | } 200 | variable "drop_invalid_header_fields" { 201 | type = bool 202 | description = "Whether HTTP headers with header fields that are not valid are removed by the load balancer" 203 | default = true 204 | } 205 | # --------------------------- 206 | # EFS Vars 207 | # --------------------------- 208 | variable "backup_policy" { 209 | type = string 210 | description = "EFS backup policy" 211 | default = "ENABLED" 212 | } 213 | variable "efs_encrypted" { 214 | type = bool 215 | description = "Encrypt the EFS share" 216 | default = true 217 | } 218 | variable "deletion_window_in_days" { 219 | type = number 220 | description = "Number of days before permanent removal" 221 | default = "30" 222 | } 223 | variable "enable_key_rotation" { 224 | type = bool 225 | description = "KMS key rotation" 226 | default = true 227 | } 228 | variable "performance_mode" { 229 | type = string 230 | description = "EFS performance mode.https://docs.aws.amazon.com/efs/latest/ug/performance.html" 231 | default = "generalPurpose" 232 | } 233 | variable "private_subnets" { 234 | type = set(any) 235 | description = "IDs of private subnets" 236 | } 237 | variable "security_groups_mount_target" { 238 | type = list(string) 239 | description = "Security groups to use for mount target subnet a. Create outside of module and pass in" 240 | } 241 | 242 | # --------------------------- 243 | # ASG & LC Vars 244 | # --------------------------- 245 | variable "max_size" { 246 | type = number 247 | description = "AutoScaling Group max size" 248 | default = 1 249 | } 250 | variable "min_size" { 251 | type = number 252 | description = "AutoScaling Group min size" 253 | default = 1 254 | } 255 | variable "desired_capacity" { 256 | type = number 257 | description = "AutoScaling Group desired capacity" 258 | default = 1 259 | } 260 | variable "enable_monitoring" { 261 | type = bool 262 | description = "AutoScaling - enables/disables detailed monitoring" 263 | default = "false" 264 | } 265 | variable "health_check_grace_period" { 266 | type = number 267 | description = "AutoScaling health check grace period" 268 | default = 180 269 | } 270 | variable "health_check_type" { 271 | type = string 272 | description = "AutoScaling health check type. EC2 or ELB" 273 | default = "ELB" 274 | } 275 | variable "volume_type" { 276 | type = string 277 | description = "ec2 volume type" 278 | default = "gp2" 279 | } 280 | variable "volume_size" { 281 | type = number 282 | description = "ec2 volume size" 283 | default = 30 284 | } 285 | variable "scale_up_cron" { 286 | type = string 287 | description = "The time when the recurring scale up action start.Cron format" 288 | default = "30 0 * * SUN" 289 | } 290 | variable "scale_down_cron" { 291 | type = string 292 | description = "The time when the recurring scale down action start.Cron format" 293 | default = "0 0 * * SUN" 294 | } 295 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0.0" 7 | } 8 | template = { 9 | source = "hashicorp/template" 10 | version = ">= 1.0.0" 11 | } 12 | } 13 | } 14 | --------------------------------------------------------------------------------