Continuous Deployment for iOS using Travis CI

Antonis Tsakiridis
15 min readDec 9, 2016

--

At Telestax we believe that Continuous Integration & Deployment is an integral part of all software we produce. As our mobile Open Source WebRTC SDKs get more mature we came to realize that more and more time was spent on manual steps for testing and deployment instead of actual development and that it was about time we bit the bullet and introduced CI/CD facilities. In this installment we ‘ll cover using Travis CI to tackle Continuous Deployment of the Restcomm Olympus Application that is part of our Open Source WebRTC Restcomm iOS SDK hosted at GitHub. More specifically, we will:

  • Build and export restcomm-olympus.ipa signed and ready for enterprise deployment.
  • Upload of restcomm-olympus.ipa to TestFairy Beta Testing Platform so that it becomes instantly available to our Beta testers and community.

This is easier said than done, so let’s get down to the details.

iOS Signing Intro

To be able to deploy to any physical device without having to update the Provisioning Profile each time we want to test on a new device, we will be using Enterprise Distribution. This allows for an App to be freely distributed outside the Apple App Store. To build an .ipa that is distributable in this fashion the build procedure is broken in two steps:

  1. Archive build step that generates an archive. For that you need to have a Development Identity and Development Provisioning Profile installed in the CI server
  2. Export step that signs the archive and generates the .ipa that is ready for actual distribution. For that you need to have a Distribution Identity and Distribution Profile installed in the CI server

But first..

A word on Automatic Signing

As of Xcode 8 a LOT has changed in the Signing area with the introduction of Automatic Signing that fixes a lot of the issues of the past -just keep in mind that you might have backward compatibility issues if you have in place build scripts that used to work with Xcode 7. For more information I urge to checkout Apple’s latest WWDC session on that topic. Also, for a more technical approach a great read is this, which starts with a general overview of Apple’s code signing mechanics and then describes the changes in Xcode 8.

We will be using Automatic Signing, and I urge you to do the same, so if you haven’t already, now is a good time to upgrade to Xcode 8. For new Xcode 8 projects Automatic Signing is the default and you are asked to choose your Team ID when creating the project and signing identities and provisioning profiles are setup for you automatically. You can find your Team ID in Apple Account Membership page in the Member Center:

Apple Account Membership

If you have an already existing App created with an earlier Xcode version you should navigate to the General Settings of your main target and tick Automatically manage signing:

Xcode General Settings

Again you will be asked to enter the Team ID and everything should be handled for you automatically.

At this point it is important to point out that Apple doesn’t really support building on headless machines where you don’t have access to a UI and Xcode IDE, like the case with Travis CI (sadly this is the response we got from Apple when we filed a Technical Support Incident for some issues we got along the way). The main issues are the following:

  • Automatic resolution to fix issues with Identities and Provisioning Profiles is not available (i.e. is only possible from the Xcode IDE).
  • You are not able to address any security prompts shown in the UI, like giving access at the codesign step.

But fear not! If you make sure that proper Identities and Provisioning Profiles are installed prior to your build, and that you have taken some additional steps in your scripts to authorize codesign to work without those UI prompts, everything will work out just fine. We will take care of both of them in a bit.

Remember that the general idea here is that you first must have a functional build system in your local OS X machine for your project that can archive and export for Enterprise Distribution via XCode IDE. So that you know that everything is setup properly locally. Then we want to replicate that to Travis CI, where even if there is no Xcode IDE to handle anything automatically there won’t be a need to as we will do it ourselves.

Which Identities and Profiles to use in Travis CI?

We said you need to have the correct Identities and Provisioning Profiles installed in Travis CI before you are able to do any builds, but we haven’t discussed how. Let’s figure it out.

Over the course of time you may find yourself with multiple Identities and multiple Provisioning Profiles installed in your system. And it can sometimes be hard to tell which ones to use in the Travis CI machine. Best approach for us was to check which ones Xcode IDE uses for local builds and use those.

A. Development Identity and Provisioning Profile

To figure out Development Identity and Provisioning Profile, things are pretty straight forward. You can go to Xcode 8 target settings and click on the info button in the Signing section:

