Jinja Templating with GitHub Actions

Consider the simple case where you have an JSON file containing an ECS task definition within a repository’s master branch:

https://github.com/someuser/my-ecs-repo/aws/ecs_task_definition.json

{
    "family": "production_metrics_app",
    "containerDefinitions": [
        {
            "name": "metrics_runner",
            "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/metrics-checker:latest",
            "memory": 300,
            "cpu": 10,
            "essential": true
        }
    ],
    "taskRoleArn": "arn:aws:iam::123456789012:role/metrics_app_iam_role",
    "executionRoleArn": "arn:aws:iam::123456789012:role/metrics_app_execution_role"
}

Let’s assume that the master branch is where all the application’s Production code resides. It’s clear from the definition that the associated AWS Account is 123456789012.

Currently, the associated workflow which runs the deployment looks like:

name: Deploy ECS Task

on: [push]

env:
    ECR_REPOSITORY: metrics-checker     
    ECS_CLUSTER: production-ecs-cluster
    ECS_TASK_DEFINITION: my-ecs-repo/aws/ecs_task_definition.json


permissions:
  contents: read
  id-token: write

jobs:
  deploy_prod:
    name: ECS Prod deployment
    runs-on: ubuntu-latest
    steps:
      - name: "AWS Credentials"
        uses: aws-actions/configure-aws-credentials@v3
        with:
          ...
          ...
          ...

      - name: "Deploy task to ECS"
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ env.ECS_TASK_DEFINITION }}
          cluster: ${{ env.ECS_CLUSTER }}

To look at how templating can benefit productivity and code reusability, we’ll tweak the workflow and JSON file to also deploy the task to a secondary AWS account 987647837647, used primarily for user acceptance testing (UAT). Code for this environment is stored in branch: uat.

Our requirement for the build is:

  • Depending on branch being pushed to (master or uat) a job should run to render the template with the correct variables.

To use Jinja templating in our workflow, we can leverage from existing GitHub jinja2-action.

Convert ECS Task JSON to Template

For now, we will only concentrate templating the following fields:

  • target environment: resolves to either “production” or “uat
  • target AWS account: resolves to either “123456789012” or “987647837647

Update the file contents, replacing hard-coded values with environment variable “look-up” equivalents—refer to GitHub action cuchi/jinja2-action@v1.2.2 standards for using variables:

{
    "family": "{{ env.get('env_target') }}_metrics_app",
    "containerDefinitions": [
        {
            "name": "metrics_runner",
            "image": "{{ env.get('target_aws_acct') }}.dkr.ecr.us-east-1.amazonaws.com/metrics-checker:latest",
            "memory": 300,
            "cpu": 10,
            "essential": true
        }
    ],
    "taskRoleArn": "arn:aws:iam::{{ env.get('target_aws_acct') }}:role/metrics_app_iam_role",
    "executionRoleArn": "arn:aws:iam::{{ env.get('target_aws_acct') }}:role/metrics_app_execution_role"
}

Taking target environment as an example, in the above template, the expression:

"{{ env.get('env_target') }}_metrics_app"

would resolve to:

"production_metrics_app"

Updated Workflow

Our revised basic workflow is shown below.

name: Deploy ECS Task

on:
  push:
    branches:
      - master
      - uat

env:
    ECR_REPOSITORY: metrics-checker     
    ECS_TASK_TEMPLATE: my-ecs-repo/aws/ecs_task_template.json
    ECS_TASK_RENDERED: my-ecs-repo/aws/ecs_task_rendered.json
    target_env: ${{ github.ref == 'refs/heads/master' && 'production' || 'uat' }}
    aws_acct_id: ${{ github.ref == 'refs/heads/master' && '123456789012' || '987647837647' }}

permissions:
  contents: read
  id-token: write

jobs:
  deploy_ecs_task:
    name: ECS Prod deployment
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - id: jinja
        uses: cuchi/jinja2-action@v1.2.2
        with:
          template: ${{ env.ECS_TASK_TEMPLATE }}
          output_file: ${{ env.ECS_TASK_RENDERED }}
          data_format: json
          strict: true
        env:
          target_aws_acct: ${{ env.aws_acct_id }}
          env_target: ${{ env.target_env }}

      - run: |
          cat ${{ env.ECS_TASK_RENDERED }}

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v3
        with:
          aws-region: us-east-1
          role-to-assume: arn:aws:iam::${{ env.aws_acct_id }}:role/${{ env.target_env }}_role
          role-session-name: samplerolesession

      - name: "Deploy task to ECS"
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ env.ECS_TASK_RENDERED }}
          cluster: ${{ env.target_env }}-ecs-cluster