@attheodo

Continuous Delivery Practices

What is Continuous Delivery?

Continuous Delivery is the ability to get changes (features, config changes, bug fixes and experiments) into QA/Production, safely and quickly but also in a sustainable way.

Continuous Delivery is more that Continuous Integration

Continuous Delivery = Continuous Integration + Continuous Deployment

Okay, but why?

Avoid the pain of the Monster Merge

Automate. Repeatable processes is a good thing

Do not hog local resources

Enforces good testing discipline

Agile

Caveats

Upfront investment for setup

Mostly free but speed comes at a cost

CI build environment needs to be exactly similar to the local development environment

Let's take a moment to appreciate Fastlane

First things first...

You should already have your build scripts (fastlane)

You need to sort out provisioning/certificates (fastlane match)

Build scripts

platform :ios do

  before_all do
    setup_circle_ci
  end

  lane :build do |values|

      target = values[:target]
      scheme = ""

      if target == "production"
          scheme = "MyApp"
      elsif target == "development"
          scheme = "MyApp Dev"
      elsif target == "staging"
          scheme = "MyApp Staging"
      end

      gym(
        scheme: scheme,
        output_directory: ".build",
        output_name: "MyApp.ipa",
        silent: true,
        clean: true,
      )

  end
lane :testflight_development do

      test

      ensure_git_status_clean
      match(app_identifier: "com.mydomain.myapp", type: "appstore")

      build(target: "development")

      testflight(
        ipa: ".build/MyApp.ipa",
        skip_submission: true,
        skip_waiting_for_build_processing: true,
      )

  end

  lane :test do
      scan(scheme: "MyApp")
  end

Sample Fastfile

Build scripts

git_url "git@github.com:my_username/ios-certificates.git"
app_identifier ["com.mydomain.myapp"]
username "me@mydomain.com"

Sample Matchfile

match(app_identifier: ["com.mydomain.MyApp"])
match(app_identifier: ["com.mydomain.MyApp], type: "appstore")

Create certificates with fastlane

(Circle)CI access to GitHub

The CI machine needs access to the repository that match manages in order to download certificates and keys.

You need to set your Match passphrase to the CI machine's ENV variables

Permissions -> Checkout SSH Keys -> Add user key

Build Settings -> Environment Variables

Configuring the CI

.circleci/config.yml

Other providers have similar config structure

This YAML configuration files basically tells the CI what machine/image to provision and what operations to perform

Configuring the CI

Let's start with the image

Remember: Your CI build environment should 100% reflect

your local development environment (software versions)

Configuring the CI

Configuring the CI

Configuring the CI

Manually installing software

aliases:
    - &bundle-install
        name: "Bundle Install"
        command: bundle install --path vendor/bundle
    - &npm-install
        name: "NPM Install"
        command: npm install
    

Configuring the CI

Manually installing software

Depending on the complexity of your project, manually installing software might be a long operation.

When we migrated to Swift5/Xcode10.2, CircleCI didn't have an XCode 10.2 image!

We had to install Xcode 10.2 every time!

Caching to the rescue...!

CI machines are "burn" machines; When the operation is complete, they spin down.

Configuring the CI

aliases:
    - &restore-gems-cache
        name: Restoring gems cache
        key: gems-cache-v2-{{ checksum "Gemfile.lock" }}
    - &bundle-install
        name: "Bundle Install"
        command: bundle install --path vendor/bundle
    - &save-gems-cache
        name: Saving gems cache
        key: gems-cache-v2-{{ checksum "Gemfile.lock" }}
        paths:
            - vendor/bundle
    - &restore-npm-cache
        name: Restoring npm cache
        key: node-modules-cache-v2-{{ checksum "package.json" }}
    - &npm-install
        name: "NPM Install"
        command: npm install
    - &save-npm-cache
        name: Saving npm cache
        key: node-modules-cache-v2-{{ checksum "package.json" }}
        paths:
            - node_modules

Configuring the CI (Jobs & Workflows)

You can think of jobs as "functions"

They consist of a series of operations, called steps, that get executed together

Each CI provider has some built-in steps, but obviously all of them support scripts execution through a (configurable) shell

Jobs

Workflows

You can think of workflows as "programmes"

The execute a desired set of jobs according certain criteria (mostly branch based)

Configuring the CI (Jobs & Workflows)

jobs:
    run-tests:
        <<: *defaults
        steps:
            - checkout
            - restore-cache: *restore-gems-cache
            - run: *bundle-install
            - save-cache: *save-gems-cache
            - restore-cache: *restore-npm-cache
            - run: *npm-install
            - save-cache: *save-npm-cache
            - run:
                name: Run tests
                command: |
                    cd MyApp
                    bundle exec fastlane test
    testflight-development:
        <<: *defaults
        steps:
            - checkout
            - restore-cache: *restore-gems-cache
            - run: *bundle-install
            - save-cache: *save-gems-cache
            - restore-cache: *restore-npm-cache
            - run: *npm-install
            - save-cache: *save-npm-cache
            - run:
                name: Run Testflight Development Lane
                command: |
                    cd MyApp
                    bundle exec fastlane testflight_development
                no_output_timeout: 30m

Configuring the CI (Interlude)

YAML sucks

CircleCI config syntax sucks

Aliases to the rescue!

defaults: &defaults
    macos:
        xcode: "10.2.0"
    working_directory: ~/my_working_dir
    shell: /bin/bash --login -o pipefail
jobs:
    run-tests:
        <<: *defaults
        steps:
            ...
    testflight-development:
        <<: *defaults
        steps:
            ...

Configuring the CI (Interlude)

aliases:
    - &restore-gems-cache
        name: Restoring gems cache
        key: gems-cache-v2-{{ checksum "Gemfile.lock" }}
    - &bundle-install
        name: "Bundle Install"
        command: bundle install --path vendor/bundle
    - &save-gems-cache
        name: Saving gems cache
        key: gems-cache-v2-{{ checksum "Gemfile.lock" }}
        paths:
            - vendor/bundle
jobs:
    run-tests:
        <<: *defaults
        steps:
            - checkout
            - restore-cache: *restore-gems-cache
            - run: *bundle-install
            - save-cache: *save-gems-cache
            ...

Configuring the CI

Workflows

Batching your jobs together

workflows:
    version: 2
    test:
        jobs:
            - run-tests:
                filters:
                    branches:
                        ignore:
                            - /testflight.*/
                            - master
                            - develop
    tf-dev:
        jobs:
            - testflight-development:
                filters:
                    branches:
                        only:
                            - /testflight-development.*/

Questions?

Continuous Delivery

By Thanos Theodoridis

Continuous Delivery

  • 209