Skip to content

Instantly share code, notes, and snippets.

@yyuu
Last active December 7, 2019 13:10
Show Gist options
  • Save yyuu/995a11644bf4ec7d61f6652a5c9dfebe to your computer and use it in GitHub Desktop.
Save yyuu/995a11644bf4ec7d61f6652a5c9dfebe to your computer and use it in GitHub Desktop.
A simple script to convert Circle CI configurations from circle.yml (1.0) to .circleci/config.yml (2.0)
#!/usr/bin/env ruby
require "fileutils"
require "shellwords"
require "yaml"
CIRCLE1_DEFAULT_CACHE_DIRECTORIES = [
"vendor/bundle",
"~/.m2",
"~/.bundle",
"~/.go_workspace",
]
circle1 = YAML.load(ARGF.read)
circle2 = {
"version" => 2,
}
circle2["jobs"] = {"build" => {}}
if circle1["machine"]
circle2["jobs"]["build"]["docker"] = %w(ruby).flat_map { |language|
Array(circle1["machine"][language]["version"]).map { |version|
{"image" => "circleci/#{language}:#{version}", "environment" => Hash(circle1["machine"]["environment"])}
}
}
end
def generate_cache_key(prefix, dir)
# Tip: Caches are immutable so it is useful to start all your cache keys with a version prefix, for example v1-.... This enables you to regenerate all of your caches by incrementing the version in this prefix.
# https://circleci.com/docs/2.0/caching/
if File.file?(File.join(dir, "Gemfile.lock"))
%|#{prefix}{{ checksum "Gemfile.lock" }}|
elsif File.file?(File.join(dir, "pom.xml"))
%|#{prefix}{{ checksum "pom.xml" }}|
elsif File.file?(File.join(dir, "build.gradle"))
%|#{prefix}{{ checksum "build.gradle" }}|
elsif File.file?(File.join(dir, "build.sbt"))
%|#{prefix}{{ checksum "build.sbt" }}|
else
%|#{prefix}{{ .Branch }}|
end
end
def convert_runnable_params(params)
Hash[params.map { |key, val|
case key
when "timeout"
["no_output_timeout", val]
else
[key, val]
end
}]
end
def convert_runnable(runnable)
# command may have some extra parameters (e.g. timeout)
if Hash === runnable
runnable.map { |key, val|
if Hash === val
{"run" => convert_runnable_params(val).merge("command" => key.strip)}
else
{"run" => {"command" => key.strip}}
end
}
else
[{"run" => {"command" => runnable}}]
end
end
# FIXME: need sophisticated way to detect the use of awscli
needs_awscli = /aws\s+/ =~ YAML.dump(circle1)
needs_docker = Array(Hash(circle1["machine"])["services"]).index("docker")
circle2["jobs"]["build"]["steps"] = []
if needs_awscli
circle2["jobs"]["build"]["steps"] << {
"run" => {"command" => [
%|set -ex -o pipefail|,
%|sudo DEBIAN_FRONTEND="noninteractive" apt-get update|,
%|sudo DEBIAN_FRONTEND="noninteractive" apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -q -y install python-pip python-yaml|,
%|sudo pip install awscli==1.11.151|,
].join("\n")},
}
end
if needs_docker
circle2["jobs"]["build"]["steps"] << "setup_remote_docker"
end
circle2["jobs"]["build"]["steps"] += %w(checkout dependencies database compile test).flat_map { |circle1_step|
steps = []
original_steps = Hash(circle1[circle1_step])
case circle1_step
when "dependencies"
steps << {
"restore_cache" => {
"key" => generate_cache_key("v1-dependency-cache-", ".")
}
}
end
original_steps_pre = Array(original_steps["pre"])
steps += original_steps_pre.flat_map { |runnable|
convert_runnable(runnable)
}
original_steps_override = Array(original_steps["override"])
if original_steps_override.empty?
# simulate circle1's default behavior
case circle1_step
when "checkout"
steps << "checkout"
when "dependencies"
if File.file?("Gemfile")
steps << {"run" => {"command" => "bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3"}}
elsif File.file?("pom.xml")
steps << {"run" => {"command" => "mvn dependency:resolve"}}
elsif 0 < Dir.glob("*.gradle").length
steps << {"run" => {"command" => "gradle dependencies"}}
elsif 0 < Dir.glob("*.sbt").length
# nop
else
steps << {"run" => {"command" => "echo FIXME: please add some command for dependency resolution"}}
end
when "compile"
if File.file?("Gemfile")
# nop
elsif File.file?("pom.xml")
steps << {"run" => {"command" => "mvn test:compile"}}
elsif 0 < Dir.glob("*.gradle").length
steps << {"run" => {"command" => "gradle compile"}}
elsif 0 < Dir.glob("*.sbt").length
steps << {"run" => {"command" => "sbt test:compile"}}
else
steps << {"run" => {"command" => "echo FIXME: please add some command for compilation"}}
end
when "test"
if File.file?("Gemfile")
steps << {"run" => {"command" => "bundle exec rake test"}}
elsif File.file?("pom.xml")
steps << {"run" => {"command" => "mvn integration-test"}}
elsif 0 < Dir.glob("*.gradle").length
steps << {"run" => {"command" => "gradle test"}}
elsif 0 < Dir.glob("*.sbt").length
steps << {"run" => {"command" => "sbt test:test"}}
else
steps << {"run" => {"command" => "echo FIXME: please add some command for testing"}}
end
end
else
steps += original_steps_override.flat_map { |runnable|
convert_runnable(runnable)
}
end
case circle1_step
when "dependencies"
if circle1[circle1_step].key?("cache_directories")
steps << {
"save_cache" => {
"key" => generate_cache_key("v1-dependency-cache-", "."),
"paths" => CIRCLE1_DEFAULT_CACHE_DIRECTORIES + Array(circle1[circle1_step]["cache_directories"]),
}
}
else
steps << {
"save_cache" => {
"key" => generate_cache_key("v1-dependency-cache-", "."),
"paths" => CIRCLE1_DEFAULT_CACHE_DIRECTORIES,
}
}
end
end
original_steps_post = Array(original_steps["post"])
steps += original_steps_post.flat_map { |runnable|
convert_runnable(runnable)
}
steps
}
if circle1.key?("deployment")
default_deployments = Array(circle1["deployment"]).select { |name, params|
Array(params["branch"]).index("/.*/")
}
deploy = []
deploy << %|if false; then|
deploy << %| true|
deploy += (Array(circle1["deployment"]) - default_deployments).flat_map { |name, params|
Array(params["branch"]).flat_map { |branch|
case branch
when %r|\A/(.*)/\z|
[%|elif [[ "${CIRCLE_BRANCH}" =~ #{$1} ]]; then|]
else
[%|elif [[ "${CIRCLE_BRANCH}" == #{Shellwords.shellescape(branch)} ]]; then|]
end + Array(params["commands"]).map { |command| command.strip }
}
}
if default_deployments.empty?
deploy << %|else|
deploy << %| echo "Not target branch so not deploying"|
else
default_deployment_name, default_deployment_params = default_deployments.first
deploy << %|else|
deploy += Array(default_deployment_params["commands"]).map { |command| command.strip }
end
deploy << %|fi|
circle2["jobs"]["build"]["steps"] << {
"deploy" => {
"name" => Array(Array(circle1["deployment"]).first).first || "deploy",
"command" => deploy.join("\n"),
}
}
end
STDOUT.puts(YAML.dump(circle2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment