alihen.xyz About Archive
Alastair Hendricks - Mobile & Platform Engineer

Trying to switch off

Switching off seems impossible. While I don’t feel like I’m ‘working’ when I’m on holiday, my brain is looking for the next problem to solve or application to optimise. It’s in constant pursuit of a small rush of endorphins, if my body wants it or not.

What’s been interesting about this challenge is that it’s similar to the challenges I’ve faced transitioning from a Software Engineer & Tech/Team Lead to an Engineering Director. My brain used to jump into action when a large engineering problem presents itself - “how can we optimise X to save R100,000, if you give me Y days I can solve with the team". These knee-jerk reactions come from a well-meaning place, but in a role where my job requires providing engineering leadership, building can create more problems than solutions.

It took months to realise the impact of jumping in - it started by building one service that ended up becoming a backbone service of the company with no shared ownership between teams (because you built it one Saturday night before a launch to solve the pressing issue at the time). I ended up with a service that had no logical ownership model, resulting in me having to form a working group to understand what the future of the service would be.

There have been many more of these weekend-projects-turned-core-services in the past couple of months and while I realise that they solved an immediate need, it should have never been my first instinct to jump in.

This brings me back to switching off. I now use my holiday time for all the interesting work and non-work projects that I never get time to explore.

Is it healthy? Probably not.
Do I get some pleasure out of it? Sure, sometimes.
Have I got life/my job/my career figured out? See Above :)

P.S: Over the holiday I’ve been re-reading The Manager’s Path. I can highly recommend it to anyone navigating growth and change in their tech career.

🚀 Fastlane Match & App Store Connect API Integration

I recently migrated a project to fastlane match and I was interested in fastlane support for the App Store Connect API (announced at WWDC18) - I’ve always found email/password auth in fastlane match a bit iffy and even more so with the push to 2FA, which can be notoriously difficult on CI/CD setups.

Turns out fastlane has done some great work to support the App Store Connect API, and it’s now widely supported by the actions.

This post assumes you have some experience with fastlane. If you want to get started with fastlane, you can find out more about it here.

Getting an API Key

In order to get a App Store Connect API key, the Account Holder of the Apple Developer account/team will need to request access on App Store Connect - This seems like mostly a formality.

I’ve outlined the process below, which can also be forwarded to the relevant person if needed.

  1. Log in to App Store Connect
  2. Click the “Users and Access” icon
  3. Click the Keys tab at top of the page.
  4. You see the message “Permission is required to access the App Store Connect API. You can request access on behalf of your organization”, along with a Request Access button.
  5. Click Request Access.
  6. Review the terms and, if acceptable, click the checkbox and then Submit.

Access seems to be granted automatically and you should be able to create a key immediately once the Account Holder has requested access. When creating an API key remember to only provide the minimum access needed - in most cases the Developer role should suffice.

Your API key will be in the form of a private key (saved as a p8 file). In order to prevent running into this issue you’ll need to base64 encode the private key.

cat AuthKey_ABCDEFGH.p8 | base64

For this tutorial, I’ll refer to the base64-encoded private key from an environment variable (named APP_STORE_CONNECT_API_KEY_B64). It’s important to store secrets in a safe, secure place. Most CI/CD solutions provide secret management.

We’ll also save as few other API key details in environment variables:

  • APP_STORE_CONNECT_KEY_ID - The Key ID is available on the API Keys page on the corresponding API key generated.
  • APP_STORE_CONNECT_ISSUER_ID - The Issuer ID is available on the API Keys page.

Setting up Fastlane Match

Fastlane Match allows you to easily sync your certificates and provisioning profiles across your team. You can find out more about it in the fastlane guide.

  1. Run fastlane match init
  2. Configure your Matchfile - storage options, app identifiers etc.
  3. Here’s where things differ - to make sure that your match commands use the API key for authentication, use your Fastfile and create a lane for configuring your app.

In order to make use of our API key, we’ll use a fastlane lane to generate the certificates and provisioning profiles for our app. This could likely also be done via command line arguments, but a fastlane lane provides a better format and allows for comments.

# Fastfile snippet
...

# A helper lane we'll use throughout the post
private_lane :match_configuration do |options|
  api_key = app_store_connect_api_key(
    key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["APP_STORE_CONNECT_API_KEY_B64"],
    is_key_content_base64: true,
    in_house: false
  )

  sync_code_signing(
    type: options[:type],
    app_identifier: ["xyz.alihen.app"], # optional, likely already defined in your Matchfile
    api_key: api_key,
    readonly: options[:readonly],
    verbose: true
  )
end

lane :generate_match do
  match_configuration(
    type: "development",
    readonly: false
  )

  match_configuration(
    type: "appstore",
    readonly: false
  )
end
...

You should then be able run fastlane generate_match to generate certificates and provisioning profiles for development and App Store releases. These certificates and provisioning profiles will be saved to the storage option you chose in your Matchfile.