Signing Information

For the Development Provisioning Profile on top of the screenshot you will see its name, so you need to find the Provisioning Profile with that name. Navigate at ~/Library/MobileDevice/Provisioning Profiles where all Provisioning Profiles managed by Xcode are kept. You need to view their contents with a text editor and find the one whose Name (xml tag <name>) is what you saw in the screenshot above. Once you find that, copy that over to a temp location (not inside your repo) like ~/Desktop/security/ and rename it to development-provisioning-profile.mobileprovision. We’ll need that in a bit.

Hint 1: The Development Provisioning Profile for your App will typically be a non wildcard profile like: com.telestax.olympus

Hint 2: I recently realized that Xcode also allows you to drag from the little document icon with the gear inside it in the screenshot above and drop it whenever you want in your filesystem, which copies the used Development Provisioning Profile so that you can see which it is pretty easily.

Now for the Development Identity you can figure it out from the screenshot above at the Certificate section. There you should see a name together with a code. You should use the Keychain Access OS X application and export the private key part of that identity as a development-key.p12 (by providing a strong password) and the certificate part as development-cert.cer. Move those to your temp location as well.

B. Distribution Identity and Provisioning Profile

We usually know the Distribution Identity and Provisioning Profile because we typically create them manually in the Apple Member Center. So you need to use once again the Keychain Access OS X application and export the private key part of that identity as a distribution-key.p12 (by providing a strong password) and the certificate part as distribution-cert.cer. Move those to your temp location as well. And for the Distribution Provisioning Profile you need to copy it from ~/Library/MobileDevice/Provisioning Profiles to your temp location and rename it to distribution-provisioning-profile.mobileprovision.

If you ‘re having issues figuring out which is the Distribution Identity and Provisioning Profile you should use, please refer to Appendix A.

Now you know which Identities and Provisioning Profiles you need to install at Travis CI to be able to build. But wait…

Security First

Now that we have all our certificates, keys and provisioning profiles stored in a temp dir we need to make sure that their contents are not readable to the general public before we copy them over to our repo and commit them (remember our GitHub repository is open to everyone, same with the Travis CI build output). To that end we need to symmetrically encrypt all of them and place the encrypted files (ending with .enc) inside our repo:

$ openssl aes-256-cbc -in ~/Desktop/security/development-key.p12 -out $REPO/scripts/certs/development-key.p12.enc -a
$ openssl aes-256-cbc -in ~/Desktop/security/development-cert.cer -out $REPO/scripts/certs/development-cert.cer.enc -a
$ openssl aes-256-cbc -in ~/Desktop/security/profile-development-olympus.mobileprovision -out $REPO/scripts/provisioning-profile/profile-development-olympus.mobileprovision.enc -a
$ openssl aes-256-cbc -in ~/Desktop/security/distribution-key.p12 -out $REPO/scripts/certs/distribution-key.p12.enc -a
$ openssl aes-256-cbc -in ~/Desktop/security/distribution-cert.cer -out $REPO/scripts/certs/distribution-cert.cer.enc -a
$ openssl aes-256-cbc -in ~/Desktop/security/profile-distribution-olympus.mobileprovision -out $REPO/scripts/provisioning-profile/profile-distribution-olympus.mobileprovision.enc -a

Once we do that we need to commit them to GitHub and push, so that they are available to Travis CI.

Remember that the unencrypted versions of certificates, keys and provisioning profiles must not ever get committed to the repo

Apple’s CA Certificate

Apart from our Identities and Provisioning Profiles we also need Apple’s certificate installed in Travis CI, so that the trust to our certificates can be verified. To achieve that you need to download it from here and copy it to your repo at scripts/certs together with our certificates as AppleWWDRCA.cer.

Enable Travis

So far we have be working locally to figure out all the configuration and make all the needed resources available to Travis by committing them to our repository. But before we can actually have Travis build for us we need to enable it for our repo. For this you need to signup with Travis CI and then enable it:

As you may have noticed enabling it in the settings above isn’t enough, you also need to include a .travis.yml in the root of your repo. So we create it with the following contents:

