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
Avoid the pain of the Monster Merge
Automate. Repeatable processes is a good thing
Do not hog local resources
Enforces good testing discipline
Agile
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
You should already have your build scripts (fastlane)
You need to sort out provisioning/certificates (fastlane match)
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
git_url "git@github.com:my_username/ios-certificates.git"
app_identifier ["com.mydomain.myapp"]
username "me@mydomain.com"
match(app_identifier: ["com.mydomain.MyApp"])
match(app_identifier: ["com.mydomain.MyApp], type: "appstore")
.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
Let's start with the image
Remember: Your CI build environment should 100% reflect
your local development environment (software versions)
Manually installing software
aliases:
- &bundle-install
name: "Bundle Install"
command: bundle install --path vendor/bundle
- &npm-install
name: "NPM Install"
command: npm install
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.
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
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)
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
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:
...
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
...
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.*/