
@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
- 249