Setting up your Fastfile

Now that we’ve done the generatiing of the certificates and provisioning profiles, we can setup our fastfile for regular usage. A lot of the private lanes we create will be similar to the generate_match lane above and we’ll re-use the match_configuration private lane.

Lanes for Installing Certificates

When a new engineer joins your team you’ll probably want to provide a frictionless onboarding experience and this applies to your iOS project too. In order to do so, it’s helpful to create some fastlane lanes that allow an engineer to get set up quickly.

A quick example of a lane to install all the certificates:

# Fastfile snippet
...

# A helper lane we'll use throughout the post
private_lane :match_configuration do |options|
  api_key = app_store_connect_api_key(
    key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["APP_STORE_CONNECT_API_KEY_B64"],
    is_key_content_base64: true,
    in_house: false
  )

  sync_code_signing(
    type: options[:type],
    app_identifier: ["xyz.alihen.app"], # optional, likely already defined in your Matchfile
    api_key: api_key,
    readonly: options[:readonly],
    verbose: true
  )
end

lane :install_match_dependencies do
  match_configuration(
    type: "development",
    readonly: true
  )

  match_configuration(
    type: "appstore",
    readonly: true
  )
end
...

Some Notes:

Here we use the readonly: true option in order to not unintentionally update our certificates and provisioning profiles, which may require other developers to install their certificates and provisioning profiles again.

We should also generate a separate API Key for development and our CI environment and regularly rotate the keys in both environments - our evironment variables are helpful in this case.

Updating Certificates & Provisioning Profiles

There are some scenarios where you’ll need to update your certificates or provisioning profiles. You should then be able to use the match_configuration private lane again with the readonly: false option. I usually opt to make this explicit, as it can have consequences for other engineers on the team.

A quick example of a lane to update certificates and provisioning profiles for a particular type:

# Fastfile snippet
...

# A helper lane we'll use throughout the post
private_lane :match_configuration do |options|
  api_key = app_store_connect_api_key(
    key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["APP_STORE_CONNECT_API_KEY_B64"],
    is_key_content_base64: true,
    in_house: false
  )

  sync_code_signing(
    type: options[:type],
    app_identifier: ["xyz.alihen.app"], # optional, likely already defined in your Matchfile
    api_key: api_key,
    readonly: options[:readonly],
    verbose: true
  )
end

lane :update_match_development_dependencies do
  match_configuration(
    type: "development",
    readonly: false
  )
end

lane :update_match_appstore_dependencies do
  match_configuration(
    type: "appstore",
    readonly: false
  )
end
...

If you need to update your certificates in a specific environment, you can then run update_match_development_dependencies or update_match_appstore_dependencies - other engineers might need to run install_match_dependencies after you’ve done this to pull down updated certificates and provisioning profiles.

Using Match for Building

Bringing everything we know about Match & App Store Connect API keys together, we can now use Fastlane to run a typical build that would be run by a CI/CD environment.

  • Installing the certificates & provisoning profiles on the machine.
  • Building a release using gym.
  • Bonus: Uploading the the release to the App Store using the App Store Connect API key.
fastlane_version "2.150.0"
default_platform(:ios)

lane :production do |options|
  match_configuration(
    type: "development",
    readonly: true
  )
  match_configuration(
    type: "appstore",
    readonly: true
  )

  gym(
    clean: false,
    scheme: "ExampleApp",
    export_method: "app-store",
    configuration: "Release"
  )
  upload_app_to_app_store()
end

# A helper lane we'll use throughout the post
private_lane :match_configuration do |options|
  api_key = app_store_connect_api_key(
    key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["APP_STORE_CONNECT_API_KEY_B64"],
    is_key_content_base64: true,
    in_house: false
  )

  sync_code_signing(
    type: options[:type],
    app_identifier: ["xyz.alihen.app"], # optional, likely already defined in your Matchfile
    api_key: api_key,
    readonly: options[:readonly],
    verbose: true
  )
end

# Upload to App Store using API Key
private_lane :upload_app_to_app_store do |options|
  # A small teak to improve upload speeds due to issues we encountered.
  ENV["DELIVER_ITMSTRANSPORTER_ADDITIONAL_UPLOAD_PARAMETERS"] = "-t Signiant"

  api_key = app_store_connect_api_key(
    key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["APP_STORE_CONNECT_API_KEY_B64"],
    is_key_content_base64: true,
    in_house: false
  )

  deliver(
    app_identifier: "xyz.alihen.app",
    api_key: api_key,
    skip_screenshots: true,
    skip_metadata: true,
    run_precheck_before_submit: false,
  )
end



Wrapping up 🚀

This post should have provided you a (not so quick) summary of using fastlane with the App Store Connect API. Fastlane also has some great documentation on the subject:

Let me know if you run into any issues following this post on Twitter and share this post if you find it helpful.