Shipping Better Software Faster: How We Saved Half a Million Dollars

Azadeh Bagheri
Loblaw Digital
Published in
7 min readMar 19, 2021
a light blue background made to look like water with a white boat moving across it. it’s an aerial view of this illustration. beneath the blue layer of water is light blue code

As a Mobile Release Engineer at Loblaw Digital (LD), I work with multiple Mobile Development teams and would like to start by sharing some feedback I have repeatedly received from our mobile developers:

“What your team is doing is very cool. At my previous company, we didn’t have any of this.”

What is it that we are doing differently at LD you ask? In the context of mobile development, we have dedicated a team of release engineers to support our mobile developers: The Productivity and Release Engineering for Mobile (PREM) team.

As an iOS Release Engineer on the team, I have taken part in many interesting initiatives to automate the manual processes that developers deal with at different stages of their software development life cycle. The one I’ll be focusing on here is a tool we developed to allow the creation of CI/CD pipelines for our iOS projects. After a brief introduction to CI/CD pipelines, I’ll walk you through our CI/CD journey and the challenges we faced along the way.

CI/CD pipeline

Continuous integration and continuous delivery are practices adopted in the process of software development to increase code quality, and to enable fast development cycles and frequent software releases.

Simply put, CI/CD pipelines are automated steps taken to build, test and deploy software upon code changes. In reality, however, these pipelines can get very involved. You can define rules for the type of tests to run or deployment platforms to deploy to, all based on changes on different branches or even files.

Loblaw Digital’s iOS CI/CD Journey

The design of CI/CD pipelines at LD has undergone major changes over time. We were first using the Atlassian’s enterprise repository hosting service called Stash, and their CI/CD tool called Bamboo. While Bamboo had a great integration with Stash, it was a separate tool that needed to be maintained and configured. Nevertheless, we used Stash and Bamboo for a few years, and then following our practice of continuous process improvement, we fully migrated our repositories to Gitlab. One of the great features of Gitlab is its powerful built-in CI/CD tool called Gitlab CI. It is a user-friendly software run through a file called .gitlab-ci.yml placed at the repository’s root. Before diving deeply into the structure of our iOS CI/CD pipelines, let’s take a look at different components involved in running a pipeline using Gitlab CI as this will help you understand the reason behind our pipeline design.

In Gitlab CI, pipeline configuration comes in the form of jobs listed in the .gitlab-ci.yml file. These jobs are then executed by another component of Gitlab called the Gitlab Runner. When a pipeline runs, the Gitlab Runner picks up a job from the .yml file and runs it using the execution method it is set to use, and then it will send the results back to Gitlab.

The execution method used by the Gitlab Runner to run the jobs depends on the executor it’s configured with. There are a number of executors that a runner can use. One of the popular ones, which is also widely used at LD, is Docker. Using the Docker executor, the runner will run the jobs inside Docker containers, which consequently provide clean build environments with easy dependency management. All dependencies needed to build the project can be put in a user-provided Docker image defined at the top of the .gitlab-ci.ymlfile.

Docker executor is definitely a great option, however, it wouldn’t work for iOS projects. Without getting too technical, this is because Docker relies on the Linux kernel and can only run on Linux, whereas to build an iOS app you need the macOS kernel. To overcome this hiccup here are some options:

  1. Forget about Docker and install Gitlab Runner on macOS and take it from there. However, at an enterprise-scale, where there are multiple projects running multiple pipelines at a time, doing everything in-house won’t be the first choice due to the high cost of setup and maintenance.
  2. Use services that offer infrastructure needed to build CI/CD pipelines for iOS apps. In other words, there are companies that provide you with physical Macs or Mac VMs to build, test and deploy your iOS apps.

As you may have guessed by now, at LD we’ve taken the second approach and our company of choice is Bitrise. Bitrise is a great option to start with as they offer a wide range of affordable mobile automation services. More importantly, they do the heavy lifting and their web interface provides a great user experience. In spite of all that, there is no native integration between Gitlab CI and Bitrise. This was, however, not a deal-breaker for the PREM team, as we decided to build our own tool to fill the gap.

Connecting Gitlab CI to Bitrise

