.circleci/config.yml を分割しておく
circleci-cli では workflows を解釈しないため、ローカルで circleci config.yml のテストをしようとすると手間がある。そこで build ごとに yml を分割しテスト、それを config.yml に反映したい。
複数の yml を連結してくれるような仕組みは circleci にはない (と思う) 。かといって手連結はめんどくさいので、とりいそぎ pre-commit で連結して config.yml を生成するようにするスクリプトを用意する。
/.circleci
- _config.yml
- apply.yml
- validate.yml
- pre_commit.rb
という構成で
_config.yml
workflows を設定しておく。
version: 2
jobs:
workflows:
version: 2
test:
jobs:
- validate:
filters:
branches:
ignore: master
- apply:
filters:
branches:
only: master
各 build を分割する
workflows にある #{build_name}.yml
という名前で yml を作成する。今回は apply
と validate
が必要なので apply.yml
と validate.yml
になる。config.yml として valid にしておかないと circleci-cli でテストできないのできっちり書く。
apply.yml
version: 2
jobs:
build:
parallelism: 1
docker:
- image: ruby
steps:
- checkout
- run:
name: work
command: apply
validate.yml
version: 2
jobs:
build:
parallelism: 1
docker:
- image: ruby
steps:
- checkout
- run:
name: work
command: validate
pre-commit
.git/hooks/pre-commit
#!/usr/bin/env sh
bundle exec ruby "$(git rev-parse --show-toplevel)/pre_commit.rb"
pre_commit.rb
.circleci
配下にある yml を連結する。
require 'yaml'
class Combine
CONFIG_PATH = '.circleci/config.yml'
CONFIG_BASE_PATH = '.circleci/_config.yml'
CONFIG_CHILDREN_PATH = '.circleci/**/*.yml'
def execute!
check!
prepare!
File.write(
CONFIG_PATH,
base_configuration.merge('jobs' => combined_configuration).to_yaml
)
commit!
rescue ConfigurationHasChange => e
puts "\e[31m#{e.message}\e[0m"
exit(1)
end
private
def check!
return unless File.exist?(CONFIG_PATH)
raise ConfigurationHasChange if config_yml_has_change?
end
def config_yml_has_change?
return false unless `git status`.match?(/modified:.+#{CONFIG_PATH}/)
`git diff HEAD^ -- #{CONFIG_PATH}` != ''
end
def prepare!
File.delete(CONFIG_PATH)
rescue => e
puts "#{e} (But ignore this error.)"
end
def base_configuration
YAML.load_file(CONFIG_BASE_PATH)
end
def configurations
Dir.glob(CONFIG_CHILDREN_PATH)
end
def pick_name(f)
File.basename(f).split('.').shift
end
def combined_configuration
configurations.inject({}) do |a, f|
name = pick_name(f)
if name == '_config'
a
else
a.merge(name => YAML.load_file(f)['jobs']['build'])
end
end
end
def commit!
`git add #{CONFIG_PATH}`
end
class ConfigurationHasChange < RuntimeError
def message
<<-EOS
"#{CONFIG_PATH}" has change.
DO NOT update "#{CONFIG_PATH}" manually.
Any configuration must be split out to "\#{BUILD_NAME}.yml".
EOS
end
end
end
Combine.new.execute! if $0 == __FILE__
結果
連結された config.yml が作成され、 git add
される。変更が入っていれば commit に含まれるはずだ。
---
version: 2
jobs:
validate:
parallelism: 1
docker:
- image: ruby
steps:
- checkout
- run:
name: work
command: validate
apply:
parallelism: 1
docker:
- image: ruby
steps:
- checkout
- run:
name: work
command: apply
workflows:
version: 2
test:
jobs:
- validate:
filters:
branches:
ignore: master
- apply:
filters:
branches:
only: master