class NliPipeline::DockerManager
handle docker build, save and run for nli-pipelines this DOES NOT handle docker login if you want to push to dockerhub or pull private images you'll need to log in in advance
Public Class Methods
creates new DockerManager
and assigns each keywoard argument as instance var with attr_reader uses image_name.tar as tar_name unless it's explicitly set
# File lib/nli_pipeline/docker_manager.rb, line 35 def initialize(**kwargs) # add nlireland/ prefix to imagename if no upstream image name is set can_guess_upstream = kwargs[:image_name] && !kwargs[:upstream_image_name] kwargs[:upstream_image_name] = "nlireland/#{kwargs[:image_name]}" if can_guess_upstream # set tar name as image name if image name is set but tar name isn't can_make_tar = kwargs[:image_name] && !kwargs[:tar_name] kwargs[:tar_name] = "#{kwargs[:image_name]}.tar" if can_make_tar # set container name manually, should not be able to change it kwargs[:container_name] = 'pipeline-container' # set git branch if it's not defined # pass debug flags down to GitManager (will show warning about extra args though) kwargs[:git_branch] = GitManager.new(**kwargs).branch unless kwargs[:git_branch] init_with_attrs(**kwargs) end
@see NliPipeline::AbstractUtil#init_with_attrs
@see NliPipeline::AbstractUtil::ClassMethods#required_args?
@return [Array]
# File lib/nli_pipeline/docker_manager.rb, line 29 def self.required_args [:path] end
static methods required by NliPipeline::AbstractUtil::init_attrs @see NliPipeline::AbstractUtil#init_with_attrs
@see NliPipeline::AbstractUtil#get_allowed_args @return [Hash]
# File lib/nli_pipeline/docker_manager.rb, line 17 def self.supported_args { path: '', debug: false, fail_on_error: false, image_name: false, upstream_image_name: false, tar_name: false, git_branch: false, proxy: false, container_name: false, commit: true, mount: false, bash_commands: [], ports: [] } end
Public Instance Methods
build docker image @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 82 def build can_build = build_commit? custom_message = 'built docker image' # if we can't build, fail early unless can_build # if --fail-on-error is passed, this will throw and exception # and "Skipping build" will not be output ret = pretty_print(custom_message: custom_message) { false } puts('Skipping build'.yellow) return ret end command = 'docker build' # command += " --build-arg proxy=#{@proxy}" if @proxy command += build_args command += " -t #{@image_name}" if @image_name pretty_print(custom_message: custom_message) { call_system("#{command} #{@path}") } end
save docker image as tarfile @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 114 def build_and_save built = build # if the build fails quit return false unless built save end
@return [String] build arguments
# File lib/nli_pipeline/docker_manager.rb, line 73 def build_args args = [] args.push("proxy=#{@proxy}") if @proxy args.push("commit=#{NliPipeline::GitManager.new.last_commit_url}") if @commit args.reduce('', &->(x, y) { x + " --build-arg #{y}" }) end
check if last commit contained build command @return Boolean
# File lib/nli_pipeline/docker_manager.rb, line 56 def build_commit? # neeed \\ since command will be passed to the shell as \[.*\] # escape chars all the way down custom_error_message = "[docker build] or variant not in last commit.\nFailing Build." custom_message = 'can build docker' # will match any combination of 'docker' and 'build' inside square brackets # TODO: refator to use GitManager and stub git log -1 call last_commit_message = NliPipeline::GitManager.new.last_commit_message command = "echo '#{last_commit_message}' | grep -o '\\[.*\\]' "\ "| grep -i 'docker' | grep -i 'build'" pretty_print(custom_message: custom_message) do call_system(command, custom_error_message: custom_error_message) end end
setup_pipeline sets fail-on-error to true for all deploys so build_commit? will raise an exception and stop the build if the last commit was not a build commit @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 211 def deploy_branch build_commit? # if gitbranch is not set, raise an error # todo: this may be unnecessary now with git branch default raise_unless_all(git_branch: @git_branch) message = "pushing tag: #{@git_branch} to #{@upstream_image_name}" tag(@git_branch) push_command = "docker push #{@upstream_image_name}:#{@git_branch}" pretty_print(custom_message: message) { call_system(push_command) } end
relies on tag to ensure image_name and upstream_image_name are set setup_pipeline sets fail-on-error to true for all deploys so build_commit? will raise an exception and stop the build if the last commit was not a build commit @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 193 def deploy_master build_commit? master_tags = ['latest', "latest-#{Time.now.strftime('%Y-%m-%d')}"] master_tags.map do |t| tag(t) message = "pushing tag: #{t} to #{@upstream_image_name}" push_command = "docker push #{@upstream_image_name}:#{t}" # deploy each tag individually on the off change # that another tag for the image that wasn't created by setup_pipeline # exists and should not be pushed pretty_print(custom_message: message) { call_system(push_command) } end end
send bash command to running docker container avoid clash with exec! @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 182 def docker_exec @bash_commands.each do |cmd| pretty_print { call_system("docker exec #{@container_name} bash -c '#{cmd}'") } end end
load saved docker image @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 123 def load load_command = "docker load -i #{@path}/#{@tar_name}" pretty_print(custom_message: "loading #{@tar_name}") { call_system(load_command) } end
create function to bind args to on docker run @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 169 def prepare_run_func base_command = 'docker run -id' base_command += " -v #{@path}:#{@mount}" if @mount base_command = @ports.reduce(base_command, &->(x, y) { x + " -p #{y}:#{y}" }) base_command += " -e CI=TRUE --name #{@container_name}" proc do |name| pretty_print(custom_message: "running #{name}") { call_system("#{base_command} #{name}") } end end
if the last commit was a build commit try to load and run the image otherwise pull and run the upstream image
in prepare_run_func
always:
1. run in detached mode with stdin open (-id) 2. pass CI=true to as a build argument 3. set the container name (pipeline-container)
@raise [SystemWrapper::ConfigError] if the local image failed to be loaded /run,
or the upstream image failed to run
@return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 145 def run run_func = prepare_run_func # if the last commit had a build instruction, load the built image if build_commit? && @image_name load run_func.call(@image_name) # otherwise try to pull the upstream image from dockerhub elsif @upstream_image_name run_func.call(@upstream_image_name) # if neither image nor upstream image were set, throw config error # can't use raise_unless_all since some, but not all, must be set else error_message = 'you must set image_name or upstream_image_name to run a docker image' config_error = "Config Error: #{error_message}" config = { image_name: @image_name, upstream_image_name: @upstream_image_name, build_commit: build_commit? } raise SystemWrapper::ConfigError.new(config: config, msg: config_error) end end
save docker image as tarfile @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 104 def save # throw an error is image_name is not set raise_unless_all(image_name: @image_name) save_command = "docker save #{@image_name} -o #{@path}/#{@tar_name}" puts 'saving docker image'.yellow pretty_print(custom_message: 'saved docker image') { call_system(save_command) } end
Private Instance Methods
@param tag [String] what to tag the docker image as @return [Boolean] success/failure
# File lib/nli_pipeline/docker_manager.rb, line 226 def tag(tag) # docker tag requires a local and upstream image # raise an error if either of these are missing raise_unless_all(image_name: @image_name, upstream_image_name: @upstream_image_name) imgs_and_tag = "#{@image_name} #{@upstream_image_name}:#{tag}" print_arg = "tagging #{imgs_and_tag}" call_system_arg = "docker tag #{imgs_and_tag}" # ret = pretty_print(custom_message: print_arg) { call_system(call_system_arg) } pretty_print(custom_message: print_arg) { call_system(call_system_arg) } # # raise an error if fail on error is set # error_messsage = "Failed to tag #{@upstream_image_name}:#{tag}" # err = NliPipeline::SystemWrapper::CallWrapperError.new(error_messsage) # raise err if !ret && @fail_on_error # ret end