iOS Continuous Delivery with Fastlane and Ansible – Part 2

This is the second part of my experience at Photobox Group dealing with Continuous Deployment of iOS apps. In the previous post, we have seen that how we used Fastlane for continuous deployment of iOS app from continuous integration server. One of the most tricky obstacles to Continuous Delivery is the works-on-my-machine phenomenon. Almost every engineer encountered this problem at least once. We also suffered from same issues mentioned in the post Works on My Machine.

 

Challenges

We came across following challenges while maintaining our continuous integration servers.

  • Manually setting up CI server machine with all iOS related software takes days of engineers.
  • The results produced on CI server are not reproducible on local developer machines.
  • Our build configuration steps are tightly coupled with GUI configuration in the TeamCity continuous integration server.
  • The versions of software on developer machine and CI server are different
  • We were losing trust on CI server and end up spending lots of time finding the root cause of the problems.

At this stage, we really needed some configuration management tool which allows us to set up all the machines with the same configuration and should able to provision them from scratch. We need our iOS infrastructure to be driven by code a.k.an Infrastructure as code. We were looking for something like  Docker for Darwin. There were various options like Chef, Puppet etc but we bumped into Ansible because it is simple but powerful tools for infrastructure automation without the need to know any other language like Ruby. In this post, we will see how we provisioned our CI server using Ansible.

Provisioning iOS CI with Ansible

Previously, we used to manually setup CI or continuous integration server with all the required software for the delivery of an iOS app. An engineer has to manually download and install Xcode with additional components, install required homebrew packages, setup RVM and RubyGems. It also involves setting build server as TeamCity Build Agent. This took almost a day or two to get build machine in the working state. This approach was time-consuming as well as it’s very difficult to manage versions of the software installed on the build server. We managed to automate almost everything mentioned above using Ansible.

It’s very important to have an ability to reset, rebuild iOS Continuous Integration Server environment whenever needed. Apple continuously release new or beta versions of Xcode and other developer tools so we should have the ability to quickly setup and use those features. The programmable infrastructure or infrastructure as code is key to Continuous Delivery so that we need to have a script to setup these things up automatically without manual intervention. Ansible allows us to write code to manage configurations and automated provisioning of the infrastructure in addition to deployment. Ansible tasks and playbooks are simple YAML files so there is no need to learn another programming language like Ruby to script an iOS infrastructure. The provisioning of iOS Continuous Integration server involves the following task

  • Provisioning installation of Xcode and Xcode Command Line tools
  • Provisioning homebrew packages required for iOS development
  • Provisioning ruby environment using RVM
  • Provisioning macOS defaults
  • Provisioning TeamCity Agent for TeamCity Server

We have Ansible task for each of above step, Let’s get bit deeper into those tasks

 

Ansible Xcode Task

The most of the Apple software including Xcode is proprietary software. You need Apple developer account to download install that software. It was a major challenge to provide an installation of Xcode. We have decided to download the Xcode .xip  file for specific Xcode version and host it on company hosted SAMBA server. We can mount  Xcode .xip  to any build server machine to install Xcode. We can then provision other Xcode related task like accept the agreement, installing command line tools, installing additional component using Ansible task. The  sample Ansible task looks like this:

We can then pass  xcode_src variable from the playbook file which is Xcode version, we need to install. In this way, we can easily switch to another Xcode version.

Ansible Homebrew Task

As part of provisioning of iOS CI server, we need to install some macOS packages. Homebrew is an awesome package manager and most importantly Ansible has inbuilt homebrew module which allows us to tap any Homebrew formulae or install homebrew packages. Our sample home-brew task looks like this

We can then pass homebrew_taps , homebrew_installed_packages  and homebrew_cask_app  from our playbook. This means we have the ability to manage all the macOS software packages from code.

Ansible RVM Task

Ruby plays very important role in the iOS application development as tools like CocoaPods, Fastlane comes as Ruby libraries. We need a higher version of ruby than pre-installed on macOS which is Ruby 2-0  to benefit from the latest features from those tools. The default macOS Ruby isn’t great to manage Rubygems using bundler. We are using Ruby version management tools RVM  to manage Ruby versions with bundler. As a provisioning task, we need to install Ruby and Bundler. Our sample RVM Ansible task looks like this:

We can then pass zlib_directory  and  rubygems_packages_to_install  from the playbook.

Ansible Task for macOS Defaults

On the build server machine, we need to disable the updates and set the machine in the ‘never sleep’ mode. We can set that from Ansible task and pass the commands from the playbook.

Ansible TeamCity Agent Task

We use TeamCity as our Continuous Integration server. As a final task, we have added the machine as TeamCity build agent to TeamCity Server and start the build agent. It involves following tasks

  • Download the TeamCity Agent .zip  package from TeamCity server
  • Add TeamCity Agent configuration inside buildAgent.properties  file
  • Start TeamCity Agent

We need to have Java installed on the server machine, in order to setup TeamCity build agent. We also need to create Ansible ninja template for buildAgent.properties.j2 file with parameters teamcity_agent_server_url and teamcity_agent_name  which can be replaced by default template file. Finally, start the TeamCity build agent. Our TeamCity Ansible task looks something like this:

Our Example Playbook

By using all the Ansible tasks mentioned above, we have created a private Ansible role which executes all the tasks. If you interested, we can share it with you.  Our example playbook looks like this:

Now that, our playbook is ready to play on any macOS machine. By using this playbook, we managed to get brand new Mac Mini server provisioned with all required software for iOS development and got it attached to TeamCity server within 20-30 min. The most importantly it doesn’t need any manual intervention. Now, we can set up our CI server from scratch in few minutes.

We execute this playbook, on our CI server whenever we want to upgrade to new Xcode version or any other Apple developer tool.  It really helps us resetting and rebuilding CI server setup easily without spending lot of time.

Benefits

The benefits of using Ansible for provisioning iOS CI are

  • Quick Setup of new CI server machine and ease of upgrading/downgrading Xcode version
  • Ability to reset, upgrade version of software whenever required
  • Decoupled all the configuration from TeamCity GUI into the code.
  • Streamline the configuration of local machines and CI servers.

Conclusion

We have achieved continuous deployment of an iOS apps using  Fastlane as build automation tool and  Ansible as Continuous Integration provisioning a.k.a configuration management tool. What are your experiences of iOS continuous deployment and what do you think of our approach to iOS continuous deployment?

Weigh in with a comment below!