├── .github ├── CODEOWNERS └── workflows │ ├── misspell.yaml │ ├── pre-commit-check.yaml │ ├── pre-commit.yaml │ ├── pullRequest.yaml │ ├── tflint.yaml │ ├── tfsec.yaml │ └── yamllint.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .tflint.hcl ├── .yamllint.yml ├── LICENSE ├── README.md ├── bin ├── install-macos.sh └── install-ubuntu.sh ├── examples ├── basic │ ├── README.md │ ├── common.tf │ ├── main.tf │ └── variables.tf └── pipeline │ ├── README.md │ └── main.tf ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rhythmictech/engineering 2 | -------------------------------------------------------------------------------- /.github/workflows/misspell.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: misspell 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - prod 9 | - develop 10 | 11 | jobs: 12 | misspell: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: misspell 17 | uses: reviewdog/action-misspell@v1 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | locale: "US" 21 | reporter: github-check 22 | filter_mode: nofilter 23 | level: error 24 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit-check.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pre-commit-check 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: macOS-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Install prerequisites 16 | run: ./bin/install-macos.sh 17 | - name: initiallize Terraform 18 | run: terraform init --backend=false 19 | - uses: actions/cache@v1 20 | with: 21 | path: ~/.cache/pre-commit 22 | key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} 23 | restore-keys: | 24 | pre-commit 25 | - name: pre-commit run all 26 | run: | 27 | pre-commit run -a 28 | env: 29 | AWS_DEFAULT_REGION: us-east-1 30 | SKIP: terraform_tflint_deep 31 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pre-commit-check 3 | on: 4 | push: 5 | branches: -- main 6 | - master 7 | - prod 8 | - develop 9 | 10 | jobs: 11 | pre-commit-check: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | - name: Install prerequisites 18 | run: ./bin/install-ubuntu.sh 19 | - name: initialize Terraform 20 | run: terraform init --backend=false 21 | - name: pre-commit 22 | uses: pre-commit/action@v2.0.0 23 | env: 24 | AWS_DEFAULT_REGION: us-east-1 25 | # many of these are covered by better reviewdog linters below 26 | SKIP: >- 27 | terraform_tflint_deep, 28 | no-commit-to-branch, 29 | terraform_tflint_nocreds, 30 | terraform_tfsec 31 | - uses: stefanzweifel/git-auto-commit-action@v4 32 | if: ${{ failure() }} 33 | with: 34 | commit_message: Apply automatic changes 35 | commit_options: "--no-verify" 36 | # Optional commit user and author settings 37 | commit_user_name: Linter Bot 38 | commit_user_email: noreply@rhythmictech.com 39 | commit_author: Linter Bot 40 | -------------------------------------------------------------------------------- /.github/workflows/pullRequest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pull request 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | pre-commit: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 12 | uses: actions/setup-python@v2 13 | - name: Install prerequisites 14 | run: ./bin/install-ubuntu.sh 15 | - name: initialize Terraform 16 | run: terraform init --backend=false 17 | - name: pre-commit 18 | uses: pre-commit/action@v2.0.0 19 | env: 20 | AWS_DEFAULT_REGION: us-east-1 21 | # many of these are covered by better reviewdog linters below 22 | SKIP: >- 23 | terraform_tflint_deep, 24 | no-commit-to-branch, 25 | terraform_tflint_nocreds, 26 | terraform_tfsec 27 | - uses: stefanzweifel/git-auto-commit-action@v4 28 | if: ${{ failure() }} 29 | with: 30 | commit_message: Apply automatic changes 31 | commit_options: "--no-verify" 32 | # Optional commit user and author settings 33 | commit_user_name: Linter Bot 34 | commit_user_email: noreply@rhythmictech.com 35 | commit_author: Linter Bot 36 | tflint: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: setup Terraform 41 | uses: hashicorp/setup-terraform@v1 42 | with: 43 | terraform_version: 0.12.26 44 | - name: Terraform init 45 | run: terraform init --backend=false 46 | - name: tflint 47 | uses: reviewdog/action-tflint@master 48 | with: 49 | github_token: ${{ secrets.GITHUB_TOKEN }} 50 | reporter: github-pr-check 51 | filter_mode: added 52 | flags: --module 53 | level: error 54 | tfsec: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v2 58 | - name: setup Terraform 59 | uses: hashicorp/setup-terraform@v1 60 | with: 61 | terraform_version: 0.12.26 62 | - name: Terraform init 63 | run: terraform init --backend=false 64 | - name: tfsec 65 | uses: reviewdog/action-tfsec@master 66 | with: 67 | github_token: ${{ secrets.GITHUB_TOKEN }} 68 | reporter: github-pr-check 69 | filter_mode: added 70 | level: warning 71 | misspell: 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v2 75 | - name: misspell 76 | uses: reviewdog/action-misspell@v1 77 | with: 78 | github_token: ${{ secrets.GITHUB_TOKEN }} 79 | locale: "US" 80 | reporter: github-pr-check 81 | filter_mode: added 82 | level: error 83 | yamllint: 84 | runs-on: ubuntu-latest 85 | steps: 86 | - uses: actions/checkout@v2 87 | - name: yamllint 88 | uses: reviewdog/action-yamllint@v1 89 | with: 90 | github_token: ${{ secrets.GITHUB_TOKEN }} 91 | reporter: github-pr-check 92 | filter_mode: added 93 | level: error 94 | -------------------------------------------------------------------------------- /.github/workflows/tflint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tflint 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - prod 9 | - develop 10 | 11 | jobs: 12 | tflint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: setup Terraform 17 | uses: hashicorp/setup-terraform@v1 18 | with: 19 | terraform_version: 0.12.26 20 | - name: Terraform init 21 | run: terraform init --backend=false 22 | - name: tflint 23 | uses: reviewdog/action-tflint@master 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | reporter: github-check 27 | filter_mode: nofilter 28 | flags: --module 29 | level: error 30 | -------------------------------------------------------------------------------- /.github/workflows/tfsec.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tfsec 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - prod 9 | - develop 10 | 11 | jobs: 12 | tfsec: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: setup Terraform 17 | uses: hashicorp/setup-terraform@v1 18 | with: 19 | terraform_version: 0.12.26 20 | - name: Terraform init 21 | run: terraform init --backend=false 22 | - name: tfsec 23 | uses: reviewdog/action-tfsec@master 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | reporter: github-check 27 | filter_mode: nofilter 28 | level: error 29 | -------------------------------------------------------------------------------- /.github/workflows/yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: yamllint 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - prod 9 | - develop 10 | 11 | jobs: 12 | yamllint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: yamllint 17 | uses: reviewdog/action-yamllint@v1 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | reporter: github-check 21 | filter_mode: nofilter 22 | level: error 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | !example/**/*.tfvars 11 | 12 | # pem files 13 | *.pem 14 | 15 | *test.yml 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.77.0 4 | hooks: 5 | - id: terraform_docs 6 | always_run: true 7 | - id: terraform_fmt 8 | - id: terraform_tflint 9 | alias: terraform_tflint_deep 10 | name: terraform_tflint_deep 11 | args: 12 | - --args=--deep 13 | - id: terraform_tflint 14 | alias: terraform_tflint_nocreds 15 | name: terraform_tflint_nocreds 16 | - id: terraform_tfsec 17 | - repo: local 18 | hooks: 19 | - id: terraform_validate 20 | name: terraform_validate 21 | entry: | 22 | bash -c ' 23 | AWS_DEFAULT_REGION=us-east-1 24 | declare -a DIRS 25 | for FILE in "$@" 26 | do 27 | DIRS+=($(dirname "$FILE")) 28 | done 29 | for DIR in $(printf "%s\n" "${DIRS[@]}" | sort -u) 30 | do 31 | cd $(dirname "$FILE") 32 | terraform init --backend=false 33 | terraform validate . 34 | done 35 | ' 36 | language: system 37 | verbose: true 38 | files: \.tf(vars)?$ 39 | exclude: examples 40 | - repo: https://github.com/pre-commit/pre-commit-hooks 41 | rev: v3.0.0 42 | hooks: 43 | - id: check-case-conflict 44 | - id: check-json 45 | - id: check-merge-conflict 46 | - id: check-symlinks 47 | - id: check-yaml 48 | args: 49 | - --unsafe 50 | - id: end-of-file-fixer 51 | - id: mixed-line-ending 52 | args: 53 | - --fix=lf 54 | - id: no-commit-to-branch 55 | args: 56 | - --branch 57 | - main 58 | - --branch 59 | - master 60 | - --branch 61 | - prod 62 | - id: pretty-format-json 63 | args: 64 | - --autofix 65 | - --top-keys=name,Name 66 | - id: trailing-whitespace 67 | args: 68 | - --markdown-linebreak-ext=md 69 | exclude: README.md 70 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | config { 2 | module = true 3 | } 4 | 5 | rule "terraform_deprecated_interpolation" { 6 | enabled = true 7 | } 8 | 9 | rule "terraform_unused_declarations" { 10 | enabled = true 11 | } 12 | 13 | rule "terraform_comment_syntax" { 14 | enabled = true 15 | } 16 | 17 | rule "terraform_documented_outputs" { 18 | enabled = true 19 | } 20 | 21 | rule "terraform_documented_variables" { 22 | enabled = true 23 | } 24 | 25 | rule "terraform_typed_variables" { 26 | enabled = true 27 | } 28 | 29 | rule "terraform_module_pinned_source" { 30 | enabled = true 31 | } 32 | 33 | rule "terraform_naming_convention" { 34 | enabled = true 35 | format = "snake_case" 36 | } 37 | 38 | rule "terraform_required_version" { 39 | enabled = true 40 | } 41 | 42 | rule "terraform_required_providers" { 43 | enabled = true 44 | } 45 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | truthy: 2 | check-keys: false 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rhythmic Technologies, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-aws-imagebuilder-pipeline 2 | [![tflint](https://github.com/rhythmictech/terraform-aws-rds-mysql/workflows/tflint/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-rds-mysql/actions?query=workflow%3Atflint+event%3Apush+branch%3Amaster) 3 | [![tfsec](https://github.com/rhythmictech/terraform-aws-rds-mysql/workflows/tfsec/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-rds-mysql/actions?query=workflow%3Atfsec+event%3Apush+branch%3Amaster) 4 | [![yamllint](https://github.com/rhythmictech/terraform-aws-rds-mysql/workflows/yamllint/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-rds-mysql/actions?query=workflow%3Ayamllint+event%3Apush+branch%3Amaster) 5 | [![misspell](https://github.com/rhythmictech/terraform-aws-rds-mysql/workflows/misspell/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-rds-mysql/actions?query=workflow%3Amisspell+event%3Apush+branch%3Amaster) 6 | [![pre-commit-check](https://github.com/rhythmictech/terraform-aws-rds-mysql/workflows/pre-commit-check/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-rds-mysql/actions?query=workflow%3Apre-commit-check+event%3Apush+branch%3Amaster) 7 | follow on Twitter 8 | 9 | 10 | Terraform module for creating EC2 Image Builder Pipelines 11 | 12 | ## Example 13 | Here's what using the module will look like. Note that this module needs at least one recipe and component to be useful. See `examples` for details. 14 | ```hcl 15 | module "test_pipeline" { 16 | source = "rhythmictech/imagebuilder-pipeline/aws" 17 | 18 | description = "Testing pipeline" 19 | name = "test-pipeline" 20 | recipe_arn = module.test_recipe.recipe_arn 21 | public = false 22 | } 23 | ``` 24 | 25 | ## About 26 | Allows the creation of EC2 Image Builder Pipelines 27 | 28 | ## Build Scheduling 29 | Builds are scheduled by a cron pattern. The pipeline takes a schedule argument as follows: 30 | 31 | ```hcl 32 | schedule_cron = "cron(0 0 * * mon)" 33 | schedule_pipeline_execution_start_condition = "EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE" 34 | 35 | ``` 36 | 37 | The default expects an upstream AMI as a parent image and will build weekly *only if an updated image is found upstream*. By setting `schedule_pipeline_execution_start_condition = "EXPRESSION_MATCH_ONLY"`, the build pipeline will always run. 38 | 39 | When scheduling linked jobs, it is important to be mindful of the cron schedules. If both pipelines run with `schedule_cron = "cron(0 0 * * mon)"`, the downstream build will always run one week late. Due to the testing phase and startup/teardown time, even a short EC2 Image Builder process can take over 15 minutes to run end to end. Complex test suites can take much longer. 40 | 41 | See Amazon's [EC2 Image Builder API Reference](https://docs.aws.amazon.com/imagebuilder/latest/APIReference/API_Schedule.html) for further details. 42 | 43 | ## Providing Launch Template configurations 44 | If you want to update launch configurations as part of the Image Build process, you can provide them with the launch_template_configurations variable. It accepts a map of regions, where each region is a list of launch template configuration maps (one per account) for that region. It will look like this: 45 | ```hcl 46 | launch_template_configurations = { 47 | "us-east-1" = [ 48 | { 49 | launch_template_id = "lt-0f1aedef76c015126" 50 | account_id = "123456789012" 51 | }, 52 | { 53 | launch_template_id = "lt-0f1aedef86c049140" 54 | account_id = "234567890123" 55 | default = "false" 56 | } 57 | ] 58 | "us-west-1" = [ 59 | { 60 | launch_template_id = "lt-0f1aedef76c015113" 61 | account_id = "123456789012" 62 | } 63 | ] 64 | } 65 | ``` 66 | Note that you do not have to provide a launch template configuration for every account and region you build AMIs in. You will also need to set up IAM permissions in the destination accounts per https://docs.aws.amazon.com/imagebuilder/latest/userguide/cross-account-dist.html. (You will need to set similar permissions via `additional_iam_policy_arns` for your own image builder pipeline if it is writing to your own account) 67 | 68 | ## Providing your own Distribution Configuration 69 | By default this module will try to handle the aws_imagebuilder_distribution_configuration configuration by itself. This works for more simple builds that only need to create EC2 images, but it may not be suitable for all users. The `custom_distribution_configs` aims to handle this by allowing users to provide a list of distribution configuration blocks, based off of the terraform described at https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_distribution_configuration#distribution. Where additional configuration blocks are present, they must be replaced with a map of the same name. An example of this is: 70 | ```hcl 71 | custom_distribution_configs = [ 72 | { 73 | region = "us-east-1", 74 | ami_distribution_configuration = { 75 | name = "example-build-{{ imagebuilder:buildDate }}" 76 | launch_permission = { 77 | user_ids = ["123456789012"] 78 | } 79 | } 80 | launch_template_configuration = { 81 | launch_template_id = "lt-0123456789abcde" 82 | } 83 | }, 84 | { 85 | region = "us-west-1" 86 | ami_distribution_configuration = { 87 | name = "example-build-{{ imagebuilder:buildDate }}" 88 | } 89 | ... 90 | } 91 | ] 92 | ``` 93 | 94 | 95 | ## Requirements 96 | 97 | | Name | Version | 98 | |------|---------| 99 | | [terraform](#requirement\_terraform) | >= 0.14 | 100 | | [aws](#requirement\_aws) | >= 4.22.0 | 101 | 102 | ## Providers 103 | 104 | | Name | Version | 105 | |------|---------| 106 | | [aws](#provider\_aws) | 4.66.0 | 107 | 108 | ## Modules 109 | 110 | No modules. 111 | 112 | ## Resources 113 | 114 | | Name | Type | 115 | |------|------| 116 | | [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | 117 | | [aws_iam_policy.log_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 118 | | [aws_iam_policy.secret_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 119 | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 120 | | [aws_iam_role_policy_attachment.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 121 | | [aws_iam_role_policy_attachment.core](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 122 | | [aws_iam_role_policy_attachment.log_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 123 | | [aws_iam_role_policy_attachment.secret_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 124 | | [aws_imagebuilder_distribution_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_distribution_configuration) | resource | 125 | | [aws_imagebuilder_image_pipeline.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_image_pipeline) | resource | 126 | | [aws_imagebuilder_infrastructure_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_infrastructure_configuration) | resource | 127 | | [aws_iam_policy_document.assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 128 | | [aws_iam_policy_document.log_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 129 | | [aws_iam_policy_document.secret_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 130 | | [aws_secretsmanager_secret.ssh_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/secretsmanager_secret) | data source | 131 | 132 | ## Inputs 133 | 134 | | Name | Description | Type | Default | Required | 135 | |------|-------------|------|---------|:--------:| 136 | | [additional\_iam\_policy\_arns](#input\_additional\_iam\_policy\_arns) | List of ARN policies for addional builder permissions | `list(string)` | `[]` | no | 137 | | [container\_recipe\_arn](#input\_container\_recipe\_arn) | ARN of the container recipe to use. Must change with Recipe version | `string` | `null` | no | 138 | | [custom\_distribution\_configs](#input\_custom\_distribution\_configs) | To use your own distribution configurations for the ImageBuilder Distribution Configuration, supply a list of distribution configuration blocks as defined at https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_distribution_configuration#distribution | `any` | `[]` | no | 139 | | [description](#input\_description) | description of component | `string` | `null` | no | 140 | | [enabled](#input\_enabled) | Whether pipeline is ENABLED or DISABLED | `bool` | `true` | no | 141 | | [enhanced\_image\_metadata\_enabled](#input\_enhanced\_image\_metadata\_enabled) | Whether additional information about the image being created is collected. Default is true. | `bool` | `true` | no | 142 | | [image\_name](#input\_image\_name) | The name prefix given to the AMI created by the pipeline (a timestamp will be added to the end) | `string` | `""` | no | 143 | | [image\_recipe\_arn](#input\_image\_recipe\_arn) | ARN of the image recipe to use. Must change with Recipe version | `string` | `null` | no | 144 | | [image\_tests\_enabled](#input\_image\_tests\_enabled) | Whether to run tests during image creation | `bool` | `true` | no | 145 | | [image\_tests\_timeout\_minutes](#input\_image\_tests\_timeout\_minutes) | Maximum time to allow for image tests to run | `number` | `60` | no | 146 | | [instance\_key\_pair](#input\_instance\_key\_pair) | EC2 key pair to add to the default user on the builder | `string` | `null` | no | 147 | | [instance\_metadata\_http\_put\_hop\_limit](#input\_instance\_metadata\_http\_put\_hop\_limit) | The number of hops that an instance can traverse to reach its metadata. | `number` | `null` | no | 148 | | [instance\_metadata\_http\_tokens](#input\_instance\_metadata\_http\_tokens) | Whether a signed token is required for instance metadata retrieval requests. Valid values: required, optional. | `string` | `"optional"` | no | 149 | | [instance\_types](#input\_instance\_types) | Instance types to create images from. It's unclear why this is a list. Possibly because different types can result in different images (like ARM instances) | `list(string)` |
[
"t3.medium"
]
| no | 150 | | [kms\_key\_id](#input\_kms\_key\_id) | KMS Key ID to use when encrypting the distributed AMI, if applicable | `string` | `null` | no | 151 | | [launch\_template\_configurations](#input\_launch\_template\_configurations) | A map of regions, where each region is a list of launch template configuration maps (one per account) for that region. Not used when custom\_distribution\_configs is in use. | `any` | `{}` | no | 152 | | [license\_config\_arns](#input\_license\_config\_arns) | If you're using License Manager, your ARNs go here | `set(string)` | `null` | no | 153 | | [log\_bucket](#input\_log\_bucket) | Bucket to store logs in. If this is ommited logs will not be stored | `string` | `null` | no | 154 | | [log\_prefix](#input\_log\_prefix) | S3 prefix to store logs at. Recommended if sharing bucket with other pipelines | `string` | `null` | no | 155 | | [name](#input\_name) | name to use for component | `string` | n/a | yes | 156 | | [public](#input\_public) | Whether resulting AMI should be public | `bool` | `false` | no | 157 | | [regions](#input\_regions) | Regions that AMIs will be available in | `list(string)` |
[
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
"ca-central-1"
]
| no | 158 | | [resource\_tags](#input\_resource\_tags) | Key-value map of tags to apply to resources created by this pipeline | `map(string)` | `null` | no | 159 | | [schedule\_cron](#input\_schedule\_cron) | Schedule (in cron) for when pipeline should run automatically https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html | `string` | `""` | no | 160 | | [schedule\_pipeline\_execution\_start\_condition](#input\_schedule\_pipeline\_execution\_start\_condition) | Start Condition Expression for when pipeline should run automatically https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html | `string` | `"EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE"` | no | 161 | | [schedule\_timezone](#input\_schedule\_timezone) | Timezone (in IANA timezone format) that scheduled builds, as specified by schedule\_cron, run on | `string` | `"Etc/UTC"` | no | 162 | | [security\_group\_ids](#input\_security\_group\_ids) | Security group IDs for the Image Builder | `list(string)` | `null` | no | 163 | | [shared\_account\_ids](#input\_shared\_account\_ids) | AWS accounts to share AMIs with. If this is left null AMIs will be public | `set(string)` | `[]` | no | 164 | | [shared\_organization\_arns](#input\_shared\_organization\_arns) | Set of AWS Organization ARNs to allow access to the created AMI | `set(string)` | `null` | no | 165 | | [shared\_ou\_arns](#input\_shared\_ou\_arns) | Set of AWS Organizational Unit ARNs to allow access to the created AMI | `set(string)` | `null` | no | 166 | | [sns\_topic\_arn](#input\_sns\_topic\_arn) | SNS topic to notify when new images are created | `string` | `null` | no | 167 | | [ssh\_key\_secret\_arn](#input\_ssh\_key\_secret\_arn) | If your ImageBuilder Components need to use an SSH Key (private repos, etc.), specify the ARN of the secretsmanager secret containing the SSH key to add access permissions (use arn OR name, not both) | `string` | `null` | no | 168 | | [ssh\_key\_secret\_name](#input\_ssh\_key\_secret\_name) | If your ImageBuilder Components need to use an SSH Key (private repos, etc.), specify the Name of the secretsmanager secret containing the SSH key to add access permissions (use arn OR name, not both) | `string` | `null` | no | 169 | | [subnet](#input\_subnet) | Subnet ID to use for builder | `string` | `null` | no | 170 | | [tags](#input\_tags) | map of tags to use for component | `map(string)` | `{}` | no | 171 | | [terminate\_on\_failure](#input\_terminate\_on\_failure) | Change to false if you want to connect to a builder for debugging after failure | `bool` | `true` | no | 172 | 173 | ## Outputs 174 | 175 | | Name | Description | 176 | |------|-------------| 177 | | [pipeline\_arn](#output\_pipeline\_arn) | ARN of EC2 Image Builder Pipeline | 178 | | [role\_name](#output\_role\_name) | The name of the IAM role for use if additional permissions are needed. | 179 | 180 | 181 | ## The Giants underneath this module 182 | - pre-commit.com/ 183 | - terraform.io/ 184 | - github.com/tfutils/tfenv 185 | - github.com/segmentio/terraform-docs 186 | -------------------------------------------------------------------------------- /bin/install-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'installing brew packages' 4 | brew update 5 | brew tap liamg/tfsec 6 | brew install tfenv tflint terraform-docs pre-commit liamg/tfsec/tfsec coreutils 7 | brew upgrade tfenv tflint terraform-docs pre-commit liamg/tfsec/tfsec coreutils 8 | 9 | echo 'installing pre-commit hooks' 10 | pre-commit install 11 | 12 | echo 'setting pre-commit hooks to auto-install on clone in the future' 13 | git config --global init.templateDir ~/.git-template 14 | pre-commit init-templatedir ~/.git-template 15 | 16 | echo 'installing terraform with tfenv' 17 | tfenv install min-required 18 | tfenv use min-required 19 | -------------------------------------------------------------------------------- /bin/install-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'installing dependencies' 4 | sudo apt install python3-pip gawk &&\ 5 | pip3 install pre-commit 6 | curl -L "$(curl -sL https://api.github.com/repos/segmentio/terraform-docs/releases/latest | grep -o -E "https://.+?-linux-amd64")" > terraform-docs && chmod +x terraform-docs && sudo mv terraform-docs /usr/bin/ 7 | curl -L "$(curl -sL https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ 8 | env GO111MODULE=on go get -u github.com/liamg/tfsec/cmd/tfsec 9 | git clone https://github.com/tfutils/tfenv.git ~/.tfenv || true 10 | mkdir -p ~/.local/bin/ 11 | . ~/.profile 12 | ln -s ~/.tfenv/bin/* ~/.local/bin 13 | 14 | echo 'installing pre-commit hooks' 15 | pre-commit install 16 | 17 | echo 'setting pre-commit hooks to auto-install on clone in the future' 18 | git config --global init.templateDir ~/.git-template 19 | pre-commit init-templatedir ~/.git-template 20 | 21 | echo 'installing terraform with tfenv' 22 | tfenv install min-required 23 | tfenv use min-required 24 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # basic example 2 | A basic example for this repository 3 | 4 | ## Code 5 | ```hcl 6 | module "test_pipeline" { 7 | source = "rhythmictech/imagebuilder-pipeline/aws" 8 | version = "~> 0.3.0" 9 | 10 | description = "Testing pipeline" 11 | name = "test-pipeline" 12 | tags = local.tags 13 | recipe_arn = module.test_recipe.recipe_arn 14 | public = false 15 | } 16 | ``` 17 | 18 | ## Applying 19 | ``` 20 | > terraform apply 21 | 22 | Apply complete! Resources: 0 added, 0 changed, 0 destroyed. 23 | 24 | Outputs: 25 | 26 | pipeline_arn = arn:aws:imagebuilder:us-east-1:000000000000:image-pipeline/test-pipeline 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/basic/common.tf: -------------------------------------------------------------------------------- 1 | ################################################## 2 | # Provider/Backend/Workspace Check 3 | ################################################## 4 | provider "aws" { 5 | region = var.region 6 | } 7 | 8 | terraform { 9 | required_version = ">= 0.12.25" 10 | required_providers { 11 | aws = "~> 2.61.0" 12 | } 13 | } 14 | 15 | variable "owner" { 16 | description = "Team/person responsible for resources defined within this project" 17 | type = string 18 | } 19 | 20 | variable "region" { 21 | description = "Region resources are being deployed to" 22 | type = string 23 | } 24 | -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | module "test_component" { 2 | source = "rhythmictech/imagebuilder-component-ansible/aws" 3 | 4 | name = "testing-component" 5 | 6 | component_version = "1.0.0" 7 | description = "Testing component" 8 | playbook_dir = "packer-generic-images/base" 9 | playbook_repo = "https://github.com/rhythmictech/packer-generic-images.git" 10 | } 11 | 12 | module "test_recipe" { 13 | source = "rhythmictech/imagebuilder-recipe/aws" 14 | 15 | name = "test-recipe" 16 | 17 | description = "Testing recipe" 18 | parent_image = "arn:aws:imagebuilder:us-east-1:aws:image/amazon-linux-2-x86/x.x.x" 19 | recipe_version = "1.0.0" 20 | update = true 21 | 22 | component_arns = [ 23 | module.test_component.component_arn, 24 | "arn:aws:imagebuilder:us-east-1:aws:component/simple-boot-test-linux/1.0.0/1", 25 | "arn:aws:imagebuilder:us-east-1:aws:component/reboot-test-linux/1.0.0/1" 26 | ] 27 | } 28 | 29 | module "test_pipeline" { 30 | source = "rhythmictech/imagebuilder-pipeline/aws" 31 | 32 | name = "test-pipeline" 33 | 34 | description = "Testing pipeline" 35 | public = false 36 | recipe_arn = module.test_recipe.recipe_arn 37 | 38 | regions = [ 39 | "us-east-1", 40 | "us-east-2" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /examples/basic/variables.tf: -------------------------------------------------------------------------------- 1 | ######################################## 2 | # General Vars 3 | ######################################## 4 | variable "additional_tags" { 5 | default = {} 6 | description = "Additional tags to add to supported resources" 7 | type = map(string) 8 | } 9 | -------------------------------------------------------------------------------- /examples/pipeline/README.md: -------------------------------------------------------------------------------- 1 | # downstream build example 2 | This example shows how to link two builds together in a golden image model. 3 | 4 | This build pipeline looks each Monday at 00:00 GMT for a new Amazon Linux 2 AMI. If found, it will kick off a build that includes an ansible playbook and a basic reboot test after the role is applied. 5 | 6 | The second build will run at 02:00 GMT _only if_ a new upstream build is found. It runs a different Ansible playbook. Both produce unique Image ARNs and AMIs that match the specified prefix pattern. (`base_image_2020*` and `app_image_2020*` respectively). 7 | -------------------------------------------------------------------------------- /examples/pipeline/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | parent_name = "parent_image" 3 | parent_version = "1._0.0" 4 | 5 | child_name = "child_image" 6 | child_version = "1.0.0" 7 | } 8 | 9 | # Parent component 10 | module "parent_component" { 11 | source = "rhythmictech/imagebuilder-component-ansible/aws" 12 | 13 | name = local.parent_name 14 | 15 | component_version = local.parent_version 16 | playbook_dir = "base" 17 | playbook_repo = "https://github.com/rhythmictech/imagepipeline-examples.git" 18 | } 19 | 20 | module "parent_recipe" { 21 | source = "rhythmictech/imagebuilder-recipe/aws" 22 | 23 | name = local.parent_name 24 | 25 | parent_image = "arn:aws:imagebuilder:us-east-1:aws:image/amazon-linux-2-x86/x.x.x" 26 | recipe_version = local.parent_version 27 | update = true 28 | 29 | component_arns = [ 30 | module.parent_component.component_arn, 31 | "arn:aws:imagebuilder:us-east-1:aws:component/simple-boot-test-linux/1.0.0/1", 32 | "arn:aws:imagebuilder:us-east-1:aws:component/reboot-test-linux/1.0.0/1" 33 | ] 34 | } 35 | 36 | module "parent_pipeline" { 37 | source = "rhythmictech/imagebuilder-pipeline/aws" 38 | 39 | name = local.parent_name 40 | 41 | public = false 42 | recipe_arn = module.parent_recipe.recipe_arn 43 | 44 | # run mondays at 00:00 GMT 45 | schedule = { 46 | PipelineExecutionStartCondition = "EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE" 47 | ScheduleExpression = "cron(0 0 * * mon)" 48 | } 49 | } 50 | 51 | # Child component 52 | module "child_component" { 53 | source = "rhythmictech/imagebuilder-component-ansible/aws" 54 | 55 | name = local.child_name 56 | 57 | component_version = local.child_version 58 | playbook_dir = "app" 59 | playbook_repo = "https://github.com/rhythmictech/imagepipeline-examples.git" 60 | } 61 | 62 | module "child_recipe" { 63 | source = "rhythmictech/imagebuilder-recipe/aws" 64 | 65 | name = local.child_name 66 | 67 | parent_image = "arn:aws:imagebuilder:us-east-1:aws:image/amazon-linux-2-x86/x.x.x" 68 | recipe_version = local.child_version 69 | update = true 70 | 71 | component_arns = [ 72 | module.child_component.component_arn, 73 | "arn:aws:imagebuilder:us-east-1:aws:component/simple-boot-test-linux/1.0.0/1", 74 | "arn:aws:imagebuilder:us-east-1:aws:component/reboot-test-linux/1.0.0/1" 75 | ] 76 | } 77 | 78 | module "child_pipeline" { 79 | source = "rhythmictech/imagebuilder-pipeline/aws" 80 | 81 | name = local.child_name 82 | 83 | public = false 84 | recipe_arn = module.child_recipe.recipe_arn 85 | 86 | # run mondays at 02:00 GMT 87 | schedule = { 88 | PipelineExecutionStartCondition = "EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE" 89 | ScheduleExpression = "cron(0 2 * * mon)" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | has_ssh_key_secret = var.ssh_key_secret_arn != null || var.ssh_key_secret_name != null 3 | 4 | image_name = coalesce( 5 | var.image_name, 6 | var.name 7 | ) 8 | 9 | log_prefix_computed = ( 10 | var.log_prefix != null 11 | ? replace("/${var.log_prefix}/*", "//{2,}/", "/") 12 | : "/*" 13 | 14 | 15 | ) 16 | shared_user_groups = var.public ? ["all"] : null 17 | use_custom_distribution_config = length(var.custom_distribution_configs) > 0 ? true : false 18 | 19 | } 20 | 21 | data "aws_iam_policy_document" "log_write" { 22 | count = var.log_bucket != null ? 1 : 0 23 | 24 | statement { 25 | actions = ["s3:PutObject"] 26 | resources = ["arn:aws:s3:::${var.log_bucket}${local.log_prefix_computed}"] 27 | } 28 | } 29 | 30 | resource "aws_iam_policy" "log_write" { 31 | count = var.log_bucket != null ? 1 : 0 32 | 33 | name_prefix = "${var.name}-logging-policy-" 34 | description = "IAM policy granting write access to the logging bucket for ${var.name}" 35 | policy = data.aws_iam_policy_document.log_write[0].json 36 | } 37 | 38 | data "aws_secretsmanager_secret" "ssh_key" { 39 | count = local.has_ssh_key_secret ? 1 : 0 40 | 41 | arn = var.ssh_key_secret_arn 42 | name = var.ssh_key_secret_name 43 | } 44 | 45 | data "aws_iam_policy_document" "secret_read" { 46 | count = local.has_ssh_key_secret ? 1 : 0 47 | 48 | statement { 49 | resources = [data.aws_secretsmanager_secret.ssh_key[0].arn] 50 | 51 | actions = [ 52 | "secretsmanager:GetSecretValue", 53 | "secretsmanager:DescribeSecret", 54 | "secretsmanager:ListSecretVersionIds" 55 | ] 56 | } 57 | } 58 | 59 | resource "aws_iam_policy" "secret_read" { 60 | count = local.has_ssh_key_secret ? 1 : 0 61 | 62 | name_prefix = "${var.name}-secret-read-policy-" 63 | description = "IAM policy granting read access to the ssh key secret at ${data.aws_secretsmanager_secret.ssh_key[0].name}" 64 | policy = data.aws_iam_policy_document.secret_read[0].json 65 | } 66 | 67 | locals { 68 | core_iam_policies = [ 69 | "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore", 70 | "arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder" 71 | ] 72 | } 73 | 74 | data "aws_iam_policy_document" "assume" { 75 | statement { 76 | actions = ["sts:AssumeRole"] 77 | 78 | principals { 79 | type = "Service" 80 | identifiers = ["ec2.amazonaws.com"] 81 | } 82 | } 83 | } 84 | 85 | resource "aws_iam_role" "this" { 86 | assume_role_policy = data.aws_iam_policy_document.assume.json 87 | name_prefix = "${var.name}-imagebuilder-role-" 88 | 89 | tags = merge( 90 | var.tags, 91 | { 92 | Name : "${var.name}-imagebuilder-role" 93 | } 94 | ) 95 | } 96 | 97 | resource "aws_iam_role_policy_attachment" "core" { 98 | count = length(local.core_iam_policies) 99 | 100 | policy_arn = local.core_iam_policies[count.index] 101 | role = aws_iam_role.this.name 102 | } 103 | 104 | resource "aws_iam_role_policy_attachment" "log_write" { 105 | count = var.log_bucket != null ? 1 : 0 106 | 107 | policy_arn = aws_iam_policy.log_write[0].arn 108 | role = aws_iam_role.this.name 109 | } 110 | 111 | resource "aws_iam_role_policy_attachment" "secret_read" { 112 | count = local.has_ssh_key_secret ? 1 : 0 113 | 114 | policy_arn = aws_iam_policy.secret_read[0].arn 115 | role = aws_iam_role.this.name 116 | } 117 | 118 | resource "aws_iam_role_policy_attachment" "additional" { 119 | count = length(var.additional_iam_policy_arns) 120 | 121 | policy_arn = var.additional_iam_policy_arns[count.index] 122 | role = aws_iam_role.this.name 123 | } 124 | 125 | resource "aws_iam_instance_profile" "this" { 126 | name_prefix = "${var.name}-imagebuilder-instance-profile-" 127 | role = aws_iam_role.this.name 128 | } 129 | 130 | 131 | resource "aws_imagebuilder_image_pipeline" "this" { 132 | name = var.name 133 | 134 | container_recipe_arn = var.container_recipe_arn 135 | description = var.description 136 | distribution_configuration_arn = aws_imagebuilder_distribution_configuration.this.arn 137 | enhanced_image_metadata_enabled = var.enhanced_image_metadata_enabled 138 | image_recipe_arn = var.image_recipe_arn 139 | infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.this.arn 140 | status = var.enabled ? "ENABLED" : "DISABLED" 141 | tags = var.tags 142 | 143 | image_tests_configuration { 144 | image_tests_enabled = var.image_tests_enabled 145 | timeout_minutes = var.image_tests_timeout_minutes 146 | } 147 | 148 | dynamic "schedule" { 149 | for_each = var.schedule_cron != "" ? ["1"] : [] 150 | content { 151 | pipeline_execution_start_condition = var.schedule_pipeline_execution_start_condition 152 | schedule_expression = var.schedule_cron 153 | timezone = var.schedule_timezone 154 | } 155 | } 156 | } 157 | 158 | resource "aws_imagebuilder_infrastructure_configuration" "this" { 159 | name = "${var.name} - Infrastructure Config" 160 | description = var.description 161 | instance_profile_name = aws_iam_instance_profile.this.name 162 | instance_types = var.instance_types 163 | key_pair = var.instance_key_pair 164 | resource_tags = var.resource_tags 165 | security_group_ids = var.security_group_ids 166 | sns_topic_arn = var.sns_topic_arn 167 | subnet_id = var.subnet 168 | tags = var.tags 169 | terminate_instance_on_failure = var.terminate_on_failure 170 | 171 | dynamic "logging" { 172 | for_each = var.log_bucket != null ? ["1"] : [] 173 | content { 174 | s3_logs { 175 | s3_bucket_name = var.log_bucket 176 | s3_key_prefix = var.log_prefix 177 | } 178 | } 179 | } 180 | 181 | instance_metadata_options { 182 | http_put_response_hop_limit = var.instance_metadata_http_put_hop_limit 183 | http_tokens = var.instance_metadata_http_tokens 184 | } 185 | 186 | } 187 | 188 | resource "aws_imagebuilder_distribution_configuration" "this" { 189 | name = "${var.name} - Distribution Config" 190 | description = var.description 191 | tags = var.tags 192 | # We'll normally use this, a sane distribution configuration for creating AMIs 193 | dynamic "distribution" { 194 | for_each = local.use_custom_distribution_config ? [] : var.regions 195 | content { 196 | region = distribution.value 197 | license_configuration_arns = var.license_config_arns 198 | ami_distribution_configuration { 199 | ami_tags = var.tags 200 | description = var.description 201 | kms_key_id = var.kms_key_id 202 | name = "${local.image_name}-{{ imagebuilder:buildDate }}" 203 | launch_permission { 204 | organization_arns = var.shared_organization_arns 205 | organizational_unit_arns = var.shared_ou_arns 206 | user_groups = local.shared_user_groups 207 | user_ids = var.shared_account_ids 208 | } 209 | } 210 | 211 | dynamic "launch_template_configuration" { 212 | for_each = lookup(var.launch_template_configurations, distribution.value, []) 213 | content { 214 | default = lookup(launch_template_configuration.value, "default", null) 215 | account_id = lookup(launch_template_configuration.value, "account_id", null) 216 | launch_template_id = lookup(launch_template_configuration.value, "launch_template_id", null) 217 | } 218 | } 219 | } 220 | } 221 | # Here be dragons. This is for specifying a custom set of distribution configurations as a parameter to the module. 222 | # If you're not using the custom_distribution_configs var you can ignore this dynamic block safely. 223 | dynamic "distribution" { 224 | for_each = var.custom_distribution_configs 225 | content { 226 | region = lookup(distribution.value, "region", null) 227 | license_configuration_arns = lookup(distribution.value, "license_configuration_arns", null) 228 | 229 | dynamic "ami_distribution_configuration" { 230 | for_each = length(keys(lookup(distribution.value, "ami_distribution_configuration", {}))) == 0 ? [] : [lookup(distribution.value, "ami_distribution_configuration", {})] 231 | content { 232 | ami_tags = lookup(ami_distribution_configuration.value, "ami_tags", null) 233 | description = lookup(ami_distribution_configuration.value, "description", null) 234 | kms_key_id = lookup(ami_distribution_configuration.value, "kms_key_id", null) 235 | name = lookup(ami_distribution_configuration.value, "name", null) 236 | target_account_ids = lookup(ami_distribution_configuration.value, "target_account_ids", null) 237 | 238 | dynamic "launch_permission" { 239 | for_each = length(keys(lookup(ami_distribution_configuration.value, "launch_permission", {}))) == 0 ? [] : [lookup(ami_distribution_configuration.value, "launch_permission", {})] 240 | content { 241 | organization_arns = lookup(launch_permission.value, "organization_arns", null) 242 | organizational_unit_arns = lookup(launch_permission.value, "organizational_unit_arns", null) 243 | user_groups = lookup(launch_permission.value, "user_groups", null) 244 | user_ids = lookup(launch_permission.value, "user_ids", null) 245 | } 246 | } 247 | } 248 | } 249 | 250 | dynamic "container_distribution_configuration" { 251 | for_each = length(keys(lookup(distribution.value, "container_distribution_configuration", {}))) == 0 ? [] : [lookup(distribution.value, "container_distribution_configuration", {})] 252 | content { 253 | container_tags = lookup(container_distribution_configuration.value, "container_tags", null) 254 | description = lookup(container_distribution_configuration.value, "description", null) 255 | dynamic "target_repository" { 256 | for_each = length(keys(lookup(container_distribution_configuration.value, "target_repository", {}))) == 0 ? [] : [lookup(container_distribution_configuration.value, "target_repository", {})] 257 | content { 258 | repository_name = lookup(target_repository.value, "repository_name", null) 259 | service = lookup(target_repository.value, "service", null) 260 | } 261 | } 262 | } 263 | } 264 | 265 | dynamic "fast_launch_configuration" { 266 | for_each = length(keys(lookup(distribution.value, "fast_launch_configuration", {}))) == 0 ? [] : [lookup(distribution.value, "fast_launch_configuration", {})] 267 | content { 268 | account_id = lookup(fast_launch_configuration.value, "account_id", null) 269 | enabled = lookup(fast_launch_configuration.value, "enabled", null) 270 | max_parallel_launches = lookup(fast_launch_configuration.value, "max_parallel_launches", null) 271 | 272 | dynamic "launch_template" { 273 | for_each = length(keys(lookup(fast_launch_configuration.value, "launch_template", {}))) == 0 ? [] : [lookup(fast_launch_configuration.value, "launch_template", {})] 274 | content { 275 | launch_template_id = lookup(launch_template.value, "launch_template_id", null) 276 | launch_template_name = lookup(launch_template.value, "launch_template_name", null) 277 | launch_template_version = lookup(launch_template.value, "launch_template_version", null) 278 | } 279 | } 280 | 281 | dynamic "snapshot_configuration" { 282 | for_each = length(keys(lookup(fast_launch_configuration.value, "snapshot_configuration", {}))) == 0 ? [] : [lookup(fast_launch_configuration.value, "snapshot_configuration", {})] 283 | content { 284 | target_resource_count = lookup(snapshot_configuration.value, "target_resource_count", null) 285 | } 286 | } 287 | } 288 | } 289 | 290 | dynamic "launch_template_configuration" { 291 | for_each = length(keys(lookup(distribution.value, "launch_template_configuration", {}))) == 0 ? [] : [lookup(distribution.value, "launch_template_configuration", {})] 292 | content { 293 | default = lookup(launch_template_configuration.value, "default", null) 294 | account_id = lookup(launch_template_configuration.value, "account_id", null) 295 | launch_template_id = lookup(launch_template_configuration.value, "launch_template_id", null) 296 | } 297 | } 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "pipeline_arn" { 2 | description = "ARN of EC2 Image Builder Pipeline" 3 | value = aws_imagebuilder_image_pipeline.this.arn 4 | } 5 | 6 | output "role_name" { 7 | description = "The name of the IAM role for use if additional permissions are needed." 8 | value = aws_iam_role.this.name 9 | } 10 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "additional_iam_policy_arns" { 2 | default = [] 3 | description = "List of ARN policies for addional builder permissions" 4 | type = list(string) 5 | } 6 | 7 | variable "container_recipe_arn" { 8 | default = null 9 | description = "ARN of the container recipe to use. Must change with Recipe version" 10 | type = string 11 | } 12 | 13 | variable "custom_distribution_configs" { 14 | default = [] 15 | description = "To use your own distribution configurations for the ImageBuilder Distribution Configuration, supply a list of distribution configuration blocks as defined at https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_distribution_configuration#distribution" 16 | type = any 17 | } 18 | 19 | variable "description" { 20 | default = null 21 | description = "description of component" 22 | type = string 23 | } 24 | 25 | variable "enabled" { 26 | default = true 27 | description = "Whether pipeline is ENABLED or DISABLED" 28 | type = bool 29 | } 30 | 31 | variable "enhanced_image_metadata_enabled" { 32 | default = true 33 | description = "Whether additional information about the image being created is collected. Default is true." 34 | type = bool 35 | } 36 | 37 | variable "image_name" { 38 | default = "" 39 | description = "The name prefix given to the AMI created by the pipeline (a timestamp will be added to the end)" 40 | type = string 41 | } 42 | 43 | variable "image_recipe_arn" { 44 | default = null 45 | description = "ARN of the image recipe to use. Must change with Recipe version" 46 | type = string 47 | } 48 | 49 | variable "image_tests_enabled" { 50 | default = true 51 | description = "Whether to run tests during image creation" 52 | type = bool 53 | } 54 | 55 | variable "image_tests_timeout_minutes" { 56 | default = 60 57 | description = "Maximum time to allow for image tests to run" 58 | type = number 59 | } 60 | 61 | variable "instance_types" { 62 | default = ["t3.medium"] 63 | description = "Instance types to create images from. It's unclear why this is a list. Possibly because different types can result in different images (like ARM instances)" 64 | type = list(string) 65 | } 66 | 67 | variable "instance_key_pair" { 68 | default = null 69 | description = "EC2 key pair to add to the default user on the builder" 70 | type = string 71 | } 72 | 73 | variable "instance_metadata_http_put_hop_limit" { 74 | default = null 75 | description = "The number of hops that an instance can traverse to reach its metadata." 76 | type = number 77 | } 78 | 79 | variable "instance_metadata_http_tokens" { 80 | default = "optional" 81 | description = "Whether a signed token is required for instance metadata retrieval requests. Valid values: required, optional." 82 | type = string 83 | } 84 | 85 | variable "kms_key_id" { 86 | default = null 87 | description = "KMS Key ID to use when encrypting the distributed AMI, if applicable" 88 | type = string 89 | } 90 | 91 | variable "launch_template_configurations" { 92 | default = {} 93 | description = "A map of regions, where each region is a list of launch template configuration maps (one per account) for that region. Not used when custom_distribution_configs is in use." 94 | type = any 95 | } 96 | 97 | variable "license_config_arns" { 98 | default = null 99 | description = "If you're using License Manager, your ARNs go here" 100 | type = set(string) 101 | } 102 | 103 | variable "log_bucket" { 104 | default = null 105 | description = "Bucket to store logs in. If this is ommited logs will not be stored" 106 | type = string 107 | } 108 | 109 | variable "log_prefix" { 110 | default = null 111 | description = "S3 prefix to store logs at. Recommended if sharing bucket with other pipelines" 112 | type = string 113 | } 114 | 115 | variable "name" { 116 | description = "name to use for component" 117 | type = string 118 | } 119 | 120 | variable "public" { 121 | default = false 122 | description = "Whether resulting AMI should be public" 123 | type = bool 124 | } 125 | 126 | variable "regions" { 127 | default = [ 128 | "us-east-1", 129 | "us-east-2", 130 | "us-west-1", 131 | "us-west-2", 132 | "ca-central-1" 133 | ] 134 | description = "Regions that AMIs will be available in" 135 | type = list(string) 136 | } 137 | 138 | variable "resource_tags" { 139 | default = null 140 | description = "Key-value map of tags to apply to resources created by this pipeline" 141 | type = map(string) 142 | } 143 | 144 | variable "schedule_cron" { 145 | default = "" 146 | description = "Schedule (in cron) for when pipeline should run automatically https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html" 147 | type = string 148 | } 149 | 150 | variable "schedule_pipeline_execution_start_condition" { 151 | default = "EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE" 152 | description = "Start Condition Expression for when pipeline should run automatically https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html" 153 | type = string 154 | } 155 | 156 | variable "schedule_timezone" { 157 | default = "Etc/UTC" 158 | description = "Timezone (in IANA timezone format) that scheduled builds, as specified by schedule_cron, run on" 159 | type = string 160 | } 161 | 162 | variable "security_group_ids" { 163 | default = null 164 | description = "Security group IDs for the Image Builder" 165 | type = list(string) 166 | } 167 | 168 | variable "shared_account_ids" { 169 | default = [] 170 | description = "AWS accounts to share AMIs with. If this is left null AMIs will be public" 171 | type = set(string) 172 | } 173 | 174 | variable "shared_organization_arns" { 175 | default = null 176 | description = "Set of AWS Organization ARNs to allow access to the created AMI" 177 | type = set(string) 178 | } 179 | 180 | variable "shared_ou_arns" { 181 | default = null 182 | description = "Set of AWS Organizational Unit ARNs to allow access to the created AMI" 183 | type = set(string) 184 | } 185 | 186 | variable "sns_topic_arn" { 187 | default = null 188 | description = "SNS topic to notify when new images are created" 189 | type = string 190 | } 191 | 192 | variable "ssh_key_secret_arn" { 193 | default = null 194 | description = "If your ImageBuilder Components need to use an SSH Key (private repos, etc.), specify the ARN of the secretsmanager secret containing the SSH key to add access permissions (use arn OR name, not both)" 195 | type = string 196 | } 197 | 198 | variable "ssh_key_secret_name" { 199 | default = null 200 | description = "If your ImageBuilder Components need to use an SSH Key (private repos, etc.), specify the Name of the secretsmanager secret containing the SSH key to add access permissions (use arn OR name, not both)" 201 | type = string 202 | } 203 | 204 | variable "subnet" { 205 | default = null 206 | description = "Subnet ID to use for builder" 207 | type = string 208 | } 209 | 210 | variable "tags" { 211 | default = {} 212 | description = "map of tags to use for component" 213 | type = map(string) 214 | } 215 | 216 | variable "terminate_on_failure" { 217 | default = true 218 | description = "Change to false if you want to connect to a builder for debugging after failure" 219 | type = bool 220 | } 221 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.14" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws", 7 | version = ">= 4.22.0" 8 | } 9 | } 10 | } 11 | --------------------------------------------------------------------------------