Categories
android github

Using Github Actions to Automate Our Release Process

At Over, we’ve recently translated our app into 12 new languages. This meant that our release process started to get a bit more tedious as more steps were introduced into making a new release for the Google Play Store. We were starting to feel the fatigue of manually completing the exact same steps for every release. 

My goal was to make this process as seamless as possible for the engineer on release duty. I decided to turn to using Github Actions to automate as much of the process as possible. In this blog post, I’ll explain different parts of our process and show you how I setup Github Actions to achieve what we needed to. 

Our Release Process 

We use Git Flow for our release process, which means when we are ready to release, we create a release/v5.4.0 branch from develop. We would manually create this branch, change the version code and version name, push the changes and create two PRs for this (One for the merge into main and one for the merge back into develop). Once a release was merged, we needed to do some release note creation, tagging on Github and notifying the team on Slack what the latest release contains. 

This process, although cumbersome, wasn’t too unpleasant to manage manually. Then a new requirement was added to our process: Submit the strings.xml file for translation when a new release is made. We also needed automation when the translations were received back from the translation system. 

This diagram below describes Git Flow (adapted from here), with our added step of submitting translations for the application:

Automating more of our release process 🤖

Although we had automatic builds set up to submit releases to Google Play and Firebase, a large chunk of our release process was still a manual process. We used Github Actions to automate more of this. 

We now have three workflows setup on Github actions:

  • Starting a release and submitting strings.xml for translation.
  • Getting translations back from translators.
  • Tagging a release on Github and notifying a slack channel.

Note: We aren’t automating the actual builds on Github Actions yet, the different builds happen on a separate build server when pushes to the relevant branches are created, I won’t be covering those configs in this post. 

Starting a release 🚝

The first workflow I created is a manual trigger to create the release branch and submit the strings.xml for translation.

Using the workflow_dispatch trigger, this offers a UI in the Github Actions tab, to enter in some user input. For our release workflow, I added versionName and versionCode to be entered in by the release engineer. 

We also introduced using a CHANGELOG.md file for documenting the new features and release notes that’ll be generated at the end of the process, so we no longer need to cobble together release notes when we make a new release. 

This is the create_release_branch.yml file, checked into git at the following location .github/workflows/create_release_branch.yml(you can call your yml anything, but they need to be located in .github/workflows/ folder in order for Github to find them):

name: Create Release Branch
on:
workflow_dispatch:
inputs:
versionName:
description: 'Name of version (ie 5.5.0)'
required: true
versionCode:
description: 'Version number (50500)'
required: true
jobs:
createrelease:
runs-on: ubuntu-latest
steps:
name: Check out code
uses: actions/checkout@v2
name: Create release branch
run: git checkout -b release/v${{ github.event.inputs.versionName }}
name: Initialize mandatory git config
run: |
git config user.name "GitHub Actions"
git config user.email noreply@github.com
name: Change version number and name
run: printf 'ext.version_code = ${{ github.event.inputs.versionCode }}\next.version_name = "${{ github.event.inputs.versionName }}"\n' > app_versions.gradle
name: Update Changelog
uses: thomaseizinger/keep-a-changelog-new-release@v1
with:
version: ${{ github.event.inputs.versionName }}
name: Commit changelog and manifest files
id: make-commit
run: |
git add app_versions.gradle
git add CHANGELOG.md
git commit –message "Prepare release ${{ github.event.inputs.versionName }}"
echo "::set-output name=commit::$(git rev-parse HEAD)"
name: Push new branch
run: git push origin release/v${{ github.event.inputs.versionName }}
name: Submit strings.xml for translation
uses: andstor/copycat-action@v3
with:
personal_token: ${{ secrets.ANDROID_I18N_PAT }}
src_path: resources/src/main/res/values/strings.xml
src_branch: release/v${{ github.event.inputs.versionName }}
dst_path: en/strings.xml
dst_owner: your-repo-owner
dst_repo_name: android-i18n
dst_branch: master
clean: true
commit_message: release/v${{ github.event.inputs.versionName }} BOT New Strings for translation submitted.
name: Create pull request into main
uses: thomaseizinger/create-pull-request@1.0.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
head: release/v${{ github.event.inputs.versionName }}
base: main
title: v${{ github.event.inputs.versionName }} into main
reviewers: ${{ github.event.issue.user.login }}
body: |
Hi!
This PR was created in response workflow running.
I've updated the version name and code commit: ${{ steps.make-commit.outputs.commit }}.
name: Create pull request to develop
uses: thomaseizinger/create-pull-request@1.0.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
head: release/v${{ github.event.inputs.versionName }}
base: develop
title: v${{ github.event.inputs.versionName }} into develop
reviewers: ${{ github.event.issue.user.login }}
body: |
Hi!
This PR was created in response workflow running.
I've updated the version name and code commit: ${{ steps.make-commit.outputs.commit }}.

Using the workflow_dispatch trigger, this is a manual trigger that allows for user input. 

This release workflow does the following steps:

  1. Check out code from app repo.
  2. Create new release branch named release/v{versionName}
  3. Change the version name and version code of release.
  4. Update the changelog using this action
  5. Commit and push changelog changes and version name changes to the branch. 
  6. Copy strings.xml file to the other repository that we use for translations using this action. This action requires setting up a PAT (Personal Access Token) and putting the variable in the “Settings” -> “Secrets” .
  7. Create two PRs, one into main and one into develop . This triggers our build server to see if these builds pass. 

After this point, the release branch will be waiting for translations before we merge the PRs. 

Now to actually run this Action: Log onto Github and select “Actions” tab, click on the “Create Release Branch”, there is a little “Run workflow” button that we can click to trigger this workflow with our inputs. 