osx_image: xcode8.1
language: objective-c
script:
- ./scripts/ci-script.bash

It’s very important to use xcode8.1 as osx_image as this also decides on the OS X image to use (in this case OS X Sierra) and all our scripts have been verified there. It’s very likely that they will break in earlier versions.

As you can imagine all the our script logic will go in scripts/ci-script.bash. We ‘ll talk about that in a bit but for now let’s create empty file scripts/ci-script.bash, make it executable, commit and push it to our repo.

Travis Hidden Environment

We have reached the point where all needed certificates, keys and provisioning profiles are encrypted and committed to the repo and hence available to Travis CI, and also have enabled Travis builds. Before we can use those files from Travis we need to make the passwords available as well so that Travis can decrypt them, but without exposing them to public. The way to do that is to use Travis hidden environment variables. So we head over to our Travis account settings page and add the password we used to encrypt all the files as a hidden environment variable:

Here we are exposing this password as environment variable SECURITY_PASSWORD. You can also see other variables that we will be using later. Notice also that variables that aren’t sensitive can be made cleartext for better logging/troubleshooting in your builds

Time for scripting

Now that we laid all the foundation let’s start putting the pieces together by adding the code snippets we need to our ci-script.bash script to be run at Travis CI.

1. Decrypt

To decrypt all the Identities (i.e. certificates and private keys) and Provisioning Profiles files we need to issue the following (notice that we are using the Travis CI variable for the password):

# Development
openssl aes-256-cbc -k "$SECURITY_PASSWORD" -in scripts/certs/development-cert.cer.enc -d -a -out scripts/certs/development-cert.cer
openssl aes-256-cbc -k "$SECURITY_PASSWORD" -in scripts/certs/development-key.p12.enc -d -a -out scripts/certs/development-key.p12openssl aes-256-cbc -k "$SECURITY_PASSWORD" -in scripts/provisioning-profile/profile-development-olympus.mobileprovision.enc -d -a -out scripts/provisioning-profile/profile-development-olympus.mobileprovision# Distribution
openssl aes-256-cbc -k "$SECURITY_PASSWORD" -in scripts/certs/distribution-cert.cer.enc -d -a -out scripts/certs/distribution-cert.cer
openssl aes-256-cbc -k "$SECURITY_PASSWORD" -in scripts/certs/distribution-key.p12.enc -d -a -out scripts/certs/distribution-key.p12openssl aes-256-cbc -k "$SECURITY_PASSWORD" -in scripts/provisioning-profile/profile-distribution-olympus.mobileprovision.enc -d -a -out scripts/provisioning-profile/profile-distribution-olympus.mobileprovision

2. Create Keychain and import Certificates and Keys

To make Certificates and Keys actually usable by our builds we need our certificates and keys imported to the default keychain. To avoid messing with Travis CI’s default keychain we will be creating a new one and setting it as default:

# Create custom keychain
security create-keychain -p $CUSTOM_KEYCHAIN_PASSWORD ios-build.keychain
# Make the ios-build.keychain default, so xcodebuild will use it
security default-keychain -s ios-build.keychain
# Unlock the keychain
security unlock-keychain -p $CUSTOM_KEYCHAIN_PASSWORD ios-build.keychain
# Set keychain timeout to 1 hour for long builds
# see here
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain

Notice the use of yet another travis environment variable: CUSTOM_KEYCHAIN_PASSWORD

Next, we need to import all the certificates and keys in that new keychain. Notice that the keys require passwords as well, which are the passwords you used when you exported them from the Keychain OS X Application in your local machine. For this example we are using the same password as the one we used for the symmetric encryption of the files to make things simpler. Obviously you should use a separate secure password there:

security import ./scripts/certs/AppleWWDRCA.cer -k ios-build.keychain -A
security import ./scripts/certs/development-cert.cer -k ios-build.keychain -A
security import ./scripts/certs/development-key.p12 -k ios-build.keychain -P $SECURITY_PASSWORD -A
security import ./scripts/certs/distribution-cert.cer -k ios-build.keychain -A
security import ./scripts/certs/distribution-key.p12 -k ios-build.keychain -P $SECURITY_PASSWORD -A
# Fix for OS X Sierra that hungs in the codesign step
security set-key-partition-list -S apple-tool:,apple: -s -k $SECURITY_PASSWORD ios-build.keychain > /dev/null

