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 }}"
}
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>>
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"
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 therepository_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.
Top comments (0)