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

new(**kwargs) click to toggle source

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
required_args() click to toggle source

@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
supported_args() click to toggle source

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() click to toggle source

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
build_and_save() click to toggle source

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
build_args() click to toggle source

@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
build_commit?() click to toggle source

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
deploy_branch() click to toggle source

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
deploy_master() click to toggle source

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
docker_exec() click to toggle source

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() click to toggle source

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
prepare_run_func() click to toggle source

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
run() click to toggle source

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() click to toggle source

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

tag(tag) click to toggle source

@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