Hint 1: To allow any application to access the imported key without warning you need the -A flag in the import commands

Hint 2: To avoid getting an issue where the codesign build step hangs forever in OS X Sierra you need to add the last command above: security set-key-partition-list … For more info please check my relevant SO question

3. Install Provisioning Profiles

For your build to actually use the unencrypted Provisioning Profiles you need to place them in a location where they will picked up. So you need to copy them from our repo to that location:

mkdir -p ~/Library/MobileDevice/Provisioning\ Profilescp "./scripts/provisioning-profile/development-provisioning-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/cp "./scripts/provisioning-profile/distribution-provisioning-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/

4. Archive Build

Now that identities and provisioning profiles are all setup it’s time to do the actual archive build. Notice that we’re outputting the archive inside the repo explicitly so that we can pick it up easily in the next step where we will export it:

xcodebuild archive -workspace Examples/restcomm-olympus/restcomm-olympus.xcworkspace -scheme restcomm-olympus -configuration Release -derivedDataPath ./build -archivePath ./build/Products/restcomm-olympus.xcarchive

Once you get everything in order you can pipe this command to ‘xcpretty’ so that you get beautified output for your build

5. Export for Distribution

Before we can export the archive and generate the actual .ipa we need to come up with an export options plist file that contains various configuration options for the export. Let’s create it in our repo at scripts/exportOptions-Enterprise.plist with the following contents:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>compileBitcode</key>
<false/>
<key>method</key>
<string>enterprise</string>
<key>teamID</key>
<string>Your Team ID here</string>
<key>uploadBitcode</key>
<true/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>

Notice that we are disabling bitcode because some of our binary dependencies aren’t built with it and hence the App as a whole won’t build (if you don’t have this issue you should be able to enable it). Also we are using the enterprise method to tell the build system that we want to create an App destined for Enterprise Distribution.

Now that we have the plist ready we can do the actual build:

xcodebuild -exportArchive -archivePath ./build/Products/restcomm-olympus.xcarchive -exportOptionsPlist ./scripts/exportOptions-Enterprise.plist -exportPath ./build/Products/IPA

Hint: If this build command fails with “No applicable devices found” you need to update ruby configuration to use system ruby. To do that please follow the logic outlined in this SO answer and you should get a successful build. You can also check out how we tackled it in the full script I point to in the end of the post -I’m avoiding placing it all here for clarity

You should now have your App .ipa at build/Products/IPA that you can freely distribute to your users! One step to go…

6. Upload to Test Fairy

Finally, you can now upload the .ipa at Test Fairy using curl:

curl -v -s http://app.testfairy.com/api/upload \
-F api_key=$TESTFAIRY_API_KEY \
-F file=@build/Products/IPA/restcomm-olympus.ipa \
-F video=wifi \
-F max-duration=10m \
-F comment="Comment for .ipa shown in TestFairy" \
-F testers-groups= \
-F auto-update=off \
-F notify=off \
-F instrumentation=off \
-A "TestFairy iOS Command Line Uploader 2.1"

Note 1: For the api_key we use yet another hidden Travis CI environment variable: TESTFAIRY_API_KEY

Note 2: You can find a ready-made upload script at Test Fairy GitHub command-line-uploader repo. Again in our full scripts where we point at in the end of the post we are using that scrips ourselves -I’m keeping it out for clarity.

7. Update GitHub repo with finished script

To test the whole thing we need to to commit our changes and push to GitHub. That will trigger a Travis CI build which in turn will build our App and upload it to TestFairy. As an example, here’s successful Travis build log and the resulting .ipa successfully uploaded to TestFairy:

Notice that the version shown in the TestFairy download page contains the Travis build number in the end (i.e. build 118). That way we can correlate issues of this App back to Travis build and subsequently back to the erroneous GitHub commit.

Final Notes

