Overview
Mason CLI is a powerful tool that allows developers to build describe a custom OS as configuration via a Mason Project, build that into an immutable artifact, and transform a fleet of devices into their own Smart Products. This power however doesn't come from the tool itself - but in the way you utilize it.
In this guide, we'll walk through integrating Mason CLI into a CI/CD pipeline to bring speed, efficency and stability to your product releases.
The guide is fairly extensive so if you want to skip to a finished example check this out. TLDR; the we pull down the Mason CLI with
curl
and run it automatically after our application builds complete!
What you'll learn
- Masonifying your Android Project
- Setting up a pipeline in Github Actions
- Automating your application build and publishing as a Mason artifact
- Deploying your project automatically
Masonifying your Android Project
The first step towards integrating Mason into your CI/CD pipeline is to add your Mason project configuration to your Android application project.
If you don't have a project configuration yet, checkout this guide to creating one.
If you're starting from scratch an easy way to do this is to simply run mason init
in the root directory of your Android project. This command is interactive and can initialize a new project which results in a mason.yml
and a .masonrc
file being created inside of your project.
The mason.yml
file is your project configuration. You can think of it as a manifest or description of the entire environment your application runs within. By adding it to your Android project and versioning it alongside your application source code you effectively expand your development capabilities from just the app to an entire operating system.
You can think of the .masonrc
file as "configuration for Mason CLI itself" or a "helper to make using the CLI easier" by telling the CLI where all of your files are on your machine. This file is completely optional but we've found .masonrc
to be especially helpful in an automated CI/CD environment (where you may have multiple apps building or unconventional file structures). Without it you would explicitly register any artifact associated with your project each time you made a new version:
mason register config /path/to/myconfiguration.yml
mason register apk /path/to/my/app1.apk
mason register apk /path/to/my/app2.apk
etc.
When prompted update the path to your APK release artifiacts (we'll touch on signing in the build and release section). Our example application is written in ReactNative so that path will be
android/app/build/outputs/apk
. If you're following standard convention in Android Studio it will likely be apps/apk/release
.
You can specify the path for each individual apk when registering artifacts as part of the release process if that is more suitable for your CI environment.
After running mason init
our Andriod Project looks something like this:
The project we're showing here is written in ReactNative and shown in VS code. If you're using Android Studio or another framework that's fine. A more advanced and finished example with an app written written in Kotlin can be found here.
If you have an existing Mason project you can use mason init
as well, it will allow you to select your existing project and download the files! If you don't want to do this for any reason simply copy the yml
file for your project into the root of your Android project.
Creating a Pipeline
We will be using Github Actions for this example however Mason CLI is agnostic of CI tools feel free to adapt this guide to your tool of choice such as Jenkins, Gitlab CI, etc.
This guide assumes you use Git for version control and the repository is hosted on GitHub. If you do not use Git/Github and want to follow along, this is a good starting point. Otherwise adapt for your tooling. Don't hesitate to contact support@bymason.com for assistance here! We're happy to create a new guide if you ask!
The cool thing about Github Actions is that you can define a pipeline for your project by adding a YML file to your project and describing the "workflow". Let's get started!
Visit Github's official documentation too learn Github Actions in more depth. Checkout their workflow templates to get started quickly.
We'll do this a bit manually so we can see how it is all working. Inside of our same Android project let's create a hidden .github
directory at the root of our repository with a workflows
subdirectory inside of it.
mkdir .github/
mkdir .github/workflows/
Now create our YML file inside of that newly created workflows directory.
touch .github/workflows/cicd.yml
You can name this file whatever you want but we'll call it cicd.yml
. Then add this YML to the file and save it:
name: Build Android
on:
push:
branches:
- production
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run a one-line script
run: echo Hello, Mason!
This is a simple hello world example just to get oriented and test that our actions are working. Your project should now look something like this:
Note: our branch is called
main
in this scenerio if you have a different branch that you want to push to change the name in the example above.
Commit this file and push it to your repo:
git add .github/
git commit -am"hello CI pipline\!"
git push origin main
If you'd like to skip this step go on ahead.
Thats it! You now have the beginnings of a CI/CD pipleine already setup and running! You should see something like this in your repo in the Actions tab:
If you drill down into the output for the job you'll see our one line 'Hello, Mason!` output:
Building and Releasing
In this section of the guide we'll update our cicd.yml
to build a fully signed APK.
Though our app is written in ReactNative we will be using gradle to build the APK so it should be fairly applicable to a Java/Kotlin based project. A more advanced and finished example with an app written written in Kotlin can be found here.
The first thing we want to do in any CI/CD pipeline is run our unit tests. Since this app is a ReactNative app we'll first npm install
dependencies and run npm test
for our unit tests. With that, let's rename our first job in the pipeline to install-and-test
and add run the appropriate npm
commands.
Replace the entire contects of your cicd.yml
file with this:
name: Build Android
on:
push:
branches:
- main
jobs:
install-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install npm dependencies
run: |
npm install
- name: Run tests
run: |
npm test
It may look a bit scary but have no fear! After running this dozens of times we've included the cache module to speed up our builds. You can leave that out if you wish for simplicity. The important bits are:
- name: Install npm dependencies
run: |
npm install
- name: Run tests
run: |
npm test
where we simply run npm install
and npm test
.
Next in our pipeline we'll want to perform the actual build. This gets a bit tricky as we will need to sign the APK too -- which involves having access to the signing key. Luckily, Github Actions has a well defined method for dealing with encrypted secrets so we can encrypt and decrypt our signing key within the pipeline.
If you already have a method for signing APKs for production releases in your CI environment feel free to skip this step.
For this we will use the gpg
command line utility to encrypt our release keys and add them to as a our repo:
gpg --symmetric --cipher-algo AES256 my-release-key.keystore
Make sure to remember the pass phrase you use when GPG asks for one! We will reference this as ENCRYPT_PASSWORD
going forward.
Next we need add to our secrets to our Github project. To do this navigate to the Settings tab in your repository and click on the "Secrets" side menu option.
Aside from the newly minted ENCRYPT_PASSWORD
we also need to the actual keystore itself (it's a string so we can do that!) using the variable name KEYSTORE
along with the typical keystore credentials and KEYSTORE_PASSWORD
, KEY_ALIAS
and KEY_PASSWORD
.
In Github our secrets now look like this:
Now that we have that all squared away, we'll add a new "job" to our workflow file called "build" with all of the nessesary logic to decrypt our key, execute the build and sign the resulting artifact. Our cicd.yml
can be appended with :
build:
needs: install-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install npm dependencies
run: |
npm install
- name: Decrypt keystore
env:
ENCRYPT_PASSWORD: ${{ secrets.ENCRYPT_PASSWORD }}
run: |
gpg --quiet --batch --yes --decrypt --passphrase="$ENCRYPT_PASSWORD" --output ./android/app/my-release-key.keystore ./android/app/my-release-key.keystore.gpg
- name: Build Android Release
env:
KEYSTORE: ${{ secrets.KEYSTORE }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
cd android && ./gradlew assembleRelease -x lintVitalRelease
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: app-release.apk
path: android/app/build/outputs/apk
That final block you'll notice places app-release.apk
in android/app/build/outputs/apk
. The path we referenced in our .masonrc
earlier! Perfect for our deploy job.
Deployment
Finally time to deploy our software! In this section of the guide we will be programatically downloading the latest release of Mason CLI, authenticating, and then deploying our project.
We suggest for this step to create a new user in Controller specfically for CI. We're working hard on allowing the CLI to be used exclusively with API keys - we'll update this guide when that is ready.
Once you have the user selected that you want to use for authentication store the email address and password for that Mason user as additional secrets in your repository.
Once you've created the MASON_USERNAME
and MASON_PASSWORD
secrets let's add the last job to our workflow file. Update cicd.yml
to:
deploy_mason_dev:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download outputs
uses: actions/download-artifact@v1
with:
name: app-release.apk
path: android/app/build/outputs/apk
- name: Prep CLI
run: |
curl -Lo mason https://github.com/MasonAmerica/mason-cli/releases/download/1.7/mason-linux
chmod +x mason
./mason login -u $MASON_USERNAME -p $MASON_PASSWORD
env:
MASON_USERNAME: ${{ secrets.MASON_USERNAME }}
MASON_PASSWORD: ${{ secrets.MASON_PASSWORD }}
- name: Register Mason project
run: ./mason register -y project
- name: Deploy Mason
run: |
./mason deploy -py config iheartlives-hd latest dev
This seem like the simplest of jobs but it is the most powerful. There's a lot going on so let's walk through it.
First we grab the artifact from the previous build
job
- name: Download outputs
uses: actions/download-artifact@v1
with:
name: app-release.apk
path: android/app/build/outputs/apk
Then we pull down the Mason CLI using curl
and authenticate
- name: Prep CLI
run: |
curl -Lo mason https://github.com/MasonAmerica/mason-cli/releases/download/1.7/mason-linux
chmod +x mason
./mason login -u $MASON_USERNAME -p $MASON_PASSWORD
env:
MASON_USERNAME: ${{ secrets.MASON_USERNAME }}
MASON_PASSWORD: ${{ secrets.MASON_PASSWORD }}
Finally we register the Mason project (it knows where our files are using .masonrc
remember!) and deploy it to a group called dev
- name: Register Mason project
run: ./mason register -y project
- name: Deploy Mason
run: |
./mason deploy -py config iheartlives-hd latest dev
Publish to Github and watch your brand new CI/CD pipeline run!
Stay tuned for our guide on running automated end-to-end tests on real devices prior to deploying to production devices. Let us know interesting ideas here with X-Ray at your disposal or reach out with any requests!
Support
- Contact us at support@bymason.com