Class: Docker::Compose::Session

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

Overview

A Ruby OOP interface to a docker-compose session. A session is bound to a particular directory and docker-compose file (which are set at initialize time) and invokes whichever docker-compose command is resident in $PATH.

Run docker-compose commands by calling instance methods of this class and passing kwargs that are equivalent to the CLI options you would pass to the command-line tool.

Note that the Ruby command methods usually expose a subset of the options allowed by the docker-compose CLI, and that options are sometimes renamed for clarity, e.g. the “-d” flag always becomes the “detached:” kwarg.

Constant Summary collapse

ANSI =

A Regex that matches all ANSI escape sequences.

/\033\[([0-9];?)+[a-z]/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(shell = Backticks::Runner.new(buffered: [:stderr], interactive: true), dir: Dir.pwd, project_name: nil, file: 'docker-compose.yml') ⇒ Session

Returns a new instance of Session.



33
34
35
36
37
38
39
40
# File 'lib/docker/compose/session.rb', line 33

def initialize(shell = Backticks::Runner.new(buffered: [:stderr], interactive: true),
               dir: Dir.pwd, project_name: nil, file: 'docker-compose.yml')
  @shell = shell
  @project_name = project_name
  @dir = dir
  @file = file
  @last_command = nil
end

Instance Attribute Details

#dirObject (readonly)

Working directory (determines compose project name); default is Dir.pwd



22
23
24
# File 'lib/docker/compose/session.rb', line 22

def dir
  @dir
end

#fileObject (readonly)

Project file; default is ‘docker-compose.yml’



28
29
30
# File 'lib/docker/compose/session.rb', line 28

def file
  @file
end

#last_commandObject (readonly)

Reference to the last executed command.



31
32
33
# File 'lib/docker/compose/session.rb', line 31

def last_command
  @last_command
end

#project_nameObject (readonly)

Project name; default is not to pass a custom name



25
26
27
# File 'lib/docker/compose/session.rb', line 25

def project_name
  @project_name
end

Instance Method Details

#build(*services, force_rm: false, no_cache: false, pull: false) ⇒ Object



240
241
242
243
244
245
# File 'lib/docker/compose/session.rb', line 240

def build(*services, force_rm: false, no_cache: false, pull: false)
  o = opts(force_rm: [force_rm, false],
           no_cache: [no_cache, false],
           pull: [pull, false])
  result = run!('build', o, services)
end

#config(*args) ⇒ Hash

Validate docker-compose file and return it as Hash

Returns:

  • (Hash)

    the docker-compose config file

Raises:

  • (Error)

    if command fails



45
46
47
48
# File 'lib/docker/compose/session.rb', line 45

def config(*args)
  config = strip_ansi(run!('config', *args))
  YAML.load(config)
end

#down(remove_volumes: false) ⇒ Object

Take the stack down



116
117
118
# File 'lib/docker/compose/session.rb', line 116

def down(remove_volumes: false)
  run!('down', opts(v: [!!remove_volumes, false]))
end

#kill(*services, signal: 'KILL') ⇒ Object

Forcibly stop running services.

Parameters:

  • services (Array)

    list of String service names to stop

  • name (String)

    of murderous signal to use, default is ‘KILL’

See Also:

  • for a list of acceptable signal names


181
182
183
184
# File 'lib/docker/compose/session.rb', line 181

def kill(*services, signal: 'KILL')
  o = opts(signal: [signal, 'KILL'])
  run!('kill', o, services)
end

#logs(*services) ⇒ true

Monitor the logs of one or more containers.

Parameters:

  • services (Array)

    list of String service names to show logs for

Returns:

  • (true)

    always returns true

Raises:

  • (Error)

    if command fails



54
55
56
57
# File 'lib/docker/compose/session.rb', line 54

def logs(*services)
  run!('logs', services)
  true
end

#pause(*services) ⇒ Object

Pause running services.

Parameters:

  • services (Array)

    list of String service names to run



158
159
160
# File 'lib/docker/compose/session.rb', line 158

def pause(*services)
  run!('pause', *services)
end

#port(service, port, protocol: 'tcp', index: 1) ⇒ String?

Figure out which interface(s) and port a given service port has been published to.

NOTE: if Docker Compose is communicating with a remote Docker host, this method returns IP addresses from the point of view of that host and its interfaces. If you need to know the address as reachable from localhost, you probably want to use ‘Mapper`.

Parameters:

  • service (String)

    name of service from docker-compose.yml

  • port (Integer)

    number of port

  • protocol (String) (defaults to: 'tcp')

    ‘tcp’ or ‘udp’

  • index (Integer) (defaults to: 1)

    of container (if multiple instances running)

Returns:

  • (String, nil)

    an ip:port pair such as “0.0.0.0:32176” or nil if the service is not running

Raises:

  • (Error)

    if command fails

See Also:



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/docker/compose/session.rb', line 201

def port(service, port, protocol: 'tcp', index: 1)
  inter = @shell.interactive
  @shell.interactive = false

  o = opts(protocol: [protocol, 'tcp'], index: [index, 1])
  s = strip_ansi(run!('port', o, service, port).strip)
  (!s.empty? && s) || nil
rescue Error => e
  # Deal with docker-compose v1.11+
  if e.detail =~ /No container found/i
    nil
  else
    raise
  end
ensure
  @shell.interactive = inter
end

#ps(*services) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/docker/compose/session.rb', line 59

def ps(*services)
  inter = @shell.interactive
  @shell.interactive = false

  lines = strip_ansi(run!('ps', {q: true}, services)).split(/[\r\n]+/)
  containers = Collection.new

  lines.each do |id|
    containers << docker_ps(strip_ansi(id))
  end

  containers
ensure
  @shell.interactive = inter
end

#pull(*services) ⇒ Object

Pull images of services

Parameters:

  • services (Array)

    list of String service names to pull



122
123
124
# File 'lib/docker/compose/session.rb', line 122

def pull(*services)
  run!('pull', *services)
end

#restart(*services, timeout: 10) ⇒ Object



151
152
153
154
# File 'lib/docker/compose/session.rb', line 151

def restart(*services, timeout:10)
  o = opts(timeout: [timeout, 10])
  run!('restart', o, *services)
end

#rm(*services, force: false, volumes: false) ⇒ Object



126
127
128
129
# File 'lib/docker/compose/session.rb', line 126

def rm(*services, force: false, volumes: false)
  o = opts(f: [force, false], v: [volumes, false])
  run!('rm', o, services)
end

#run(service, *cmd, detached: false, no_deps: false, volumes: [], env: [], rm: false, no_tty: false, user: nil, service_ports: false) ⇒ Object

Idempotently run an arbitrary command with a service container.

Parameters:

  • service (String)

    name to run

  • cmd (String)

    command statement to run

  • detached (Boolean) (defaults to: false)

    if true, to start services in the background; otherwise, monitor logs in the foreground and shutdown on Ctrl+C

  • no_deps (Boolean) (defaults to: false)

    if true, just run specified services without running the services that they depend on

  • env (Array) (defaults to: [])

    a list of environment variables (see: -e flag)

  • volumes (Array) (defaults to: [])

    a list of volumes to bind mount (see: -v flag)

  • rm (Boolean) (defaults to: false)

    remove the container when done

  • no_tty (Boolean) (defaults to: false)

    disable pseudo-tty allocation (see: -T flag)

  • user (String) (defaults to: nil)

    run as specified username or uid (see: -u flag)

Raises:

  • (Error)

    if command fails



144
145
146
147
148
149
# File 'lib/docker/compose/session.rb', line 144

def run(service, *cmd, detached: false, no_deps: false, volumes: [], env: [], rm: false, no_tty: false, user: nil, service_ports: false)
  o = opts(d: [detached, false], no_deps: [no_deps, false], rm: [rm, false], T: [no_tty, false], u: [user, nil], service_ports: [service_ports, false])
  env_params = env.map { |v| { e: v } }
  volume_params = volumes.map { |v| { v: v } }
  run!('run', o, *env_params, *volume_params, service, cmd)
end

#run!(*args) ⇒ String

Run a docker-compose command without validating that the CLI parameters make sense. Prepend project and file options if suitable.

Parameters:

  • args (Array)

    command-line arguments in the format accepted by Backticks::Runner#command

Returns:

  • (String)

    output of the command

Raises:

  • (Error)

    if command fails

See Also:

  • Docker::Compose::Shell#command


256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/docker/compose/session.rb', line 256

def run!(*args)
  project_name_args = if @project_name
    [{ project_name: @project_name }]
  else
    []
  end
  file_args = case @file
  when 'docker-compose.yml'
    []
  when Array
    # backticks sugar can't handle array values; build a list of hashes
    # IMPORTANT: preserve the order of the files so overrides work correctly
    file_args = @file.map { |filepath| { :file => filepath } }
  else
    # a single String (or Pathname, etc); use normal sugar to add it
    [{ file: @file.to_s }]
  end

  @shell.chdir = dir
  @last_command = @shell.run('docker-compose', *project_name_args, *file_args, *args).join
  status = @last_command.status
  out = @last_command.captured_output
  err = @last_command.captured_error
  status.success? || fail(Error.new(args.first, status, out+err))
  out
end

#scale(container_count, timeout: 10) ⇒ Object

Idempotently scales the number of containers for given services in the project.

Parameters:

  • container_count (Hash)

    per service, e.g. 2, worker: 3

  • timeout (Integer) (defaults to: 10)

    how long to wait for each service to scale



109
110
111
112
113
# File 'lib/docker/compose/session.rb', line 109

def scale(container_count, timeout: 10)
  args = container_count.map {|service, count| "#{service}=#{count}"}
  o = opts(timeout: [timeout, 10])
  run!('scale', o, *args)
end

#stop(*services, timeout: 10) ⇒ Object

Stop running services.

Parameters:

  • services (Array)

    list of String service names to stop

  • timeout (Integer) (defaults to: 10)

    how long to wait for each service to stop

Raises:

  • (Error)

    if command fails



172
173
174
175
# File 'lib/docker/compose/session.rb', line 172

def stop(*services, timeout: 10)
  o = opts(timeout: [timeout, 10])
  run!('stop', o, services)
end

#unpause(*services) ⇒ Object

Unpause running services.

Parameters:

  • services (Array)

    list of String service names to run



164
165
166
# File 'lib/docker/compose/session.rb', line 164

def unpause(*services)
  run!('unpause', *services)
end

#up(*services, abort_on_container_exit: false, detached: false, timeout: 10, build: false, exit_code_from: nil, no_build: false, no_deps: false, no_start: false) ⇒ true

Idempotently up the given services in the project.

Parameters:

  • services (Array)

    list of String service names to run

  • detached (Boolean) (defaults to: false)

    if true, to start services in the background; otherwise, monitor logs in the foreground and shutdown on Ctrl+C

  • timeout (Integer) (defaults to: 10)

    how long to wait for each service to start

  • build (Boolean) (defaults to: false)

    if true, build images before starting containers

  • no_build (Boolean) (defaults to: false)

    if true, don’t build images, even if they’re missing

  • no_deps (Boolean) (defaults to: false)

    if true, just run specified services without running the services that they depend on

Returns:

  • (true)

    always returns true

Raises:

  • (Error)

    if command fails



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/docker/compose/session.rb', line 87

def up(*services,
       abort_on_container_exit: false,
       detached: false, timeout: 10, build: false,
       exit_code_from: nil,
       no_build: false, no_deps: false, no_start: false)
  o = opts(
           abort_on_container_exit: [abort_on_container_exit, false],
           d: [detached, false],
           timeout: [timeout, 10],
           build: [build, false],
           exit_code_from: [exit_code_from, nil],
           no_build: [no_build, false],
           no_deps: [no_deps, false],
           no_start: [no_start, false]
  )
  run!('up', o, services)
  true
end

#version(short: false) ⇒ String, Hash

Determine the installed version of docker-compose.

Parameters:

  • short (Boolean) (defaults to: false)

    whether to return terse version information

Returns:

  • (String, Hash)

    if short==true, returns a version string; otherwise, returns a Hash of component-name strings to version strings

Raises:

  • (Error)

    if command fails



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/docker/compose/session.rb', line 224

def version(short: false)
  o = opts(short: [short, false])
  result = run!('version', o, file: false, dir: false)

  if short
    result.strip
  else
    lines = result.split(/[\r\n]+/)
    lines.inject({}) do |h, line|
      kv = line.split(/: +/, 2)
      h[kv.first] = kv.last
      h
    end
  end
end