The Ops Community ⚙️

Cover image for Automating mobile application deployments using Fastlane and CI/CD tools
Jeiman Jeya
Jeiman Jeya

Posted on

Automating mobile application deployments using Fastlane and CI/CD tools

The Problem


Engineering teams these days find it troublesome to build, test and deploy their mobile application changes locally without having to maintain the tools required for it. There is a lot of maintenance involved as you need to keep track of what versions of these tools have been installed to avoid compatibility issues when you’re building the application bundles, especially hybrid apps built on React Native.

The Cure


Alt Text

fastlane, an automation tool that aids in handling all of the tedious tasks so you don't have to. It's by far, the easiest way to build and release your mobile apps.

fastlane can easily:

  • Distribute beta builds to your testers
  • Publish a new release to the app store in seconds
  • Reliably code sign your application - alleviates all of your headaches
  • Reliably maintain your provisioning profiles and application certificates in a Git repository

How it works


All actions in fastlane are written into lanes. Defining lanes are easy. Think of them as functions in any programming language of your choosing. You define all of your actions within that lane.

An example of a lane is as follows:

lane :beta do
  increment_build_number
  build_app
  upload_to_testflight
end
Enter fullscreen mode Exit fullscreen mode

So when it comes to executing that lane, all you do is run fastlane beta in your terminal.

Installation & Setup


In this article, we will look at setting up a fastlane script to build, sign and deploy an iOS application to Testflight.

Pre-requisites

As with most projects, you need to perform the initial project setup to support building your application, such as:

  • Installing your project dependencies
  • Install XCode and Android Studio
  • Install Java SDK
  • Setup git repository for Android and iOS certificates - will be explained later in the article

fastlane folder structure

In ideal cases, you would have an Android application project and an iOS application project, both hosted in the same code repository as your project

projectFolder/
    app/
    scripts/
    ios/
        fastlane/
            ....
  android/
        fastlane/
            ....
    package.json
    .gitignore
  ...
Enter fullscreen mode Exit fullscreen mode

In this case, you will want to initialise the fastlane folder within the respective android and ios project sub-directories.

Setup

  1. Install fastlane
    • via Homebrew (brew install fastlane)
    • via RubyGems (sudo gem install fastlane)
  2. Navigate your terminal to your respective android and ios project directory and run fastlane init
  3. You should have a folder structure that is similar to the one below:
fastlane/
    Fastfile
    Appfile
Enter fullscreen mode Exit fullscreen mode

The most interesting file is fastlane/Fastfile, which contains all the information that is needed to distribute your app.

  1. Inside your Fastfile, you can start writing lanes:
lane :my_lane do
  # Whatever actions you like go in here.
end
Enter fullscreen mode Exit fullscreen mode
  1. You can start adding in actions into your lanes. fastlane actions can be found here.

  2. Copy the following file structure to your Fastfile

default_platform(:ios)

def beta(arg1, arg2)
  # You may use Ruby functions to write custom actions for your app deployment
end

platform :ios do

    desc "Building the IPA file only"
      lane :build_ios_app_ipa do
        app_identifier = "com.appbundle.id"
        beta("AppSchemeName", app_identifier)
      end

end
Enter fullscreen mode Exit fullscreen mode
  1. default_platform(:ios) - Initialise your Fastfile file with a default platform

  2. platform :ios do - Add all actions under a platform

From 5 to 6, it will inform fastlane that this particular Fastfile is purely for iOS operations. So instead of running the command fastlane <lane_name>, you will actually run fastlane ios <lane_name>. Therefore, anything parked under platform :ios do will be executed when lanes are invoked in your terminal.

If you are well-versed in Ruby, you may write your own Ruby functions to help you write custom actions that you require for further flexibility, especially when it comes to building several apps with different environments across your organisation.

fastlane will identify them as an action regardless due to the fact that fastlane is written in Ruby.

In this article, we will follow writing the actions using a Ruby function. This is so we can promote action re-usability across other lanes deployments.

Action Steps


