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).



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

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.



217
218
219
220
221
222
223
224
# File 'lib/gdkbox/docker.rb', line 217

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.



255
256
257
258
259
260
261
262
263
264
# File 'lib/gdkbox/docker.rb', line 255

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



239
240
241
242
243
244
# File 'lib/gdkbox/docker.rb', line 239

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: [], hostname: nil) ⇒ Object

Start a detached container, returning its id. When a hostname is given, Docker also maps it to the container's IP in the container's /etc/hosts and re-applies that mapping on every start (a manual edit would be lost: Docker regenerates the file when the container starts).



199
200
201
202
203
204
205
206
207
208
# File 'lib/gdkbox/docker.rb', line 199

def run_container(name:, image:, publish: [], labels: {}, env: {}, args: [], hostname: nil)
  cmd = ["docker", "run", "-d", "--name", name]
  cmd.push("--hostname", hostname) if hostname
  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



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

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

#state(name) ⇒ Object

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



247
248
249
250
251
252
# File 'lib/gdkbox/docker.rb', line 247

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



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

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