# frozen_string_literal: true
namespace :docker do
class DockerImage def initialize(repo = nil, name = nil, tag = nil) @repository = repo.nil? || repo.empty? ? nil : repo @name = name.nil? || name.empty? ? nil : name @tag = tag.nil? || tag.empty? ? nil : tag end def to_s return nil unless @name "#{@repository ? "#{@repository}/" : nil}#{@name}#{@tag ? ":#{@tag}" : nil}" end def repo return @repository end def name return @name end def tag return @tag end def valid? return false if @repository.nil? && @name.nil? && @tag.nil? unless @repository.nil? return false if (@repository =~ /^([\d\w])*(:)?\d*$/).nil? end return false if (@name =~ /^([\d\w])*$/).nil? unless @tag.nil? return false if (@tag =~ /^([\d\w-])*$/).nil? end return true end def self.from_str(str) return new unless str repo_str = repo_from_str(str) name_str = name_from_str(str) tag_str = tag_from_str(str) return new repo_str, name_str, tag_str end def self.repo_from_str(str) last_slash = str.rindex('/') return nil unless last_slash return str[0...last_slash] end def self.name_from_str(str) # remove any repo bits repo_str = repo_from_str(str) str = str.sub("#{repo_str}/", '') if repo_str # remove any tag bits str.split(':')[0] end def self.tag_from_str(str) # remove any repo bits repo_str = repo_from_str(str) str = str.sub("#{repo_str}/", '') if repo_str # remove any name bits str_split = str.split(':') return str_split.size > 1 ? str_split[1] : nil end end # Any extra command-line opts you'd like to insert into the `docker build` command # Normally has no preset value (fastest option), but a popular full-rebuild-every-time (thorough build) selection is '--pull --no-cache --force-rm' def docker_build_opts fetch(:docker_build_opts, nil) end # Relative path to the Dockerfile for image building def dockerfile fetch(:dockerfile, 'Dockerfile') end # Docker build source path def docker_build_context fetch(:docker_build_context, '.') end def docker_cmd fetch(:docker_cmd, 'docker') end def docker_build_image (fetch(:docker_build_image) { fetch(:application) }) || raise('unable to discern docker_build_image name; specify :application or :docker_build_image') end def docker_build_custom_tag return nil unless fetch(:docker_build_custom_tag, nil) return "#{docker_build_image}:#{fetch(:docker_build_custom_tag)}" end def docker_build_image_latest_tag? return fetch(:docker_build_image_latest_tag?, true) end def docker_build_image_latest_tag return fetch(:docker_build_image_latest_tag) || set(:docker_build_image_latest_tag, "#{docker_build_image}:latest") end def docker_build_image_release_tag? return fetch(:docker_build_image_release_tag?, true) end def docker_build_image_release_tag return fetch(:docker_build_image_release_tag) || set(:docker_build_image_release_tag, "#{docker_build_image}:release-#{release_timestamp}") end def docker_build_image_revision_tag? return fetch(:docker_build_image_revision_tag?, true) end def docker_build_image_revision_tag return nil unless fetch(:current_revision) return fetch(:docker_build_image_revision_tag) || set(:docker_build_image_revision_tag, "#{docker_build_image}:REVISION-#{fetch(:current_revision)}") end def docker_build_image_shortrev_tag? return fetch(:docker_build_image_shortrev_tag?, true) end def docker_build_image_shortrev_tag return nil unless fetch(:current_revision) return fetch(:docker_build_image_shortrev_tag) || set(:docker_build_image_shortrev_tag, "#{docker_build_image}:rev-#{fetch(:current_revision)[0..6]}") end def docker_build_image_tags tags = [] tags << docker_build_custom_tag if docker_build_custom_tag tags << docker_build_image_release_tag if docker_build_image_release_tag? tags << docker_build_image_revision_tag if docker_build_image_revision_tag? tags << docker_build_image_shortrev_tag if docker_build_image_shortrev_tag? tags << docker_build_image_latest_tag if docker_build_image_latest_tag? return tags.uniq.compact end def docker_build_image_tags_opt return docker_build_image_tags.map{|t| "--tag=#{t}"}.join(' ') end def docker_repo_url fetch :docker_repo_url end def docker_repo_tag(local_tag) return nil unless docker_repo_url return "#{docker_repo_url}/#{local_tag}" end def docker_repo_tags docker_build_image_tags.map{|t| docker_repo_tag(t)} end def docker_build_promote_image promote_image_tag = fetch(:docker_build_promote_image, nil) image_name = DockerImage.from_str(promote_image_tag) image_name = DockerImage.new(image_name.repo ? image_name.repo : docker_repo_url, image_name.name ? image_name.name : docker_build_image, image_name.tag ? image_name.tag : docker_build_tag) promote_tag = image_name.to_s return promote_tag end task :check_docker_build_role do if roles(:docker_build).empty? run_locally do fatal ':docker_build role is required for remote builds' raise 'missing :docker_build role' end end unless roles(:docker_build).size == 1 run_locally do fatal 'cannot assign :docker_build role to more than one server' raise 'multiple :docker_build role assignments' end end end task :check_docker_build_root do on roles(:docker_build).first do # |buildremote| info "checking build path: #{build_path}" execute :test, '-d', build_path # path exists info 'checking for Dockerfile...' execute :test, '-f', build_path.join(dockerfile) # Dockerfile is present info "checking docker_build_context: #{docker_build_context}" execute :test, '-d', build_path.join(docker_build_context) # Dockerfile is present end end task :local_build_warning do run_locally do warn 'Building docker image directly from local workspace... (typically undesirable, skips several sanity checks)' warn '>> assign :docker_build role to enable remote builds' end end task :build_decision do if roles(:docker_build).empty? invoke 'docker:build_local' else invoke 'docker:build_remote' end end task :local_build_deps => [:local_build_warning, :set_current_revision] task :build_local => :local_build_deps do current_build_dir = pwd run_locally do info "Current Working Directory: #{current_build_dir}" execute docker_cmd, 'build', docker_build_opts, "--file=#{dockerfile}", docker_build_image_tags_opt, docker_build_context end end task :remote_build_deps => [:check_docker_build_role, :check_docker_build_root, :set_current_revision] task :build_remote => :remote_build_deps do on roles(:docker_build).first do |buildremote| info "Building docker image on :docker_build role: #{buildremote}" within build_path do execute docker_cmd, :ps execute docker_cmd, 'build', docker_build_opts, "--file=#{dockerfile}", docker_build_image_tags_opt, docker_build_context end end end task :set_current_revision => 'deploy:set_current_revision' task :get_docker_build_image_id => :set_current_revision do if roles(:docker_build).empty? run_locally do docker_build_image_tags.each do |image_tag| docker_build_image_id = capture docker_cmd, :image, :ls, '-q', image_tag unless docker_build_image_id.empty? set(:docker_build_image_id, docker_build_image_id) break end end end else on roles(:docker_build).first do # |buildremote| docker_build_image_tags.each do |image_tag| docker_build_image_id = capture docker_cmd, :image, :ls, '-q', image_tag unless docker_build_image_id.empty? set(:docker_build_image_id, docker_build_image_id) break end end end end unless fetch(:docker_build_image_id) fatal 'unable to discern image id' raise 'missing image id' end end desc "Push the 'latest' image to the repository" task :push do if roles(:docker_build).empty? invoke 'docker:push_local' else invoke 'docker:push_remote' end end task :push_local => :get_docker_build_image_id do run_locally do unless docker_repo_url warn ':docker_repo_url not defined! No push destination' next end docker_build_image_tags.each do |local_tag| repo_tag = docker_repo_tag(local_tag) next unless repo_tag run_locally do execute docker_cmd, :tag, fetch(:docker_build_image_id), repo_tag execute docker_cmd, :push, repo_tag end end end end task :push_remote => [:check_docker_build_role, :get_docker_build_image_id] do on roles(:docker_build).first do # |buildremote| unless docker_repo_url warn ':docker_repo_url not defined! No push destination' next end docker_build_image_tags.each do |local_tag| repo_tag = docker_repo_tag(local_tag) next unless repo_tag on roles(:docker_build).first do # |buildremote| execute docker_cmd, :tag, fetch(:docker_build_image_id), repo_tag execute docker_cmd, :push, repo_tag end end end end desc 'Build the docker image' task :build do invoke 'docker:build_decision' end desc 'Build and push docker image' task :build_push do invoke 'docker:build' invoke 'docker:push' end desc 'Promote an existing docker image (pull and retag)' task :promote do if roles(:docker_build).empty? invoke 'docker:promote_local' else invoke 'docker:promote_remote' end end task :check_docker_build_promote_image do unless fetch :docker_build_promote_image run_locally do fatal ':docker_build_promote_image setting is required for promote task' raise 'missing :docker_build_promote_image' end end unless DockerImage.from_str(docker_build_promote_image).valid? run_locally do fatal ":docker_build_promote_image setting is not valid: '#{fetch(:docker_build_promote_image)}' => '#{docker_build_promote_image}'" raise 'invalid :docker_build_promote_image' end end end task :promote_local => :check_docker_build_promote_image do run_locally do source_tag = docker_build_promote_image info "Promoting image: #{source_tag}" execute docker_cmd, :pull, source_tag docker_repo_tags.each do |repo_tag| next if repo_tag == source_tag info "Promoted to: #{repo_tag}" execute docker_cmd, :tag, source_tag, repo_tag execute docker_cmd, :push, repo_tag end end end task :promote_remote => :check_docker_build_promote_image do on roles(:docker_build).first do # |buildremote| source_tag = docker_build_promote_image info "Promoting image: #{source_tag}" execute docker_cmd, :pull, source_tag docker_repo_tags.each do |repo_tag| next if repo_tag == source_tag info "Promoted to: #{repo_tag}" execute docker_cmd, :tag, source_tag, repo_tag execute docker_cmd, :push, repo_tag end end end desc '(alias for docker:build_push)' task :deploy => :build_push # Default `cap env deploy` flow hook task :capdeploy_hook do if fetch(:dockerbuild_deployhook, true) invoke 'docker:build_push' end end task :trim_release_roles do if fetch(:dockerbuild_trim_release_roles, true) docker_build_server = [roles(:docker_build).first] (release_roles(:all) - docker_build_server).each do |server| server.set :no_release, true end end end
end # namespace :docker do