Class: Cutlass::ContainerBoot

Inherits:
Object
  • Object
show all
Defined in:
lib/cutlass/container_boot.rb

Overview

Boots containers and tears ‘em down

Has a single method ContainerBoot#call which returns an instance of

boot = ContainerBoot.new(image_id: @image.id)
boot.call do |container_control|
  container_control.class # => ContainerControl
  container_control.bash_exec("pwd")
end

The number one reason to boot a container is to be able to exercise a booted server from within the container. To do this you need to tell docker want port to expose inside of the container. Docker will expose that port and bind it to a free port on the “host” i.e. your local machine. From there you can make queries to various docker ports:

boot = ContainerBoot.new(image_id: @image.id, expose_ports: [8080])
boot.call do |container_control|
  local_port = container_control.get_host_port(8080)

  `curl localhost:#{local_port}`
end

Note: Booting a container only works if the image has an ENTRYPOINT that does not exit.

Note: Running ‘bash_exec` commands from this context gives you a raw access to the container. It does not execute the container’s entrypoint. That means if you’re running inside of a CNB image, that env vars won’t be set and the directory might be different.

Instance Method Summary collapse

Constructor Details

#initialize(image_id:, env: {}, expose_ports: [], memory: nil) ⇒ ContainerBoot

Returns a new instance of ContainerBoot.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/cutlass/container_boot.rb', line 36

def initialize(image_id:, env: {}, expose_ports: [], memory: nil)
  @expose_ports = Array(expose_ports)
  config = {
    "Image" => image_id,
    "ExposedPorts" => {},
    "HostConfig" => {
      "PortBindings" => {}
    }
  }

  port_bindings = config["HostConfig"]["PortBindings"]

  @expose_ports.each do |port|
    config["ExposedPorts"]["#{port}/tcp"] = {}

    # If we do not specify a port, Docker will grab a random unused one:
    port_bindings["#{port}/tcp"] = [{"HostPort" => ""}]
  end

  config["Memory"] = memory.to_i if memory
  config["Env"] = env.map { |k, v| "#{k}=#{v}" }
  @container = Docker::Container.create(config)
end

Instance Method Details

#callObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/cutlass/container_boot.rb', line 60

def call
  raise "Must call with a block" unless block_given?

  @container.start!

  puts @container.logs(stdout: 1) if Cutlass.debug?
  puts @container.logs(stderr: 1) if Cutlass.debug?

  yield ContainerControl.new(@container, ports: @expose_ports)
rescue => error
  raise error, <<~EOM
    message #{error.message}

    boot stdout: #{@container.logs(stdout: 1)}
    boot stderr: #{@container.logs(stderr: 1)}
  EOM
ensure
  @container&.delete(force: true)
end