Before we start writing our functionality in the lanes, we first list down our action steps:

  1. Setup API Key for App Store Connect (app_store_connect_api_key) - This will allow fastlane to connect to your App Store to perform other actions that requires user authentication
  2. Setup CI (setup_ci) - This will setup a temporary keychain to work on your CI pipeline of your choice
  3. Create and sync provisioning profiles and certificates (match) - This will help us maintain our provisioning profiles and certificates across your teams
  4. Update code signing settings (update_code_signing_settings) - This is to update the code signing identities to match your profile name and app bundle identifier
  5. Increment your app build number (increment_build_number) - This will automate your application build number by retrieving the latest Testflight build number and incrementing the value
  6. Build the app (build_app) - This will build the app for us and generate a binary (IPA) file
  7. Upload your binary to Testflight (upload_to_testflight) - This will automate the process of uploading the binary file to Testflight and informing your testers accordingly

Steps

Step 1: Setup your App Store Connect API key

  1. Visit the following page. It will provide you a step-by-step process in generating an API key
  2. Once you have generated a key, take note of:
    • Issuer ID
    • Key ID
  3. Download the generated API key - A .p8 file
  4. Store it in a secure location in which you can easily access them. Avoid storing them in the same repository as anyone in your organisation will have access to the company's Apple account.
    • In this article, we are storing them in another Git repository with limited read and write scopes to specific engineers within our organisation.

Step 2: Setup the fastlane script

  1. Setup your App Store API Key to generate a hash used for JWT authorization
def setup_api_key()
  sh "if [ -d \"appstoreapi\" ]; then echo 'Folder exist, executing next step'; else git clone #{ENV['APPSTORE_API_GIT_URL']} appstoreapi; fi"
  app_store_connect_api_key(
    key_id: ENV['APPSTORE_KEY_ID'],
    issuer_id: ENV['APPSTORE_ISSUER_ID'],
    key_filepath: Dir.pwd + "/appstoreapi/AuthKey_xxx.p8",
  )
end
Enter fullscreen mode Exit fullscreen mode

In the snippet above, I am cloning a Git repository which contains my App Store Connect API key, followed by utilising the app_store_connect_api_key action from fastlane. It takes in several parameters, however, there are 3 vital parameters:

  • key_id - The Key ID from which you took note when you generated the API key
  • issuer_id - The Issuer ID from which you took note when you generated the API key
  • key_filepath - The file path to your .p8 file

This step will generate a hash that will be used to authenticate the App Store using JWT.

I would highly recommend storing sensitive credentials or URLs and access them from an Environment Variable (.env) file in the same project folder - fastlane/.env

Once you have prepared the file, you can easily access any Environment Variable by simply passing in ENV['ENV_NAME'] into the Fastfile.

2. Define a Ruby function with 2 arguments

def beta(scheme, bundle_id)
end
Enter fullscreen mode Exit fullscreen mode

Within this function, we specify the action steps we mentioned above

def beta(scheme, bundle_id)
    setup_api_key() # Import the setup_api_key function
    setup_ci() # Uses fastlane action to create a temporary keychain access on a CI pipeline
end
Enter fullscreen mode Exit fullscreen mode

3. Utilise the match action from fastlane

match is a fastlane action that allows you to easily sync your certificates and provisioning profiles. It takes a new approach to iOS and macOS code signing, where you share one code signing identity across your engineering team to simplify the setup and prevent code signing issues. The foundation of match was built using the implementation of codesigning.guide concept.

You can store your code signing identities in:

  • Git repository
  • Google Cloud
  • Amazon S3 bucket

In this article, we have chosen to store it a Git repository. However, with the case of storing in a Git repository, you would need to provide a form of basic authorization in order for match to access and clone your repository.

Whichever Git provider you choose (GitHub, Bitbucket, Gitlab or Azure DevOps), you would need to setup a Personal Access Token (PAT), which fastlane will use to clone repository and sync your code signing identities.

In your Fastfile, you will need to encode your PAT with a Base64 encryption

authorization_token_str = ENV['GITHUB_TOKEN']
basic_authorization_token = Base64.strict_encode64(authorization_token_str)
Enter fullscreen mode Exit fullscreen mode

The basic_authorization_token variable will be used in setting up the match implementation below.

match(
  git_url: "<git_url>",
  git_basic_authorization: basic_authorization_token,
  readonly: true,
  type: "appstore",
  app_identifier: [
    bundle_id
])
Enter fullscreen mode Exit fullscreen mode

