diff --git a/.github/.gitkeep b/.github/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.gitignore b/.gitignore index 397af322..627f0686 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.DS_Store + # Local .terraform directories **/.terraform/* @@ -7,6 +9,7 @@ # .tfstate files *.tfstate *.tfstate.* +*.tfplan # Crash log files crash.log diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05e799a8..45941f1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.76.0 + rev: v1.83.5 hooks: - id: terraform_fmt - id: terraform_validate @@ -23,6 +23,6 @@ repos: - '--args=--only=terraform_standard_module_structure' - '--args=--only=terraform_workspace_remote' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: check-merge-conflict diff --git a/README.md b/README.md index a2b2c594..869cc806 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,174 @@ -# AWS Terraform module which runs Atlantis on AWS Fargate - -[Atlantis](https://www.runatlantis.io/) is tool which provides unified workflow for collaborating on Terraform through GitHub, GitLab and Bitbucket Cloud. +# Atlantis on AWS Fargate Terraform Module [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) -This repository contains Terraform infrastructure code which creates AWS resources required to run [Atlantis](https://www.runatlantis.io/) on AWS, including: +[Atlantis](https://www.runatlantis.io/) is tool which provides unified workflow for collaborating on Terraform through GitHub, GitLab and Bitbucket Cloud. -- Virtual Private Cloud (VPC) -- SSL certificate using Amazon Certificate Manager (ACM) -- Application Load Balancer (ALB) -- Domain name using AWS Route53 which points to ALB -- [AWS Elastic Container Service (ECS)](https://aws.amazon.com/ecs/) and [AWS Fargate](https://aws.amazon.com/fargate/) running Atlantis Docker image -- AWS Parameter Store to keep secrets and access them in ECS task natively +> Before using Atlantis and the code in this repository, please make sure that you have read and understood the security implications described in [the official Atlantis documentation](https://www.runatlantis.io/docs/security.html). -[AWS Fargate](https://aws.amazon.com/fargate/) with optional support for [Fargate Spot](https://aws.amazon.com/blogs/aws/aws-fargate-spot-now-generally-available/) is used to reduce the bill, and it is also a cool AWS service. +## Usage -Depending on which SCM system you use, Github repositories or Gitlab projects has to be configured to post events to Atlantis webhook URL. +GitHub is shown below in usage examples; however, any git provider supported by Atlantis can be used by simply using the correct Atlantis environment variables and configuring the respective webhook for the given git provider. -See `README.md` in `examples` for Github or Gitlab for complete details. +See the [Supplemental Docs](https://github.com/terraform-aws-modules/terraform-aws-atlantis/blob/master/docs/README.md) for additional details on integrating with git providers. -### Before using Atlantis and the code in this repository please make sure that you have read and understood the security implications described in [the official Atlantis documentation](https://www.runatlantis.io/docs/security.html). +### GitHub Complete -## How to use this? +The Atlantis module creates all resources required to run Atlantis on AWS Fargate. -As often with the code published in [terraform-aws-modules GitHub organization](https://github.com/terraform-aws-modules) you should have everything to run this code and get Atlantis up and running. +```hcl +module "atlantis" { + source = "terraform-aws-modules/atlantis/aws" -There are three ways to do this: + name = "atlantis" -1. [As a standalone project](https://github.com/terraform-aws-modules/terraform-aws-atlantis#run-atlantis-as-a-standalone-project) -1. [As a Terraform module](https://github.com/terraform-aws-modules/terraform-aws-atlantis#run-atlantis-as-a-terraform-module) -1. [As a part of an existing AWS infrastructure](https://github.com/terraform-aws-modules/terraform-aws-atlantis#run-atlantis-as-a-part-of-an-existing-aws-infrastructure-use-existing-vpc) + # ECS Container Definition + atlantis = { + environment = [ + { + name = "ATLANTIS_GH_USER" + value = "myuser" + }, + { + name = "ATLANTIS_REPO_ALLOWLIST" + value = "github.com/terraform-aws-modules/*" + }, + ] + secrets = [ + { + name = "ATLANTIS_GH_TOKEN" + valueFrom = "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes256-7g8H9i" + }, + { + name = "ATLANTIS_GH_WEBHOOK_SECRET" + valueFrom = "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes192-4D5e6F" + }, + ] + } -### Run Atlantis as a standalone project + # ECS Service + service = { + task_exec_secret_arns = [ + "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes256-7g8H9i", + "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes192-4D5e6F", + ] + # Provide Atlantis permission necessary to create/destroy resources + tasks_iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + } + service_subnets = ["subnet-xyzde987", "subnet-slkjf456", "subnet-qeiru789"] + vpc_id = "vpc-1234556abcdef" -1. Clone this github repository: + # ALB + alb_subnets = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + certificate_domain_name = "example.com" + route53_zone_id = "Z2ES7B9AZ6SHAE" -``` -$ git clone git@github.com:terraform-aws-modules/terraform-aws-atlantis.git -$ cd terraform-aws-atlantis + tags = { + Environment = "dev" + Terraform = "true" + } +} ``` -2. Copy sample `terraform.tfvars.sample` into `terraform.tfvars` and specify required variables there. +### GitHub Separate -3. Run `terraform init` to download required providers and modules. - -4. Run `terraform apply` to apply the Terraform configuration and create required infrastructure. - -5. Run `terraform output atlantis_url` to get URL where Atlantis is publicly reachable. (Note: It may take a minute or two to get it reachable for the first time) - -6. Github webhook is automatically created if `github_token`, `github_owner` and `github_repo_names` were specified. Read [Add GitHub Webhook](https://github.com/runatlantis/atlantis#add-github-webhook) in the official Atlantis documentation or check [example "GitHub repository webhook for Atlantis"](https://github.com/terraform-aws-modules/terraform-aws-atlantis/tree/master/examples/github-repository-webhook) to add more webhooks. - -### Run Atlantis as a Terraform module - -This way allows integration with your existing Terraform configurations. +The Atlantis module creates most of resources required to run Atlantis on AWS Fargate, except for the ECS Cluster and ALB. This allows you to integrate Atlantis with your existing AWS infrastructure. ```hcl module "atlantis" { source = "terraform-aws-modules/atlantis/aws" - version = "~> 3.0" name = "atlantis" - # VPC - cidr = "10.20.0.0/16" - azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] - private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] - public_subnets = ["10.20.101.0/24", "10.20.102.0/24", "10.20.103.0/24"] + # Existing cluster + create_cluster = false + cluster_arn = "arn:aws:ecs:eu-west-1:123456789012:cluster/default" + + # Existing ALB + create_alb = false + alb_target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + alb_security_group_id = "sg-12345678" + + # ECS Container Definition + atlantis = { + environment = [ + { + name = "ATLANTIS_GH_USER" + value = "myuser" + }, + { + name = "ATLANTIS_REPO_ALLOWLIST" + value = "github.com/terraform-aws-modules/*" + }, + ] + secrets = [ + { + name = "ATLANTIS_GH_TOKEN" + valueFrom = "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes256-7g8H9i" + }, + { + name = "ATLANTIS_GH_WEBHOOK_SECRET" + valueFrom = "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes192-4D5e6F" + }, + ] + } + + # ECS Service + service = { + task_exec_secret_arns = [ + "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes256-7g8H9i", + "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes192-4D5e6F", + ] + # Provide Atlantis permission necessary to create/destroy resources + tasks_iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + } + service_subnets = ["subnet-xyzde987", "subnet-slkjf456", "subnet-qeiru789"] + vpc_id = "vpc-1234556abcdef" + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` - # DNS (without trailing dot) - route53_zone_name = "example.com" +### Utilize EFS for Persistent Storage - # ACM (SSL certificate) - Specify ARN of an existing certificate or new one will be created and validated using Route53 DNS - certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" +You can enable EFS to ensure that any plan outputs are persisted to EFS in the event that the Atlantis Task is replaced: - # Atlantis - atlantis_github_app_id = "1234567" - atlantis_github_app_key = "-----BEGIN RSA PRIVATE KEY-----(...)" - atlantis_repo_allowlist = ["github.com/terraform-aws-modules/*"] +```hcl +```hcl +module "atlantis" { + source = "terraform-aws-modules/atlantis/aws" + + # Truncated for brevity ... + + # EFS + enable_efs = true + efs = { + mount_targets = { + "eu-west-1a" = { + subnet_id = "subnet-xyzde987" + } + "eu-west-1b" = { + subnet_id = "subnet-slkjf456" + } + "eu-west-1c" = { + subnet_id = "subnet-qeiru789" + } + } + } } ``` -### Provide Atlantis with server yaml configuration +### Supply Atlantis server configuration `server-atlantis.yaml` + ```yaml repos: - id: /.*/ @@ -96,390 +182,95 @@ repos: ``` `main.tf` + ```hcl module "atlantis" { source = "terraform-aws-modules/atlantis/aws" # ... - custom_environment_variables = [ - { - name : "ATLANTIS_REPO_CONFIG_JSON", - value : jsonencode(yamldecode(file("${path.module}/server-atlantis.yaml"))), - }, - ] - - # ... -} -``` - -### Run Atlantis as a part of an existing AWS infrastructure (use existing VPC) - -This way allows integration with your existing AWS resources - VPC, public and private subnets. Specify the following arguments (see methods described above): - -``` -vpc_id = "vpc-1651acf1" -private_subnet_ids = ["subnet-1fe3d837", "subnet-129d66ab"] -public_subnet_ids = ["subnet-1211eef5", "subnet-163466ab"] -``` - -If `vpc_id` is specified it will take precedence over `cidr` and existing VPC will be used. `private_subnet_ids` and `public_subnet_ids` must be specified also. - -Make sure that both private and public subnets were created in the same set of availability zones (ALB will be created in public subnets, ECS Fargate service in private subnets). - -If all provided subnets are public (no NAT gateway) then `ecs_service_assign_public_ip` should be set to `true`. - -### Using GitHub App -An Atlantis GitHub App can be generated using multiple methods: - -- You can follow Atlantis instructions depicted [here](https://www.runatlantis.io/docs/access-credentials.html#github-app). The Atlantis method mostly automates the GitHub App generation using [GitHub App Manifest](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app-from-a-manifest), but you need an exposed endpoint to complete the process. -- The other method is to manually create the GitHub App as instructed [here](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app). -1. You create a GitHub App and give it a name - that name must be unique across the world (you can change it later). -2. Provide a valid Homepage URL (this can be the atlantis server url, for instance https://atlantis.mydomain.com) -3. Provide a valid Webhook URL. The Atlantis webhook server path is located by default at https://atlantis.mydomain.com/events -4. Generate a Webhook Secret - this is used for Atlantis to trust the deliveries. This is your github_webhook_secret. -5. Generate a Private Key - this is your github_app_key -6. On the App's settings page (at the top) you find the App ID. This is your github_app_id -7. On the Permissions & Events you need to setup all the permissions and events according to Atlantis documentation - -Now you need to install the App on your organization. - -A self-provisioned GitHub App usually has two parts: the App and the Installation. - -The App part is the first step and its where you setup all the requirements, such as authentication, webhook, permissions, etc... -The Installation part is where you add the created App to an organization/personal-account. It is on the installation page where you setup which repositories the application can access and receive events from. - -Once you have your GitHub App registered you will be able to access/manage the required parameters: - -- `atlantis_github_app_id` to identify the GitHub app. -- `atlantis_github_app_key` to interact with GitHub. -- `atlantis_github_webhook_secret` to receive and validate incoming webhook invocations from GitHub. - -#### GitHub Personal Access Token (PAT) is no longer recommended - -While still supported, the use of GitHub Personal Access Token (PAT) is no longer the recommended method in favor of GitHub App. - -[GitHub Apps](https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps) provide more control over repository access/permissions and does not require the use of bot accounts. - - -### Secure Atlantis with ALB Built-in Authentication - -#### OpenID Connect (OIDC) - -You can use service like [Auth0](https://www.auth0.com) to secure access to Atlantis and require authentication on ALB. To enable this, you need to create Auth0 application and provide correct arguments to Atlantis module. Make sure to update application hostname, client id and client secret: - -Read more in [this post](https://medium.com/@sandrinodm/securing-your-applications-with-aws-alb-built-in-authentication-and-auth0-310ad84c8595). - -##### Auth0 - -```hcl -alb_authenticate_oidc = { - issuer = "https://youruser.eu.auth0.com/" - token_endpoint = "https://youruser.eu.auth0.com/oauth/token" - user_info_endpoint = "https://youruser.eu.auth0.com/userinfo" - authorization_endpoint = "https://youruser.eu.auth0.com/authorize" - authentication_request_extra_params = {} - client_id = "clientid" - client_secret = "secret123" # a data source would be good here -} -``` - -Read more in [this post](https://medium.com/@sandrinodm/securing-your-applications-with-aws-alb-built-in-authentication-and-auth0-310ad84c8595). - -##### Okta - -```hcl -alb_authenticate_oidc = { - issuer = "https://dev-42069.okta.com/" - token_endpoint = "https://dev-42069.okta.com/oauth2/v1/token" - user_info_endpoint = "https://dev-42069.okta.com/oauth2/v1/userinfo" - authorization_endpoint = "https://dev-42069.okta.com/oauth2/v1/authorize" - authentication_request_extra_params = {} - client_id = "clientid" - client_secret = "secret123" # a data source would be good here -} -``` - -Read more in [this post](https://medium.com/swlh/aws-alb-authentication-with-okta-oidc-using-terraform-902cd8289db4). - -##### Google - - - -```hcl - alb_authenticate_oidc = { - issuer = "https://accounts.google.com" - token_endpoint = "https://oauth2.googleapis.com/token" - user_info_endpoint = "https://openidconnect.googleapis.com/v1/userinfo" - authorization_endpoint = "https://accounts.google.com/o/oauth2/v2/auth" - authentication_request_extra_params = {} - client_id = "google_client_id" - client_secret = "google_client_secret" + atlantis = { + environment = [ + { + name : "ATLANTIS_REPO_CONFIG_JSON", + value : jsonencode(yamldecode(file("${path.module}/server-atlantis.yaml"))), + }, + ] } -``` -* See the [iap_client resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iap_client) in the Google provider if you want to create this configuration in Terraform. -* Remember to set your google consent screen to internal to only allow users from your own domain. - -#### AWS Cognito with SAML - -The AWS Cognito service allows you to define SAML applications tied to an identity provider (e.g., GSuite). The Atlantis ALB can then be configured to require an authenticated user managed by your identity provider. - -To configure AWS Cognito connecting to a GSuite SAML application, you can use the [gsuite-saml-cognito](https://github.com/alloy-commons/alloy-open-source/tree/master/terraform-modules/gsuite-saml-cognito#example-usage) Terraform module. - -To enable Cognito authentication on the Atlantis ALB, specify the following arguments containing attributes from your Cognito configuration. - -```hcl -alb_authenticate_cognito = { - user_pool_arn = "arn:aws:cognito-idp:us-west-2:1234567890:userpool/us-west-2_aBcDeFG" - user_pool_client_id = "clientid123" - user_pool_domain = "sso.your-corp.com" } ``` -#### Allow GitHub Webhooks Unauthenticated Access - -If you are using one of the authentication methods above along with managed GitHub (not self-hosted enterprise version), you'll need to allow unauthenticated access to GitHub's Webhook static IPs: - -```hcl -allow_unauthenticated_access = true -allow_github_webhooks = true -``` - -## Notes - -1. AWS Route53 zone is not created by this module, so zone specified as a value in `route53_zone_name` should be created before using this module. Check documentation for [aws_route53_zone](https://www.terraform.io/docs/providers/aws/r/route53_zone.html). -1. Currently this module configures Atlantis in a way that it can not be used to work with GitHub and Gitlab simultaneously (can't make list of ECS secrets conditionally). -1. For Bitbucket Cloud webhook configuration follow instructions in [the official Atlantis documentation](https://www.runatlantis.io/docs/configuring-webhooks.html#bitbucket-cloud-bitbucket-org-webhook). - - - ## Examples - [Complete Atlantis with GitHub webhook](https://github.com/terraform-aws-modules/terraform-aws-atlantis/tree/master/examples/github-complete) -- [GitHub repository webhook for Atlantis](https://github.com/terraform-aws-modules/terraform-aws-atlantis/tree/master/examples/github-repository-webhook) -- [GitLab repository webhook for Atlantis](https://github.com/terraform-aws-modules/terraform-aws-atlantis/tree/master/examples/gitlab-repository-webhook) +- [Separate Atlantis with GitHub webhook](https://github.com/terraform-aws-modules/terraform-aws-atlantis/tree/master/examples/github-separate) ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.69 | -| [random](#requirement\_random) | >= 2.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | ## Providers -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 3.69 | -| [random](#provider\_random) | >= 2.0 | +No providers. ## Modules | Name | Source | Version | |------|--------|---------| -| [acm](#module\_acm) | terraform-aws-modules/acm/aws | v3.2.0 | -| [alb](#module\_alb) | terraform-aws-modules/alb/aws | v6.5.0 | -| [alb\_http\_sg](#module\_alb\_http\_sg) | terraform-aws-modules/security-group/aws//modules/http-80 | v4.3.0 | -| [alb\_https\_sg](#module\_alb\_https\_sg) | terraform-aws-modules/security-group/aws//modules/https-443 | v4.3.0 | -| [atlantis\_sg](#module\_atlantis\_sg) | terraform-aws-modules/security-group/aws | v4.3.0 | -| [container\_definition\_bitbucket](#module\_container\_definition\_bitbucket) | cloudposse/ecs-container-definition/aws | v0.58.1 | -| [container\_definition\_github\_gitlab](#module\_container\_definition\_github\_gitlab) | cloudposse/ecs-container-definition/aws | v0.58.1 | -| [ecs](#module\_ecs) | terraform-aws-modules/ecs/aws | v3.3.0 | -| [efs\_sg](#module\_efs\_sg) | terraform-aws-modules/security-group/aws | v4.8.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | v3.6.0 | +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | 5.0.0 | +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 9.1.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | 5.6.0 | +| [ecs\_service](#module\_ecs\_service) | terraform-aws-modules/ecs/aws//modules/service | 5.6.0 | +| [efs](#module\_efs) | terraform-aws-modules/efs/aws | 1.3.1 | ## Resources -| Name | Type | -|------|------| -| [aws_cloudwatch_log_group.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | -| [aws_ecs_service.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | -| [aws_ecs_task_definition.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | -| [aws_efs_access_point.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_access_point) | resource | -| [aws_efs_file_system.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource | -| [aws_efs_mount_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource | -| [aws_iam_role.ecs_task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy.ecs_task_access_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | -| [aws_iam_role_policy_attachment.ecs_task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_lb_listener_rule.unauthenticated_access_for_cidr_blocks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource | -| [aws_lb_listener_rule.unauthenticated_access_for_webhook](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource | -| [aws_route53_record.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | -| [aws_route53_record.atlantis_aaaa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | -| [aws_ssm_parameter.atlantis_bitbucket_user_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | -| [aws_ssm_parameter.atlantis_github_app_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | -| [aws_ssm_parameter.atlantis_github_user_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | -| [aws_ssm_parameter.atlantis_gitlab_user_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | -| [aws_ssm_parameter.webhook](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | -| [random_id.webhook](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | -| [aws_ecs_task_definition.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_task_definition) | data source | -| [aws_iam_policy_document.ecs_task_access_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.ecs_task_access_secrets_with_kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.ecs_tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | -| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | -| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | +No resources. ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [acm\_certificate\_domain\_name](#input\_acm\_certificate\_domain\_name) | Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance. Specify if it is different from value in `route53_zone_name` | `string` | `""` | no | -| [alb\_authenticate\_cognito](#input\_alb\_authenticate\_cognito) | Map of AWS Cognito authentication parameters to protect ALB (eg, using SAML). See https://www.terraform.io/docs/providers/aws/r/lb_listener.html#authenticate-cognito-action | `any` | `{}` | no | -| [alb\_authenticate\_oidc](#input\_alb\_authenticate\_oidc) | Map of Authenticate OIDC parameters to protect ALB (eg, using Auth0). See https://www.terraform.io/docs/providers/aws/r/lb_listener.html#authenticate-oidc-action | `any` | `{}` | no | -| [alb\_drop\_invalid\_header\_fields](#input\_alb\_drop\_invalid\_header\_fields) | Indicates whether invalid header fields are dropped in application load balancers. Defaults to false. | `bool` | `null` | no | -| [alb\_enable\_cross\_zone\_load\_balancing](#input\_alb\_enable\_cross\_zone\_load\_balancing) | Whether cross-zone load balancing is enabled for the load balancer | `bool` | `null` | no | -| [alb\_enable\_deletion\_protection](#input\_alb\_enable\_deletion\_protection) | If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false. | `bool` | `null` | no | -| [alb\_http\_security\_group\_tags](#input\_alb\_http\_security\_group\_tags) | Additional tags to put on the http security group | `map(string)` | `{}` | no | -| [alb\_https\_security\_group\_tags](#input\_alb\_https\_security\_group\_tags) | Additional tags to put on the https security group | `map(string)` | `{}` | no | -| [alb\_ingress\_cidr\_blocks](#input\_alb\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules of the ALB. | `list(string)` |
[
"0.0.0.0/0"
]
| no | -| [alb\_ingress\_ipv6\_cidr\_blocks](#input\_alb\_ingress\_ipv6\_cidr\_blocks) | List of IPv6 CIDR ranges to use on all ingress rules of the ALB. | `list(string)` |
[
"::/0"
]
| no | -| [alb\_ip\_address\_type](#input\_alb\_ip\_address\_type) | The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack | `string` | `"ipv4"` | no | -| [alb\_listener\_ssl\_policy\_default](#input\_alb\_listener\_ssl\_policy\_default) | The security policy if using HTTPS externally on the load balancer. [See](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html). | `string` | `"ELBSecurityPolicy-2016-08"` | no | -| [alb\_log\_bucket\_name](#input\_alb\_log\_bucket\_name) | S3 bucket (externally created) for storing load balancer access logs. Required if alb\_logging\_enabled is true. | `string` | `""` | no | -| [alb\_log\_location\_prefix](#input\_alb\_log\_location\_prefix) | S3 prefix within the log\_bucket\_name under which logs are stored. | `string` | `""` | no | -| [alb\_logging\_enabled](#input\_alb\_logging\_enabled) | Controls if the ALB will log requests to S3. | `bool` | `false` | no | -| [allow\_github\_webhooks](#input\_allow\_github\_webhooks) | Whether to allow access for GitHub webhooks | `bool` | `false` | no | -| [allow\_repo\_config](#input\_allow\_repo\_config) | When true allows the use of atlantis.yaml config files within the source repos. | `string` | `"false"` | no | -| [allow\_unauthenticated\_access](#input\_allow\_unauthenticated\_access) | Whether to create ALB listener rule to allow unauthenticated access for certain CIDR blocks (eg. allow GitHub webhooks to bypass OIDC authentication) | `bool` | `false` | no | -| [allow\_unauthenticated\_access\_priority](#input\_allow\_unauthenticated\_access\_priority) | ALB listener rule priority for allow unauthenticated access rule | `number` | `10` | no | -| [allow\_unauthenticated\_webhook\_access\_priority](#input\_allow\_unauthenticated\_webhook\_access\_priority) | ALB listener rule priority for allow unauthenticated webhook access rule | `number` | `15` | no | -| [atlantis\_bitbucket\_base\_url](#input\_atlantis\_bitbucket\_base\_url) | Base URL of Bitbucket Server, use for Bitbucket on prem (Stash) | `string` | `""` | no | -| [atlantis\_bitbucket\_user](#input\_atlantis\_bitbucket\_user) | Bitbucket username that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_bitbucket\_user\_token](#input\_atlantis\_bitbucket\_user\_token) | Bitbucket token of the user that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_bitbucket\_user\_token\_ssm\_parameter\_name](#input\_atlantis\_bitbucket\_user\_token\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_bitbucket\_user\_token | `string` | `"/atlantis/bitbucket/user/token"` | no | -| [atlantis\_fqdn](#input\_atlantis\_fqdn) | FQDN of Atlantis to use. Set this only to override Route53 and ALB's DNS name. | `string` | `null` | no | -| [atlantis\_github\_app\_id](#input\_atlantis\_github\_app\_id) | GitHub App ID that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_github\_app\_key](#input\_atlantis\_github\_app\_key) | GitHub App private key that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_github\_app\_key\_ssm\_parameter\_name](#input\_atlantis\_github\_app\_key\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_github\_app\_key | `string` | `"/atlantis/github/app/key"` | no | -| [atlantis\_github\_user](#input\_atlantis\_github\_user) | GitHub username that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_github\_user\_token](#input\_atlantis\_github\_user\_token) | GitHub token of the user that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_github\_user\_token\_ssm\_parameter\_name](#input\_atlantis\_github\_user\_token\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_github\_user\_token | `string` | `"/atlantis/github/user/token"` | no | -| [atlantis\_github\_webhook\_secret](#input\_atlantis\_github\_webhook\_secret) | GitHub webhook secret of an app that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_gitlab\_hostname](#input\_atlantis\_gitlab\_hostname) | Gitlab server hostname, defaults to gitlab.com | `string` | `"gitlab.com"` | no | -| [atlantis\_gitlab\_user](#input\_atlantis\_gitlab\_user) | Gitlab username that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_gitlab\_user\_token](#input\_atlantis\_gitlab\_user\_token) | Gitlab token of the user that is running the Atlantis command | `string` | `""` | no | -| [atlantis\_gitlab\_user\_token\_ssm\_parameter\_name](#input\_atlantis\_gitlab\_user\_token\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_gitlab\_user\_token | `string` | `"/atlantis/gitlab/user/token"` | no | -| [atlantis\_hide\_prev\_plan\_comments](#input\_atlantis\_hide\_prev\_plan\_comments) | Enables atlantis server --hide-prev-plan-comments hiding previous plan comments on update | `string` | `"false"` | no | -| [atlantis\_image](#input\_atlantis\_image) | Docker image to run Atlantis with. If not specified, official Atlantis image will be used | `string` | `""` | no | -| [atlantis\_log\_level](#input\_atlantis\_log\_level) | Log level that Atlantis will run with. Accepted values are: | `string` | `"debug"` | no | -| [atlantis\_port](#input\_atlantis\_port) | Local port Atlantis should be running on. Default value is most likely fine. | `number` | `4141` | no | -| [atlantis\_repo\_allowlist](#input\_atlantis\_repo\_allowlist) | List of allowed repositories Atlantis can be used with | `list(string)` | n/a | yes | -| [atlantis\_security\_group\_tags](#input\_atlantis\_security\_group\_tags) | Additional tags to put on the atlantis security group | `map(string)` | `{}` | no | -| [atlantis\_version](#input\_atlantis\_version) | Verion of Atlantis to run. If not specified latest will be used | `string` | `"latest"` | no | -| [atlantis\_write\_git\_creds](#input\_atlantis\_write\_git\_creds) | Write out a .git-credentials file with the provider user and token to allow cloning private modules over HTTPS or SSH | `string` | `"true"` | no | -| [azs](#input\_azs) | A list of availability zones in the region | `list(string)` | `[]` | no | +| [alb](#input\_alb) | Map of values passed to ALB module definition. See the [ALB module](https://github.com/terraform-aws-modules/terraform-aws-alb) for full list of arguments supported | `any` | `{}` | no | +| [alb\_security\_group\_id](#input\_alb\_security\_group\_id) | ID of an existing security group that will be used by ALB. Required if `create_alb` is `false` | `string` | `""` | no | +| [alb\_subnets](#input\_alb\_subnets) | List of subnets to place ALB in. Required if `create_alb` is `true` | `list(string)` | `[]` | no | +| [alb\_target\_group\_arn](#input\_alb\_target\_group\_arn) | ARN of an existing ALB target group that will be used to route traffic to the Atlantis service. Required if `create_alb` is `false` | `string` | `""` | no | +| [atlantis](#input\_atlantis) | Map of values passed to Atlantis container definition. See the [ECS container definition module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/container-definition) for full list of arguments supported | `any` | `{}` | no | +| [atlantis\_gid](#input\_atlantis\_gid) | GID of the atlantis user | `number` | `1000` | no | +| [atlantis\_uid](#input\_atlantis\_uid) | UID of the atlantis user | `number` | `100` | no | | [certificate\_arn](#input\_certificate\_arn) | ARN of certificate issued by AWS ACM. If empty, a new ACM certificate will be created and validated using Route53 DNS | `string` | `""` | no | -| [cidr](#input\_cidr) | The CIDR block for the VPC which will be created if `vpc_id` is not specified | `string` | `""` | no | -| [cloudwatch\_log\_retention\_in\_days](#input\_cloudwatch\_log\_retention\_in\_days) | Retention period of Atlantis CloudWatch logs | `number` | `7` | no | -| [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | -| [command](#input\_command) | The command that is passed to the container | `list(string)` | `null` | no | -| [container\_cpu](#input\_container\_cpu) | The number of cpu units used by the atlantis container. If not specified ecs\_task\_cpu will be used | `number` | `null` | no | -| [container\_depends\_on](#input\_container\_depends\_on) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
containerName = string
condition = string
}))
| `null` | no | -| [container\_memory](#input\_container\_memory) | The amount (in MiB) of memory used by the atlantis container. If not specified ecs\_task\_memory will be used | `number` | `null` | no | -| [container\_memory\_reservation](#input\_container\_memory\_reservation) | The amount of memory (in MiB) to reserve for the container | `number` | `128` | no | -| [create\_ecs\_cluster](#input\_create\_ecs\_cluster) | Whether to create an ECS cluster or not | `bool` | `true` | no | -| [create\_route53\_aaaa\_record](#input\_create\_route53\_aaaa\_record) | Whether to create Route53 AAAA record for Atlantis | `bool` | `false` | no | -| [create\_route53\_record](#input\_create\_route53\_record) | Whether to create Route53 A record for Atlantis | `bool` | `true` | no | -| [custom\_container\_definitions](#input\_custom\_container\_definitions) | A list of valid container definitions provided as a single valid JSON document. By default, the standard container definition is used. | `string` | `""` | no | -| [custom\_environment\_secrets](#input\_custom\_environment\_secrets) | List of additional secrets the container will use (list should contain maps with `name` and `valueFrom`) |
list(object(
{
name = string
valueFrom = string
}
))
| `[]` | no | -| [custom\_environment\_variables](#input\_custom\_environment\_variables) | List of additional environment variables the container will use (list should contain maps with `name` and `value`) |
list(object(
{
name = string
value = string
}
))
| `[]` | no | -| [default\_security\_group\_egress](#input\_default\_security\_group\_egress) | List of maps of egress rules to set on the default security group | `list(map(string))` | `[]` | no | -| [default\_security\_group\_ingress](#input\_default\_security\_group\_ingress) | List of maps of ingress rules to set on the default security group | `list(map(string))` | `[]` | no | -| [docker\_labels](#input\_docker\_labels) | The configuration options to send to the `docker_labels` | `map(string)` | `null` | no | -| [ecs\_cluster\_id](#input\_ecs\_cluster\_id) | ID of an existing ECS cluster where resources will be created | `string` | `""` | no | -| [ecs\_container\_insights](#input\_ecs\_container\_insights) | Controls if ECS Cluster has container insights enabled | `bool` | `false` | no | -| [ecs\_fargate\_spot](#input\_ecs\_fargate\_spot) | Whether to run ECS Fargate Spot or not | `bool` | `false` | no | -| [ecs\_service\_assign\_public\_ip](#input\_ecs\_service\_assign\_public\_ip) | Should be true, if ECS service is using public subnets (more info: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_cannot_pull_image.html) | `bool` | `false` | no | -| [ecs\_service\_deployment\_maximum\_percent](#input\_ecs\_service\_deployment\_maximum\_percent) | The upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment | `number` | `100` | no | -| [ecs\_service\_deployment\_minimum\_healthy\_percent](#input\_ecs\_service\_deployment\_minimum\_healthy\_percent) | The lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `0` | no | -| [ecs\_service\_desired\_count](#input\_ecs\_service\_desired\_count) | The number of instances of the task definition to place and keep running | `number` | `1` | no | -| [ecs\_service\_enable\_execute\_command](#input\_ecs\_service\_enable\_execute\_command) | Enable ECS exec for the service. This can be used to allow interactive sessions and commands to be executed in the container | `bool` | `true` | no | -| [ecs\_service\_force\_new\_deployment](#input\_ecs\_service\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g. myimage:latest) | `bool` | `false` | no | -| [ecs\_service\_platform\_version](#input\_ecs\_service\_platform\_version) | The platform version on which to run your service | `string` | `"LATEST"` | no | -| [ecs\_task\_cpu](#input\_ecs\_task\_cpu) | The number of cpu units used by the task | `number` | `256` | no | -| [ecs\_task\_memory](#input\_ecs\_task\_memory) | The amount (in MiB) of memory used by the task | `number` | `512` | no | -| [efs\_file\_system\_encrypted](#input\_efs\_file\_system\_encrypted) | If true, the disk will be encrypted. | `bool` | `false` | no | -| [efs\_file\_system\_token](#input\_efs\_file\_system\_token) | Be able to import other EFS instance created by the other module | `string` | `""` | no | -| [efs\_provisioned\_throughput\_in\_mibps](#input\_efs\_provisioned\_throughput\_in\_mibps) | The throughput, measured in MiB/s, that you want to provision for the file system. Only applicable with efs\_throughput\_mode set to provisioned | `number` | `null` | no | -| [efs\_throughput\_mode](#input\_efs\_throughput\_mode) | (Optional) Throughput mode for the file system. Defaults to bursting. Valid values: bursting, provisioned, or elastic. When using provisioned, also set provisioned\_throughput\_in\_mibps. | `string` | `null` | no | -| [enable\_ecs\_managed\_tags](#input\_enable\_ecs\_managed\_tags) | Specifies whether to enable Amazon ECS managed tags for the tasks within the service | `bool` | `false` | no | -| [enable\_ephemeral\_storage](#input\_enable\_ephemeral\_storage) | Enable to use Fargate Ephemeral Storage | `bool` | `false` | no | -| [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Should be true if you want to provision NAT Gateways for each of your private networks | `bool` | `true` | no | -| [entrypoint](#input\_entrypoint) | The entry point that is passed to the container | `list(string)` | `null` | no | -| [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | Size of Ephemeral Storage in GiB | `number` | `21` | no | -| [essential](#input\_essential) | Determines whether all other containers in a task are stopped, if this container fails or stops for any reason. Due to how Terraform type casts booleans in json it is required to double quote this value | `bool` | `true` | no | -| [external\_task\_definition\_updates](#input\_external\_task\_definition\_updates) | Enable to allow the task definition to be updated outside of this Terraform module. This should be enabled when using a deployment tool such as ecs-deploy which updates the task definition and will then keep the ECS service using the latest version of the task definition. | `bool` | `false` | no | -| [extra\_container\_definitions](#input\_extra\_container\_definitions) | A list of valid container definitions provided as a single valid JSON document. These will be provided as supplimentary to the main Atlantis container definition | `any` | `[]` | no | -| [extra\_load\_balancers](#input\_extra\_load\_balancers) | A list of maps for additional ECS task load balancers | `list(map(string))` | `[]` | no | -| [firelens\_configuration](#input\_firelens\_configuration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more details, see https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_FirelensConfiguration.html |
object({
type = string
options = map(string)
})
| `null` | no | -| [github\_webhooks\_cidr\_blocks](#input\_github\_webhooks\_cidr\_blocks) | List of IPv4 CIDR blocks used by GitHub webhooks | `list(string)` |
[
"140.82.112.0/20",
"185.199.108.0/22",
"192.30.252.0/22",
"143.55.64.0/20"
]
| no | -| [github\_webhooks\_ipv6\_cidr\_blocks](#input\_github\_webhooks\_ipv6\_cidr\_blocks) | List of IPv6 CIDR blocks used by GitHub webhooks | `list(string)` |
[
"2a0a:a440::/29",
"2606:50c0::/32"
]
| no | -| [internal](#input\_internal) | Whether the load balancer is internal or external | `bool` | `false` | no | -| [manage\_default\_security\_group](#input\_manage\_default\_security\_group) | Should be true to adopt and manage default security group | `bool` | `false` | no | -| [max\_session\_duration](#input\_max\_session\_duration) | Maximum session duration (in seconds) for ecs task execution role. Default is 3600. | `number` | `null` | no | -| [mount\_points](#input\_mount\_points) | Container mount points. This is a list of maps, where each map should contain a `containerPath` and `sourceVolume`. The `readOnly` key is optional. | `list(any)` | `[]` | no | -| [name](#input\_name) | Name to use on all resources created (VPC, ALB, etc) | `string` | `"atlantis"` | no | -| [path](#input\_path) | If provided, all IAM roles will be created with this path. | `string` | `"/"` | no | -| [permissions\_boundary](#input\_permissions\_boundary) | If provided, all IAM roles will be created with this permissions boundary attached. | `string` | `null` | no | -| [policies\_arn](#input\_policies\_arn) | A list of the ARN of the policies you want to apply | `list(string)` | `null` | no | -| [private\_subnet\_ids](#input\_private\_subnet\_ids) | A list of IDs of existing private subnets inside the VPC | `list(string)` | `[]` | no | -| [private\_subnets](#input\_private\_subnets) | A list of private subnets inside the VPC | `list(string)` | `[]` | no | -| [propagate\_tags](#input\_propagate\_tags) | Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are SERVICE and TASK\_DEFINITION | `string` | `null` | no | -| [public\_subnet\_ids](#input\_public\_subnet\_ids) | A list of IDs of existing public subnets inside the VPC | `list(string)` | `[]` | no | -| [public\_subnets](#input\_public\_subnets) | A list of public subnets inside the VPC | `list(string)` | `[]` | no | -| [readonly\_root\_filesystem](#input\_readonly\_root\_filesystem) | Determines whether a container is given read-only access to its root filesystem. Due to how Terraform type casts booleans in json it is required to double quote this value | `bool` | `false` | no | -| [repository\_credentials](#input\_repository\_credentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials | `map(string)` | `null` | no | -| [route53\_private\_zone](#input\_route53\_private\_zone) | Enable to use a private Route53 zone | `bool` | `false` | no | +| [certificate\_domain\_name](#input\_certificate\_domain\_name) | Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance. Specify if it is different from value in `route53_zone_name` | `string` | `""` | no | +| [cluster](#input\_cluster) | Map of values passed to ECS cluster module definition. See the [ECS cluster module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/cluster) for full list of arguments supported | `any` | `{}` | no | +| [cluster\_arn](#input\_cluster\_arn) | ARN of an existing ECS cluster where resources will be created. Required when `create_cluster` is `false` | `string` | `""` | no | +| [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | +| [create\_alb](#input\_create\_alb) | Determines whether to create an ALB or not | `bool` | `true` | no | +| [create\_certificate](#input\_create\_certificate) | Determines whether to create an ACM certificate or not. If `false`, `certificate_arn` must be provided | `bool` | `true` | no | +| [create\_cluster](#input\_create\_cluster) | Whether to create an ECS cluster or not | `bool` | `true` | no | +| [create\_route53\_records](#input\_create\_route53\_records) | Determines whether to create Route53 `A` and `AAAA` records for the loadbalancer | `bool` | `true` | no | +| [efs](#input\_efs) | Map of values passed to EFS module definition. See the [EFS module](https://github.com/terraform-aws-modules/terraform-aws-efs) for full list of arguments supported | `any` | `{}` | no | +| [enable\_efs](#input\_enable\_efs) | Determines whether to create and utilize an EFS filesystem | `bool` | `false` | no | +| [name](#input\_name) | Common name to use on all resources created unless a more specific name is provided | `string` | `"atlantis"` | no | | [route53\_record\_name](#input\_route53\_record\_name) | Name of Route53 record to create ACM certificate in and main A-record. If null is specified, var.name is used instead. Provide empty string to point root domain name to ALB. | `string` | `null` | no | -| [route53\_zone\_name](#input\_route53\_zone\_name) | Route53 zone name to create ACM certificate in and main A-record, without trailing dot | `string` | `""` | no | -| [runtime\_platform](#input\_runtime\_platform) | Configuration block for runtime\_platform that containers in your task may use. | `any` | `null` | no | -| [security\_group\_ids](#input\_security\_group\_ids) | List of one or more security groups to be added to the load balancer | `list(string)` | `[]` | no | -| [single\_nat\_gateway](#input\_single\_nat\_gateway) | Should be true if you want to provision a single shared NAT Gateway across all of your private networks | `bool` | `true` | no | -| [ssm\_kms\_key\_arn](#input\_ssm\_kms\_key\_arn) | ARN of KMS key to use for encryption and decryption of SSM Parameters. Required only if your key uses a custom KMS key and not the default key | `string` | `""` | no | -| [start\_timeout](#input\_start\_timeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | -| [stop\_timeout](#input\_stop\_timeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `30` | no | -| [tags](#input\_tags) | A map of tags to use on all resources | `map(string)` | `{}` | no | -| [trusted\_entities](#input\_trusted\_entities) | A list of users or roles, that can assume the task role | `list(string)` | `[]` | no | -| [trusted\_principals](#input\_trusted\_principals) | A list of principals, in addition to ecs-tasks.amazonaws.com, that can assume the task role | `list(string)` | `[]` | no | -| [ulimits](#input\_ulimits) | Container ulimit settings. This is a list of maps, where each map should contain "name", "hardLimit" and "softLimit" |
list(object({
name = string
hardLimit = number
softLimit = number
}))
| `null` | no | -| [use\_ecs\_old\_arn\_format](#input\_use\_ecs\_old\_arn\_format) | A flag to enable/disable tagging the ecs resources that require the new longer arn format | `bool` | `false` | no | -| [user](#input\_user) | The user to run as inside the container. Must be in the uid:gid or the default (null) will use the container's configured `USER` directive or root if not set. | `string` | `null` | no | -| [volumes\_from](#input\_volumes\_from) | A list of VolumesFrom maps which contain "sourceContainer" (name of the container that has the volumes to mount) and "readOnly" (whether the container can write to the volume) |
list(object({
sourceContainer = string
readOnly = bool
}))
| `[]` | no | -| [vpc\_id](#input\_vpc\_id) | ID of an existing VPC where resources will be created | `string` | `""` | no | -| [webhook\_ssm\_parameter\_name](#input\_webhook\_ssm\_parameter\_name) | Name of SSM parameter to keep webhook secret | `string` | `"/atlantis/webhook/secret"` | no | -| [whitelist\_unauthenticated\_cidr\_blocks](#input\_whitelist\_unauthenticated\_cidr\_blocks) | List of allowed CIDR blocks to bypass authentication | `list(string)` | `[]` | no | -| [working\_directory](#input\_working\_directory) | The working directory to run commands inside the container | `string` | `null` | no | +| [route53\_zone\_id](#input\_route53\_zone\_id) | Route53 zone ID to use for ACM certificate and Route53 records | `string` | `""` | no | +| [service](#input\_service) | Map of values passed to ECS service module definition. See the [ECS service module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/service) for full list of arguments supported | `any` | `{}` | no | +| [service\_subnets](#input\_service\_subnets) | List of subnets to place ECS service within | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [validate\_certificate](#input\_validate\_certificate) | Determines whether to validate ACM certificate using Route53 DNS. If `false`, certificate will be created but not validated | `bool` | `true` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where the resources will be provisioned | `string` | `""` | no | ## Outputs | Name | Description | |------|-------------| -| [alb\_arn](#output\_alb\_arn) | ARN of alb | -| [alb\_dns\_name](#output\_alb\_dns\_name) | Dns name of alb | -| [alb\_http\_listeners\_arn](#output\_alb\_http\_listeners\_arn) | ARNs of alb http listeners | -| [alb\_http\_listeners\_id](#output\_alb\_http\_listeners\_id) | Ids of alb http listeners | -| [alb\_https\_listeners\_arn](#output\_alb\_https\_listeners\_arn) | ARN of alb https listeners | -| [alb\_https\_listeners\_id](#output\_alb\_https\_listeners\_id) | Ids of alb https listeners | -| [alb\_security\_group\_id](#output\_alb\_security\_group\_id) | Security group of alb | -| [alb\_zone\_id](#output\_alb\_zone\_id) | Zone ID of alb | -| [atlantis\_repo\_allowlist](#output\_atlantis\_repo\_allowlist) | Git repositories where webhook should be created | -| [atlantis\_url](#output\_atlantis\_url) | URL of Atlantis | -| [atlantis\_url\_events](#output\_atlantis\_url\_events) | Webhook events URL of Atlantis | -| [ecs\_cluster\_arn](#output\_ecs\_cluster\_arn) | ECS cluster ARN | -| [ecs\_cluster\_id](#output\_ecs\_cluster\_id) | ECS cluster id | -| [ecs\_security\_group](#output\_ecs\_security\_group) | Security group assigned to ECS Service in network configuration | -| [ecs\_task\_definition](#output\_ecs\_task\_definition) | Task definition for ECS service (used for external triggers) | -| [private\_subnet\_ids](#output\_private\_subnet\_ids) | IDs of the VPC private subnets that were created or passed in | -| [public\_subnet\_ids](#output\_public\_subnet\_ids) | IDs of the VPC public subnets that were created or passed in | -| [task\_role\_arn](#output\_task\_role\_arn) | The Atlantis ECS task role arn | -| [task\_role\_id](#output\_task\_role\_id) | The Atlantis ECS task role id | -| [task\_role\_name](#output\_task\_role\_name) | The Atlantis ECS task role name | -| [task\_role\_unique\_id](#output\_task\_role\_unique\_id) | The stable and unique string identifying the Atlantis ECS task role. | -| [vpc\_id](#output\_vpc\_id) | ID of the VPC that was created or passed in | -| [vpc\_nat\_public\_ips](#output\_vpc\_nat\_public\_ips) | List of public Elastic IPs created for AWS NAT Gateway | -| [webhook\_secret](#output\_webhook\_secret) | Webhook secret | +| [alb](#output\_alb) | ALB created and all of its associated outputs | +| [cluster](#output\_cluster) | ECS cluster created and all of its associated outputs | +| [efs](#output\_efs) | EFS created and all of its associated outputs | +| [service](#output\_service) | ECS service created and all of its associated outputs | +| [url](#output\_url) | URL of Atlantis | ## Authors @@ -492,6 +283,6 @@ Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraf ## Additional information for users from Russia and Belarus -* Russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine). -* Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee. -* [Putin khuylo!](https://en.wikipedia.org/wiki/Putin_khuylo!) +- Russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine). +- Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee. +- [Putin khuylo!](https://en.wikipedia.org/wiki/Putin_khuylo!) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..a2b94e06 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,57 @@ +# Supplemental Documentation + +## GitHub + +### Authenticated Access via GitHub App + +A [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) can be generated to provide Atlantis access instead of using a GitHub personal access token (PAT): + +1. Create a GitHub App and give it a name - that name must be globally unique, and you can change it later if needed. +2. Provide a valid Homepage URL - this can be the atlantis server url, for instance `https://atlantis.mydomain.com` +3. Provide a valid [Webhook URL](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/using-webhooks-with-github-apps). The Atlantis webhook server path is located by default at `https://atlantis.mydomain.com/events`. +4. Generate a [Webhook Secret](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries). This is the value supplied to the `ATLANTIS_GH_WEBHOOK_SECRET` in the Atlantis server configuration. +5. Generate a Private Key. This is the value supplied to the `ATLANTIS_GH_APP_KEY` in the Atlantis server configuration. +6. On the App's settings page (at the top) you find the App ID. This is the value supplied to `ATLANTIS_GH_APP_ID` in the Atlantis server configuration. +7. On the Permissions & Events you need to setup all the permissions and events according to [Atlantis documentation](https://www.runatlantis.io/docs/access-credentials.html#github-app) + +Now you need to [install the App](https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app) on your organization. + +A self-provisioned GitHub App usually has two parts: the App and the Installation. + +The App part is the first step and its where you setup all the requirements, such as authentication, webhook, permissions, etc... The Installation part is where you add the created App to an organization/personal-account. It is on the installation page where you setup which repositories the application can access and receive events from. + +Once you have your GitHub App registered you will be able to access/manage the required parameters either through `environment` or `secret` (we strongly suggest supplying these through `secret`): + +```hcl +module "atlantis" { + source = "terraform-aws-modules/atlantis/aws" + + # Truncated for brevity ... + + # ECS Container Definition + atlantis = { + secrets = [ + { + name = "ATLANTIS_GH_APP_ID" + valueFrom = "" + }, + { + name = "ATLANTIS_GH_APP_KEY" + valueFrom = "" + }, + { + name = "ATLANTIS_GH_WEBHOOK_SECRET" + valueFrom = "" + }, + ] + } +} +``` + +## GitLab + +> TODO + +## BitBucket + +> TODO diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..f417c0ad --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index f5011f0c..b5837cdd 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -1,86 +1,70 @@ -# Complete Atlantis example with GitHub App and Webhooks +# Complete GitHub example -Configuration in this directory creates the necessary infrastructure and resources for running Atlantis on Fargate plus GitHub repository webhooks configured to Atlantis URL. - -An existing Route53 hosted zone and domain is required to deploy this example. +Configuration in this directory provisions Atlantis on ECS with EFS storage, ALB, Route53 record and GitHub repository webhooks. ## Usage To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Ensure that `bootstrap_github_app` is `true`. Once ready, execute: ```bash -$ terraform init -$ terraform plan -$ terraform apply +terraform init +terraform plan +terraform apply ``` -Terraform will output a URL to setup a new Github App via Atlantis, which should look something like https://$ATLANTIS_HOST/github-app/setup. Open that URL and go through the setup process. Before closing the window, click the link to install the new GitHub App on you repositories and copy the values `github_app_id`, `github_app_key`, and `github_webhook_secret` into `terraform.tfvars`. You should also set `bootstrap_github_app` to `false` . Now execute: - -```bash -$ terraform plan -$ terraform apply - -``` - -Note - if you receive the following error when running apply: - -`Error: InvalidParameterException: The new ARN and resource ID format must be enabled to add tags to the service. Opt in to the new format and try again. "atlantiscomplete"` - -Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settings (update for your region of use) and change `Container instance`, `Service`, and `Task` to `Enabled`. - -⚠️ This example will create resources which cost money. Run `terraform destroy` when you don't need these resources. ⚠️ +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.69 | -| [github](#requirement\_github) | >= 4.8 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | +| [github](#requirement\_github) | >= 5.0 | +| [random](#requirement\_random) | >= 3.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.69 | +| [aws](#provider\_aws) | >= 5.0 | +| [random](#provider\_random) | >= 3.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [atlantis](#module\_atlantis) | ../../ | n/a | -| [atlantis\_access\_log\_bucket](#module\_atlantis\_access\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [github\_repository\_webhooks](#module\_github\_repository\_webhooks) | ../../modules/github-repository-webhook | n/a | +| [secrets\_manager](#module\_secrets\_manager) | terraform-aws-modules/secrets-manager/aws | ~> 1.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | ## Resources | Name | Type | |------|------| -| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | -| [aws_elb_service_account.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account) | data source | -| [aws_iam_policy_document.atlantis_access_log_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [random_password.webhook_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [alb\_ingress\_cidr\_blocks](#input\_alb\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules of the ALB - use your personal IP in the form of `x.x.x.x/32` for restricted testing | `list(string)` | n/a | yes | -| [bootstrap\_github\_app](#input\_bootstrap\_github\_app) | Flag to configure Atlantis to bootstrap a new Github App | `bool` | n/a | yes | +| [atlantis\_github\_user](#input\_atlantis\_github\_user) | GitHub user or organization name | `string` | n/a | yes | +| [atlantis\_repo\_allowlist](#input\_atlantis\_repo\_allowlist) | List of GitHub repositories that Atlantis will be allowed to access | `list(string)` | n/a | yes | | [domain](#input\_domain) | Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance | `string` | n/a | yes | -| [github\_app\_id](#input\_github\_app\_id) | GitHub App ID that is running the Atlantis command | `string` | n/a | yes | -| [github\_app\_key](#input\_github\_app\_key) | The PEM encoded private key for the GitHub App | `string` | n/a | yes | -| [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | -| [github\_repo\_names](#input\_github\_repo\_names) | List of Github repositories that should be monitored by Atlantis | `list(string)` | n/a | yes | -| [github\_webhook\_secret](#input\_github\_webhook\_secret) | Webhook secret | `string` | n/a | yes | +| [github\_owner](#input\_github\_owner) | Github owner to use when creating webhook | `string` | n/a | yes | +| [github\_token](#input\_github\_token) | Github token to use when creating webhook | `string` | n/a | yes | ## Outputs | Name | Description | |------|-------------| -| [atlantis\_github\_app\_setup\_url](#output\_atlantis\_github\_app\_setup\_url) | URL to create a new Github App with Atlantis | -| [atlantis\_repo\_allowlist](#output\_atlantis\_repo\_allowlist) | Git repositories where webhook should be created | +| [alb](#output\_alb) | ALB created and all of its associated outputs | | [atlantis\_url](#output\_atlantis\_url) | URL of Atlantis | -| [ecs\_task\_definition](#output\_ecs\_task\_definition) | Task definition for ECS service (used for external triggers) | -| [task\_role\_arn](#output\_task\_role\_arn) | The Atlantis ECS task role arn | +| [cluster](#output\_cluster) | ECS cluster created and all of its associated outputs | +| [efs](#output\_efs) | EFS created and all of its associated outputs | +| [service](#output\_service) | ECS service created and all of its associated | diff --git a/examples/github-complete/main.tf b/examples/github-complete/main.tf index 24f7f398..5dd66398 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -2,28 +2,33 @@ provider "aws" { region = local.region } -locals { - name = "github-complete" - region = "eu-west-1" +provider "github" { + token = var.github_token + owner = var.github_owner +} - tags = { - Owner = "user" - Environment = "dev" - } +data "aws_route53_zone" "this" { + name = var.domain } -################################################################################ -# Supporting Resources -################################################################################ +data "aws_availability_zones" "available" {} -data "aws_caller_identity" "current" {} +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" -data "aws_region" "current" {} + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) -data "aws_elb_service_account" "current" {} + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-atlantis" + } +} ############################################################## -# Atlantis Service +# Atlantis ############################################################## module "atlantis" { @@ -31,226 +36,127 @@ module "atlantis" { name = local.name - # VPC - cidr = "10.20.0.0/16" - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] - private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] - public_subnets = ["10.20.101.0/24", "10.20.102.0/24", "10.20.103.0/24"] - - # EFS - enable_ephemeral_storage = true - # ECS - ecs_service_platform_version = "LATEST" - ecs_container_insights = true - ecs_task_cpu = 512 - ecs_task_memory = 1024 - container_memory_reservation = 256 - container_cpu = 512 - container_memory = 1024 - - runtime_platform = { - operating_system_family = "LINUX" - cpu_architecture = "ARM64" + atlantis = { + environment = [ + { + name = "ATLANTIS_GH_USER" + value = var.atlantis_github_user + }, + { + name = "ATLANTIS_REPO_ALLOWLIST" + value = join(",", var.atlantis_repo_allowlist) + }, + { + name = "ATLANTIS_ENABLE_DIFF_MARKDOWN_FORMAT" + value = "true" + }, + ] + secrets = [ + { + name = "ATLANTIS_GH_TOKEN" + valueFrom = try(module.secrets_manager["github-token"].secret_arn, "") + }, + { + name = "ATLANTIS_GH_WEBHOOK_SECRET" + valueFrom = try(module.secrets_manager["github-webhook-secret"].secret_arn, "") + }, + ] } - entrypoint = ["docker-entrypoint.sh"] - command = ["server"] - working_directory = "/tmp" - docker_labels = { - "org.opencontainers.image.title" = "Atlantis" - "org.opencontainers.image.description" = "A self-hosted golang application that listens for Terraform pull request events via webhooks." - "org.opencontainers.image.url" = "https://github.com/runatlantis/atlantis/pkgs/container/atlantis" + service = { + task_exec_secret_arns = [for sec in module.secrets_manager : sec.secret_arn] + # Provide Atlantis permission necessary to create/destroy resources + tasks_iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } } - start_timeout = 30 - stop_timeout = 30 - - readonly_root_filesystem = false # atlantis currently mutable access to root filesystem - ulimits = [{ - name = "nofile" - softLimit = 4096 - hardLimit = 16384 - }] - - # DNS - route53_zone_name = var.domain - - # Trusted roles - trusted_principals = ["ssm.amazonaws.com"] - - # IAM role options - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/cloud/developer-boundary-policy" - path = "/delegatedadmin/developer/" - # Bootstrapping a new Github App - atlantis_github_user = var.bootstrap_github_app ? "fake" : "" - atlantis_github_user_token = var.bootstrap_github_app ? "fake" : "" - - # Atlantis w/ GitHub app - ################################################################################ - # Suggestion: instead of allocating the values of the atlantis_github_app_key - # and atlantis_github_webhook_secret in the tfvars file,it is suggested to - # upload the values in the AWS Parameter Store of the atlantis account and - # call the values via the data source function - # (e.g. data.aws_ssm_parameter.ghapp_key.value) for security reasons. - ################################################################################ - - atlantis_github_app_id = var.bootstrap_github_app ? "" : var.github_app_id - atlantis_github_app_key = var.bootstrap_github_app ? "" : var.github_app_key - atlantis_github_webhook_secret = var.bootstrap_github_app ? "" : var.github_webhook_secret - atlantis_repo_allowlist = [for repo in var.github_repo_names : "github.com/${var.github_owner}/${repo}"] - - # ALB access - alb_ingress_cidr_blocks = var.alb_ingress_cidr_blocks - alb_logging_enabled = true - alb_log_bucket_name = module.atlantis_access_log_bucket.s3_bucket_id - alb_log_location_prefix = "atlantis-alb" - alb_listener_ssl_policy_default = "ELBSecurityPolicy-TLS-1-2-2017-01" - alb_drop_invalid_header_fields = true - alb_enable_cross_zone_load_balancing = true - - allow_unauthenticated_access = true - allow_github_webhooks = true - allow_repo_config = true + # ALB + alb = { + # For example only + enable_deletion_protection = false + } - # Extra container definitions - extra_container_definitions = [ - { - name = "log-router" - image = "amazon/aws-for-fluent-bit:latest" - essential = true + alb_subnets = module.vpc.public_subnets + service_subnets = module.vpc.private_subnets + vpc_id = module.vpc.vpc_id - firelens_configuration = { - type = "fluentbit" + # ACM + certificate_domain_name = "${local.name}.${var.domain}" + route53_zone_id = data.aws_route53_zone.this.id - logConfiguration = { - logDriver = "awslogs", - options = { - awslogs-group = "firelens-container", - awslogs-region = local.region, - awslogs-create-group = true, - awslogs-stream-prefix = "firelens" - } - } + # EFS + enable_efs = true + efs = { + mount_targets = { + "eu-west-1a" = { + subnet_id = module.vpc.private_subnets[0] + } + "eu-west-1b" = { + subnet_id = module.vpc.private_subnets[1] + } + "eu-west-1c" = { + subnet_id = module.vpc.private_subnets[2] } } - ] + } tags = local.tags } -################################################################################ -# ALB Access Log Bucket + Policy -################################################################################ -module "atlantis_access_log_bucket" { - source = "terraform-aws-modules/s3-bucket/aws" - version = "~> 3.0" +module "github_repository_webhooks" { + source = "../../modules/github-repository-webhook" - bucket = "atlantis-access-logs-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" + repositories = var.atlantis_repo_allowlist - attach_policy = true - policy = data.aws_iam_policy_document.atlantis_access_log_bucket_policy.json + webhook_url = "${module.atlantis.url}/events" + webhook_secret = random_password.webhook_secret.result +} - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true +################################################################################ +# Supporting Resources +################################################################################ - force_destroy = true +resource "random_password" "webhook_secret" { + length = 32 + special = false +} - tags = local.tags +module "secrets_manager" { + source = "terraform-aws-modules/secrets-manager/aws" + version = "~> 1.0" - server_side_encryption_configuration = { - rule = { - apply_server_side_encryption_by_default = { - sse_algorithm = "AES256" - } + for_each = { + github-token = { + secret_string = var.github_token + } + github-webhook-secret = { + secret_string = random_password.webhook_secret.result } } - lifecycle_rule = [ - { - id = "all" - enabled = true - - transition = [ - { - days = 30 - storage_class = "ONEZONE_IA" - }, { - days = 60 - storage_class = "GLACIER" - } - ] + # Secret + name_prefix = each.key + recovery_window_in_days = 0 # For example only + secret_string = each.value.secret_string - expiration = { - days = 90 - } - - noncurrent_version_expiration = { - days = 30 - } - }, - ] + tags = local.tags } -data "aws_iam_policy_document" "atlantis_access_log_bucket_policy" { - statement { - sid = "LogsLogDeliveryWrite" - effect = "Allow" - actions = ["s3:PutObject"] - resources = [ - "${module.atlantis_access_log_bucket.s3_bucket_arn}/*/AWSLogs/${data.aws_caller_identity.current.account_id}/*" - ] - - principals { - type = "AWS" - identifiers = [ - # https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions - data.aws_elb_service_account.current.arn, - ] - } - } - - statement { - sid = "AWSLogDeliveryWrite" - effect = "Allow" - actions = ["s3:PutObject"] - resources = [ - "${module.atlantis_access_log_bucket.s3_bucket_arn}/*/AWSLogs/${data.aws_caller_identity.current.account_id}/*" - ] - - principals { - type = "Service" - identifiers = [ - "delivery.logs.amazonaws.com" - ] - } +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" - condition { - test = "StringEquals" - variable = "s3:x-amz-acl" + name = local.name + cidr = local.vpc_cidr - values = [ - "bucket-owner-full-control" - ] - } - } + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - statement { - sid = "AWSLogDeliveryAclCheck" - effect = "Allow" - actions = ["s3:GetBucketAcl"] - resources = [ - module.atlantis_access_log_bucket.s3_bucket_arn - ] + enable_nat_gateway = true + single_nat_gateway = true - principals { - type = "Service" - identifiers = [ - "delivery.logs.amazonaws.com" - ] - } - } + tags = local.tags } - diff --git a/examples/github-complete/outputs.tf b/examples/github-complete/outputs.tf index f512bff1..676ef08f 100644 --- a/examples/github-complete/outputs.tf +++ b/examples/github-complete/outputs.tf @@ -1,25 +1,36 @@ -# Atlantis output "atlantis_url" { description = "URL of Atlantis" - value = module.atlantis.atlantis_url + value = module.atlantis.url } -output "atlantis_github_app_setup_url" { - description = "URL to create a new Github App with Atlantis" - value = "${module.atlantis.atlantis_url}/github-app/setup" +################################################################################ +# Load Balancer +################################################################################ + +output "alb" { + description = "ALB created and all of its associated outputs" + value = module.atlantis.alb } -output "atlantis_repo_allowlist" { - description = "Git repositories where webhook should be created" - value = module.atlantis.atlantis_repo_allowlist +################################################################################ +# ECS +################################################################################ + +output "cluster" { + description = "ECS cluster created and all of its associated outputs" + value = module.atlantis.cluster } -output "task_role_arn" { - description = "The Atlantis ECS task role arn" - value = module.atlantis.task_role_arn +output "service" { + description = "ECS service created and all of its associated" + value = module.atlantis.service } -output "ecs_task_definition" { - description = "Task definition for ECS service (used for external triggers)" - value = module.atlantis.ecs_task_definition +################################################################################ +# EFS +################################################################################ + +output "efs" { + description = "EFS created and all of its associated outputs" + value = module.atlantis.efs } diff --git a/examples/github-complete/terraform.tfvars.sample b/examples/github-complete/terraform.tfvars.sample index ca86b85e..05e2c0e3 100644 --- a/examples/github-complete/terraform.tfvars.sample +++ b/examples/github-complete/terraform.tfvars.sample @@ -1,10 +1,7 @@ +github_token = "ghp_aldkfjadkfjaldfjk" +github_owner = "me" + domain = "mydomain.com" -alb_ingress_cidr_blocks = ["x.x.x.x/32"] -github_owner = "myorg" -github_repo_names = ["mycoolrepo1", "mycoolrepo2"] -bootstrap_github_app = true -github_app_id = "mygithubappid" -github_app_key = <<-EOL ------BEGIN RSA PRIVATE KEY-----(...) -EOL -github_webhook_secret = "mywebhooksecretforatlantis" + +atlantis_github_user = "JohnDoe" +atlantis_repo_allowlist = ["my-repo"] diff --git a/examples/github-complete/variables.tf b/examples/github-complete/variables.tf index 81222e40..d5d13659 100644 --- a/examples/github-complete/variables.tf +++ b/examples/github-complete/variables.tf @@ -1,39 +1,24 @@ -variable "domain" { - description = "Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance" +variable "github_token" { + description = "Github token to use when creating webhook" type = string } -variable "alb_ingress_cidr_blocks" { - description = "List of IPv4 CIDR ranges to use on all ingress rules of the ALB - use your personal IP in the form of `x.x.x.x/32` for restricted testing" - type = list(string) -} - variable "github_owner" { - description = "Github owner" - type = string -} - -variable "github_repo_names" { - description = "List of Github repositories that should be monitored by Atlantis" - type = list(string) -} - -variable "github_app_id" { + description = "Github owner to use when creating webhook" type = string - description = "GitHub App ID that is running the Atlantis command" } -variable "github_app_key" { - description = "The PEM encoded private key for the GitHub App" +variable "domain" { + description = "Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance" type = string } -variable "github_webhook_secret" { - description = "Webhook secret" +variable "atlantis_github_user" { + description = "GitHub user or organization name" type = string } -variable "bootstrap_github_app" { - description = "Flag to configure Atlantis to bootstrap a new Github App" - type = bool +variable "atlantis_repo_allowlist" { + description = "List of GitHub repositories that Atlantis will be allowed to access" + type = list(string) } diff --git a/examples/github-complete/versions.tf b/examples/github-complete/versions.tf index 54b8a5b5..e759c653 100644 --- a/examples/github-complete/versions.tf +++ b/examples/github-complete/versions.tf @@ -1,15 +1,20 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.69" + version = ">= 5.0" } github = { source = "integrations/github" - version = ">= 4.8" + version = ">= 5.0" + } + + random = { + source = "hashicorp/random" + version = ">= 3.0" } } } diff --git a/examples/github-repository-webhook/README.md b/examples/github-repository-webhook/README.md deleted file mode 100644 index fc9c3373..00000000 --- a/examples/github-repository-webhook/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# GitHub repository webhook for Atlantis - -Configuration in this directory creates GitHub repository webhooks configured to Atlantis URL. This example uses value of webhook secret which got generated when Atlantis setup by referring to `terraform.tfstate`, so this example has to run after Atlantis. - -GitHub's personal access token can be generated at https://github.com/settings/tokens - -## Usage - -To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and put your GitHub token and Github owner there or specify them using environment variables (`TF_VAR_github_token` and `TF_VAR_github_owner`). Once ready, execute: - -```bash -$ terraform init -$ terraform plan -$ terraform apply -``` - -Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.45 | -| [github](#requirement\_github) | >= 4.8 | - -## Providers - -| Name | Version | -|------|---------| -| [terraform](#provider\_terraform) | n/a | - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [github\_repository\_webhook](#module\_github\_repository\_webhook) | ../../modules/github-repository-webhook | n/a | - -## Resources - -| Name | Type | -|------|------| -| [terraform_remote_state.atlantis](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | -| [github\_token](#input\_github\_token) | Github token | `string` | n/a | yes | - -## Outputs - -| Name | Description | -|------|-------------| -| [github\_webhook\_secret](#output\_github\_webhook\_secret) | Github webhook secret | -| [github\_webhook\_urls](#output\_github\_webhook\_urls) | Github webhook URL | - diff --git a/examples/github-repository-webhook/main.tf b/examples/github-repository-webhook/main.tf deleted file mode 100644 index a863c67a..00000000 --- a/examples/github-repository-webhook/main.tf +++ /dev/null @@ -1,25 +0,0 @@ -data "terraform_remote_state" "atlantis" { - backend = "local" - - config = { - path = "../github-complete/terraform.tfstate" - } -} - -module "github_repository_webhook" { - source = "../../modules/github-repository-webhook" - - create_github_repository_webhook = true - - github_token = var.github_token - github_owner = var.github_owner - - # Fetching these attributes from created already Atlantis Terraform state file - # - # This assumes that you are the owner of these repositories and they are available at: - # https://github.com/mygithubusername/awesome-repo and https://github.com/mygithubusername/another-awesome-repo - atlantis_repo_allowlist = data.terraform_remote_state.atlantis.outputs.atlantis_repo_allowlist - - webhook_url = element(data.terraform_remote_state.atlantis.outputs.github_webhook_urls, 0) - webhook_secret = data.terraform_remote_state.atlantis.outputs.github_webhook_secret -} diff --git a/examples/github-repository-webhook/outputs.tf b/examples/github-repository-webhook/outputs.tf deleted file mode 100644 index 7d070414..00000000 --- a/examples/github-repository-webhook/outputs.tf +++ /dev/null @@ -1,10 +0,0 @@ -output "github_webhook_urls" { - description = "Github webhook URL" - value = module.github_repository_webhook.repository_webhook_urls -} - -output "github_webhook_secret" { - description = "Github webhook secret" - value = module.github_repository_webhook.repository_webhook_secret - sensitive = true -} diff --git a/examples/github-repository-webhook/terraform.tfvars.sample b/examples/github-repository-webhook/terraform.tfvars.sample deleted file mode 100644 index 4601dd03..00000000 --- a/examples/github-repository-webhook/terraform.tfvars.sample +++ /dev/null @@ -1,2 +0,0 @@ -github_token = "mygithubtoken" -github_owner = "myusername" diff --git a/examples/github-repository-webhook/variables.tf b/examples/github-repository-webhook/variables.tf deleted file mode 100644 index a5b5666c..00000000 --- a/examples/github-repository-webhook/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "github_token" { - description = "Github token" - type = string -} - -variable "github_owner" { - description = "Github owner" - type = string -} diff --git a/examples/github-repository-webhook/versions.tf b/examples/github-repository-webhook/versions.tf deleted file mode 100644 index 28ce680e..00000000 --- a/examples/github-repository-webhook/versions.tf +++ /dev/null @@ -1,15 +0,0 @@ -terraform { - required_version = ">= 0.13.1" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 3.45" - } - - github = { - source = "integrations/github" - version = ">= 4.8" - } - } -} diff --git a/examples/github-separate/README.md b/examples/github-separate/README.md new file mode 100644 index 00000000..db88f678 --- /dev/null +++ b/examples/github-separate/README.md @@ -0,0 +1,70 @@ +# Separate GitHub example + +Configuration in this directory provisions Atlantis on ECS with EFS storage and GitHub repository webhooks. It demonstrates how users can deploy this module on an existing ECS Fargate cluster and with an existing ALB. + +## Usage + +To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Once ready, execute: + +```bash +terraform init +terraform plan +terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | +| [github](#requirement\_github) | >= 5.0 | +| [random](#requirement\_random) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.0 | +| [random](#provider\_random) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 9.1.0 | +| [atlantis](#module\_atlantis) | ../../ | n/a | +| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | 5.6.0 | +| [github\_repository\_webhooks](#module\_github\_repository\_webhooks) | ../../modules/github-repository-webhook | n/a | +| [secrets\_manager](#module\_secrets\_manager) | terraform-aws-modules/secrets-manager/aws | ~> 1.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [random_password.webhook_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [atlantis\_github\_user](#input\_atlantis\_github\_user) | GitHub user or organization name | `string` | n/a | yes | +| [atlantis\_repo\_allowlist](#input\_atlantis\_repo\_allowlist) | List of GitHub repositories that Atlantis will be allowed to access | `list(string)` | n/a | yes | +| [github\_owner](#input\_github\_owner) | Github owner to use when creating webhook | `string` | n/a | yes | +| [github\_token](#input\_github\_token) | Github token to use when creating webhook | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [alb](#output\_alb) | ALB created and all of its associated outputs | +| [atlantis\_url](#output\_atlantis\_url) | URL of Atlantis | +| [cluster](#output\_cluster) | ECS cluster created and all of its associated outputs | +| [efs](#output\_efs) | EFS created and all of its associated outputs | +| [service](#output\_service) | ECS service created and all of its associated | + diff --git a/examples/github-separate/main.tf b/examples/github-separate/main.tf new file mode 100644 index 00000000..83a6db8d --- /dev/null +++ b/examples/github-separate/main.tf @@ -0,0 +1,238 @@ +provider "aws" { + region = local.region +} + +provider "github" { + token = var.github_token + owner = var.github_owner +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-atlantis" + } +} + +############################################################## +# Atlantis +############################################################## + +module "atlantis" { + source = "../../" + + name = local.name + + # Existing cluster + create_cluster = false + cluster_arn = module.ecs_cluster.arn + + # Existing ALB + create_alb = false + alb_target_group_arn = module.alb.target_groups["atlantis"].arn + alb_security_group_id = module.alb.security_group_id + + # ECS + atlantis = { + environment = [ + { + name = "ATLANTIS_GH_USER" + value = var.atlantis_github_user + }, + { + name = "ATLANTIS_REPO_ALLOWLIST" + value = join(",", var.atlantis_repo_allowlist) + }, + { + name = "ATLANTIS_ENABLE_DIFF_MARKDOWN_FORMAT" + value = "true" + }, + ] + secrets = [ + { + name = "ATLANTIS_GH_TOKEN" + valueFrom = try(module.secrets_manager["github-token"].secret_arn, "") + }, + { + name = "ATLANTIS_GH_WEBHOOK_SECRET" + valueFrom = try(module.secrets_manager["github-webhook-secret"].secret_arn, "") + }, + ] + } + + service = { + task_exec_secret_arns = [for sec in module.secrets_manager : sec.secret_arn] + # Provide Atlantis permission necessary to create/destroy resources + tasks_iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + } + + service_subnets = module.vpc.private_subnets + vpc_id = module.vpc.vpc_id + + # EFS + enable_efs = true + efs = { + mount_targets = { + "eu-west-1a" = { + subnet_id = module.vpc.private_subnets[0] + } + "eu-west-1b" = { + subnet_id = module.vpc.private_subnets[1] + } + "eu-west-1c" = { + subnet_id = module.vpc.private_subnets[2] + } + } + } + + tags = local.tags +} + +module "github_repository_webhooks" { + source = "../../modules/github-repository-webhook" + + repositories = var.atlantis_repo_allowlist + + webhook_url = "http://${module.alb.dns_name}/events" + webhook_secret = random_password.webhook_secret.result +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "5.6.0" + + # Cluster + cluster_name = local.name + cluster_settings = { + name = "containerInsights" + value = "enabled" + } + + tags = local.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "9.1.0" + + name = local.name + + # Load balancer + enable_deletion_protection = false # For example only + subnets = module.vpc.public_subnets + + # Listener(s) + default_port = 80 + default_protocol = "HTTP" + listeners = { + http = { + port = 80 + protocol = "HTTP" + + forward = { + target_group_key = "atlantis" + } + } + } + + # Target group(s) + target_groups = { + atlantis = { + backend_protocol = "HTTP" + backend_port = 4141 + create_attachment = false + target_type = "ip" + deregistration_delay = 10 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/healthz" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + } + } + + # Security group + vpc_id = module.vpc.vpc_id + security_group_ingress_rules = { + http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + } + } + + tags = local.tags +} + +resource "random_password" "webhook_secret" { + length = 32 + special = false +} + +module "secrets_manager" { + source = "terraform-aws-modules/secrets-manager/aws" + version = "~> 1.0" + + for_each = { + github-token = { + secret_string = var.github_token + } + github-webhook-secret = { + secret_string = random_password.webhook_secret.result + } + } + + # Secret + name_prefix = each.key + recovery_window_in_days = 0 # For example only + secret_string = each.value.secret_string + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/examples/github-separate/outputs.tf b/examples/github-separate/outputs.tf new file mode 100644 index 00000000..676ef08f --- /dev/null +++ b/examples/github-separate/outputs.tf @@ -0,0 +1,36 @@ +output "atlantis_url" { + description = "URL of Atlantis" + value = module.atlantis.url +} + +################################################################################ +# Load Balancer +################################################################################ + +output "alb" { + description = "ALB created and all of its associated outputs" + value = module.atlantis.alb +} + +################################################################################ +# ECS +################################################################################ + +output "cluster" { + description = "ECS cluster created and all of its associated outputs" + value = module.atlantis.cluster +} + +output "service" { + description = "ECS service created and all of its associated" + value = module.atlantis.service +} + +################################################################################ +# EFS +################################################################################ + +output "efs" { + description = "EFS created and all of its associated outputs" + value = module.atlantis.efs +} diff --git a/examples/github-separate/terraform.tfvars.sample b/examples/github-separate/terraform.tfvars.sample new file mode 100644 index 00000000..83877255 --- /dev/null +++ b/examples/github-separate/terraform.tfvars.sample @@ -0,0 +1,5 @@ +github_token = "ghp_aldkfjadkfjaldfjk" +github_owner = "me" + +atlantis_github_user = "JohnDoe" +atlantis_repo_allowlist = ["my-repo"] diff --git a/examples/github-separate/variables.tf b/examples/github-separate/variables.tf new file mode 100644 index 00000000..ac1bf38f --- /dev/null +++ b/examples/github-separate/variables.tf @@ -0,0 +1,19 @@ +variable "github_token" { + description = "Github token to use when creating webhook" + type = string +} + +variable "github_owner" { + description = "Github owner to use when creating webhook" + type = string +} + +variable "atlantis_github_user" { + description = "GitHub user or organization name" + type = string +} + +variable "atlantis_repo_allowlist" { + description = "List of GitHub repositories that Atlantis will be allowed to access" + type = list(string) +} diff --git a/examples/github-separate/versions.tf b/examples/github-separate/versions.tf new file mode 100644 index 00000000..e759c653 --- /dev/null +++ b/examples/github-separate/versions.tf @@ -0,0 +1,20 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + + github = { + source = "integrations/github" + version = ">= 5.0" + } + + random = { + source = "hashicorp/random" + version = ">= 3.0" + } + } +} diff --git a/examples/gitlab-repository-webhook/README.md b/examples/gitlab-repository-webhook/README.md deleted file mode 100644 index 63fffb83..00000000 --- a/examples/gitlab-repository-webhook/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Gitlab repository webhook for Atlantis - -Configuration in this directory creates Gitlab repository webhooks configured to Atlantis URL. This example uses value of webhook secret which got generated when Atlantis setup by referring to `terraform.tfstate`, so this example has to run after Atlantis. - -## Usage - -To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and put your GitHub token and Github owner there or specify them using environment variables (`TF_VAR_gitlab_token` and `TF_VAR_gitlab_base_url`). Once ready, execute: - -```bash -$ terraform init -$ terraform plan -$ terraform apply -``` - -Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.45 | -| [gitlab](#requirement\_gitlab) | 3.20.0 | - -## Providers - -| Name | Version | -|------|---------| -| [terraform](#provider\_terraform) | n/a | - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [gitlab\_repository\_webhook](#module\_gitlab\_repository\_webhook) | ../../modules/gitlab-repository-webhook | n/a | - -## Resources - -| Name | Type | -|------|------| -| [terraform_remote_state.atlantis](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [gitlab\_base\_url](#input\_gitlab\_base\_url) | Gitlab base\_url | `string` | `""` | no | -| [gitlab\_token](#input\_gitlab\_token) | Gitlab token | `string` | n/a | yes | - -## Outputs - -| Name | Description | -|------|-------------| -| [gitlab\_webhook\_secret](#output\_gitlab\_webhook\_secret) | Gitlab webhook secret | -| [gitlab\_webhook\_urls](#output\_gitlab\_webhook\_urls) | Gitlab webhook URL | - diff --git a/examples/gitlab-repository-webhook/main.tf b/examples/gitlab-repository-webhook/main.tf deleted file mode 100644 index f4b36ccd..00000000 --- a/examples/gitlab-repository-webhook/main.tf +++ /dev/null @@ -1,21 +0,0 @@ -data "terraform_remote_state" "atlantis" { - backend = "local" - - config = { - path = "../../terraform.tfstate" - } -} - -module "gitlab_repository_webhook" { - source = "../../modules/gitlab-repository-webhook" - - create_gitlab_repository_webhook = true - - gitlab_token = var.gitlab_token - gitlab_base_url = var.gitlab_base_url - - # Fetching these attributes from created already Atlantis Terraform state file - atlantis_repo_allowlist = data.terraform_remote_state.atlantis.outputs.atlantis_repo_allowlist - webhook_url = data.terraform_remote_state.atlantis.outputs.atlantis_url_events - webhook_secret = data.terraform_remote_state.atlantis.outputs.webhook_secret -} diff --git a/examples/gitlab-repository-webhook/outputs.tf b/examples/gitlab-repository-webhook/outputs.tf deleted file mode 100644 index a267ae2e..00000000 --- a/examples/gitlab-repository-webhook/outputs.tf +++ /dev/null @@ -1,10 +0,0 @@ -output "gitlab_webhook_urls" { - description = "Gitlab webhook URL" - value = module.gitlab_repository_webhook.repository_webhook_urls -} - -output "gitlab_webhook_secret" { - description = "Gitlab webhook secret" - value = module.gitlab_repository_webhook.repository_webhook_secret - sensitive = true -} diff --git a/examples/gitlab-repository-webhook/terraform.tfvars.sample b/examples/gitlab-repository-webhook/terraform.tfvars.sample deleted file mode 100644 index e2cb13c7..00000000 --- a/examples/gitlab-repository-webhook/terraform.tfvars.sample +++ /dev/null @@ -1,2 +0,0 @@ -gitlab_token = "mygitlabtoken" -gitlab_base_url = "" diff --git a/examples/gitlab-repository-webhook/variables.tf b/examples/gitlab-repository-webhook/variables.tf deleted file mode 100644 index 02df1adf..00000000 --- a/examples/gitlab-repository-webhook/variables.tf +++ /dev/null @@ -1,10 +0,0 @@ -variable "gitlab_token" { - description = "Gitlab token" - type = string -} - -variable "gitlab_base_url" { - description = "Gitlab base_url" - type = string - default = "" -} diff --git a/examples/gitlab-repository-webhook/versions.tf b/examples/gitlab-repository-webhook/versions.tf deleted file mode 100644 index 15466361..00000000 --- a/examples/gitlab-repository-webhook/versions.tf +++ /dev/null @@ -1,16 +0,0 @@ -terraform { - required_version = ">= 0.13.1" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 3.45" - } - - gitlab = { - # gitlab provider requires terraform 1.0 or later starting in 15.x.x - source = "gitlabhq/gitlab" - version = "3.20.0" - } - } -} diff --git a/main.tf b/main.tf index 3d6ae4a1..e73450ae 100644 --- a/main.tf +++ b/main.tf @@ -1,848 +1,539 @@ locals { - # VPC - existing or new? - vpc_id = var.vpc_id == "" ? module.vpc.vpc_id : var.vpc_id - private_subnet_ids = coalescelist(module.vpc.private_subnets, var.private_subnet_ids, [""]) - public_subnet_ids = coalescelist(module.vpc.public_subnets, var.public_subnet_ids, [""]) - # Atlantis - atlantis_image = var.atlantis_image == "" ? "ghcr.io/runatlantis/atlantis:${var.atlantis_version}" : var.atlantis_image - atlantis_url = "https://${coalesce( - var.atlantis_fqdn, - element(concat(aws_route53_record.atlantis[*].fqdn, [""]), 0), - module.alb.lb_dns_name, - "_" - )}" - atlantis_url_events = "${local.atlantis_url}/events" - - # Include only one group of secrets - for github, github app, gitlab or bitbucket - has_secrets = try(coalesce(var.atlantis_gitlab_user_token, var.atlantis_github_user_token, var.atlantis_github_app_key, var.atlantis_bitbucket_user_token) != "", false) - - # token/key - secret_name_key = local.has_secrets ? var.atlantis_gitlab_user_token != "" ? "ATLANTIS_GITLAB_TOKEN" : var.atlantis_github_user_token != "" ? "ATLANTIS_GH_TOKEN" : var.atlantis_github_app_key != "" ? "ATLANTIS_GH_APP_KEY" : "ATLANTIS_BITBUCKET_TOKEN" : "" - secret_name_value_from = local.has_secrets ? var.atlantis_gitlab_user_token != "" ? var.atlantis_gitlab_user_token_ssm_parameter_name : var.atlantis_github_user_token != "" ? var.atlantis_github_user_token_ssm_parameter_name : var.atlantis_github_app_key != "" ? var.atlantis_github_app_key_ssm_parameter_name : var.atlantis_bitbucket_user_token_ssm_parameter_name : "" - - # webhook - secret_webhook_key = local.has_secrets || var.atlantis_github_webhook_secret != "" ? var.atlantis_gitlab_user_token != "" ? "ATLANTIS_GITLAB_WEBHOOK_SECRET" : var.atlantis_github_user_token != "" || var.atlantis_github_webhook_secret != "" ? "ATLANTIS_GH_WEBHOOK_SECRET" : var.atlantis_github_app_key != "" || var.atlantis_github_webhook_secret != "" ? "ATLANTIS_GH_WEBHOOK_SECRET" : "ATLANTIS_BITBUCKET_WEBHOOK_SECRET" : "" - - # determine if the alb has authentication enabled, otherwise forward the traffic unauthenticated - alb_authentication_method = length(keys(var.alb_authenticate_oidc)) > 0 ? "authenticate-oidc" : length(keys(var.alb_authenticate_cognito)) > 0 ? "authenticate-cognito" : "forward" - - # ECS - existing or new? - ecs_cluster_id = var.create_ecs_cluster ? module.ecs.ecs_cluster_id : var.ecs_cluster_id - - # Container definitions - container_definitions = var.custom_container_definitions == "" ? var.atlantis_bitbucket_user_token != "" ? jsonencode(concat([module.container_definition_bitbucket.json_map_object], var.extra_container_definitions)) : jsonencode(concat([module.container_definition_github_gitlab.json_map_object], var.extra_container_definitions)) : var.custom_container_definitions - - container_definition_environment = [ - { - name = "ATLANTIS_ALLOW_REPO_CONFIG" - value = var.allow_repo_config - }, - { - name = "ATLANTIS_GITLAB_HOSTNAME" - value = var.atlantis_gitlab_hostname - }, - { - name = "ATLANTIS_LOG_LEVEL" - value = var.atlantis_log_level - }, - { - name = "ATLANTIS_PORT" - value = var.atlantis_port - }, - { - name = "ATLANTIS_ATLANTIS_URL" - value = local.atlantis_url - }, - { - name = "ATLANTIS_GH_USER" - value = var.atlantis_github_user - }, - { - name = "ATLANTIS_GITLAB_USER" - value = var.atlantis_gitlab_user - }, - { - name = "ATLANTIS_BITBUCKET_USER" - value = var.atlantis_bitbucket_user - }, - { - name = "ATLANTIS_BITBUCKET_BASE_URL" - value = var.atlantis_bitbucket_base_url - }, - { - name = "ATLANTIS_REPO_ALLOWLIST" - value = join(",", var.atlantis_repo_allowlist) - }, - { - name = "ATLANTIS_HIDE_PREV_PLAN_COMMENTS" - value = var.atlantis_hide_prev_plan_comments - }, - { - name = "ATLANTIS_GH_APP_ID" - value = var.atlantis_github_app_id - }, - { - name = "ATLANTIS_WRITE_GIT_CREDS" - value = var.atlantis_write_git_creds - } - ] + atlantis_url = "https://${try(coalesce( + try(var.atlantis.fqdn, null), + module.alb.route53_records["A"].fqdn, + module.alb.dns_name, + ), "")}" - # ECS task definition - latest_task_definition_rev = var.external_task_definition_updates ? max(aws_ecs_task_definition.atlantis.revision, data.aws_ecs_task_definition.atlantis[0].revision) : aws_ecs_task_definition.atlantis.revision - - # Secret access tokens - container_definition_secrets_1 = local.secret_name_key != "" && local.secret_name_value_from != "" ? [ - { - name = local.secret_name_key - valueFrom = local.secret_name_value_from - }, - ] : [] - - # Webhook secrets are not supported by BitBucket - container_definition_secrets_2 = local.secret_webhook_key != "" ? [ - { - name = local.secret_webhook_key - valueFrom = var.webhook_ssm_parameter_name - }, - ] : [] - - tags = merge( - { - "Name" = var.name - }, - var.tags, - ) - - policies_arn = var.policies_arn != null ? var.policies_arn : ["arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"] - - # Chunk these into groups of 5, the limit for IPs in an AWS lb listener - whitelist_unauthenticated_cidr_block_chunks = chunklist( - sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_cidr_blocks : [], var.whitelist_unauthenticated_cidr_blocks))), - 5 - ) - - # break up user to uid and gid -- set both to 0 if null - uid = var.user == null ? 0 : split(":", var.user)[0] - gid = var.user == null ? 0 : split(":", var.user)[1] - - # default mount points for efs if ephemeral storage is not enabled and mount points aren't specified - mount_points = var.enable_ephemeral_storage || length(var.mount_points) > 0 ? var.mount_points : [{ - containerPath = "/home/atlantis" - sourceVolume = "efs-storage" - readOnly = "false" - }] -} - -data "aws_partition" "current" {} - -data "aws_region" "current" {} - -data "aws_route53_zone" "this" { - count = var.create_route53_record || var.create_route53_aaaa_record ? 1 : 0 - - name = var.route53_zone_name - private_zone = var.route53_private_zone + atlantis_port = try(var.atlantis.port, 4141) } ################################################################################ -# Secret for webhook -################################################################################ -resource "random_id" "webhook" { - count = var.atlantis_github_webhook_secret != "" ? 0 : 1 - - byte_length = "64" -} - -resource "aws_ssm_parameter" "webhook" { - count = var.atlantis_bitbucket_user_token != "" ? 0 : 1 - - name = var.webhook_ssm_parameter_name - type = "SecureString" - value = coalesce(var.atlantis_github_webhook_secret, join("", random_id.webhook[*].hex)) - - tags = local.tags -} - -resource "aws_ssm_parameter" "atlantis_github_user_token" { - count = var.atlantis_github_user_token != "" ? 1 : 0 - - name = var.atlantis_github_user_token_ssm_parameter_name - type = "SecureString" - value = var.atlantis_github_user_token - - tags = local.tags -} - -resource "aws_ssm_parameter" "atlantis_gitlab_user_token" { - count = var.atlantis_gitlab_user_token != "" ? 1 : 0 - - name = var.atlantis_gitlab_user_token_ssm_parameter_name - type = "SecureString" - value = var.atlantis_gitlab_user_token - - tags = local.tags -} - -resource "aws_ssm_parameter" "atlantis_bitbucket_user_token" { - count = var.atlantis_bitbucket_user_token != "" ? 1 : 0 - - name = var.atlantis_bitbucket_user_token_ssm_parameter_name - type = "SecureString" - value = var.atlantis_bitbucket_user_token - - tags = local.tags -} - -resource "aws_ssm_parameter" "atlantis_github_app_key" { - count = var.atlantis_github_app_key != "" ? 1 : 0 - - name = var.atlantis_github_app_key_ssm_parameter_name - type = "SecureString" - value = var.atlantis_github_app_key - - tags = local.tags -} - -################################################################################ -# VPC +# ALB ################################################################################ -module "vpc" { - source = "terraform-aws-modules/vpc/aws" - version = "v3.6.0" - - create_vpc = var.vpc_id == "" - - name = var.name - - cidr = var.cidr - azs = var.azs - private_subnets = var.private_subnets - public_subnets = var.public_subnets - - enable_nat_gateway = var.enable_nat_gateway - single_nat_gateway = var.single_nat_gateway - enable_dns_hostnames = !var.enable_ephemeral_storage - - manage_default_security_group = var.manage_default_security_group - default_security_group_ingress = var.default_security_group_ingress - default_security_group_egress = var.default_security_group_egress - - tags = local.tags +locals { + route53_records = { + A = { + name = try(coalesce(var.route53_record_name, var.name), "") + type = "A" + zone_id = var.route53_zone_id + } + AAAA = { + name = try(coalesce(var.route53_record_name, var.name), "") + type = "AAAA" + zone_id = var.route53_zone_id + } + } } -################################################################################ -# ALB -################################################################################ module "alb" { source = "terraform-aws-modules/alb/aws" - version = "v6.5.0" - - name = var.name - internal = var.internal - - enable_cross_zone_load_balancing = var.alb_enable_cross_zone_load_balancing - vpc_id = local.vpc_id - subnets = local.public_subnet_ids - security_groups = flatten([module.alb_https_sg.security_group_id, module.alb_http_sg.security_group_id, var.security_group_ids]) - - access_logs = { - enabled = var.alb_logging_enabled - bucket = var.alb_log_bucket_name - prefix = var.alb_log_location_prefix - } - - ip_address_type = var.alb_ip_address_type + version = "9.1.0" + + create = var.create && var.create_alb + + # Load balancer + access_logs = lookup(var.alb, "access_logs", {}) + customer_owned_ipv4_pool = try(var.alb.customer_owned_ipv4_pool, null) + desync_mitigation_mode = try(var.alb.desync_mitigation_mode, null) + dns_record_client_routing_policy = try(var.alb.dns_record_client_routing_policy, null) + drop_invalid_header_fields = try(var.alb.drop_invalid_header_fields, true) + enable_cross_zone_load_balancing = try(var.alb.enable_cross_zone_load_balancing, true) + enable_deletion_protection = try(var.alb.enable_deletion_protection, true) + enable_http2 = try(var.alb.enable_http2, null) + enable_tls_version_and_cipher_suite_headers = try(var.alb.enable_tls_version_and_cipher_suite_headers, null) + enable_waf_fail_open = try(var.alb.enable_waf_fail_open, null) + enable_xff_client_port = try(var.alb.enable_xff_client_port, null) + idle_timeout = try(var.alb.idle_timeout, null) + internal = try(var.alb.internal, false) + ip_address_type = try(var.alb.ip_address_type, null) + load_balancer_type = try(var.alb.load_balancer_type, "application") + name = try(var.alb.name, var.name) + preserve_host_header = try(var.alb.preserve_host_header, null) + security_groups = try(var.alb.security_groups, []) + subnets = try(var.alb.subnets, var.alb_subnets) + xff_header_processing_mode = try(var.alb.xff_header_processing_mode, null) + timeouts = try(var.alb.timeouts, {}) + + # Listener(s) + default_port = try(var.alb.default_port, 80) + default_protocol = try(var.alb.default_protocol, "HTTP") + listeners = merge( + { + http-https-redirect = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } - enable_deletion_protection = var.alb_enable_deletion_protection + https = { + port = 443 + protocol = "HTTPS" + ssl_policy = try(var.alb.https_listener_ssl_policy, "ELBSecurityPolicy-TLS13-1-2-Res-2021-06") + certificate_arn = var.create_certificate ? module.acm.acm_certificate_arn : var.certificate_arn - drop_invalid_header_fields = var.alb_drop_invalid_header_fields + authenticate_cognito = try(var.alb.https_listener_authenticate_cognito, []) + authenticate_oidc = try(var.alb.https_listener_authenticate_oidc, []) - listener_ssl_policy_default = var.alb_listener_ssl_policy_default - https_listeners = [ - { - target_group_index = 0 - port = 443 - protocol = "HTTPS" - certificate_arn = var.certificate_arn == "" ? module.acm.acm_certificate_arn : var.certificate_arn - action_type = local.alb_authentication_method - authenticate_oidc = var.alb_authenticate_oidc - authenticate_cognito = var.alb_authenticate_cognito + forward = { + target_group_key = "atlantis" + } + } }, - ] + lookup(var.alb, "listeners", {}) + ) - http_tcp_listeners = [ + # Target group(s) + target_groups = merge( { - port = 80 - protocol = "HTTP" - action_type = "redirect" - redirect = { - port = 443 - protocol = "HTTPS" - status_code = "HTTP_301" + atlantis = { + backend_protocol = "HTTP" + backend_port = local.atlantis_port + create_attachment = false + target_type = "ip" + deregistration_delay = 10 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/healthz" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } } }, - ] + lookup(var.alb, "target_groups", {}) + ) - target_groups = [ + # Security group + create_security_group = try(var.alb.create_security_group, true) + security_group_name = try(var.alb.security_group_name, var.name) + security_group_use_name_prefix = try(var.alb.security_group_use_name_prefix, true) + security_group_description = try(var.alb.security_group_description, null) + vpc_id = var.vpc_id + security_group_ingress_rules = lookup(var.alb, "security_group_ingress_rules", { - name = var.name - backend_protocol = "HTTP" - backend_port = var.atlantis_port - target_type = "ip" - deregistration_delay = 10 - health_check = { - path = "/healthz" + http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + https = { + from_port = 443 + to_port = 443 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" } - }, - ] - - tags = local.tags -} - -# Forward action for certain CIDR blocks to bypass authentication (eg. GitHub webhooks) -resource "aws_lb_listener_rule" "unauthenticated_access_for_cidr_blocks" { - count = var.allow_unauthenticated_access ? length(local.whitelist_unauthenticated_cidr_block_chunks) : 0 - - listener_arn = module.alb.https_listener_arns[0] - priority = var.allow_unauthenticated_access_priority + count.index - - action { - type = "forward" - target_group_arn = module.alb.target_group_arns[0] - } - - condition { - source_ip { - values = local.whitelist_unauthenticated_cidr_block_chunks[count.index] - } - } -} - -# Forward action for certain URL paths to bypass authentication (eg. GitHub webhooks) -resource "aws_lb_listener_rule" "unauthenticated_access_for_webhook" { - count = var.allow_unauthenticated_access && var.allow_github_webhooks ? 1 : 0 - - listener_arn = module.alb.https_listener_arns[0] - priority = var.allow_unauthenticated_webhook_access_priority - - action { - type = "forward" - target_group_arn = module.alb.target_group_arns[0] - } - - condition { - path_pattern { - values = ["/events"] } - } -} - -################################################################################ -# Security groups -################################################################################ -module "alb_https_sg" { - source = "terraform-aws-modules/security-group/aws//modules/https-443" - version = "v4.3.0" - - name = "${var.name}-alb-https" - vpc_id = local.vpc_id - description = "Security group with HTTPS ports open for specific IPv4 CIDR block (or everybody), egress ports are all world open" - - ingress_cidr_blocks = sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_cidr_blocks : [], var.alb_ingress_cidr_blocks))) - ingress_ipv6_cidr_blocks = sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_ipv6_cidr_blocks : [], var.alb_ingress_ipv6_cidr_blocks))) - - tags = merge(local.tags, var.alb_https_security_group_tags) -} - -module "alb_http_sg" { - source = "terraform-aws-modules/security-group/aws//modules/http-80" - version = "v4.3.0" - - name = "${var.name}-alb-http" - vpc_id = local.vpc_id - description = "Security group with HTTP ports open for specific IPv4 CIDR block (or everybody), egress ports are all world open" - - ingress_cidr_blocks = sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_cidr_blocks : [], var.alb_ingress_cidr_blocks))) - ingress_ipv6_cidr_blocks = sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_ipv6_cidr_blocks : [], var.alb_ingress_ipv6_cidr_blocks))) - tags = merge(local.tags, var.alb_http_security_group_tags) -} - -module "atlantis_sg" { - source = "terraform-aws-modules/security-group/aws" - version = "v4.3.0" - - name = var.name - vpc_id = local.vpc_id - description = "Security group with open port for Atlantis (${var.atlantis_port}) from ALB, egress ports are all world open" - - ingress_with_source_security_group_id = [ + ) + security_group_egress_rules = lookup(var.alb, "security_group_ingress_rules", { - from_port = var.atlantis_port - to_port = var.atlantis_port - protocol = "tcp" - description = "Atlantis" - source_security_group_id = module.alb_https_sg.security_group_id - }, - ] - - egress_rules = ["all-all"] - - tags = merge(local.tags, var.atlantis_security_group_tags) -} - -module "efs_sg" { - source = "terraform-aws-modules/security-group/aws" - version = "v4.8.0" - count = var.enable_ephemeral_storage ? 0 : 1 + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + } + } + ) + security_group_tags = try(var.alb.security_group_tags, {}) - name = "${var.name}-efs" - vpc_id = local.vpc_id - description = "Security group allowing access to the EFS storage" + # Route53 record(s) + route53_records = merge( + { for k, v in local.route53_records : k => v if var.create_route53_records }, + lookup(var.alb, "route53_records", {}) + ) - ingress_with_source_security_group_id = [{ - rule = "nfs-tcp", - source_security_group_id = module.atlantis_sg.security_group_id - }] + # WAF + associate_web_acl = try(var.alb.associate_web_acl, false) + web_acl_arn = try(var.alb.web_acl_arn, null) - tags = local.tags + tags = var.tags } ################################################################################ -# ACM (SSL certificate) +# ACM ################################################################################ + module "acm" { source = "terraform-aws-modules/acm/aws" - version = "v3.2.0" - - create_certificate = var.certificate_arn == "" - - domain_name = var.acm_certificate_domain_name == "" ? join(".", [var.name, var.route53_zone_name]) : var.acm_certificate_domain_name - - zone_id = var.certificate_arn == "" ? element(concat(data.aws_route53_zone.this[*].id, [""]), 0) : "" - - tags = local.tags -} - -################################################################################ -# Route53 records -################################################################################ -resource "aws_route53_record" "atlantis" { - count = var.create_route53_record ? 1 : 0 + version = "5.0.0" - zone_id = data.aws_route53_zone.this[0].zone_id - name = var.route53_record_name != null ? var.route53_record_name : var.name - type = "A" + create_certificate = var.create && var.create_certificate && var.create_alb - alias { - name = module.alb.lb_dns_name - zone_id = module.alb.lb_zone_id - evaluate_target_health = true - } -} - -resource "aws_route53_record" "atlantis_aaaa" { - count = var.create_route53_aaaa_record ? 1 : 0 + domain_name = var.certificate_domain_name + validate_certificate = var.validate_certificate + validation_method = "DNS" + zone_id = var.route53_zone_id - zone_id = data.aws_route53_zone.this[0].zone_id - name = var.route53_record_name != null ? var.route53_record_name : var.name - type = "AAAA" - - alias { - name = module.alb.lb_dns_name - zone_id = module.alb.lb_zone_id - evaluate_target_health = true - } -} - -################################################################################ -# EFS -################################################################################ - -resource "aws_efs_file_system" "this" { - count = var.enable_ephemeral_storage ? 0 : 1 - - creation_token = coalesce(var.efs_file_system_token, var.name) - - throughput_mode = var.efs_throughput_mode - provisioned_throughput_in_mibps = var.efs_provisioned_throughput_in_mibps - - encrypted = var.efs_file_system_encrypted -} - -resource "aws_efs_mount_target" "this" { - # we coalescelist in order to specify the resource keys when we create the subnets using the VPC or they're specified for us. This works around the for_each value depends on attributes which can't be determined until apply error - for_each = { - for k, v in zipmap(coalescelist(var.private_subnets, var.private_subnet_ids, [""]), local.private_subnet_ids) : k => v - if var.enable_ephemeral_storage == false - } - - file_system_id = aws_efs_file_system.this[0].id - subnet_id = each.value - security_groups = [module.efs_sg[0].security_group_id, module.atlantis_sg.security_group_id] -} - -resource "aws_efs_access_point" "this" { - count = var.enable_ephemeral_storage ? 0 : 1 - - file_system_id = aws_efs_file_system.this[0].id - posix_user { - gid = local.gid - uid = local.uid - } - - root_directory { - path = "/home/atlantis" - creation_info { - owner_gid = local.gid - owner_uid = local.uid - permissions = 0750 - } - } + tags = var.tags } ################################################################################ # ECS ################################################################################ -module "ecs" { - source = "terraform-aws-modules/ecs/aws" - version = "v3.3.0" - - create_ecs = var.create_ecs_cluster - - name = var.name - container_insights = var.ecs_container_insights - capacity_providers = ["FARGATE", "FARGATE_SPOT"] +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "5.6.0" - default_capacity_provider_strategy = [ - { - capacity_provider = var.ecs_fargate_spot ? "FARGATE_SPOT" : "FARGATE" - } - ] - - tags = local.tags -} - -data "aws_iam_policy_document" "ecs_tasks" { - statement { - actions = [ - "sts:AssumeRole", - ] + create = var.create && var.create_cluster - effect = "Allow" - - principals { - type = "Service" - identifiers = compact(distinct(concat(["ecs-tasks.amazonaws.com"], var.trusted_principals))) + # Cluster + cluster_name = try(var.cluster.name, var.name) + cluster_configuration = try(var.cluster.configuration, {}) + cluster_settings = try(var.cluster.settings, { + name = "containerInsights" + value = "enabled" } - - dynamic "principals" { - for_each = length(var.trusted_entities) > 0 ? [true] : [] - - content { - type = "AWS" - identifiers = var.trusted_entities - } - } - } -} - -resource "aws_iam_role" "ecs_task_execution" { - name = "${var.name}-ecs_task_execution" - assume_role_policy = data.aws_iam_policy_document.ecs_tasks.json - max_session_duration = var.max_session_duration - permissions_boundary = var.permissions_boundary - path = var.path - - tags = local.tags -} - -resource "aws_iam_role_policy_attachment" "ecs_task_execution" { - for_each = toset(local.policies_arn) - - role = aws_iam_role.ecs_task_execution.id - policy_arn = each.value -} - -# ref: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html -data "aws_iam_policy_document" "ecs_task_access_secrets" { - statement { - effect = "Allow" - - resources = flatten([ - aws_ssm_parameter.webhook[*].arn, - aws_ssm_parameter.atlantis_github_user_token[*].arn, - aws_ssm_parameter.atlantis_gitlab_user_token[*].arn, - aws_ssm_parameter.atlantis_bitbucket_user_token[*].arn, - aws_ssm_parameter.atlantis_github_app_key[*].arn, - try(var.repository_credentials["credentialsParameter"], []) - ]) - - actions = [ - "ssm:GetParameters", - "secretsmanager:GetSecretValue", - ] - } -} - -data "aws_iam_policy_document" "ecs_task_access_secrets_with_kms" { - count = var.ssm_kms_key_arn == "" ? 0 : 1 - - source_json = data.aws_iam_policy_document.ecs_task_access_secrets.json - - statement { - sid = "AllowKMSDecrypt" - effect = "Allow" - actions = ["kms:Decrypt"] - resources = [var.ssm_kms_key_arn] - } -} - -resource "aws_iam_role_policy" "ecs_task_access_secrets" { - count = local.has_secrets ? 1 : 0 - - name = "ECSTaskAccessSecretsPolicy" - - role = aws_iam_role.ecs_task_execution.id - - policy = element( - compact( - concat( - data.aws_iam_policy_document.ecs_task_access_secrets_with_kms[*].json, - data.aws_iam_policy_document.ecs_task_access_secrets[*].json, - ), - ), - 0, ) -} -module "container_definition_github_gitlab" { - source = "cloudposse/ecs-container-definition/aws" - version = "v0.58.1" - - container_name = var.name - container_image = local.atlantis_image - - container_cpu = var.container_cpu != null ? var.container_cpu : var.ecs_task_cpu - container_memory = var.container_memory != null ? var.container_memory : var.ecs_task_memory - container_memory_reservation = var.container_memory_reservation - - user = var.user - ulimits = var.ulimits - entrypoint = var.entrypoint - command = var.command - working_directory = var.working_directory - repository_credentials = var.repository_credentials - docker_labels = var.docker_labels - start_timeout = var.start_timeout - stop_timeout = var.stop_timeout - container_depends_on = var.container_depends_on - essential = var.essential - readonly_root_filesystem = var.readonly_root_filesystem - mount_points = local.mount_points - volumes_from = var.volumes_from - - port_mappings = [ + # Cloudwatch log group + create_cloudwatch_log_group = try(var.cluster.create_cloudwatch_log_group, true) + cloudwatch_log_group_retention_in_days = try(var.cluster.cloudwatch_log_group_retention_in_days, 90) + cloudwatch_log_group_kms_key_id = try(var.cluster.cloudwatch_log_group_kms_key_id, null) + cloudwatch_log_group_tags = try(var.cluster.cloudwatch_log_group_tags, {}) + + # Capacity providers + fargate_capacity_providers = try(var.cluster.fargate_capacity_providers, {}) + + tags = var.tags +} + +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "5.6.0" + + create = var.create + + # Service + ignore_task_definition_changes = try(var.service.ignore_task_definition_changes, false) + alarms = try(var.service.alarms, {}) + capacity_provider_strategy = try(var.service.capacity_provider_strategy, {}) + cluster_arn = var.create_cluster ? module.ecs_cluster.arn : var.cluster_arn + deployment_controller = try(var.service.deployment_controller, {}) + deployment_maximum_percent = try(var.service.deployment_maximum_percent, 200) + deployment_minimum_healthy_percent = try(var.service.deployment_minimum_healthy_percent, 66) + desired_count = try(var.service.desired_count, 1) + enable_ecs_managed_tags = try(var.service.enable_ecs_managed_tags, true) + enable_execute_command = try(var.service.enable_execute_command, false) + force_new_deployment = try(var.service.force_new_deployment, true) + health_check_grace_period_seconds = try(var.service.health_check_grace_period_seconds, null) + launch_type = try(var.service.launch_type, "FARGATE") + load_balancer = merge( { - containerPort = var.atlantis_port - hostPort = var.atlantis_port - protocol = "tcp" + service = { + target_group_arn = var.create_alb ? module.alb.target_groups["atlantis"].arn : var.alb_target_group_arn + container_name = "atlantis" + container_port = local.atlantis_port + } }, - ] - - log_configuration = { - logDriver = "awslogs" - options = { - awslogs-region = data.aws_region.current.name - awslogs-group = aws_cloudwatch_log_group.atlantis.name - awslogs-stream-prefix = "ecs" - } - secretOptions = [] - } - firelens_configuration = var.firelens_configuration - - environment = concat( - local.container_definition_environment, - var.custom_environment_variables, + lookup(var.service, "load_balancer", {}) ) - - secrets = concat( - local.container_definition_secrets_1, - local.container_definition_secrets_2, - var.custom_environment_secrets, - ) -} - -module "container_definition_bitbucket" { - source = "cloudposse/ecs-container-definition/aws" - version = "v0.58.1" - - container_name = var.name - container_image = local.atlantis_image - - container_cpu = var.container_cpu != null ? var.container_cpu : var.ecs_task_cpu - container_memory = var.container_memory != null ? var.container_memory : var.ecs_task_memory - container_memory_reservation = var.container_memory_reservation - - user = var.user - ulimits = var.ulimits - entrypoint = var.entrypoint - command = var.command - working_directory = var.working_directory - repository_credentials = var.repository_credentials - docker_labels = var.docker_labels - start_timeout = var.start_timeout - stop_timeout = var.stop_timeout - container_depends_on = var.container_depends_on - essential = var.essential - readonly_root_filesystem = var.readonly_root_filesystem - mount_points = var.mount_points - volumes_from = var.volumes_from - - port_mappings = [ + name = try(var.service.name, var.name) + assign_public_ip = try(var.service.assign_public_ip, false) + security_group_ids = try(var.service.security_group_ids, []) + subnet_ids = try(var.service.subnet_ids, var.service_subnets) + ordered_placement_strategy = try(var.service.ordered_placement_strategy, {}) + placement_constraints = try(var.service.placement_constraints, {}) + platform_version = try(var.service.platform_version, null) + propagate_tags = try(var.service.propagate_tags, null) + scheduling_strategy = try(var.service.scheduling_strategy, null) + service_connect_configuration = lookup(var.service, "service_connect_configuration", {}) + service_registries = lookup(var.service, "service_registries", {}) + timeouts = try(var.service.timeouts, {}) + triggers = try(var.service.triggers, {}) + wait_for_steady_state = try(var.service.wait_for_steady_state, null) + + # Service IAM role + create_iam_role = try(var.service.create_iam_role, true) + iam_role_arn = try(var.service.iam_role_arn, null) + iam_role_name = try(var.service.iam_role_name, null) + iam_role_use_name_prefix = try(var.service.iam_role_use_name_prefix, true) + iam_role_path = try(var.service.iam_role_path, null) + iam_role_description = try(var.service.iam_role_description, null) + iam_role_permissions_boundary = try(var.service.iam_role_permissions_boundary, null) + iam_role_tags = try(var.service.iam_role_tags, {}) + iam_role_statements = lookup(var.service, "iam_role_statements", {}) + + # Task definition + create_task_definition = try(var.service.create_task_definition, true) + task_definition_arn = try(var.service.task_definition_arn, null) + container_definitions = merge( { - containerPort = var.atlantis_port - hostPort = var.atlantis_port - protocol = "tcp" + atlantis = { + command = try(var.atlantis.command, []) + cpu = try(var.atlantis.cpu, 1024) + dependencies = try(var.atlantis.dependencies, []) # depends_on is a reserved word + disable_networking = try(var.atlantis.disable_networking, null) + dns_search_domains = try(var.atlantis.dns_search_domains, []) + dns_servers = try(var.atlantis.dns_servers, []) + docker_labels = try(var.atlantis.docker_labels, {}) + docker_security_options = try(var.atlantis.docker_security_options, []) + enable_execute_command = try(var.atlantis.enable_execute_command, try(var.service.enable_execute_command, false)) + entrypoint = try(var.atlantis.entrypoint, []) + environment = concat( + [ + { + name = "ATLANTIS_PORT" + value = local.atlantis_port + }, + { + name = "ATLANTIS_ATLANTIS_URL" + value = local.atlantis_url + }, + ], + lookup(var.atlantis, "environment", []) + ) + environment_files = try(var.atlantis.environment_files, []) + essential = try(var.atlantis.essential, true) + extra_hosts = try(var.atlantis.extra_hosts, []) + firelens_configuration = try(var.atlantis.firelens_configuration, {}) + health_check = try(var.atlantis.health_check, {}) + hostname = try(var.atlantis.hostname, null) + image = try(var.atlantis.image, "ghcr.io/runatlantis/atlantis:latest") + interactive = try(var.atlantis.interactive, false) + links = try(var.atlantis.links, []) + linux_parameters = try(var.atlantis.linux_parameters, {}) + log_configuration = lookup(var.atlantis, "log_configuration", {}) + memory = try(var.atlantis.memory, 2048) + memory_reservation = try(var.atlantis.memory_reservation, null) + mount_points = try(var.atlantis.mount_points, []) + name = "atlantis" + port_mappings = [{ + name = "atlantis" + containerPort = local.atlantis_port + hostPort = local.atlantis_port + protocol = "tcp" + }] + privileged = try(var.atlantis.privileged, false) + pseudo_terminal = try(var.atlantis.pseudo_terminal, false) + readonly_root_filesystem = try(var.atlantis.readonly_root_filesystem, false) + repository_credentials = try(var.atlantis.repository_credentials, {}) + resource_requirements = try(var.atlantis.resource_requirements, []) + secrets = try(var.atlantis.secrets, []) + start_timeout = try(var.atlantis.start_timeout, 30) + stop_timeout = try(var.atlantis.stop_timeout, 120) + system_controls = try(var.atlantis.system_controls, []) + ulimits = try(var.atlantis.ulimits, []) + user = try(var.atlantis.user, "${var.atlantis_uid}:${var.atlantis_gid}") + volumes_from = try(var.atlantis.volumes_from, []) + working_directory = try(var.atlantis.working_directory, null) + + # CloudWatch Log Group + service = var.name + enable_cloudwatch_logging = try(var.atlantis.enable_cloudwatch_logging, true) + create_cloudwatch_log_group = try(var.atlantis.create_cloudwatch_log_group, true) + cloudwatch_log_group_use_name_prefix = try(var.atlantis.cloudwatch_log_group_use_name_prefix, true) + cloudwatch_log_group_retention_in_days = try(var.atlantis.cloudwatch_log_group_retention_in_days, 14) + cloudwatch_log_group_kms_key_id = try(var.atlantis.cloudwatch_log_group_kms_key_id, null) + }, }, - ] - - log_configuration = { - logDriver = "awslogs" - options = { - awslogs-region = data.aws_region.current.name - awslogs-group = aws_cloudwatch_log_group.atlantis.name - awslogs-stream-prefix = "ecs" - } - secretOptions = [] - } - firelens_configuration = var.firelens_configuration - - environment = concat( - local.container_definition_environment, - var.custom_environment_variables, - ) - - secrets = concat( - local.container_definition_secrets_1, - var.custom_environment_secrets, + lookup(var.service, "container_definitions", {}) ) -} - -resource "aws_ecs_task_definition" "atlantis" { - family = var.name - execution_role_arn = aws_iam_role.ecs_task_execution.arn - task_role_arn = aws_iam_role.ecs_task_execution.arn - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = var.ecs_task_cpu - memory = var.ecs_task_memory - - container_definitions = local.container_definitions - - dynamic "runtime_platform" { - for_each = var.runtime_platform != null ? [var.runtime_platform] : [] - - content { - operating_system_family = try(runtime_platform.value.operating_system_family, null) - cpu_architecture = try(runtime_platform.value.cpu_architecture, null) - } - } - - dynamic "ephemeral_storage" { - for_each = var.enable_ephemeral_storage ? [1] : [] - - content { - size_in_gib = var.ephemeral_storage_size - } - } - - dynamic "volume" { - for_each = var.enable_ephemeral_storage ? [] : [1] - - content { - name = "efs-storage" - efs_volume_configuration { - file_system_id = aws_efs_file_system.this[0].id - transit_encryption = "ENABLED" - transit_encryption_port = 2999 - authorization_config { - access_point_id = aws_efs_access_point.this[0].id + container_definition_defaults = lookup(var.service, "container_definition_defaults", {}) + cpu = try(var.service.cpu, 1024) + ephemeral_storage = try(var.service.ephemeral_storage, {}) + family = try(var.service.family, null) + inference_accelerator = try(var.service.inference_accelerator, {}) + ipc_mode = try(var.service.ipc_mode, null) + memory = try(var.service.memory, 2048) + network_mode = try(var.service.network_mode, "awsvpc") + pid_mode = try(var.service.pid_mode, null) + task_definition_placement_constraints = try(var.service.task_definition_placement_constraints, {}) + proxy_configuration = try(var.service.proxy_configuration, {}) + requires_compatibilities = try(var.service.requires_compatibilities, ["FARGATE"]) + runtime_platform = try(var.service.runtime_platform, { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + }) + skip_destroy = try(var.service.skip_destroy, null) + volume = { for k, v in merge( + { + name = "efs" + efs_volume_configuration = { + file_system_id = module.efs.id + transit_encryption = "ENABLED" + authorization_config = { + access_point_id = module.efs.access_points["atlantis"].id iam = "ENABLED" } } - } - } - - tags = local.tags -} - -data "aws_ecs_task_definition" "atlantis" { - count = var.external_task_definition_updates ? 1 : 0 - - task_definition = var.name + }, + lookup(var.service, "volume", {}) + ) : k => v if var.enable_efs } + task_tags = try(var.service.task_tags, {}) + + # Task execution IAM role + create_task_exec_iam_role = try(var.service.create_task_exec_iam_role, true) + task_exec_iam_role_arn = try(var.service.task_exec_iam_role_arn, null) + task_exec_iam_role_name = try(var.service.task_exec_iam_role_name, null) + task_exec_iam_role_use_name_prefix = try(var.service.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_role_path = try(var.service.task_exec_iam_role_path, null) + task_exec_iam_role_description = try(var.service.task_exec_iam_role_description, null) + task_exec_iam_role_permissions_boundary = try(var.service.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_tags = try(var.service.task_exec_iam_role_tags, {}) + task_exec_iam_role_policies = lookup(var.service, "task_exec_iam_role_policies", {}) + + # Task execution IAM role policy + create_task_exec_policy = try(var.service.create_task_exec_policy, true) + task_exec_ssm_param_arns = try(var.service.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_secret_arns = try(var.service.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_iam_statements = lookup(var.service, "task_exec_iam_statements", {}) + + # Tasks - IAM role + create_tasks_iam_role = try(var.service.create_tasks_iam_role, true) + tasks_iam_role_arn = try(var.service.tasks_iam_role_arn, null) + tasks_iam_role_name = try(var.service.tasks_iam_role_name, null) + tasks_iam_role_use_name_prefix = try(var.service.tasks_iam_role_use_name_prefix, true) + tasks_iam_role_path = try(var.service.tasks_iam_role_path, null) + tasks_iam_role_description = try(var.service.tasks_iam_role_description, null) + tasks_iam_role_permissions_boundary = try(var.service.tasks_iam_role_permissions_boundary, null) + tasks_iam_role_tags = try(var.service.tasks_iam_role_tags, {}) + tasks_iam_role_policies = lookup(var.service, "tasks_iam_role_policies", {}) + tasks_iam_role_statements = lookup(var.service, "tasks_iam_role_statements", {}) + + # Task set + external_id = try(var.service.external_id, null) + scale = try(var.service.scale, {}) + force_delete = try(var.service.force_delete, null) + wait_until_stable = try(var.service.wait_until_stable, null) + wait_until_stable_timeout = try(var.service.wait_until_stable_timeout, null) + + # Autoscaling + enable_autoscaling = try(var.service.enable_autoscaling, false) + autoscaling_min_capacity = try(var.service.autoscaling_min_capacity, 1) + autoscaling_max_capacity = try(var.service.autoscaling_max_capacity, 10) + autoscaling_policies = try(var.service.autoscaling_policies, {}) + autoscaling_scheduled_actions = try(var.service.autoscaling_scheduled_actions, {}) + + # Security Group + create_security_group = try(var.service.create_security_group, true) + security_group_name = try(var.service.security_group_name, null) + security_group_use_name_prefix = try(var.service.security_group_use_name_prefix, true) + security_group_description = try(var.service.security_group_description, null) + security_group_rules = merge( + { + atlantis = { + type = "ingress" + from_port = local.atlantis_port + to_port = local.atlantis_port + protocol = "tcp" + source_security_group_id = var.create_alb ? module.alb.security_group_id : var.alb_security_group_id + } + }, + lookup(var.service, "security_group_rules", { + egress = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + }) + ) + security_group_tags = try(var.service.security_group_tags, {}) - depends_on = [aws_ecs_task_definition.atlantis] + tags = var.tags } -resource "aws_ecs_service" "atlantis" { - name = var.name - cluster = local.ecs_cluster_id - - task_definition = "${var.name}:${local.latest_task_definition_rev}" - desired_count = var.ecs_service_desired_count - launch_type = var.ecs_fargate_spot ? null : "FARGATE" - platform_version = var.ecs_service_platform_version - deployment_maximum_percent = var.ecs_service_deployment_maximum_percent - deployment_minimum_healthy_percent = var.ecs_service_deployment_minimum_healthy_percent - force_new_deployment = var.ecs_service_force_new_deployment - enable_execute_command = var.ecs_service_enable_execute_command - - network_configuration { - subnets = local.private_subnet_ids - security_groups = [module.atlantis_sg.security_group_id] - assign_public_ip = var.ecs_service_assign_public_ip - } - - load_balancer { - container_name = var.name - container_port = var.atlantis_port - target_group_arn = element(module.alb.target_group_arns, 0) - } - - dynamic "load_balancer" { - for_each = var.extra_load_balancers - content { - container_name = load_balancer.value["container_name"] - container_port = load_balancer.value["container_port"] - target_group_arn = load_balancer.value["target_group_arn"] - } - } - - dynamic "capacity_provider_strategy" { - for_each = var.ecs_fargate_spot ? [true] : [] +################################################################################ +# EFS +################################################################################ - content { - capacity_provider = "FARGATE_SPOT" - weight = 100 - } - } +module "efs" { + source = "terraform-aws-modules/efs/aws" + version = "1.3.1" + + create = var.create && var.enable_efs + + # File system + availability_zone_name = try(var.efs.availability_zone_name, null) + creation_token = try(var.efs.creation_token, var.name) + performance_mode = try(var.efs.performance_mode, null) + encrypted = try(var.efs.encrypted, true) + kms_key_arn = try(var.efs.kms_key_arn, null) + provisioned_throughput_in_mibps = try(var.efs.provisioned_throughput_in_mibps, null) + throughput_mode = try(var.efs.throughput_mode, null) + lifecycle_policy = try(var.efs.lifecycle_policy, {}) + + # File system policy + attach_policy = try(var.efs.attach_policy, true) + bypass_policy_lockout_safety_check = try(var.efs.bypass_policy_lockout_safety_check, null) + source_policy_documents = try(var.efs.source_policy_documents, []) + override_policy_documents = try(var.efs.override_policy_documents, []) + policy_statements = concat( + [{ + actions = ["elasticfilesystem:ClientMount"] + principals = [ + { + type = "AWS" + identifiers = [module.ecs_service.tasks_iam_role_arn] + } + ] + }], + lookup(var.efs, "policy_statements", []) + ) + deny_nonsecure_transport = try(var.efs.deny_nonsecure_transport, true) + + # Mount targets + mount_targets = lookup(var.efs, "mount_targets", {}) + + # Security group + create_security_group = try(var.efs.create_security_group, true) + security_group_name = try(var.efs.security_group_name, "${var.name}-efs-") + security_group_use_name_prefix = try(var.efs.security_group_use_name_prefix, true) + security_group_description = try(var.efs.security_group_description, null) + security_group_vpc_id = try(var.efs.security_group_vpc_id, var.vpc_id) + security_group_rules = merge( + { + atlantis = { + # relying on the defaults provdied for EFS/NFS (2049/TCP + ingress) + description = "NFS ingress from Atlantis" + source_security_group_id = module.ecs_service.security_group_id + } + }, + lookup(var.efs, "security_group_rules", {}) + ) - enable_ecs_managed_tags = var.enable_ecs_managed_tags - propagate_tags = var.propagate_tags + # Access point(s) + access_points = merge( + { + atlantis = { + posix_user = { + gid = var.atlantis_gid + uid = var.atlantis_uid + } + root_directory = { + path = "/home/atlantis" + creation_info = { + owner_gid = var.atlantis_gid + owner_uid = var.atlantis_uid + permissions = "0750" + } + } + } + }, + lookup(var.efs, "access_points", {}) + ) - tags = var.use_ecs_old_arn_format ? null : local.tags -} + # Backup policy + create_backup_policy = try(var.efs.create_backup_policy, false) + enable_backup_policy = try(var.efs.enable_backup_policy, false) -################################################################################ -# Cloudwatch logs -################################################################################ -resource "aws_cloudwatch_log_group" "atlantis" { - name = var.name - retention_in_days = var.cloudwatch_log_retention_in_days - kms_key_id = var.cloudwatch_logs_kms_key_id + # Replication configuration + create_replication_configuration = try(var.efs.create_replication_configuration, false) + replication_configuration_destination = try(var.efs.replication_configuration_destination, {}) - tags = local.tags + tags = var.tags } diff --git a/modules/github-repository-webhook/README.md b/modules/github-repository-webhook/README.md index 9505a54b..8cf564ae 100644 --- a/modules/github-repository-webhook/README.md +++ b/modules/github-repository-webhook/README.md @@ -5,14 +5,14 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [github](#requirement\_github) | >= 4.8 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [github](#requirement\_github) | >= 5.0 | ## Providers | Name | Version | |------|---------| -| [github](#provider\_github) | >= 4.8 | +| [github](#provider\_github) | >= 5.0 | ## Modules @@ -28,11 +28,8 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [atlantis\_repo\_allowlist](#input\_atlantis\_repo\_allowlist) | List of names of repositories which belong to the owner specified in `github_owner` | `list(string)` | n/a | yes | -| [create\_github\_repository\_webhook](#input\_create\_github\_repository\_webhook) | Whether to create Github repository webhook for Atlantis | `bool` | `true` | no | -| [github\_base\_url](#input\_github\_base\_url) | Github base URL to use when creating webhook (when using GitHub Enterprise) | `string` | `null` | no | -| [github\_owner](#input\_github\_owner) | Github owner to use when creating webhook | `string` | `""` | no | -| [github\_token](#input\_github\_token) | Github token to use when creating webhook | `string` | `""` | no | +| [create](#input\_create) | Whether to create Github repository webhook for Atlantis | `bool` | `true` | no | +| [repositories](#input\_repositories) | List of names of repositories which belong to the owner specified in `github_owner` | `list(string)` | `[]` | no | | [webhook\_secret](#input\_webhook\_secret) | Webhook secret | `string` | `""` | no | | [webhook\_url](#input\_webhook\_url) | Webhook URL | `string` | `""` | no | @@ -40,6 +37,5 @@ No modules. | Name | Description | |------|-------------| -| [repository\_webhook\_secret](#output\_repository\_webhook\_secret) | Webhook secret | | [repository\_webhook\_urls](#output\_repository\_webhook\_urls) | Webhook URL | diff --git a/modules/github-repository-webhook/main.tf b/modules/github-repository-webhook/main.tf index a3221da2..a96231b3 100644 --- a/modules/github-repository-webhook/main.tf +++ b/modules/github-repository-webhook/main.tf @@ -1,13 +1,7 @@ -provider "github" { - base_url = var.github_base_url - token = var.github_token - owner = var.github_owner -} - resource "github_repository_webhook" "this" { - count = var.create_github_repository_webhook ? length(var.atlantis_repo_allowlist) : 0 + count = var.create ? length(var.repositories) : 0 - repository = var.atlantis_repo_allowlist[count.index] + repository = var.repositories[count.index] configuration { url = var.webhook_url diff --git a/modules/github-repository-webhook/outputs.tf b/modules/github-repository-webhook/outputs.tf index 1ee5be53..ef020d25 100644 --- a/modules/github-repository-webhook/outputs.tf +++ b/modules/github-repository-webhook/outputs.tf @@ -2,9 +2,3 @@ output "repository_webhook_urls" { description = "Webhook URL" value = github_repository_webhook.this[*].url } - -output "repository_webhook_secret" { - description = "Webhook secret" - value = var.webhook_secret - sensitive = true -} diff --git a/modules/github-repository-webhook/variables.tf b/modules/github-repository-webhook/variables.tf index 6d6509e1..ab0d2392 100644 --- a/modules/github-repository-webhook/variables.tf +++ b/modules/github-repository-webhook/variables.tf @@ -1,30 +1,13 @@ -variable "create_github_repository_webhook" { +variable "create" { description = "Whether to create Github repository webhook for Atlantis" type = bool default = true } -variable "github_base_url" { - description = "Github base URL to use when creating webhook (when using GitHub Enterprise)" - type = string - default = null -} - -variable "github_token" { - description = "Github token to use when creating webhook" - type = string - default = "" -} - -variable "github_owner" { - description = "Github owner to use when creating webhook" - type = string - default = "" -} - -variable "atlantis_repo_allowlist" { +variable "repositories" { description = "List of names of repositories which belong to the owner specified in `github_owner`" type = list(string) + default = [] } variable "webhook_url" { diff --git a/modules/github-repository-webhook/versions.tf b/modules/github-repository-webhook/versions.tf index 117bc28d..51af6b4f 100644 --- a/modules/github-repository-webhook/versions.tf +++ b/modules/github-repository-webhook/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { github = { source = "integrations/github" - version = ">= 4.8" + version = ">= 5.0" } } } diff --git a/modules/gitlab-repository-webhook/README.md b/modules/gitlab-repository-webhook/README.md index 0eed8cf2..92cab01f 100644 --- a/modules/gitlab-repository-webhook/README.md +++ b/modules/gitlab-repository-webhook/README.md @@ -5,14 +5,14 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [gitlab](#requirement\_gitlab) | 3.20.0 | +| [terraform](#requirement\_terraform) | >= 1.1 | +| [gitlab](#requirement\_gitlab) | >= 16.0 | ## Providers | Name | Version | |------|---------| -| [gitlab](#provider\_gitlab) | 3.20.0 | +| [gitlab](#provider\_gitlab) | >= 16.0 | ## Modules @@ -22,16 +22,14 @@ No modules. | Name | Type | |------|------| -| [gitlab_project_hook.this](https://registry.terraform.io/providers/gitlabhq/gitlab/3.20.0/docs/resources/project_hook) | resource | +| [gitlab_project_hook.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/project_hook) | resource | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [atlantis\_repo\_allowlist](#input\_atlantis\_repo\_allowlist) | List of names of repositories which belong to the `gitlab_base_url` specified | `list(string)` | n/a | yes | -| [create\_gitlab\_repository\_webhook](#input\_create\_gitlab\_repository\_webhook) | Whether to create Gitlab repository webhook for Atlantis | `bool` | `true` | no | -| [gitlab\_base\_url](#input\_gitlab\_base\_url) | Gitlab base\_url use | `string` | `""` | no | -| [gitlab\_token](#input\_gitlab\_token) | Gitlab token to use when creating webhook | `string` | `""` | no | +| [create](#input\_create) | Whether to create Gitlab repository webhook for Atlantis | `bool` | `true` | no | +| [repositories](#input\_repositories) | List of names of repositories which belong to the `gitlab_base_url` specified | `list(string)` | `[]` | no | | [webhook\_secret](#input\_webhook\_secret) | Webhook secret | `string` | `""` | no | | [webhook\_url](#input\_webhook\_url) | Webhook URL | `string` | `""` | no | @@ -39,6 +37,5 @@ No modules. | Name | Description | |------|-------------| -| [repository\_webhook\_secret](#output\_repository\_webhook\_secret) | Webhook secret | | [repository\_webhook\_urls](#output\_repository\_webhook\_urls) | Webhook URL | diff --git a/modules/gitlab-repository-webhook/main.tf b/modules/gitlab-repository-webhook/main.tf index 65d61d85..1664a2f5 100644 --- a/modules/gitlab-repository-webhook/main.tf +++ b/modules/gitlab-repository-webhook/main.tf @@ -1,12 +1,7 @@ -provider "gitlab" { - token = var.gitlab_token - base_url = var.gitlab_base_url -} - resource "gitlab_project_hook" "this" { - count = var.create_gitlab_repository_webhook ? length(var.atlantis_repo_allowlist) : 0 + count = var.create ? length(var.repositories) : 0 - project = var.atlantis_repo_allowlist[count.index] + project = var.repositories[count.index] url = var.webhook_url token = var.webhook_secret enable_ssl_verification = false diff --git a/modules/gitlab-repository-webhook/outputs.tf b/modules/gitlab-repository-webhook/outputs.tf index faa2d5db..28003190 100644 --- a/modules/gitlab-repository-webhook/outputs.tf +++ b/modules/gitlab-repository-webhook/outputs.tf @@ -2,9 +2,3 @@ output "repository_webhook_urls" { description = "Webhook URL" value = gitlab_project_hook.this[*].url } - -output "repository_webhook_secret" { - description = "Webhook secret" - value = var.webhook_secret - sensitive = true -} diff --git a/modules/gitlab-repository-webhook/variables.tf b/modules/gitlab-repository-webhook/variables.tf index b0cd85ab..61c11e04 100644 --- a/modules/gitlab-repository-webhook/variables.tf +++ b/modules/gitlab-repository-webhook/variables.tf @@ -1,24 +1,13 @@ -variable "create_gitlab_repository_webhook" { +variable "create" { description = "Whether to create Gitlab repository webhook for Atlantis" type = bool default = true } -variable "gitlab_base_url" { - description = "Gitlab base_url use" - type = string - default = "" -} - -variable "gitlab_token" { - description = "Gitlab token to use when creating webhook" - type = string - default = "" -} - -variable "atlantis_repo_allowlist" { +variable "repositories" { description = "List of names of repositories which belong to the `gitlab_base_url` specified" type = list(string) + default = [] } variable "webhook_url" { diff --git a/modules/gitlab-repository-webhook/versions.tf b/modules/gitlab-repository-webhook/versions.tf index 12718efa..d5e390fe 100644 --- a/modules/gitlab-repository-webhook/versions.tf +++ b/modules/gitlab-repository-webhook/versions.tf @@ -1,11 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.1" required_providers { gitlab = { - source = "gitlabhq/gitlab" - # gitlab provider requires terraform 1.0 or later starting in 15.x.x - version = "3.20.0" + source = "gitlabhq/gitlab" + version = ">= 16.0" } } } diff --git a/outputs.tf b/outputs.tf index 81321ff3..a85e5394 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,124 +1,36 @@ -# Atlantis -output "atlantis_url" { +output "url" { description = "URL of Atlantis" value = local.atlantis_url } -output "atlantis_url_events" { - description = "Webhook events URL of Atlantis" - value = local.atlantis_url_events -} - -output "atlantis_repo_allowlist" { - description = "Git repositories where webhook should be created" - value = var.atlantis_repo_allowlist -} +################################################################################ +# Load Balancer +################################################################################ -output "webhook_secret" { - description = "Webhook secret" - value = try(random_id.webhook[0].hex, "") - sensitive = true +output "alb" { + description = "ALB created and all of its associated outputs" + value = module.alb } +################################################################################ # ECS -output "task_role_arn" { - description = "The Atlantis ECS task role arn" - value = aws_iam_role.ecs_task_execution.arn -} - -output "task_role_id" { - description = "The Atlantis ECS task role id" - value = aws_iam_role.ecs_task_execution.id -} - -output "task_role_name" { - description = "The Atlantis ECS task role name" - value = aws_iam_role.ecs_task_execution.name -} - -output "task_role_unique_id" { - description = "The stable and unique string identifying the Atlantis ECS task role." - value = aws_iam_role.ecs_task_execution.unique_id -} - -output "ecs_task_definition" { - description = "Task definition for ECS service (used for external triggers)" - value = aws_ecs_service.atlantis.task_definition -} - -output "ecs_security_group" { - description = "Security group assigned to ECS Service in network configuration" - value = module.atlantis_sg.security_group_id -} - -output "ecs_cluster_id" { - description = "ECS cluster id" - value = local.ecs_cluster_id -} - -output "ecs_cluster_arn" { - description = "ECS cluster ARN" - value = module.ecs.ecs_cluster_arn -} +################################################################################ -# VPC -output "vpc_id" { - description = "ID of the VPC that was created or passed in" - value = local.vpc_id +output "cluster" { + description = "ECS cluster created and all of its associated outputs" + value = module.ecs_cluster } -output "private_subnet_ids" { - description = "IDs of the VPC private subnets that were created or passed in" - value = local.private_subnet_ids +output "service" { + description = "ECS service created and all of its associated outputs" + value = module.ecs_service } -output "public_subnet_ids" { - description = "IDs of the VPC public subnets that were created or passed in" - value = local.public_subnet_ids -} - -# ALB -output "alb_dns_name" { - description = "Dns name of alb" - value = module.alb.lb_dns_name -} - -output "alb_zone_id" { - description = "Zone ID of alb" - value = module.alb.lb_zone_id -} - -output "alb_arn" { - description = "ARN of alb" - value = module.alb.lb_arn -} - -output "alb_security_group_id" { - description = "Security group of alb" - value = module.alb_https_sg.security_group_id -} - -output "alb_http_listeners_id" { - description = "Ids of alb http listeners" - value = module.alb.http_tcp_listener_ids -} - -output "alb_http_listeners_arn" { - description = "ARNs of alb http listeners" - value = module.alb.http_tcp_listener_arns -} - -output "alb_https_listeners_id" { - description = "Ids of alb https listeners" - value = module.alb.https_listener_ids -} - -output "alb_https_listeners_arn" { - description = "ARN of alb https listeners" - value = module.alb.https_listener_arns -} +################################################################################ +# EFS +################################################################################ -output "vpc_nat_public_ips" { - description = "List of public Elastic IPs created for AWS NAT Gateway" - value = module.vpc.nat_public_ips +output "efs" { + description = "EFS created and all of its associated outputs" + value = module.efs } diff --git a/terraform.tfvars.sample b/terraform.tfvars.sample deleted file mode 100644 index a03a92dc..00000000 --- a/terraform.tfvars.sample +++ /dev/null @@ -1,47 +0,0 @@ -# VPC - use these parameters to create new VPC resources -cidr = "10.10.0.0/16" - -azs = ["eu-west-1a", "eu-west-1b"] - -private_subnets = ["10.10.1.0/24", "10.10.2.0/24"] - -public_subnets = ["10.10.11.0/24", "10.10.12.0/24"] - -# VPC - use these parameters to use existing VPC resources -# vpc_id = "vpc-1651acf1" -# private_subnet_ids = ["subnet-1fe3d837", "subnet-129d66ab"] -# public_subnet_ids = ["subnet-1211eef5", "subnet-163466ab"] - -# DNS -route53_zone_name = "example.com" - -# ACM (SSL certificate) -# Specify ARN of an existing certificate or new one will be created and validated using Route53 DNS: -# certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" - -# ECS Service and Task -ecs_service_assign_public_ip = true - -# Atlantis -atlantis_repo_allowlist = ["github.com/terraform-aws-modules/*"] - -# Specify one of the following block. -# For Github -atlantis_github_user = "" -atlantis_github_user_token = "" - -# For Gitlab -atlantis_gitlab_user = "" -atlantis_gitlab_user_token = "" - -# For Bitbucket -atlantis_bitbucket_user = "" -atlantis_bitbucket_user_token = "" - -# For Bitbucket on prem (Stash) -# atlantis_bitbucket_base_url = "" - -# Tags -tags = { - Name = "atlantis" -} diff --git a/variables.tf b/variables.tf index debe09dc..5a34dbe3 100644 --- a/variables.tf +++ b/variables.tf @@ -1,798 +1,171 @@ -variable "name" { - description = "Name to use on all resources created (VPC, ALB, etc)" - type = string - default = "atlantis" -} - -variable "internal" { - description = "Whether the load balancer is internal or external" +variable "create" { + description = "Controls if resources should be created (affects nearly all resources)" type = bool - default = false + default = true } variable "tags" { - description = "A map of tags to use on all resources" + description = "A map of tags to add to all resources" type = map(string) default = {} } -variable "alb_https_security_group_tags" { - description = "Additional tags to put on the https security group" - type = map(string) - default = {} +variable "name" { + description = "Common name to use on all resources created unless a more specific name is provided" + type = string + default = "atlantis" } -variable "alb_http_security_group_tags" { - description = "Additional tags to put on the http security group" - type = map(string) +variable "atlantis" { + description = "Map of values passed to Atlantis container definition. See the [ECS container definition module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/container-definition) for full list of arguments supported" + type = any default = {} } -variable "atlantis_security_group_tags" { - description = "Additional tags to put on the atlantis security group" - type = map(string) - default = {} +variable "atlantis_gid" { + description = "GID of the atlantis user" + type = number + default = 1000 } -variable "atlantis_fqdn" { - description = "FQDN of Atlantis to use. Set this only to override Route53 and ALB's DNS name." - type = string - default = null +variable "atlantis_uid" { + description = "UID of the atlantis user" + type = number + default = 100 } -# VPC variable "vpc_id" { - description = "ID of an existing VPC where resources will be created" + description = "ID of the VPC where the resources will be provisioned" type = string default = "" } -variable "public_subnet_ids" { - description = "A list of IDs of existing public subnets inside the VPC" - type = list(string) - default = [] -} +################################################################################ +# Load Balancer +################################################################################ -variable "private_subnet_ids" { - description = "A list of IDs of existing private subnets inside the VPC" - type = list(string) - default = [] -} - -variable "cidr" { - description = "The CIDR block for the VPC which will be created if `vpc_id` is not specified" - type = string - default = "" -} - -variable "azs" { - description = "A list of availability zones in the region" - type = list(string) - default = [] -} - -variable "enable_nat_gateway" { - description = "Should be true if you want to provision NAT Gateways for each of your private networks" - type = bool - default = true -} - -variable "single_nat_gateway" { - description = "Should be true if you want to provision a single shared NAT Gateway across all of your private networks" +variable "create_alb" { + description = "Determines whether to create an ALB or not" type = bool default = true } -variable "manage_default_security_group" { - description = "Should be true to adopt and manage default security group" - type = bool - default = false -} - -variable "default_security_group_ingress" { - description = "List of maps of ingress rules to set on the default security group" - type = list(map(string)) - default = [] -} - -variable "default_security_group_egress" { - description = "List of maps of egress rules to set on the default security group" - type = list(map(string)) - default = [] -} - -variable "public_subnets" { - description = "A list of public subnets inside the VPC" - type = list(string) - default = [] -} - -variable "private_subnets" { - description = "A list of private subnets inside the VPC" - type = list(string) - default = [] -} - -# ALB -variable "alb_ingress_cidr_blocks" { - description = "List of IPv4 CIDR ranges to use on all ingress rules of the ALB." - type = list(string) - default = ["0.0.0.0/0"] -} - -variable "alb_ingress_ipv6_cidr_blocks" { - description = "List of IPv6 CIDR ranges to use on all ingress rules of the ALB." - type = list(string) - default = ["::/0"] -} - -variable "alb_log_bucket_name" { - description = "S3 bucket (externally created) for storing load balancer access logs. Required if alb_logging_enabled is true." +variable "alb_target_group_arn" { + description = "ARN of an existing ALB target group that will be used to route traffic to the Atlantis service. Required if `create_alb` is `false`" type = string default = "" } -variable "alb_log_location_prefix" { - description = "S3 prefix within the log_bucket_name under which logs are stored." +variable "alb_security_group_id" { + description = "ID of an existing security group that will be used by ALB. Required if `create_alb` is `false`" type = string default = "" } -variable "alb_logging_enabled" { - description = "Controls if the ALB will log requests to S3." - type = bool - default = false -} - -variable "alb_authenticate_oidc" { - description = "Map of Authenticate OIDC parameters to protect ALB (eg, using Auth0). See https://www.terraform.io/docs/providers/aws/r/lb_listener.html#authenticate-oidc-action" - type = any - default = {} -} - -variable "alb_authenticate_cognito" { - description = "Map of AWS Cognito authentication parameters to protect ALB (eg, using SAML). See https://www.terraform.io/docs/providers/aws/r/lb_listener.html#authenticate-cognito-action" +variable "alb" { + description = "Map of values passed to ALB module definition. See the [ALB module](https://github.com/terraform-aws-modules/terraform-aws-alb) for full list of arguments supported" type = any default = {} } -variable "alb_enable_deletion_protection" { - description = "If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false." - type = bool - default = null -} - -variable "alb_drop_invalid_header_fields" { - description = "Indicates whether invalid header fields are dropped in application load balancers. Defaults to false." - type = bool - default = null -} - -variable "allow_unauthenticated_access" { - description = "Whether to create ALB listener rule to allow unauthenticated access for certain CIDR blocks (eg. allow GitHub webhooks to bypass OIDC authentication)" - type = bool - default = false -} - -variable "allow_unauthenticated_access_priority" { - description = "ALB listener rule priority for allow unauthenticated access rule" - type = number - default = 10 -} - -variable "allow_unauthenticated_webhook_access_priority" { - description = "ALB listener rule priority for allow unauthenticated webhook access rule" - type = number - default = 15 -} - -variable "allow_github_webhooks" { - description = "Whether to allow access for GitHub webhooks" - type = bool - default = false -} - -variable "github_webhooks_cidr_blocks" { - description = "List of IPv4 CIDR blocks used by GitHub webhooks" # This is hardcoded to avoid dependency on github provider. Source: https://api.github.com/meta - type = list(string) - default = ["140.82.112.0/20", "185.199.108.0/22", "192.30.252.0/22", "143.55.64.0/20"] -} - -variable "github_webhooks_ipv6_cidr_blocks" { - description = "List of IPv6 CIDR blocks used by GitHub webhooks" # This is hardcoded to avoid dependency on github provider. Source: https://api.github.com/meta - type = list(string) - default = ["2a0a:a440::/29", "2606:50c0::/32"] -} - -variable "whitelist_unauthenticated_cidr_blocks" { - description = "List of allowed CIDR blocks to bypass authentication" +variable "alb_subnets" { + description = "List of subnets to place ALB in. Required if `create_alb` is `true`" type = list(string) default = [] } -variable "alb_listener_ssl_policy_default" { - description = "The security policy if using HTTPS externally on the load balancer. [See](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html)." - type = string - default = "ELBSecurityPolicy-2016-08" -} - -variable "extra_load_balancers" { - description = "A list of maps for additional ECS task load balancers" - type = list(map(string)) - default = [] -} - -# ACM -variable "certificate_arn" { - description = "ARN of certificate issued by AWS ACM. If empty, a new ACM certificate will be created and validated using Route53 DNS" - type = string - default = "" -} - -variable "acm_certificate_domain_name" { - description = "Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance. Specify if it is different from value in `route53_zone_name`" - type = string - default = "" -} - -# Route53 -variable "route53_zone_name" { - description = "Route53 zone name to create ACM certificate in and main A-record, without trailing dot" - type = string - default = "" -} - -variable "route53_record_name" { - description = "Name of Route53 record to create ACM certificate in and main A-record. If null is specified, var.name is used instead. Provide empty string to point root domain name to ALB." - type = string - default = null -} - -variable "route53_private_zone" { - description = "Enable to use a private Route53 zone" - type = bool - default = false -} - -variable "create_route53_record" { - description = "Whether to create Route53 A record for Atlantis" +variable "create_route53_records" { + description = "Determines whether to create Route53 `A` and `AAAA` records for the loadbalancer" type = bool default = true } -# Cloudwatch -variable "cloudwatch_log_retention_in_days" { - description = "Retention period of Atlantis CloudWatch logs" - type = number - default = 7 -} - -variable "cloudwatch_logs_kms_key_id" { - description = "The ARN of the KMS Key to use when encrypting log data." - type = string - default = null -} - -# SSM parameters for secrets -variable "webhook_ssm_parameter_name" { - description = "Name of SSM parameter to keep webhook secret" - type = string - default = "/atlantis/webhook/secret" -} - -variable "atlantis_github_user_token_ssm_parameter_name" { - description = "Name of SSM parameter to keep atlantis_github_user_token" - type = string - default = "/atlantis/github/user/token" -} +################################################################################ +# ECS +################################################################################ -variable "atlantis_gitlab_user_token_ssm_parameter_name" { - description = "Name of SSM parameter to keep atlantis_gitlab_user_token" - type = string - default = "/atlantis/gitlab/user/token" -} - -variable "atlantis_bitbucket_user_token_ssm_parameter_name" { - description = "Name of SSM parameter to keep atlantis_bitbucket_user_token" - type = string - default = "/atlantis/bitbucket/user/token" -} - -variable "atlantis_github_app_key_ssm_parameter_name" { - description = "Name of SSM parameter to keep atlantis_github_app_key" - type = string - default = "/atlantis/github/app/key" -} - -variable "ssm_kms_key_arn" { - description = "ARN of KMS key to use for encryption and decryption of SSM Parameters. Required only if your key uses a custom KMS key and not the default key" - type = string - default = "" -} - -# ECS Service / Task -variable "ecs_service_assign_public_ip" { - description = "Should be true, if ECS service is using public subnets (more info: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_cannot_pull_image.html)" - type = bool - default = false -} - -variable "permissions_boundary" { - description = "If provided, all IAM roles will be created with this permissions boundary attached." - type = string - default = null -} - -variable "path" { - description = "If provided, all IAM roles will be created with this path." - type = string - default = "/" -} - -variable "policies_arn" { - description = "A list of the ARN of the policies you want to apply" - type = list(string) - default = null -} - -variable "trusted_principals" { - description = "A list of principals, in addition to ecs-tasks.amazonaws.com, that can assume the task role" - type = list(string) - default = [] -} - -variable "trusted_entities" { - description = "A list of users or roles, that can assume the task role" - type = list(string) - default = [] -} - -variable "create_ecs_cluster" { +variable "create_cluster" { description = "Whether to create an ECS cluster or not" type = bool default = true } -variable "ecs_cluster_id" { - description = "ID of an existing ECS cluster where resources will be created" - type = string - default = "" -} - -variable "ecs_fargate_spot" { - description = "Whether to run ECS Fargate Spot or not" - type = bool - default = false -} - -variable "ecs_container_insights" { - description = "Controls if ECS Cluster has container insights enabled" - type = bool - default = false -} - -variable "ecs_service_desired_count" { - description = "The number of instances of the task definition to place and keep running" - type = number - default = 1 -} - -variable "ecs_service_platform_version" { - description = "The platform version on which to run your service" - type = string - default = "LATEST" -} - -variable "ecs_service_deployment_maximum_percent" { - description = "The upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment" - type = number - default = 100 -} - -variable "ecs_service_deployment_minimum_healthy_percent" { - description = "The lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment" - type = number - default = 0 -} - -variable "ecs_task_cpu" { - description = "The number of cpu units used by the task" - type = number - default = 256 -} - -variable "ecs_task_memory" { - description = "The amount (in MiB) of memory used by the task" - type = number - default = 512 -} - -variable "container_cpu" { - description = "The number of cpu units used by the atlantis container. If not specified ecs_task_cpu will be used" - type = number - default = null -} - -variable "container_memory" { - description = "The amount (in MiB) of memory used by the atlantis container. If not specified ecs_task_memory will be used" - type = number - default = null -} - -variable "container_memory_reservation" { - description = "The amount of memory (in MiB) to reserve for the container" - type = number - default = 128 -} - -variable "custom_container_definitions" { - description = "A list of valid container definitions provided as a single valid JSON document. By default, the standard container definition is used." +variable "cluster_arn" { + description = "ARN of an existing ECS cluster where resources will be created. Required when `create_cluster` is `false`" type = string default = "" } -variable "extra_container_definitions" { - description = "A list of valid container definitions provided as a single valid JSON document. These will be provided as supplimentary to the main Atlantis container definition" +variable "cluster" { + description = "Map of values passed to ECS cluster module definition. See the [ECS cluster module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/cluster) for full list of arguments supported" type = any - default = [] + default = {} } -variable "entrypoint" { - description = "The entry point that is passed to the container" - type = list(string) - default = null +variable "service" { + description = "Map of values passed to ECS service module definition. See the [ECS service module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/service) for full list of arguments supported" + type = any + default = {} } -variable "command" { - description = "The command that is passed to the container" +variable "service_subnets" { + description = "List of subnets to place ECS service within" type = list(string) - default = null -} - -variable "working_directory" { - description = "The working directory to run commands inside the container" - type = string - default = null -} - -variable "repository_credentials" { - description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" - type = map(string) - default = null -} - -variable "docker_labels" { - description = "The configuration options to send to the `docker_labels`" - type = map(string) - default = null -} - -variable "start_timeout" { - description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" - type = number - default = 30 -} - -variable "stop_timeout" { - description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" - type = number - default = 30 -} - -variable "container_depends_on" { - description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" - type = list(object({ - containerName = string - condition = string - })) - default = null -} - -variable "essential" { - description = "Determines whether all other containers in a task are stopped, if this container fails or stops for any reason. Due to how Terraform type casts booleans in json it is required to double quote this value" - type = bool - default = true -} - -variable "readonly_root_filesystem" { - description = "Determines whether a container is given read-only access to its root filesystem. Due to how Terraform type casts booleans in json it is required to double quote this value" - type = bool - default = false -} - -variable "mount_points" { - description = "Container mount points. This is a list of maps, where each map should contain a `containerPath` and `sourceVolume`. The `readOnly` key is optional." - type = list(any) default = [] } -variable "volumes_from" { - description = "A list of VolumesFrom maps which contain \"sourceContainer\" (name of the container that has the volumes to mount) and \"readOnly\" (whether the container can write to the volume)" - type = list(object({ - sourceContainer = string - readOnly = bool - })) - default = [] -} - -variable "user" { - description = "The user to run as inside the container. Must be in the uid:gid or the default (null) will use the container's configured `USER` directive or root if not set." - type = string - default = null - validation { - condition = can(regex("[0-9]+:[0-9]+", var.user)) || var.user == null - error_message = "User variable must be in the uid:gid format or null." - } -} - -variable "ulimits" { - description = "Container ulimit settings. This is a list of maps, where each map should contain \"name\", \"hardLimit\" and \"softLimit\"" - type = list(object({ - name = string - hardLimit = number - softLimit = number - })) - default = null -} +################################################################################ +# ACM +################################################################################ -variable "external_task_definition_updates" { - description = "Enable to allow the task definition to be updated outside of this Terraform module. This should be enabled when using a deployment tool such as ecs-deploy which updates the task definition and will then keep the ECS service using the latest version of the task definition." +variable "create_certificate" { + description = "Determines whether to create an ACM certificate or not. If `false`, `certificate_arn` must be provided" type = bool - default = false -} - -# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_FirelensConfiguration.html -variable "firelens_configuration" { - description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more details, see https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_FirelensConfiguration.html" - type = object({ - type = string - options = map(string) - }) - default = null -} - -# Atlantis -variable "atlantis_image" { - description = "Docker image to run Atlantis with. If not specified, official Atlantis image will be used" - type = string - default = "" -} - -variable "atlantis_version" { - description = "Verion of Atlantis to run. If not specified latest will be used" - type = string - default = "latest" -} - -variable "atlantis_port" { - description = "Local port Atlantis should be running on. Default value is most likely fine." - type = number - default = 4141 -} - -variable "atlantis_repo_allowlist" { - description = "List of allowed repositories Atlantis can be used with" - type = list(string) -} - -variable "allow_repo_config" { - description = "When true allows the use of atlantis.yaml config files within the source repos." - type = string - default = "false" -} - -variable "atlantis_log_level" { - description = "Log level that Atlantis will run with. Accepted values are: " - type = string - default = "debug" -} - -variable "atlantis_hide_prev_plan_comments" { - description = "Enables atlantis server --hide-prev-plan-comments hiding previous plan comments on update" - type = string - default = "false" -} - -variable "atlantis_write_git_creds" { - description = "Write out a .git-credentials file with the provider user and token to allow cloning private modules over HTTPS or SSH" - type = string - default = "true" -} - -# Github -variable "atlantis_github_user" { - description = "GitHub username that is running the Atlantis command" - type = string - default = "" -} - -variable "atlantis_github_app_id" { - description = "GitHub App ID that is running the Atlantis command" - type = string - default = "" -} - -variable "atlantis_github_app_key" { - description = "GitHub App private key that is running the Atlantis command" - type = string - default = "" -} - -variable "atlantis_github_user_token" { - description = "GitHub token of the user that is running the Atlantis command" - type = string - default = "" -} - -variable "atlantis_github_webhook_secret" { - description = "GitHub webhook secret of an app that is running the Atlantis command" - type = string - default = "" -} - -# Gitlab -variable "atlantis_gitlab_user" { - description = "Gitlab username that is running the Atlantis command" - type = string - default = "" -} - -variable "atlantis_gitlab_user_token" { - description = "Gitlab token of the user that is running the Atlantis command" - type = string - default = "" -} - -variable "atlantis_gitlab_hostname" { - description = "Gitlab server hostname, defaults to gitlab.com" - type = string - default = "gitlab.com" -} - -# Bitbucket -variable "atlantis_bitbucket_user" { - description = "Bitbucket username that is running the Atlantis command" - type = string - default = "" + default = true } -variable "atlantis_bitbucket_user_token" { - description = "Bitbucket token of the user that is running the Atlantis command" +variable "certificate_arn" { + description = "ARN of certificate issued by AWS ACM. If empty, a new ACM certificate will be created and validated using Route53 DNS" type = string default = "" } -variable "atlantis_bitbucket_base_url" { - description = "Base URL of Bitbucket Server, use for Bitbucket on prem (Stash)" +variable "certificate_domain_name" { + description = "Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance. Specify if it is different from value in `route53_zone_name`" type = string default = "" } -variable "custom_environment_secrets" { - description = "List of additional secrets the container will use (list should contain maps with `name` and `valueFrom`)" - type = list(object( - { - name = string - valueFrom = string - } - )) - default = [] -} - -variable "custom_environment_variables" { - description = "List of additional environment variables the container will use (list should contain maps with `name` and `value`)" - type = list(object( - { - name = string - value = string - } - )) - default = [] -} - -variable "security_group_ids" { - description = "List of one or more security groups to be added to the load balancer" - type = list(string) - default = [] -} - -variable "propagate_tags" { - description = "Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are SERVICE and TASK_DEFINITION" - type = string - default = null -} - -variable "enable_ecs_managed_tags" { - description = "Specifies whether to enable Amazon ECS managed tags for the tasks within the service" - type = bool - default = false -} - -variable "use_ecs_old_arn_format" { - description = "A flag to enable/disable tagging the ecs resources that require the new longer arn format" - type = bool - default = false -} - -variable "ecs_service_force_new_deployment" { - description = "Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g. myimage:latest)" - type = bool - default = false -} - -variable "ecs_service_enable_execute_command" { - description = "Enable ECS exec for the service. This can be used to allow interactive sessions and commands to be executed in the container" +variable "validate_certificate" { + description = "Determines whether to validate ACM certificate using Route53 DNS. If `false`, certificate will be created but not validated" type = bool default = true } -variable "enable_ephemeral_storage" { - description = "Enable to use Fargate Ephemeral Storage" - type = bool - default = false -} - -variable "ephemeral_storage_size" { - description = "Size of Ephemeral Storage in GiB" - type = number - default = 21 - - validation { - condition = var.ephemeral_storage_size >= 21 && var.ephemeral_storage_size <= 200 - error_message = "The minimum supported value is 21 GiB and the maximum supported value is 200 GiB." - } -} - -variable "efs_file_system_encrypted" { - description = "If true, the disk will be encrypted." - type = bool - default = false -} - -variable "efs_file_system_token" { - description = "Be able to import other EFS instance created by the other module" +variable "route53_zone_id" { + description = "Route53 zone ID to use for ACM certificate and Route53 records" type = string default = "" } -variable "efs_throughput_mode" { - description = "(Optional) Throughput mode for the file system. Defaults to bursting. Valid values: bursting, provisioned, or elastic. When using provisioned, also set provisioned_throughput_in_mibps." +variable "route53_record_name" { + description = "Name of Route53 record to create ACM certificate in and main A-record. If null is specified, var.name is used instead. Provide empty string to point root domain name to ALB." type = string default = null } -variable "efs_provisioned_throughput_in_mibps" { - description = "The throughput, measured in MiB/s, that you want to provision for the file system. Only applicable with efs_throughput_mode set to provisioned" - type = number - default = null -} - -variable "alb_ip_address_type" { - description = "The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack" - type = string - default = "ipv4" -} +################################################################################ +# EFS +################################################################################ -variable "create_route53_aaaa_record" { - description = "Whether to create Route53 AAAA record for Atlantis" +variable "enable_efs" { + description = "Determines whether to create and utilize an EFS filesystem" type = bool default = false } -variable "runtime_platform" { - description = "Configuration block for runtime_platform that containers in your task may use." +variable "efs" { + description = "Map of values passed to EFS module definition. See the [EFS module](https://github.com/terraform-aws-modules/terraform-aws-efs) for full list of arguments supported" type = any - default = null -} - -variable "max_session_duration" { - description = "Maximum session duration (in seconds) for ecs task execution role. Default is 3600." - type = number - default = null -} - -variable "alb_enable_cross_zone_load_balancing" { - description = "Whether cross-zone load balancing is enabled for the load balancer" - type = bool - default = null + default = {} } diff --git a/versions.tf b/versions.tf index c3666f9a..ddfcb0e0 100644 --- a/versions.tf +++ b/versions.tf @@ -1,15 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.69" - } - - random = { - source = "hashicorp/random" - version = ">= 2.0" + version = ">= 5.0" } } }