Fortunately, Bitrise offers a great collection of public APIs which we were able to use to build a command line tool that makes a bridge between Gitlab CI and Bitrise. Since this tool was going to be extensively used in our pipelines, we wanted to make it as lightweight as possible and without requiring a lot of dependencies. Out of all the tools at our disposal, Bash met the criteria. We developed a Bash tool called Gitrise that runs on our Gitlab Runners. Thanks to Bash, Gitrise is very portable and can run on any Unix-like operating system as long Bash, jq, and curl are installed. The example below shows how we are using Gitrise in our pipelines:

a screenshot of a black screen: Run_tests: stage: test script: — ./gitrise.sh -w Btirise_workflow_name -b $CI_COMMIT_REF_NAME -a $BITRISE_ACCESS_TOKEN -s $BITRISE_PROJ_SLUG -e ENABLE_CODE_COVERAGE: true,API_ENVIRONMENT:$ENVIRONMENT

As you can see above, project-specific parameters are passed to Gitrise as command line arguments:

  1. Bitrise Workflow name passed with -w flag
  2. Gitlab branch name with -b flag
  3. Project slug on Bitrise with -s flag
  4. Bitrise user access token to access the Bitrise API with -a flag

In order to securely pass these arguments to Gitrise, we have taken advantage of Gitlab custom CI/CD environment variables as well as their pre-defined CI/CD variables.

Besides connecting Gitlab CI to Bitrise, Gitrise offers some invaluable features including:

  • The ability to use the full power of Gitlab CI and the Docker executor for iOS projects.
  • Passing dynamic environment variables to the build servers on Bitrise (using the -e flag).
  • Smooth user experience, as users don’t have to leave Gitlab at all to see the build logs.

One of our biggest considerations when designing Gitrise was to provide a seamless user experience to our internal clients, the iOS developers. We wanted to allow our developers to start a pipeline and see the results all in one place, Gitlab, and Gitrise brings that about. With Gitrise, the pipeline workflow of an iOS project is as follows:

  1. Gitlab CI starts the pipeline and runs Gitrise.
  2. Gitrise triggers a build on Bitrise and monitors the build.
  3. Once the build is done, Gitrise fetches the build status and logs and appends the logs to the Gitlab pipeline logs.
  4. Gitrise uses the build status to determine its execution status; Any unsuccessful build status will cause Gitrise to fail. Gitlab CI will then pass or fail a job using the execution status of the Gitrise.
  5. Build logs will be available under the Gitlab pipeline logs.

In the image below you can see what build logs look like under the Gitlab pipeline logs when using Gitrise:

A Gitlab screen with BITRISE in large all-caps letters on the black screen. Below BITRISE it reads version 1.38.0 INFO[04:41:34] bitwise runs in Secret Filtering mode INFO[04:41:34] bitwise runs in CI mode INFO[04:41:34] Running workflow: build_app Switching to workflow: build_app + — — — — — — — — — — — — — — — — — + | (0) activate-ssh-key@3.1.1. | + — — — — — — — — — — — — — — — — — + | id: activate-ssh-key. | | version: 3.1.1 | | collection: https://github.com/bitrise-io/bitrise-stepl

One of our core objectives at PREM is increasing developer productivity and Gitrise proves that very well. With Gitrise being extensively used in all the pipelines (build, test, and deployment), we have managed to significantly improve developer productivity across our iOS business. It is important to note that, without Gitrise, we would still be able to trigger builds on Bitrise using the Bitrise webhook for Gitlab. However, we would have then lost all the great benefits of using Gitlab CI. More importantly, for every single pipeline, our developers would have needed to log in to Bitrise and navigate to their corresponding project and job to check the results, and this would have easily cost them at least 10 minutes of their development time per job. This number adds up very rapidly considering the high volume of pipelines running on our projects every year.

In fact, looking at the total number of pipelines run across our iOS business last year, we have estimated to have saved more than half a million dollars worth of development hours by using Gitrise.

But what’s priceless is the increased opportunity developers get to deliver better digital experiences to our customers.

For PREM, Gitrise has been a great success and an invaluable opportunity to benefit a broader community of iOS developers. We are big on giving back to the open-source community and have made Gitrise available on Github. To use Gitrise in your pipelines, you can simply store it in your Gitlab repo, or embed it in your pipeline Docker image. Give it a try and let us know what you think!

--

--