Terraform and Gitlab CI pipeline for multiple projects - amazon-web-services

We have an existing Gitlab CI EE pipeline for Terraform that works, one env at a time, each of them in a different AWS account.
However, we want to be able to scale pipeline for different teams which uses TF for their IaC requirements.
Is there a way to do it? different IaC repo/branch per team with same pipeline to handle TF deployments, with S3 as backend?
Any other way to do it? We have Gitlab CI EE and Terraform opensource edition.

I did something similar.
I had multiple repositories for each terraform module and a main repo that calls each module You can check how to do it here, in this main repository I had something like this:
- dev/
- main.tf
- variables.tf
- dev.tfvars
- backend.tf
- test/
- prd/
- .gitlab-ci.yml
Each environment had its own folder/files with the s3 and variables necessary to run. The main file is where all the other repos are called. The pipeline is triggered when it detects changes in the paths and it is initialized on the environment depending on the commit branch:
include:
- template: Terraform/Base.latest.gitlab-ci.yml
before_script:
- echo "${CI_COMMIT_BRANCH}"
- mkdir -p ~/.ssh
- touch ~/.ssh/known_hosts
- echo "$SSH_PUBLIC_KEY" > ~/.ssh/id_rsa.pub #this is to allow the main repository to get the information from the other ones, all of them are private
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
- terraform --version
stages:
- init
init:
stage: init
environment: $CI_COMMIT_BRANCH
script:
- terraform init # just an example
rules:
- if: $CI_COMMIT_BRANCH == "dev" || $CI_COMMIT_BRANCH == "tst" || $CI_COMMIT_BRANCH == "prd"
changes:
- dev/*
- tst/*
- prd/*
I must say this is not the best way to do it, there are some "security" points to mention but they can be solved with a little ingenuity such as: I have understood that the backend.tf shouldn't be explicit in each folder neither the .tfvars file. Someone told me that using terraform enterprise these issues could be solved. Another "dirty thing" about this is that there's kind of duplicated code because each environment folder contains the same main file, outputs and variables.
So far, the way I did it works. I hope this can give you an idea :)
Good luck.

Related

How to setup terraform cicd with gcp and github actions in a multidirectory repository

Introduction
I have a repository with all the infrastructure defined using IaC, separated in folders. For instance, all terraform configuration is in /terraform/. I want to apply all terraform files inside that directory from the CI/CD.
Configuration
The used github action is shown below:
name: 'Terraform'
on: [push]
permissions:
contents: read
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
environment: production
# Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
defaults:
run:
shell: bash
#working-directory: terraform
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout#v3
# Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token
- name: Setup Terraform
uses: hashicorp/setup-terraform#v1
- id: 'auth'
uses: 'google-github-actions/auth#v1'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud#v1'
# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
- name: Terraform Init
run: terraform init
# Checks that all Terraform configuration files adhere to a canonical format
- name: Terraform Format
run: terraform fmt -check
# On push to "master", build or change infrastructure according to Terraform configuration files
# Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
- name: Terraform Apply
run: terraform apply -auto-approve -input=false
Problem
If I log in and then change directory to apply terraform it doesn't find to log in.
storage.NewClient() failed: dialing: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
On the other hand, if I don't change the directory then it doesn't find the configuration files as expected.
Error: No configuration files
Tried to move the terraform configuration files to the root of the repository and works. How could I implement it in a multidirectory repository?
Such feature was requested before. As explained in the issue, auth files is named as follows gha-creds-*.json.
Therefore, added a step just before using terraform to update the variable environment and moving the file itself:
- name: 'Setup google auth in multidirectory repo'
run: |
echo "GOOGLE_APPLICATION_CREDENTIALS=$GITHUB_WORKSPACE/terraform/`ls -1 $GOOGLE_APPLICATION_CREDENTIALS | xargs basename`" >> $GITHUB_ENV
mv $GITHUB_WORKSPACE/gha-creds-*.json $GITHUB_WORKSPACE/terraform/

Terraform fails in GitLab due to cache

I am trying to deploy my AWS infrastructure using Terraform from GitLab CI CD Pipeline.
I am using the GitLab managed image and it's default Terraform template.
I have configured S3 backend and it's pointing to the S3 bucket used to store the tf state file.
I had stored CI CD variables in GitLab for: AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY' and 'S3_BUCKET'.
Everything was working fine, until I changed the 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY' and 'S3_BUCKET' which was pointing to a different AWS account.
Now I am getting the following error:
$ terraform init
Initializing the backend...
Backend configuration changed!
Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.
Error: Error loading state:
AccessDenied: Access Denied
status code: 403, request id: XXXXXXXXXXXX,
host id: XXXXXXXXXXXXXXXXXXXXX
Terraform failed to load the default state from the "s3" backend.
State migration cannot occur unless the state can be loaded. Backend
modification and state migration has been aborted. The state in both the
source and the destination remain unmodified. Please resolve the
above error and try again.
Cleaning up file based variables 00:00
ERROR: Job failed: exit code 1
Since this issue happened because I changed the access_key and secret_key (It was working fine from my local VS Code), I commented out the 'cache:' block in the .gitlab-ci.yml file and it worked!
The following is my .gitlab-ci.yml file:
.gitlab-ci.yml
stages:
- validate
- plan
- apply
- destroy
image:
name: registry.gitlab.com/gitlab-org/gitlab-build-images:terraform
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
# Default output file for Terraform plan
variables:
PLAN: plan.tfplan
JSON_PLAN_FILE: tfplan.json
STATE: dbrest.tfstate
cache:
paths:
- .terraform
before_script:
- alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create \":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
- terraform --version
- terraform init
validate:
stage: validate
script:
- terraform validate
only:
- tags
plan:
stage: plan
script:
- terraform plan -out=plan_file
- terraform show --json plan_file > plan.json
artifacts:
paths:
- plan.json
expire_in: 2 weeks
when: on_success
reports:
terraform: plan.json
only:
- tags
allow_failure: true
apply:
stage: apply
extends: plan
environment:
name: production
script:
- terraform apply --auto-approve
dependencies:
- plan
only:
- tags
when: manual
terraform destroy:
extends: apply
stage: destroy
script:
- terraform destroy --auto-approve
needs: ["plan","apply"]
when: manual
only:
- tags
The issue clearly happens if I don't comment out the below block. However it used to work before I made changes to the AWS access_key and secret_key.
#cache:
# paths:
# - .terraform
When the cache was not commented, the following was the result in the CI CI Pipeline:
Is cache being stored anywhere? And How do I clear it?
Think it's related to GitLab.
It seems that runner cache can be cleared off from GitLab from the UI itself.
Go to GitLab -> CI CD -> Pipelines and hit the 'Clear Runner Cache' button to clear the cache.
It actually works!

AWS pass large number of ENV variables into codebuild

Currently our singleton application including 5 containers goes through AWS pipeline into code build and then code deploy into ECS services. During codebuild base on an ENV set in codebuild $Stage it can be dev, prod or staging and loads a specific config file for which contains all the ENV variables each container needs. See below:
build:
commands:
#Get commit id
- "echo STAGE $STAGE"
- "export STAGE=$STAGE"
#Assigning AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY needs to be done in two steps, otherwise it ends up in "Partial credentials found in env" error
- "export ANSIBLE_VARS=\"\
USE_EXISTING_VPC=true \
DISABLE_BASIC_AUTH=true\""
- "export DOCKER_ARGS=\"-e COMMIT_ID=$GIT_COMMIT -e APP_ENV=$STAGE
Problem 1: is these config files are within the repo and anybody can modify them. So there are lots of human errors like the production redirect Url is pointing to the wrong place, or new ENV is not set.
So I want to move away from loading different config files and move ENV variables to AWS to handle. Something like during code build it will load from parameter store. Is this correct way?
Problem 2 is there are lots of ENV variables, is the only option to list them one by one in the CloudFormation template ? Are there any other better way to load all of ENV variable into DOCKER_ARG from above build command ?

How to solve an AWS Lamba function deployment problem?

.. aaaand me again :)
This time with a very interesting problem.
Again AWS Lambda function, node.js 12, Javascript, Ubuntu 18.04 for local development, aws cli/aws sam/Docker/IntelliJ, everything is working perfectly in local and is time to deploy.
So I did set up an AWS account for tests, created and assigned an access key/secret and finally did try to deploy.
Almost at the end an error pop up aborting the deployment.
I'm showing the SAM cli version from a terminal, but the same happens with IntelliJ.
(of course I mask/change some names)
From a terminal I'm going where I have my local sandbox with the project and then :
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: MyActualProjectName
AWS Region [us-east-1]: us-east-2
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]: y
SAM configuration environment [default]:
Looking for resources needed for deployment: Not found.
Creating the required resources...
Successfully created!
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-7qo1hy7mdu9z
A different default S3 bucket can be set in samconfig.toml
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Error: Unable to upload artifact MyFunctionName referenced by CodeUri parameter of MyFunctionName resource.
ZIP does not support timestamps before 1980
$
I spent quite some time looking around for this problem but I found only some old threads.
In theory this problems was solved in 2018 ... but probably some npm libraries I had to use contains something old ... how in the world I fix this stuff ?
In one thread I found a kind of workaround.
In the file buildspec.yml somebody suggested to add AFTER the npm install :
ls $CODEBUILD_SRC_DIR
find $CODEBUILD_SRC_DIR/node_modules -mtime +10950 -exec touch {} ;
Basically the idea is to touch all the files installed after the npm install but still the error happens.
This my buildspec.yml file after the modification :
version: 0.2
phases:
install:
commands:
# Install all dependencies (including dependencies for running tests)
- npm install
- ls $CODEBUILD_SRC_DIR
- find $CODEBUILD_SRC_DIR/node_modules -mtime +10950 -exec touch {} ;
pre_build:
commands:
# Discover and run unit tests in the '__tests__' directory
- npm run test
# Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda
- rm -rf ./__tests__
# Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json)
- npm prune --production
build:
commands:
# Use AWS SAM to package the application by using AWS CloudFormation
- aws cloudformation package --template template.yml --s3-bucket $S3_BUCKET --output-template template-export.yml
artifacts:
type: zip
files:
- template-export.yml
I will continue to search but again I wonder if somebody here had this kind of problem and thus some suggestions/methodology about how to solve it.
Many many thanks !
Steve

Is it possible to set environment variables per branch in the Amplify.yml file (AWS Amplify)?

I'm currently using AWS Amplify to manage my front-end. I've been manually injecting the environment variables throughout the console.
While I have seen that (at least in this case), the environment variables are correctly protected as mentioned in the AWS docs. I wanted to know if it was possible to set in the amplify.yml file variables per branch that do not necessarily need protection.
Something like this:
version: 0.1
env:
variables:
myvarOne:
branch: master
value: ad
branch: dev
value otherval
frontend:
phases:
preBuild:
commands:
- yarn install
- yarn lint
- yarn test
build:
commands:
- yarn build build
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- node_modules/**/*
So far, it seems there is no ideal solution for your problem
However, it is possible to do some workaround to have something like that working
You cannot have per branch environment variables, but you can have per branch commands
So, you can define different variables for different branches and run the appropriate command as you wish
version: 0.1
env:
variables:
myvarOne:
value_master: val
value_dev: otherval
frontend:
phases:
preBuild:
commands:
- if [ "${AWS_BRANCH}" = "master" ]; then export VALUE=${value_master}; fi
- if [ "${AWS_BRANCH}" = "dev" ]; then export VALUE=${value_dev}; fi
- yarn install
- yarn lint
- yarn test
build:
commands:
- yarn build build
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- node_modules/**/*
I might be pretty late with the answer, but as of the time of writing, it seems like there exists an out-of-the-box solution to your case.
According to the documentation almost verbatim, it asks you to do the following:
Sign in to the AWS Management Console here.
Navigate to App Settings > Environment variables > Manage variables.
In the Manage variables section, under Variable, enter your key. For Value, enter your value.
Choose Actions and then choose Add variable override.. You can override the environment variable for the key based on branches.
You now have a set of environment variables specific to your branch.
This GIF better illustrates steps 4-5.
I don't have enough stackoverflow reputation to add an image, so please refer to the 5th point in the documentation where it describes a way to Add variable override for specific branches.
https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html