Skip to content

Instantly share code, notes, and snippets.

@terry90
Last active November 24, 2021 10:46
Show Gist options
  • Save terry90/e2c27fb57514cc7d04dc59d60d61d948 to your computer and use it in GitHub Desktop.
Save terry90/e2c27fb57514cc7d04dc59d60d61d948 to your computer and use it in GitHub Desktop.
Docker build/push script with semantic versioning
#!/usr/bin/env ruby
# coding: utf-8
# Colors utils
GREY = "\e[38;5;247m".freeze
GREEN = "\e[0;92m".freeze
YELLOW = "\e[0;93m".freeze
CYAN = "\e[0;96m".freeze
RED = "\e[0;91m".freeze
NC = "\e[0m".freeze # No Color
def green(str)
"#{GREEN}#{str}#{NC}"
end
def red(str)
"#{RED}#{str}#{NC}"
end
def cyan(str)
"#{CYAN}#{str}#{NC}"
end
def grey(str)
"#{GREY}#{str}#{NC}"
end
def yellow(str)
"#{YELLOW}#{str}#{NC}"
end
# String utils
def unaccent(str)
return str.tr(
"ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
"AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
)
end
# Script begins
VERSION_REGEX = /(?<version>[0-9.]+(-(?<letter>[a-z])[a-z]*)?)/.freeze
trap("INT") do
puts "\r \n#{yellow("Build cancelled")}"
exit
end
def fail_because(reason)
puts reason
exit
end
def locate_packages_json
Dir.glob("**/package.json").reject { |f| f.include?("node_modules") }
end
def update_packages_json_version(version)
files = locate_packages_json
for file in files
puts "Updating #{cyan file} to #{green "v#{version}"}"
outdata = File.read(file).gsub(/"version": "[0-9.]+",/, "\"version\": \"#{version}\",")
File.open(file, "w") do |out|
out << outdata
end
end
end
def get_random_word(first_letter)
dico = `curl -s https://gist.githubusercontent.com/terry90/485a404135651fe5b5da275fec90cf30/raw/cbbe09df668965204837827f9debe130ab12b347/dicobuild`
dico = dico.split("\n").group_by { |word| word[0] }
words = (1..10).map { unaccent dico[first_letter].sample }
puts words.map.with_index { |w, i| "#{i + 1}: #{w}" }
puts "Choose a version codename"
nb = $stdin.gets.chomp
until (1..10).include?(nb.to_i)
puts "Choose a number between 1 and 5"
nb = $stdin.gets.chomp
end
words[nb.to_i - 1]
end
def write_metadata(name, version)
File.write(".dockerbuild", "name=#{name}\nversion=#{version}\n")
end
def get_current_metadata
begin
dbuild = File.read(".dockerbuild")
matches = /name=(?<name>[^\n]+)\nversion=(v)?(?<version>[^\n]+)/.match(dbuild)
name = matches[:name]
puts "\nApp: #{name}"
vmatches = VERSION_REGEX.match(matches[:version])
puts "Current version: #{cyan("v#{matches[:version]}")}"
version = vmatches[:version]
letter = vmatches[:letter]
rescue Errno::ENOENT
puts "No .dockerbuild found, creating one..."
puts "Please enter image name (e.g eu.gcr.io/project/app) without the version"
name = $stdin.gets.chomp
puts "Please enter the current version of the app (e.g v0.1.0)"
version = $stdin.gets.chomp
begin
version = VERSION_REGEX.match(version)[:version]
rescue StandardError
puts "Bad version"
exit
end
letter = nil
write_metadata(name, version)
end
{ name: name, version: version, letter: letter }
end
def ask_type_of_update
loop do
puts "Please choose update type:"
puts cyan "1: Major"
puts green "2: Minor"
puts yellow "3: Patch"
puts red "4: Test"
puts grey "5: Keep current"
input = $stdin.gets.to_i
if [1, 2, 3, 4, 5].include? input
return { 1 => :major, 2 => :minor, 3 => :patch, 4 => :test, 5 => :nothing }[input]
end
end
end
def ask_packages_json(version)
puts
puts "Update packages json ? [Y/n]"
for file in locate_packages_json
puts grey(file)
end
update_packages_json_version(version.split("-")[0]) if ["y", "Y"].include?($stdin.gets.strip)
end
def ask_what_build
default = "Dockerfile"
builds = Dir.entries("./").reject { |e| !e.include? "Dockerfile" }.map { |e| e.split(".")[1] }
return [default] if builds == [nil]
loop do
puts
puts "Please choose build:"
puts "1: Both"
builds.each_with_index do |e, i|
puts "#{i + 2}: #{e ? e.capitalize : "Default"}"
end
input = $stdin.gets.to_i
if (1..(builds.length + 1)).to_a.include? input
if input == 1
return builds.map { |e| e ? "Dockerfile.#{e}" : "Dockerfile" }
end
b = builds[input - 2]
return !b ? [default] : ["Dockerfile.#{b}"]
end
end
end
def major(vs)
[vs[0].to_i + 1, 0, 0].join(".")
end
def minor(vs)
[vs[0], vs[1].to_i + 1, 0].join(".")
end
def patch(vs)
[vs[0], vs[1], vs[2].to_i + 1].join(".")
end
def testv(vs, word)
[[vs[0], vs[1], vs[2].to_i].join("."), word].join("-")
end
def increment_version(metadata, deploy)
return metadata[:version] if deploy == :nothing
vs = metadata[:version].split("-")[0].split(".")
case deploy
when :major
major(vs)
when :minor
minor(vs)
when :patch
patch(vs)
when :test
letter = metadata[:letter] ? metadata[:letter].next : "a"
word = get_random_word(letter)
testv(vs, word)
else
raise red("ERROR ! Cannot get new version")
end
end
def check_pull()
system("git remote update 1>/dev/null")
local = `git rev-parse @`
base = `git merge-base @ master`
remote = `git rev-parse master`
if local == remote
puts green "Up to date"
elsif local == base
puts yellow "Need to pull"
puts "Do you want to pull (master) ? [Y/n]"
system("git pull origin master") if ["y", "Y"].include?($stdin.gets.strip)
else
puts red "Aborting ! Please sync your git repo"
exit 1
end
end
def commit(version)
system("git add .dockerbuild")
system("git commit -m \"Bump to #{version}\"")
puts "Commit created"
puts "Do you want to push (master) ? [Y/n]"
system("git push origin master") if ["y", "Y"].include?($stdin.gets.strip)
end
def build(staging)
puts yellow("\n/!\\ ") + red("Staging") + yellow(" build /!\\\n") if staging
check_pull
metadata = get_current_metadata
type_of_update = ask_type_of_update
version = increment_version(metadata, type_of_update)
ask_packages_json version
build_files = ask_what_build
for build_file in build_files
build_ext = build_file.split(".")[1]
metadata[:version] = version
name = metadata[:name]
name = "#{metadata[:name]}-#{build_ext}" if build_ext
name = "#{name}-staging" if staging
puts "Building: #{cyan("v#{version}")}#{" #{green build_ext}" if build_ext}..."
docker_build_cmd = if staging
system("DOCKER_BUILDKIT=1 docker build . -f #{build_file} -t #{name}:v#{version} --build-arg CARGO_FLAGS=--features=staging --build-arg STAGING=true")
else
system("DOCKER_BUILDKIT=1 docker build . -f #{build_file} -t #{name}:v#{version}")
end
fail_because("Cannot build docker image") unless docker_build_cmd
fail_because("Cannot push docker image") unless system("docker push #{name}:v#{version}")
end
write_metadata(metadata[:name], metadata[:version])
commit version
puts
puts "Done ! :)"
end
def ask_environment
puts "Deploy #{cyan "production"} build ? [Y/n]"
return !["y", "Y"].include?($stdin.gets.strip)
end
staging = ask_environment
build staging
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment