Automating your Cloud Infrastructure with Github Actions
Tags: aws, cloudformation, github, devops
Published: 2021-02-15 00:00:00

---
title: 'Automating your Cloud Infrastructure with Github Actions'
subtitle: 'A step by step guide on how you can use github actions to create a pipeline for your cloud infrastructure on AWS.'
date: '2021-02-15'

image: 'rodion-kutsaev-xNdPWGJ6UCQ-unsplash.jpg'
imageAltText: 'Man standing on an oil pipeline'
imageWidth: 6000
imageHeight: 4000

urlPath: '/posts/automating-your-cloud-with-github'
canonical_url: 'https://charliethomas.codes/posts/automating-your-cloud-with-github'

tags: [ 'AWS', 'CloudFormation', 'github', 'devops']
readingTime: 6
published: true

---

Managing cloud resources for a production application can be a major pain. To help teams manage resources, AWS provides the Infrastructure as Code solution _Cloudformation_, but sometimes automating workflows with cloudformation can be painful in and of itself. Luckily, Github actions and Amazon's out of the box actions kit can make your life much easier!

In this guide we will walk through a series of steps to get a simple pipeline up and running.

### Step 1: Define your Cloudformation Stack!

For this example, we will define a simple Cloudformation Stack containing a single dynamo table. 

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: My Cloud Resource

Parameters:
  Environment:
    Type: String
    Default: stage

Resources:
  SimpleTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      BillingMode: PAY_PER_REQUEST
      TableName: !Sub ${Environment}-tablename
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S        
      KeySchema:
        - AttributeName: id
          KeyType: HASH          
```

Notice that we have a parameter in the template defined for environment. You can use this if your pipeline needs to deploy to multiple environments.

### Step 2: Add permissions keys to your infrastructure github repo!

Next up, you'll want to create some IAM permissions for Github actions to use. As a secure approach, I recommend you create a dedicated user and role for your pipeline to assume when running. In this guide we will define it all in a reusable template to make this a bit future-proof.

Start by defining a user with correlating access keys that we can reference later.

```yaml
Resources:
  User:
    Type: AWS::IAM::User
    Properties:
      UserName: github-actions-user
  AccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: github-actions-user
      Serial: 1
  Credentials:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: github-actions-user
      SecretString: !Sub |
        {
          "AccessKeyId":"${AccessKey}",
          "SecretAccessKey":"${AccessKey.SecretAccessKey}"
        }
```

Once you have a the **User** defined, create a **Role** that we will allow this **User** to assume, giving it the permissions it needs to manage the resources in our infrastructure templates.

```yaml
Resources:
  # ... previous User resources from above
  Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: github-actions-deployer-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - cloudformation.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
        # Any other resources you need to manage should be listed here
```

Notice that we are giving exact permissions for the deployer to only manage DynamoDB resources in this case. You may even want to restrict it even tighter with your own more specific policies.

Finally, tie the **User** and **Role** together with a **UserPolicy** that allows the **User** to manage Cloudformation stacks and pass our defined **Role** to Cloudformation.

```yaml
Resources:
  # ... previous User and Role resources from above
  UserPolicy:
    Type: AWS::IAM::Policy
    Properties:
      Users:
        - github-actions-user
      PolicyName: github-cloudformation-deploy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "cloudformation:*"
            Effect: Allow
            Resource: "*"
          - Action: "iam:PassRole"
            Effect: Allow
            Resource: !GetAtt Role.Arn
```

Once you have all of your IAM written you can manually deploy this with the AWS CLI and subsequently fetch the access credentials.

```bash
aws cloudformation deploy \
  --stack-name github-actions-cloudformation-deployer \
  --template-file path/to/your/yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --region us-east-1

aws secretsmanager get-secret-value \
  --secret-id github-actions-user \
  --region us-east-1 \
  --query SecretString \
  --output text
```

Then take the output credentials and set up 2 secrets in your github repository under **Setting > Secrets > Actions Secrets**.

<img class="rounded-lg" src="/images/actions-secrets.png" />

### Step 3: Create the pipeline in Github!

Finally, now that you have all the permissions set up and a Cloudformation template ready to deploy, create a Github actions workflow in the **.github/workflows** directory of your project.

```yaml
on:
  push:
    branches:
      - master

name: Deploy Cloudformation

jobs:
  deploy-cfn:
    name: Deploy Cloudformation Template
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: "us-east-1"
    - name: Execute Changeset
      uses: aws-actions/aws-cloudformation-github-deploy@v1
      with:
        name: my-cloudformation-stack
        template: path/to/my/yaml
        role-arn: arn:aws:iam::${{ steps.creds.outputs.aws-account-id }}:role/github-actions-deployer-role
        no-fail-on-empty-changeset: "1"
        parameter-overrides: >-
          Environment=dev
```

Using the **aws-actions** actions kit allows us to assume the proper IAM roles and the execute *cloudformation* changes with ease. Make sure to point to the right yaml file and pass all your parameters that you defined on the stack from *Step 1* (in this example we just override the *Environment* param). Once in place, every commit to the repo will result in your *cloudformation* template being deployed automatically to the correlating AWS account. From here, you can expand on these workflows to support more advanced features.

Such as:
- Multi-stack deployments with [Github Actions Matrices](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix)
- Multi-environment workflows with [Github Actions Environments](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idenvironment)
- Change-set linting to get in front of yaml syntax errors and invalid changes.

### References
- [AWS Quick-start Github Template](https://github.com/aws-samples/aws-cloudformation-starter-workflow-for-github-actions)
- [Cloudfromation CLI Docs](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/index.html)
- [Github Actions Docs](https://docs.github.com/en/actions/reference)