DEV Community

Alugbin Abiodun Olutola
Alugbin Abiodun Olutola

Posted on • Updated on

My Simple Github Actions CI/CD Pipeline:

A little story of how I combined 2 repositories with github actions' repository_dispatch webhook to manage my deployment pipeline.

Background.

I have always wanted to deploy a new image to kubernetes once my build is build is successful, but I always need to update the deployment file to achieve this.

I started using ArgoCD recently, and I enjoy the simplicity and the insights I get from the UI. Only problem was that I need to initialize it from my github repo. My repository is private, got some challenges with that, but that's for another time.

Challenge

The challenge here is that I want my deployment repository to be separate from my application (app) repository (repo). Also, because I am using branches in ArgoCD, I don't get to run my tests or build my image before the changes get picked up by ArgoCD. For these reasons, it makes sense to separate the repos and find a way to notify the deployment repo once the app repo has completed all the checks and the docker image is built, we can then proceed to communicate with the deployment repo, sending the new image tag over. The deployment repo then updates the image in kubernetes deployment file, checks out the code and pushes. ArgoCD picks up the new change and syncs automagically.
Enough Talks, here is the actions code.

Application

Application was written in go, but this can be swapped out for other services as well.

app_repo/.github/workflows/ci.yml

name: Test and Build

on:
  push:
    branches: [ master, develop, staging, testing ]
  pull_request:
    branches: [ master ]
jobs:
  lint:
    name: Linting
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v2

      - name: Lint and Vet
        uses: golangci/golangci-lint-action@v2
        with:
          version: latest
          args: --timeout=3m
  test:
    name: Test
    runs-on: ubuntu-18.04
    services:
      mysql:
        image: mysql:5.7
        env:
          MYSQL_DATABASE: app_name
          MYSQL_USER: cicd-user
          MYSQL_PASSWORD: password
          MYSQL_ROOT_PASSWORD: password
        ports:
          - 3306:3306
    steps:
      - uses: actions/checkout@v2

      - name: Set up Go
        uses: actions/setup-go@v2
        with:
          go-version: 1.17

      - name: Test
        run: go test ./...
        env:
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
          ENVIRONMENT: ci-cd
          DB_USERNAME: cicd-user
          DB_PASSWORD: password
          DATABASE: app_name
          TOKEN_STRING: "hello hello world"

  build:
    name: Build Image
    runs-on: ubuntu-latest
    needs:
      - test
      - lint
    steps:
      - name: Extract branch name
        shell: bash
        run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
        id: extract_branch

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Login to Dockerhub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and Push
        uses: docker/build-push-action@v2
        with:
          file: .docker/Dockerfile
          push: true
          tags: <<docker_repo>>/<<image_name>>:${{ steps.extract_branch.outputs.branch }}-${{ github.run_id }}-${{ github.run_number }}


  deploy:
    name: Trigger New Deployment
    runs-on: ubuntu-latest
    needs:
      - build
    steps:
      - name: Extract branch name
        shell: bash
        run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
        id: extract_branch

      - name: Deployment
        uses: mvasigh/dispatch-action@main
        with:
          token: ${{ secrets.G_ACCESS_TOKEN }}
          repo: <<deploy_repo>>
          owner: <<owner>>
          event_type: update_image
          message: |
            {
              "branch": "${{ steps.extract_branch.outputs.branch }}",
              "tag": "${{ steps.extract_branch.outputs.branch }}-${{ github.run_id }}-${{ github.run_number }}"
            }
Enter fullscreen mode Exit fullscreen mode

Deployment.

Here, I keep a folder for each environment:

  • production
  • staging
  • testing

There is a stubs folder called stubs with this sample file for each environment:

./stubs/environment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: <<name>>
  namespace: <<environment>>
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: backend
      app: <<app_name>>
  template:
    metadata:
      labels:
        tier: backend
        app: <<app_name>>
    spec:
      containers:
        - name: <<app_name>>
          image: <<docker_repo>>/<<image>>:#TAG#
          imagePullPolicy: Always
          envFrom:
            - configMapRef:
                name: <<app_config>>
            - secretRef:
                name: <<app_secret>>
Enter fullscreen mode Exit fullscreen mode

PS: all data between << and >> should be swapped with their actual value. Only #TAG should be the placeholder.

deploy_repo/.github/workflows/deploy.yml

name: Deploy
on:
  repository_dispatch:
    branches: ["master"]
    types: [update_image]

jobs:
  build:
    name: Run API
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
        with:
          ref: ${{github.event.client_payload.message.branch}}

      - name: Update Stub With New Tag
        shell: bash
        run: |
          cp stubs/${{ github.event.client_payload.message.branch }}.yaml ${{ github.event.client_payload.message.branch }}/deployment.yaml.stub
          sed -i "s/#TAG#/${{ github.event.client_payload.message.tag }}/g" ${{ github.event.client_payload.message.branch }}/deployment.yaml.stub
          cp ${{github.event.client_payload.message.branch}}/deployment.yaml.stub ${{github.event.client_payload.message.branch}}/deployment.yaml

      - name: Commit files
        run: |
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git commit -m "Update Tag ${{ github.event.client_payload.message.tag }}" -a

      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.G_ACCESS_TOKEN }}
          branch: ${{ github.event.client_payload.message.branch }}        

      - name: All Done
        run: |
          echo "All is Well"
Enter fullscreen mode Exit fullscreen mode

Here is a breakdown of the deploy.yml action:

  • Checks out the repo using the provided branch
  • Copies the environment stub into a deployment.yaml.stub file.
  • Updates the #TAG# placeholder with the new tag from the repository_dispatch
  • Commit the files using the bot email and using the latest tag as the commit message. This allows us to compare the sync in argocd
  • Push the changes using ad-m/github-push-action.
  • Congratulatory message from me :)

Please if there is an easier way to achieve this, please share in the comment. I will love to learn how to make my life easier.

Discussion (0)