Class: GDKBox::Docker

Inherits:
Object
  • Object
show all
Defined in:
lib/gdkbox/docker.rb

Overview

A small, focused wrapper around the docker CLI.

Only the verbs gdkbox needs are exposed. Every container created by gdkbox is tagged with the gdkbox=true label plus a gdkbox.name label so they can be discovered later without relying on local metadata alone.

Defined Under Namespace

Classes: PullProgress

Instance Method Summary collapse

Constructor Details

#initialize(shell: Shell.new) ⇒ Docker

Returns a new instance of Docker.



10
11
12
# File 'lib/gdkbox/docker.rb', line 10

def initialize(shell: Shell.new)
  @shell = shell
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


14
15
16
# File 'lib/gdkbox/docker.rb', line 14

def available?
  !@shell.which("docker").nil?
end

#cp_into(name, src, dest) ⇒ Object

Copy a host path into a container at dest (a full destination path).



223
224
225
# File 'lib/gdkbox/docker.rb', line 223

def cp_into(name, src, dest)
  @shell.run!("docker", "cp", src, "#{name}:#{dest}")
end

#exec(name, script, user: nil, workdir: nil, env: {}, input: nil, check: true) ⇒ Object

Run a bash script inside a running container. The script is passed to bash -lc as a single argument; values that vary (keys, usernames) are passed through the environment to avoid quoting pitfalls.

When check is true (the default) a non-zero exit raises; pass check: false to capture output and exit status without raising, which is what agent runs want so the caller can surface the agent's output either way.



213
214
215
216
217
218
219
220
# File 'lib/gdkbox/docker.rb', line 213

def exec(name, script, user: nil, workdir: nil, env: {}, input: nil, check: true)
  cmd = ["docker", "exec", "-i"]
  cmd.push("-u", user) if user
  cmd.push("-w", workdir) if workdir
  env.each { |key, value| cmd.push("-e", "#{key}=#{value}") }
  cmd.push(name, "bash", "-lc", script)
  check ? @shell.run!(*cmd, input: input) : @shell.run(*cmd, input: input)
end

#image_exists?(image) ⇒ Boolean

Whether the image is already present locally.

Returns:

  • (Boolean)


19
20
21
# File 'lib/gdkbox/docker.rb', line 19

def image_exists?(image)
  @shell.run("docker", "image", "inspect", image).success?
end

#list_namesObject

Names of all gdkbox-managed containers, derived from labels.



251
252
253
254
255
256
257
258
259
260
# File 'lib/gdkbox/docker.rb', line 251

def list_names
  result = @shell.run(
    "docker", "ps", "-a",
    "--filter", "label=gdkbox=true",
    "--format", '{{index .Labels "gdkbox.name"}}'
  )
  return [] unless result.success?

  result.stdout.split("\n").map(&:strip).reject(&:empty?)
end

#pull(image, &block) ⇒ Object

Pull an image. With no block the pull runs quietly and the (captured) Result is returned. With a block the pull is streamed and the block is called with a PullProgress after every line of docker pull output, so a caller can drive a progress bar. Raises CommandError on a non-zero exit.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/gdkbox/docker.rb', line 27

def pull(image, &block)
  return @shell.run!("docker", "pull", image) unless block

  tracker = PullProgress.new
  result = @shell.stream_tty("docker", "pull", image) do |line|
    tracker.ingest(line)
    block.call(tracker)
  end
  unless result.success?
    raise CommandError.new(["docker", "pull", image], result.status, result.stderr)
  end

  result
end

#rm(name, force: true) ⇒ Object



235
236
237
238
239
240
# File 'lib/gdkbox/docker.rb', line 235

def rm(name, force: true)
  cmd = ["docker", "rm"]
  cmd << "-f" if force
  cmd << name
  @shell.run(*cmd)
end

#run_container(name:, image:, publish: [], labels: {}, env: {}, args: []) ⇒ Object

Start a detached container, returning its id.



196
197
198
199
200
201
202
203
204
# File 'lib/gdkbox/docker.rb', line 196

def run_container(name:, image:, publish: [], labels: {}, env: {}, args: [])
  cmd = ["docker", "run", "-d", "--name", name]
  labels.each { |key, value| cmd.push("--label", "#{key}=#{value}") }
  env.each { |key, value| cmd.push("-e", "#{key}=#{value}") }
  publish.each { |mapping| cmd.push("-p", mapping) }
  cmd << image
  cmd.concat(args)
  @shell.run!(*cmd).stdout.strip
end

#start(name) ⇒ Object



227
228
229
# File 'lib/gdkbox/docker.rb', line 227

def start(name)
  @shell.run!("docker", "start", name)
end

#state(name) ⇒ Object

The container's lifecycle state, or :absent if it does not exist.



243
244
245
246
247
248
# File 'lib/gdkbox/docker.rb', line 243

def state(name)
  result = @shell.run("docker", "inspect", "-f", "{{.State.Status}}", name)
  return :absent unless result.success?

  result.stdout.strip.to_sym
end

#stop(name) ⇒ Object



231
232
233
# File 'lib/gdkbox/docker.rb', line 231

def stop(name)
  @shell.run!("docker", "stop", name)
end