From the snippet above, this is a very simple implementation. Take note on the readonly parameter. This is crucial in creating and syncing your profiles and certs with your Apple Developer account. If you set readonly to false, you will allow match to automatically sync and create new profiles and certs, should it deem your existing certs and profiles expired or corrupted. Once it has provisioned the profiles, you can set readonly to true. This is to avoid egde cases where it might accidentally create new certs and profiles.

4. Update code signing identities

This step is mainly used to update the Xcode settings on a CI pipeline due to its default behaviour of selecting a default cert and profile from the Mac OS agent.

update_code_signing_settings(
  use_automatic_signing: false,
  path: "../ios/AppName.xcodeproj",
  code_sign_identity: "iPhone Distribution",
  profile_name: "match AppStore com.appbundle.id",
  bundle_identifier: "com.appbundle.id"
)
Enter fullscreen mode Exit fullscreen mode

From the above snippet, you can retrieve your profile_name and bundle_identifier from your Apple Developer account or taking note of them from the match step once it has been executed.

5. Increment Application build number

We simply increment the number by accessing your latest testflight build number and incrementing the value with 1.

increment_build_number({
  build_number: latest_testflight_build_number + 1
})

puts "BUILD_NUMBER: #{lane_context[SharedValues::BUILD_NUMBER]}"
Enter fullscreen mode Exit fullscreen mode

The puts command will print out the Build number in the console, which is shared across the lane context.

You may have other app build versioning models than the one mentioned in this article. Increment the build number however you see fit to your project needs.

6. Build the application

This step will involve building your application to generate a binary (APK) file.

build_app(
  workspace: "../ios/AppName.xcworkspace",
  export_xcargs: "-allowProvisioningUpdates",
  scheme: scheme,
  clean: true,
  silent: true,
  sdk: "iphoneos"
)
puts "IPA: #{lane_context[SharedValues::IPA_OUTPUT_PATH]}"
Enter fullscreen mode Exit fullscreen mode

The build_app action is provided by fastlane.

The puts command will print out the file path location of the IPA file in which you can use to manually upload to Testflight.

7. Upload the binary file to Testflight

This step will involve uploading your binary file to your Testflight account.

app_identifier = "com.appbunde.id"
upload_to_testflight(app_identifier: app_identifier)
Enter fullscreen mode Exit fullscreen mode

upload_to_testflight is an action provided by fastlane.

Putting them all together


The final lane should look something like this:

desc "Build and push a new build to TestFlight"
  lane :release_build do
    app_identifier = "com.appbundle.id"
    beta("AppSchemeName", app_identifier) # The custom function we wrote earlier
    upload_to_testflight(app_identifier: app_identifier)
  end
Enter fullscreen mode Exit fullscreen mode

When you execute fastlane ios release_build in your terminal, it will operate based on the aforementioned steps above.

As you can see, we utilised a Ruby function to group all common operations under the same roof so is to ensure:

  • Code re-usability
  • Consistency
  • Clean code

Integration with CI/CD tools

With the fastlane scripts, you can easily integrate it with any CI/CD tools of your choosing:

  • GitHub Actions
  • Gitlab CI
  • Azure Devops
  • Bitbucket Pipelines

When setting up your pre-requisite steps in your pipeline YAML file, you can simply include a bash script that executes your fastlane script

cd ios/android folder

fastlane ios/android release_build
Enter fullscreen mode Exit fullscreen mode

The command above will execute your fastlane script by following the aforementioned steps above.

Conclusion

fastlane is a powerful tool that helps streamline your build process across your organisation. It can be used along side your existing CI/CD pipelines or as a standalone pipeline script to be used on your local machines or custom pipeline tools such as Buildkite. It would require additional steps such as, cloning the repository and checking out branches, however, all readily available from fastlane as actions.

Spend some time reading through the documentation as it contains a lot of actions out of the box that will prove useful for your engineering teams (match, pilot, cert, sigh, appium, xctool, supply, and many more) . Furthermore, they provide an extensive list of plugins for third-party integrations such as Firebase, App Center, Yarn, Android Versioning, Dropbox, Slack Upload, S3, Bugsnag and many more. You can supercharge your fastlane scripts by coupling it with a CI/CD tool, such as GitHub Actions, Bitbucket Pipelines or Azure DevOps. The sky's the limit!

Top comments (0)