Class: NliPipeline::DockerManager

Inherits:
SystemWrapper::CallWrapper show all
Includes:
AbstractUtil
Defined in:
lib/nli_pipeline/docker_manager.rb

Overview

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

Instance Attribute Summary

Attributes inherited from SystemWrapper::CallWrapper

#last_return_code

Class Method Summary collapse

Instance Method Summary collapse

Methods included from AbstractUtil

#add_attrs, #drop_forbidden_args_message, included, #init_with_attrs, #raise_unless_all, #to_s

Methods inherited from SystemWrapper::CallWrapper

#call_system

Constructor Details

#initialize(**kwargs) ⇒ DockerManager

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



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 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

Class Method Details

.required_argsArray[Symbol]



29
30
31
# File 'lib/nli_pipeline/docker_manager.rb', line 29

def self.required_args
  [:path]
end

.supported_argsHash

static methods required by NliPipeline::AbstractUtil::init_attrs

Returns:

  • (Hash)

See Also:



17
18
19
20
21
22
23
24
# 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

Instance Method Details

#buildBoolean

build docker image

Returns:

  • (Boolean)

    success/failure



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 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_saveBoolean

save docker image as tarfile

Returns:

  • (Boolean)

    success/failure



114
115
116
117
118
119
# 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_argsString

Returns build arguments.

Returns:

  • (String)

    build arguments



73
74
75
76
77
78
# 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?Boolean

check if last commit contained build command

Returns:

  • (Boolean)

    Boolean



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 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_branchBoolean

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

Returns:

  • (Boolean)

    success/failure



211
212
213
214
215
216
217
218
219
220
# 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_masterBoolean

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

Returns:

  • (Boolean)

    success/failure



193
194
195
196
197
198
199
200
201
202
203
204
205
# 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_execBoolean

send bash command to running docker container avoid clash with exec!

Returns:

  • (Boolean)

    success/failure



182
183
184
185
186
# 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

#loadBoolean

load saved docker image

Returns:

  • (Boolean)

    success/failure



123
124
125
126
# 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_funcBoolean

create function to bind args to on docker run

Returns:

  • (Boolean)

    success/failure



169
170
171
172
173
174
175
176
177
# 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

#runBoolean

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)

Returns:

  • (Boolean)

    success/failure

Raises:



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# 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

#saveBoolean

save docker image as tarfile

Returns:

  • (Boolean)

    success/failure



104
105
106
107
108
109
110
# 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