Continuous delivery with Travis and fastlane

Continuous delivery is a key to a healthy communication with your customer. However, it is not a common practice for iOS developers to set up a continuous delivery pipeline. It is likely caused by the inferior tooling provided by the Apple itself.

This tutorial aims to present a painless way to set up the continuous delivery for your project. It also describes common pitfalls encountered during the process and how to avoid them.

The tutorial is based on a continuous delivery implementation for EL Debate open source project. In case anything goes wrong, you can always check out the sources and see for yourself. 😊

The goals

The aim of this tutorial is to have a new version of the application uploaded to TestFlight beta every time pull request is merged into a master branch of the git repository.

Since we always merge pull requests through Github, the tutorial takes a significant shortcut. The merges are detected by Merge pull request #{no.} from #{branch} pattern lookup in the last commit message.

Prerequisites

Following preliminary steps are required to follow the tutorial:

Github repository containing project's sources.

Travis CI integration configured to build the project on every branch push. The simplest .travis.yml will suffice.

Bundler should be installed on your machine. Just run gem install bundler command.

Unfortunately, at the time of writing the article you cannot enable two-factor authentication on the Apple Developer's account. There is an unresolved issue that prevents using it on Travis CI specifically. If it was fixed, you would have to set two additional environment variables to usie two-factor authentication:

Creates encoded .autodeployment.sh.enc file that you can safely commit to the repository.

Adds a new before_install step to your .travis.yml that decrypts the file contents before running actual build.

Define two additional before_install steps in .travis.yml:

- chmod +x .autodeployment.sh
- . ./.autodeployment.sh

The steps must be preceded by decrypting the script file.

The script execution command (./.autodeployment.sh) is prefixed with additional .. It sources the script, executing all the instructions in the same shell as the command itself. Otherwise, the script would be executed in a child process and the export results would not be visible to the Travis shell.

Let's break down what .autodeployment.sh script does:

It modifies SSH configuration by appending two lines to ~/.ssh/config:

Host *
StrictHostKeyChecking no

This disables an interactive prompt that asks to add server fingerprint to known_hosts. If you have ever used SSH, you have probably seen this prompt:

The authenticity of host '192.168.0.169 (192.168.0.169)' can't be established.
ECDSA key fingerprint is 74:39:3b:09:43:57:ea:fb:12:18:45:0e:c6:55:bf:58.
Are you sure you want to continue connecting (yes/no)?

It needs to be disabled in order to perform a SSH git clone in a non-interactive environment such as Travis CI. This is exactly what StrictHostKeyChecking no takes care of.

It copies private (id_rsa) and public (id_rsa.pub) keys used to authenticate to match repository. You need to paste in the actual key contents that you have generated when setting up the Github account earlier.

It adds the key to SSH agent (ssh-add ~/.ssh/id_rsa).

It sets up the credentials used by fastlane to access Apple Developer Center. Those are exported FASTLANE_USER and FASTLANE_PASSWORD environment variables.

It sets up the credentials to download certificates from match repository (MATCH_PASSWORD variable).

Why not provide environment variables directly?

Instead of exporting environment variables in a script file I could simply define them in Travis build settings. I like this approach better. However, Travis has a length limit of 128 bytes per variable and also requires variables to be bash-escaped for double quote input. After encountering those issues on a couple of occasions, I decided to go with the script approach instead.

Creating auto deployment lane

Having all the secrets in place, we can proceed to creating a fastlane script for auto deployment. Let's start by invoking fastlane init command in the project root directory. After answering a couple of simple questions, the script will generate the Fastfile located under fastlane directory.

As described in the goals section, we want the script that is executed only upon merging a pull request. Fastlane does not provide such a check out of the box. Fortunately, it is really easy to implement this action by hand. Let's create a new action by calling fastlane new_action and call it merges_pull_request. Now, open fastlane/actions/merges_pull_request.rb file and paste in following contents: