Creating an automatic Helm Repository with GitHub Actions

Creating an automatic Helm Repository with GitHub Actions

I needed a Helm repository to house my private charts, but at the same time I didn't want to have to deal with and maintain a server for it.

In my research I discovered that it is possible and quite easy to use a git repository for this purpose. Great! However, I feel that did not go far enough for my use. For once, I did not want to have to manually package and index the repository on every chart change and so I decided to expand on it.

The end result

At the end, we should have a Helm repository that updates itself on every change to the git repository structured as follows:

├── master
│   ├── charts/
│   │   └── example/
│   └── sync_repo.sh
└── repo
    ├── example-0.1.0.tgz
    └── index.yaml

The full example is available at my GitHub

The setup

For this, create a new git repository, clone it locally and create a new chart in the charts directory;

mkdir charts
cd charts
helm create example

Once that's done and committed, we will create an orphan branch to store the actual Helm repository. Why an orphan branch? On every commit to the repository, CircleCI will do its own commit to update the Helm repository.

While this step is optional, it prevents developers from having to pull-rebase every time they make a change to the repo, and has the added benefit of keeping the history of the master branch clean. Create an orphan branch called repo as follows:

git checkout --orphan repo
git rm -rf . 
touch index.yaml
git add index.yaml
git commit -m 'Initial Commit'
git push -u origin repo

Now let's return to the master branch. Our next step is to create a script which our CI will use on every commit. The script will package all charts, and re-generate the index.yaml file.

Use your text editor of choice to create sync_repo.sh and add the following to it:

#!/bin/sh
mkdir -p repo
cd repo
helm package ../charts/*
helm repo index .

At last, we add the last piece of this puzzle and integrate GitHub Actions into this process.

Our action will pick off where our shell script left us off; It will clone the separate repo branch and copy the generated files to it, finally committing and pushing back the changes to origin.

For this, we'll create the following action at .github/workflows/sync_repo.yml.

name: Sync Helm Repo
on:
  push:
    branches: [ master ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          ref: 'master'
          path: 'master'
      - uses: actions/checkout@v2
        with:
          ref: 'repo'
          path: 'repo'   
      - uses: azure/setup-helm@v1
      - name: Sync Helm Repo
        run: cd master && sh sync_repo.sh
      - name: Move repo folder
        run: |
          mv master/repo/* repo/
          cd repo
          git config user.email "idan@elhalwani.com"
          git config user.name "Idan Elhalwani"
          git add -A
          git diff --quiet && git diff --staged --quiet || git commit -m "Update repo [SKIP CI]" -m "${{ github.event.head_commit.message }}"
          git push

Let's break down the action;

# Move our generated files from the master branch folder to the repo branch folder
mv master/repo/* repo/
cd repo
# Configure git for when we're ready to commit
git config user.email "idan@elhalwani.com"
git config user.name "Idan Elhalwani"
# Add all new files and attempt to commit. If there is nothing new, the commit won't go through and nothing will be pushed
git add -A
git diff --quiet && git diff --staged --quiet || git commit -m "Update repo [SKIP CI]" -m "${{ github.event.head_commit.message }}"
git push

Conclusion

At this point, we have a fully functional and automatically updating Helm repository. To use it, get the raw URL for the repo branch.

In our case, we will add the repository as follows:

helm repo add example https://raw.githubusercontent.com/idane/helm-repo-example/repo