Using similar logic you should be able to add CI/CD facilities to any GitHub iOS repository using Travis CI. For a more full picture you can inspect the facilities yourself by visiting the WebRTC Restcomm iOS SDK repository a following the code starting at .travis.yml. A more complete version of ci-script.bash can be found here that we use that to build and deploy Restcomm Olympus. For more information about latest Restcomm iOS SDK release please check out our announcement.

Last but not least a huge thanks to Mattes Groeger for his post on Travis CI for iOS. This present post tries to identify the challenges that have emerged since Mattes’ post came out 3 years ago and hopefully tackle them successfully ;).

Appendix A: Inferring the Distribution Identity and Provisioning Profile

If you ‘re having issues figuring out which is the Distribution Identity and Provisioning Profile you should use with Travis CI, or you ‘re having build issues at Travis CI saying that the ones you are using are wrong, you can infer them by making a local build and checking out which are used. First build an archive of your App and then make an export of that archive in the terminal that will show you both the Identity and the Provisioning Profile used, that you can then export/copy, encrypt and move to your repo as discussed previously.

Reason I’m not using Xcode IDE for the build/export but the terminal is that I haven’t found a way to get build details for the export step :(. Seems to be buried somewhere.

To make an archive build, please follow the instructions at section 4. Archive Build of this post. After you do that then follow the instructions at section 5. Export for Distribution

At this point we aren’t interested in the actual .ipa that is generated, but in which was the Distribution Identity and Provisioning Profile that was used by the system, so that we know which ones we need to use in Travis CI. To figure all that out you need to notice the output of the export command:

$ xcodebuild -exportArchive -archivePath ./build/Products/restcomm-olympus.xcarchive -exportOptionsPlist ./scripts/exportOptions-Enterprise.plist -exportPath ./build/Products/IPA2016–12–08 13:26:58.971 xcodebuild[26052:5606768] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path ‘/var/folders/hn/3nnkp2mn3b971z549c6klmr40000gn/T/restcomm-olympus_2016–12–08_13–26–58.970.xcdistributionlogs’.Exported restcomm-olympus.xcarchive to: /Users/antonis/Documents/telestax/code/restcomm-ios-sdk/build/Products/IPA** EXPORT SUCCEEDED **

Navigate with the Finder to the created bundle (i.e./var/folders/hn/3nnkp2mn3b971z549c6klmr40000gn/T/restcomm-olympus_2016–12–08_13–26–58.970.xcdistributionlogs) and inside it open file IDECodesignResolver.log. There you should see all the steps xcodebuild took to figure out which Identity and which Provisioning Profile to use for distribution.

For the Identity used for the export you should find a line like this (hashes, etc are fictional):

2016-12-08 11:26:59 +0000 [MT] limiting to signing identities: {(
<DVTSigningCertificate: 0x7ff031ddb7b0; name='iPhone Distribution: Telestax, Inc.', hash='19284627f9A798A9D878F8D9A8789A8D7F90F6A7', serialNumber='8A5D9F54C8A8D6FF', certificateKind='1.2.840.113635.100.6.1.4, issueDate='2015-10-12 09:04:10 +0000''>
)}

Voila, in bold you can see the Certificate name you needed. Now for the Provisioning Profile, in the bottom of IDECodesignResolver.log file you should see a line like this (UUIDs and Team ID are fictional):

2016–12–08 11:26:59 +0000 [MT] selecting top entry in sorted array: (
“<DTDKProvisioningProfile 0x7ff031df76f0: UUID: 53a8d3d0–2092–1829-8121-ddf211b923f1, name: XC iOS: com.telestax.olympus, team: G6RG91NSAE (Telestax, Inc.), platform: iOS>”,
“<DTDKProvisioningProfile 0x7ff036c049b0: UUID: a7d8a870–1038–2829-1947-d1f2113913f1, name: TeleStax Enterprise Distribution Profile, team: G6RG91NSAE (Telestax, Inc.), platform: iOS>”
)

In this case we had two possible Provisioning Profiles valid for distribution, the first is XCode managed and the second was manually created and xcodebuild selects the first one. You need to see which one is selected in your case and use this one.

--

--