The manual trigger now on Github actions, where we can enter in the version name and number of the release to create.

Getting translations back from translators 🌎

We have a separate repository where our translations for the application live, the new translations are submitted to us via new commits on that repository. The repository also lives on Github, so I created a new workflow trigger on that repository to process the translations. 

name: Translation Export to Android Repo
on:
push:
branches: [ master ]
workflow_dispatch:
jobs:
push_strings_to_over:
runs-on: ubuntu-latest
if: "contains(github.event.head_commit.message, 'Automated checkin')"
steps:
name: Check out code
uses: actions/checkout@v2
with:
path: 'android-i18n'
name: Check out my other private repo
uses: actions/checkout@v2
with:
path: 'android-app'
repository: repo-owner/android-app
token: ${{ secrets.I18N_TO_OVER_PAT }}
name: Run rename script (renameall.sh) and commit to Android repo
run: |
cd android-app/
git fetch
releaseBranchToCheckout=$(git branch -a | grep release | head -n 1 | xargs | cut -c 16-)
git checkout -b $releaseBranchToCheckout
echo "BASE_RELEASE_BRANCH=$releaseBranchToCheckout" >> $GITHUB_ENV
cd ..
cd android-i18n/
./renameall.sh
cd ..
cp -R android-i18n/values-* android-app/resources/src/main/res/
cd android-app/
git config user.name "GitHub Actions"
git config user.email noreply@github.com
date=$(date '+%Y-%m-%d%H%M%S')
newBranchName=feature/new-translations-$date
echo "NEW_BRANCH_NAME=$newBranchName" >> $GITHUB_ENV
name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.I18N_TO_OVER_PAT }}
path: android-app
base: ${{ env.BASE_RELEASE_BRANCH }}
commit-message: Update translations
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
branch: ${{ env.NEW_BRANCH_NAME }}
title: '🤖 Automation – New translations received for ${{ env.BASE_RELEASE_BRANCH }}'
body: |
New translations received from GoLF.
draft: false
name: Slack notify
uses: rtCamp/action-slack-notify@master
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: #android-bots
SLACK_TITLE: New translations have been received! 🌎
SLACK_MESSAGE: Translation system has submitted new translations. Check them out here – ${{ steps.cpr.outputs.pull-request-url }}
MSG_MINIMAL: true
view raw export_strings.yml hosted with ❤ by GitHub

This workflow does the following steps:

  1. Check out translations repo (android-i18n) and checkout app repo (android-app). 
  2. On android-app repo, switch to the latest release branch. 
  3. On android-i18n repo, run renameall.sh script. This script changes the folder names from the translation system names to android resource folder names (ie from de to values-de).
  4. Copy the renamed folders (with their string changes) to the android-app repo from the android-i18n repo. 
  5. Create Pull Request into release branch on the main android-app repository.
  6. Notify on slack that the new translation PR is available. 

This workflow really helped ease some of the pain of manual string copying back and forth between repositories. 

Tagging a release 🔖

Once a release has been merged into our main branch, we had the next manual step of creating a release on Github, copying release notes from CHANGELOG.md and tagging it. We would also need to at this point, notify a certain slack channel of the new release build. As you can tell, this was also a perfect candidate for automation since it was the same process every single time. 

This is the workflow for tagging a release, getting the CHANGELOG.md release notes, and notifying the relevant slack channel:

name: Tag Release
on:
push:
branches: [ main ]
jobs:
tag_release:
runs-on: ubuntu-latest
steps:
name: Check out code
uses: actions/checkout@v2
name: Initialize mandatory git config
run: |
git config user.name "GitHub Actions"
git config user.email noreply@github.com
name: Setup release information
#get version name from app_versions.gradle file (5.6.2)
run: |
versionName=`sed '2q;d' app_versions.gradle | cut -d "=" -f2 | xargs`
export VERSION_NAME=$versionName
echo "::set-env name=VERSION_NAME::$VERSION_NAME"
name: Extract release notes
id: extract_release_notes
uses: ffurrer2/extract-release-notes@v1
name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.VERSION_NAME }}
release_name: v${{ env.VERSION_NAME }}
body: ${{ steps.extract_release_notes.outputs.release_notes }}
draft: false
prerelease: false
name: Slack notify
uses: rtCamp/action-slack-notify@master
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OVER_APP }}
SLACK_CHANNEL: #over-app-android
SLACK_TITLE: 🚨 Version ${{ env.VERSION_NAME }} is being published to Google Play Internal Test
SLACK_MESSAGE: ${{ steps.extract_release_notes.outputs.release_notes }}
MSG_MINIMAL: true
view raw tag_release.yml hosted with ❤ by GitHub

This workflow runs when a new push has happened on the main branch. It does the following:

  1. Check out code.
  2. Get the version name from app_versions.gradle file. Set an environment variable for use in later steps.
  3. Extract release notes from the CHANGELOG.md file, using this action
  4. Create a Github Release with the tag_name set to the VERSION_NAME extracted from step 2. 
  5. Notify slack channel with that a new version is going to be published on Google Play with the release notes from CHANGELOG.md.
Shiny new release on Github.

Summary

I found Github Actions to be simple enough to set up and get started. The part that convinced me to use Github Actions was the amount of existing Github Actions that you can leverage really easily by just using the uses tag. For instance, finding actions that deal with CHANGELOG.md files, creating PRs and copying files, really helped ease the process of creating these new automations easier. 

Github Actions are really powerful, ActionsFlow is a great example of what can be built on-top of Github Actions. It is basically a Zapier/IFTTT replacement, all running off Github Actions. 

I can’t wait to automate more of my life with Github Actions. What have you built with Github actions? Let me know on Twitter — @riggaroo


References: