Automating away the pains in development
Automation, Noun: The use or introduction of automatic equipment in a manufacturing or other process or facility
As a developer the thing I like most is writing code, building new things, making awesome features and creating awesome experience for our users. For example: our AR experience which you can read about here.
One of the things that is less liked by me is our iOS release process. The default iOS release process consists of multiple manual steps that take quite some time, during which it’s not always possible to use your machine for other things. So, how can we fix this?
Let us first dive into our problem to better understand the solution later on. The next image is an example of a standard iOS Release process:
- You develop, package and sign you App in Xcode
- After that upload it to iTunes Connect (now Appstore Connect) and add all your metadata
- Then from ITC publish it to the App Store.
All of these steps are manual and take quite some time, depending of course on the size of your app.
Our first step in improving this is making use of a Ruby Gem called Fastlane created by Felix Krause in 2014. Fastlane provides tools to automate various bits and pieces around your App release/distribution pipeline. So we were able to extract some manual work into this. For example: signing and uploading to ITC, the setting of metadata like release notes and screenshots were automated.
Fastlane however, still ran on a local machine. Meaning we had to pretty much dedicate our machines for the duration of this process, of course depending on the hardware in your machine. So, we ended up with a new flow looking like this:
So, next up we moved from our own machine to Jenkins, an open source automation service that would run our Fastlane “lanes” for us. This once again was a great improvement, automating away part of our work. But this came with some downsides too.
Our Jenkins machine was only accessible through the local network or remote control software like Team Viewer. Meaning that if we worked remote, or something had to happen in the evenings, we had no decent way of running our jobs.
Our final solution
What we ended up doing might, at a first glance, look like a big over-complication of the process. However trust me when I say, it’s way less manual work for us devs.
The solution looks something like this:
Let’s go in some detail!
Step one: Slack
Slack is a collaboration hub for work. We use it at Wehkamp for most of our internal tech communications. One of the cool things about Slack is that next to having apps for all platforms, it also has an API. Allowing you to create programs to interface with Slack and automate certain things.
Automated Slack users, also known as Bots, can do lots of things. From simple things like automated responses to certain messages to more complex things like automating release processes. Which brings us to the next step!
Step two: Vapor
Vapor is a serverside framework in Swift, which I used to create our Slack bot in. Our bot contains of a few simple things: a web socket connection to Slack (a web socket is a way of connecting a server and client so data can be transmitted from client to server, but also from server back to client), a message parser that reads incoming messages and checks what to do with them, and a connector part connecting allowing it to connect to other services over HTTP to, in this case, start a Travis job. But, I hear you thinking, what is Travis?!
Step three: Travis
Travis is a cloud based CI platform. It’s comparable to Jenkins, except for a few key differences. Like I just mentioned, it’s cloud based. Meaning it’s not running on a local machine. This also allows you to connect with it from anywhere through Travis’ web interface. This eliminated the reachability issue we had with our local Jenkins machine. Next to that, it has a really simple setup that is contained within your git repo in a
.travis.yml file, declaring some simple steps. See the example below.
What Travis reads from this file is the following:
- Only run the master branch
- The language and OS for this project are Swift and MacOS, using Xcode 10.1
- Before we install anything, update the Cocoapods repo
- As our main script, compile the app using xcodebuild and xcpretty (xcpretty reduces output to only important things and avoids reaching storage limits)
- For notifications (job success/failure) use Slack
This file is at the root of your repo and can contain a lot more config as outlined here. For more info, see the Travis Docs.
In our case, we use Travis to run our Fastlane jobs in a cloud based environment.
Step four: Fastlane
In our deployment pipeline, Fastlane is the step doing the actual work. It communicates with Appstore Connect, compiles the app, signs it, uploads our metadata, and when the time is there, pushes the app to the App Store.
Fastlane works with a concept they call lanes. A lane can be thought of as a dedicated script for a single action. Our travis file contains 3 important lanes.
- A beta lane, which submits new builds to Appstore Connect and adds them to TestFlight as a beta build
- A submission lane submitting an app for App Store review
- A release lane that actually pushes a version live when the time is there.
Here are some examples of the lanes I described above. For more info, check the Fastlane docs.
So that’s basically it! From the thedious manual process I described at the start, we went to having to send 2 commands in slack for every release:
!submit <version> <build> and
!release <version> which takes care of everything for us!
So, our release process is pretty much entirely automated away! Cool!
Of course, this doesn’t mean this is the end of our automation journey. We’ve already been looking into connecting our bot to Jira and GitHub to, for example, close a Jira issue when a Pull Request is closed and assigning some default things like the version it’ll be released in. The possibilities are endless!
Thanks for reading! If you have any questions or remarks, please leave a comment! Also, if we get enough interest, we might partially opensource the bot I talked about in this article, including the lower level connecting to the Slack API!