diff --git a/docs/user-guide.md b/docs/user-guide.md index 822291596..59f778bab 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -1077,6 +1077,8 @@ stages defined in the following CodeBuild build specification: run a Terraform plan. - `tf_apply.yml`: get the list of accounts from the organization and run a Terraform plan and apply. +- `tf_destroy.yml`: get the list of accounts from the organization and + run a Terraform plan and destroy. An optional approval step could be added between plan and apply as shown in the pipeline definition below. @@ -1132,6 +1134,9 @@ pipelines: - name: terraform-apply properties: spec_filename: tf_apply.yml # Terraform apply + - name: terraform-destroy # (optional stage) + properties: + spec_filename: tf_destroy.yml # Terraform destroy ``` 1. Add a sample-terraform pipeline in ADF `deployment-map.yml` as shown above. diff --git a/linters/custom-adf-dict.txt b/linters/custom-adf-dict.txt index 8fd5ddbe5..8df486765 100644 --- a/linters/custom-adf-dict.txt +++ b/linters/custom-adf-dict.txt @@ -48,9 +48,11 @@ sdkman stefanzweifel stubber tfapply +tfdestroy tfinit tflint tflocktable +tfplandestroy tfrun tfstate tfvars diff --git a/samples/sample-terraform/tf_destroy.yml b/samples/sample-terraform/tf_destroy.yml new file mode 100644 index 000000000..5dc54f55d --- /dev/null +++ b/samples/sample-terraform/tf_destroy.yml @@ -0,0 +1,18 @@ +version: 0.2 + +env: + variables: + TF_VAR_TARGET_ACCOUNT_ROLE: adf-terraform-role # The IAM Role Terraform will assume to deploy resources + TF_IN_AUTOMATION: true + TF_STAGE: "destroy" + TF_CLI_ARGS: "-no-color" + +phases: + install: + runtime-versions: + python: 3.9 + + build: + commands: + - python adf-build/helpers/terraform/get_accounts.py + - bash adf-build/helpers/terraform/adf_terraform.sh diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml index c6762d6d1..39a733b88 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml @@ -64,6 +64,14 @@ Parameters: Type: String Default: "adf-" + ADFTerraformExtension: + Type: "AWS::SSM::Parameter::Value" + Default: /adf/extensions/terraform/enabled + +Conditions: + ADFTerraformExtensionEnabled: + !Equals [!Ref ADFTerraformExtension, "True"] + Globals: Function: CodeUri: lambda_codebase @@ -1458,6 +1466,21 @@ Resources: RoleArn: !GetAtt PipelineCloudWatchEventRole.Arn Id: adf-codepipeline-trigger-pipeline + TerraformLockTable: + Condition: ADFTerraformExtensionEnabled + Type: "AWS::DynamoDB::Table" + DeletionPolicy: Retain + UpdateReplacePolicy: Retain + Properties: + AttributeDefinitions: + - AttributeName: LockID + AttributeType: S + KeySchema: + - AttributeName: LockID + KeyType: HASH + BillingMode: PAY_PER_REQUEST + TableName: adf-tflocktable + Outputs: ADFVersionNumber: Value: !Ref ADFVersion diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml index faa105f6a..74eab131b 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml @@ -15,9 +15,7 @@ Parameters: Conditions: ADFTerraformExtensionEnabled: - Fn::Equals: - - !Ref ADFTerraformExtension - - true + !Equals [!Ref ADFTerraformExtension, "True"] Resources: DeploymentFrameworkRegionalS3Bucket: diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh old mode 100755 new mode 100644 index 6547a5419..5ca974a1f --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh @@ -10,12 +10,14 @@ tfinit(){ S3_BUCKET_REGION_NAME=$(aws ssm get-parameter --name "/cross_region/s3_regional_bucket/$AWS_REGION" --region "$AWS_DEFAULT_REGION" | jq .Parameter.Value | sed s/\"//g) mkdir -p "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" cd "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" || exit - cp -R "$CURRENT/tf/*" "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" + cp -R "${CURRENT}"/tf/* "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" # if account related variables exist copy the folder in the work directory - if [ -d "$CURRENT/tfvars/$TF_VAR_TARGET_ACCOUNT_ID" ]; then - cp -R "${CURRENT}/tfvars/${TF_VAR_TARGET_ACCOUNT_ID}/*" "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" + if [ -d "${CURRENT}/tfvars/${TF_VAR_TARGET_ACCOUNT_ID}" ]; then + cp -R "${CURRENT}/tfvars/${TF_VAR_TARGET_ACCOUNT_ID}"/* "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" + fi + if [ -f "${CURRENT}/tfvars/global.auto.tfvars" ]; then + cp -R "${CURRENT}/tfvars/global.auto.tfvars" "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" fi - cp -R "${CURRENT}/tfvars/global.auto.tfvars" "${CURRENT}/tmp/${TF_VAR_TARGET_ACCOUNT_ID}-${AWS_REGION}" terraform init \ -backend-config "bucket=$S3_BUCKET_REGION_NAME" \ -backend-config "region=$AWS_REGION" \ @@ -30,7 +32,7 @@ tfinit(){ tfplan(){ DATE=$(date +%Y-%m-%d) TS=$(date +%Y%m%d%H%M%S) - bash "$CURRENT/adf-build/helpers/sts.sh" "$TF_VAR_TARGET_ACCOUNT_ID" "$TF_VAR_TARGET_ACCOUNT_ROLE" + bash "${CURRENT}/adf-build/helpers/sts.sh" "${TF_VAR_TARGET_ACCOUNT_ID}" "${TF_VAR_TARGET_ACCOUNT_ROLE}" terraform plan -out "${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}" 2>&1 | tee -a "${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}-${TS}.log" # Save Terraform plan results to the S3 bucket aws s3 cp "${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}-${TS}.log" "s3://${S3_BUCKET_REGION_NAME}/${ADF_PROJECT_NAME}/tf-plan/${DATE}/${TF_VAR_TARGET_ACCOUNT_ID}/${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}-${TS}.log" @@ -39,6 +41,12 @@ tfplan(){ tfapply(){ terraform apply "${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}" } +tfplandestroy(){ + terraform plan -destroy -out "${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}-destroy" +} +tfdestroy(){ + terraform apply "${ADF_PROJECT_NAME}-${TF_VAR_TARGET_ACCOUNT_ID}-destroy" +} tfrun(){ export TF_VAR_TARGET_ACCOUNT_ID=$ACCOUNT_ID echo "Running terraform $TF_STAGE on account $ACCOUNT_ID and region $REGION" @@ -60,6 +68,13 @@ tfrun(){ tfplan tfapply set +e + elif [[ "$TF_STAGE" = "destroy" ]] + then + set -e + tfinit + tfplandestroy + tfdestroy + set +e else echo "Invalid Terraform stage: TF_STAGE = $TF_STAGE" exit 1 diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py index 2e7b02bd4..b8fda6e3f 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py @@ -96,7 +96,7 @@ def get_accounts_from_ous(): 'organizations', ( f'arn:{PARTITION}:sts::{MANAGEMENT_ACCOUNT_ID}:role/' - f'{CROSS_ACCOUNT_ACCESS_ROLE}-readonly', + f'{CROSS_ACCOUNT_ACCESS_ROLE}-readonly' ), 'getRootAccountIDs', )