docker-compose 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,8 @@
1
- # encoding: utf-8
2
- require 'json'
3
1
  require 'rake/tasklib'
4
- require 'shellwords'
5
2
 
6
3
  # In case this file is required directly
7
4
  require 'docker/compose'
8
5
 
9
- # Only used here, so only required here
10
- require 'docker/compose/shell_printer'
11
-
12
6
  module Docker::Compose
13
7
  class RakeTasks < Rake::TaskLib
14
8
  # Set the directory in which docker-compose commands will be run. Default
@@ -17,155 +11,148 @@ module Docker::Compose
17
11
  # @return [String]
18
12
  attr_accessor :dir
19
13
 
20
- # Set the project name. Default is not to pass a custom name.
21
- # @return [String]
22
- attr_accessor :project_name
23
-
24
14
  # Set the name of the docker-compose file. Default is`docker-compose.yml`.
25
15
  # @return [String]
26
16
  attr_accessor :file
27
17
 
28
- # Provide a mapping of environment variables that should be set in
29
- # _host_ processes, e.g. when running docker:compose:env or
30
- # docker:compose:host.
31
- #
18
+ # Provide a mapping of environment variables that should be set in the
19
+ # _host_ shell for docker:compose:env or docker:compose:server.
32
20
  # The values of the environment variables can refer to names of services
33
- # and ports defined in the docker-compose file, and this gem will substitute
34
- # the actual IP and port that the containers are reachable on. This allows
35
- # commands invoked via "docker:compose:host" to reach services running
36
- # inside containers.
21
+ # and ports defined in the docker-compose file, and this gem will query
22
+ # docker-compose to find out which host IP and port the services are
23
+ # reachable on. This allows components running on the host to connect to
24
+ # services running inside containers.
37
25
  #
38
26
  # @see Docker::Compose::Mapper for information about the substitution syntax
39
- attr_accessor :host_env
40
-
41
- # Extra environment variables to set before invoking host processes. These
42
- # are set _in addition_ to server_env, but are not substituted in any way
43
- # and must not contain any service information.
27
+ attr_accessor :env
28
+
29
+ # Extra environment variables that should be set before invoking the command
30
+ # specified for docker:compose:server. These are set _in addition_ to env
31
+ # (and should be disjoint from env), and do not necessarily need to map the
32
+ # location of a container; they can be simple extra env values that are
33
+ # useful to change the server's behavior when it runs in cooperation
34
+ # with containers.
44
35
  #
45
- # Extra host env should be disjoint from host_env; if there is overlap
46
- # between the two, then extra_host_env will "win."
47
- attr_accessor :extra_host_env
36
+ # If there is overlap between env and server_env, then keys of server_env
37
+ # will "win"; they are set last.
38
+ attr_accessor :server_env
48
39
 
49
- # Services to bring up with `docker-compose up` before running any hosted
50
- # command. This is useful if your `docker-compose.yml` contains a service
51
- # definition for the app you will be hosting; if you host the app, you
52
- # want to specify all of the _other_ services, but not the app itself, since
53
- # that will run on the host.
54
- attr_accessor :host_services
55
-
56
- # Namespace to define the rake tasks under. Defaults to "docker:compose'.
57
- attr_accessor :rake_namespace
40
+ # Command to exec on the _host_ when someone invokes docker:compose:server.
41
+ # This is used to start up all containers and then run a server that
42
+ # depends on them and is properly linked to them.
43
+ attr_accessor :server
58
44
 
59
45
  # Construct Rake wrapper tasks for docker-compose. If a block is given,
60
46
  # yield self to the block before defining any tasks so their behavior
61
- # can be configured by calling #server_env=, #file= and so forth.
47
+ # can be configured by calling #env=, #file= and so forth.
62
48
  def initialize
63
49
  self.dir = Rake.application.original_dir
64
- self.project_name = nil
65
50
  self.file = 'docker-compose.yml'
66
- self.host_env = {}
67
- self.extra_host_env = {}
68
- self.rake_namespace = 'docker:compose'
51
+ self.env = {}
52
+ self.server_env = {}
69
53
  yield self if block_given?
70
54
 
71
- @shell = Backticks::Runner.new
72
- @session = Docker::Compose::Session.new(@shell, dir: dir, project_name: project_name, file: file)
55
+ @shell = Docker::Compose::Shell.new
56
+ @session = Docker::Compose::Session.new(@shell, dir:dir, file:file)
73
57
  @net_info = Docker::Compose::NetInfo.new
74
- @shell_printer = Docker::Compose::ShellPrinter.new
75
-
76
- @shell.interactive = true
77
58
 
78
59
  define
79
60
  end
80
61
 
81
- def define
82
- namespace rake_namespace do
83
- desc 'Print bash exports with IP/ports of running services'
84
- task :env do
85
- @shell.interactive = false # suppress useless 'port' output
86
-
87
- tty = STDOUT.tty?
88
- tlt = Rake.application.top_level_tasks.include?(task_name('env'))
89
-
90
- # user invoked this task directly; print some helpful tips on
91
- # how we intend it to be used.
92
- print_usage if tty && tlt
62
+ private def define
63
+ namespace :docker do
64
+ namespace :compose do
65
+ desc 'Print bash exports with IP/ports of running services'
66
+ task :env do
67
+ @shell.interactive = false # suppress useless 'port' output
68
+
69
+ if Rake.application.top_level_tasks.include? 'docker:compose:env'
70
+ # This task is being run as top-level; print some bash export
71
+ # statements or usage information depending on whether STDOUT
72
+ # is a tty.
73
+ if STDOUT.tty?
74
+ print_usage
75
+ else
76
+ export_env(print:true)
77
+ end
78
+ else
79
+ # This task is a dependency of something else; just export the
80
+ # environment variables for use in-process by other Rake tasks.
81
+ export_env(print:false)
82
+ end
83
+ end
93
84
 
94
- export_env(print: tlt)
85
+ desc 'Launch services needed to run this application'
86
+ task :up do
87
+ @shell.interactive = true # let user see what's happening
88
+ @session.up(detached:true)
89
+ end
95
90
 
96
- @shell.interactive = true
97
- end
91
+ desc 'Tail logs of all running services'
92
+ task :logs do
93
+ @session.logs
94
+ end
98
95
 
99
- desc 'Run command on host, linked to services in containers'
100
- task :host, [:command] => [task_name('env')] do |_task, args|
101
- if host_services
102
- @session.up(*host_services, detached: true)
103
- else
104
- @session.up(detached: true)
96
+ desc 'Stop services needed to run this application'
97
+ task :stop do
98
+ @session.stop
105
99
  end
106
100
 
107
- exec(args[:command])
101
+ desc 'Run application on the host, linked to services in containers'
102
+ task :server => ['docker:compose:up', 'docker:compose:env'] do
103
+ exec(self.server)
104
+ end
108
105
  end
109
106
  end
110
107
  end
111
- private :define
112
108
 
113
109
  # Substitute and set environment variables that point to network ports
114
110
  # published by docker-compose services. Optionally also print bash export
115
111
  # statements so this information can be made available to a user's shell.
116
- def export_env(print:)
117
- Docker::Compose::Mapper.map(host_env,
118
- session: @session,
119
- net_info: @net_info) do |k, v|
120
- ENV[k] = serialize_for_env(v)
121
- print_env(k, ENV[k]) if print
112
+ private def export_env(print:)
113
+ # First, do env substitutions in strict mode; don't catch BadSubstitution
114
+ # so the caller knows when he has a bogus value
115
+ mapper = Docker::Compose::Mapper.new(@session,
116
+ @net_info.docker_routable_ip)
117
+ self.env.each_pair do |k, v|
118
+ begin
119
+ v = mapper.map(v)
120
+ ENV[k] = v
121
+ print_env(k, v) if print
122
+ rescue Docker::Compose::Mapper::NoService
123
+ ENV[k] = nil
124
+ print_env(k, nil) if print
125
+ end
122
126
  end
123
127
 
124
- extra_host_env.each do |k, v|
125
- ENV[k] = serialize_for_env(v)
126
- print_env(k, ENV[k]) if print
128
+ # Next, do server substitutions in non-strict mode since server_env
129
+ # can contain arbitrary values.
130
+ mapper = Docker::Compose::Mapper.new(@session,
131
+ @net_info.docker_routable_ip,
132
+ strict:false)
133
+ self.server_env.each_pair do |k, v|
134
+ v = mapper.map(v)
135
+ ENV[k] = v
136
+ print_env(k, v) if print
127
137
  end
128
138
  end
129
- private :export_env
130
-
131
- # Transform a Ruby value into a String that can be stored in the
132
- # environment. This accepts nil, String, or Array and returns nil, String
133
- # or JSON-serialized Array.
134
- def serialize_for_env(v)
135
- case v
136
- when String
137
- v
138
- when NilClass
139
- nil
140
- when Array
141
- JSON.dump(v)
142
- else
143
- fail ArgumentError, "Can't represent a #{v.class} in the environment"
144
- end
145
- end
146
- private :serialize_for_env
147
139
 
148
- # Print an export or unset statement suitable for user's shell
149
- def print_env(k, v)
140
+ # Print a bash export or unset statement
141
+ private def print_env(k, v)
150
142
  if v
151
- puts @shell_printer.export(k, v)
143
+ puts format('export %s=%s', k, v)
152
144
  else
153
- puts @shell_printer.unset(k)
145
+ puts format('unset %s # service not running', k)
154
146
  end
155
147
  end
156
- private :print_env
157
-
158
- def print_usage
159
- command = "rake #{task_name('env')}"
160
- command = 'bundle exec ' + command if defined?(Bundler)
161
- puts @shell_printer.comment('To export these variables to your shell, run:')
162
- puts @shell_printer.comment(@shell_printer.eval_output(command))
163
- end
164
- private :print_usage
165
148
 
166
- def task_name(task)
167
- [rake_namespace, task].join(':')
149
+ private def print_usage
150
+ be = 'bundle exec ' if defined?(Bundler)
151
+ puts "# To export container network locations to your environment:"
152
+ puts %Q{eval "$(#{be}rake docker:compose:env)"}
153
+ puts
154
+ puts '# To learn which environment variables we will export:'
155
+ puts %Q{echo "$(#{be}rake docker:compose:env)"}
168
156
  end
169
- private :task_name
170
157
  end
171
158
  end
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
- require 'backticks'
3
- require 'yaml'
1
+ require 'docker/compose/future/session'
4
2
 
5
3
  module Docker::Compose
6
4
  # A Ruby OOP interface to a docker-compose session. A session is bound to
@@ -15,220 +13,69 @@ module Docker::Compose
15
13
  # allowed by the docker-compose CLI, and that options are sometimes renamed
16
14
  # for clarity, e.g. the "-d" flag always becomes the "detached:" kwarg.
17
15
  class Session
18
- # A Regex that matches all ANSI escape sequences.
19
- ANSI = /\033\[([0-9];?)+[a-z]/
20
-
21
- # Working directory (determines compose project name); default is Dir.pwd
22
- attr_reader :dir
23
-
24
- # Project name; default is not to pass a custom name
25
- attr_reader :project_name
26
-
27
- # Project file; default is 'docker-compose.yml'
28
- attr_reader :file
29
-
30
- # Reference to the last executed command.
31
- attr_reader :last_command
32
-
33
- def initialize(shell = Backticks::Runner.new(buffered: [:stderr], interactive: true),
34
- dir: Dir.pwd, project_name: nil, file: 'docker-compose.yml')
16
+ def initialize(shell=Docker::Compose::Shell.new,
17
+ dir:Dir.pwd, file:'docker-compose.yml')
35
18
  @shell = shell
36
- @project_name = project_name
37
19
  @dir = dir
38
20
  @file = file
39
- @last_command = nil
40
- end
41
-
42
- # Validate docker-compose file and return it as Hash
43
- # @return [Hash] the docker-compose config file
44
- # @raise [Error] if command fails
45
- def config(*args)
46
- config = strip_ansi(run!('config', *args))
47
- YAML.load(config)
48
21
  end
49
22
 
50
23
  # Monitor the logs of one or more containers.
51
24
  # @param [Array] services list of String service names to show logs for
52
25
  # @return [true] always returns true
53
- # @raise [Error] if command fails
26
+ # @raise [RuntimeError] if command fails
54
27
  def logs(*services)
55
28
  run!('logs', services)
56
29
  true
57
30
  end
58
31
 
59
- def ps(*services)
60
- inter = @shell.interactive
61
- @shell.interactive = false
62
-
63
- lines = strip_ansi(run!('ps', {q: true}, services)).split(/[\r\n]+/)
64
- containers = Collection.new
65
-
66
- lines.each do |id|
67
- containers << docker_ps(strip_ansi(id))
68
- end
69
-
70
- containers
71
- ensure
72
- @shell.interactive = inter
73
- end
74
-
75
- # Idempotently up the given services in the project.
32
+ # Idempotently run services in the project,
76
33
  # @param [Array] services list of String service names to run
77
34
  # @param [Boolean] detached if true, to start services in the background;
78
35
  # otherwise, monitor logs in the foreground and shutdown on Ctrl+C
79
- # @param [Integer] timeout how long to wait for each service to start
80
- # @param [Boolean] build if true, build images before starting containers
81
- # @param [Boolean] no_build if true, don't build images, even if they're
82
- # missing
36
+ # @param [Integer] timeout how long to wait for each service to stostart
37
+ # @param [Boolean] no_build if true, to skip building images for services
38
+ # that have a `build:` instruction in the docker-compose file
83
39
  # @param [Boolean] no_deps if true, just run specified services without
84
40
  # running the services that they depend on
85
41
  # @return [true] always returns true
86
- # @raise [Error] if command fails
42
+ # @raise [RuntimeError] if command fails
87
43
  def up(*services,
88
- abort_on_container_exit: false,
89
- detached: false, timeout: 10, build: false,
90
- exit_code_from: nil,
91
- no_build: false, no_deps: false, no_start: false)
92
- o = opts(
93
- abort_on_container_exit: [abort_on_container_exit, false],
94
- d: [detached, false],
95
- timeout: [timeout, 10],
96
- build: [build, false],
97
- exit_code_from: [exit_code_from, nil],
98
- no_build: [no_build, false],
99
- no_deps: [no_deps, false],
100
- no_start: [no_start, false]
101
- )
102
- run!('up', o, services)
44
+ detached:false, timeout:10, no_build:false, no_deps:false)
45
+ run!('up',
46
+ {d:detached, timeout:timeout, no_build:no_build, no_deps:no_deps},
47
+ services)
103
48
  true
104
49
  end
105
50
 
106
- # Idempotently scales the number of containers for given services in the project.
107
- # @param [Hash] container_count per service, e.g. {web: 2, worker: 3}
108
- # @param [Integer] timeout how long to wait for each service to scale
109
- def scale(container_count, timeout: 10)
110
- args = container_count.map {|service, count| "#{service}=#{count}"}
111
- o = opts(timeout: [timeout, 10])
112
- run!('scale', o, *args)
113
- end
114
-
115
- # Take the stack down
116
- def down(remove_volumes: false)
117
- run!('down', opts(v: [!!remove_volumes, false]))
118
- end
119
-
120
- # Pull images of services
121
- # @param [Array] services list of String service names to pull
122
- def pull(*services)
123
- run!('pull', *services)
124
- end
125
-
126
- def rm(*services, force: false, volumes: false)
127
- o = opts(f: [force, false], v: [volumes, false])
128
- run!('rm', o, services)
129
- end
130
-
131
- # Idempotently run an arbitrary command with a service container.
132
- # @param [String] service name to run
133
- # @param [String] cmd command statement to run
134
- # @param [Boolean] detached if true, to start services in the background;
135
- # otherwise, monitor logs in the foreground and shutdown on Ctrl+C
136
- # @param [Boolean] no_deps if true, just run specified services without
137
- # running the services that they depend on
138
- # @param [Array] env a list of environment variables (see: -e flag)
139
- # @param [Array] volumes a list of volumes to bind mount (see: -v flag)
140
- # @param [Boolean] rm remove the container when done
141
- # @param [Boolean] no_tty disable pseudo-tty allocation (see: -T flag)
142
- # @param [String] user run as specified username or uid (see: -u flag)
143
- # @raise [Error] if command fails
144
- def run(service, *cmd, detached: false, no_deps: false, volumes: [], env: [], rm: false, no_tty: false, user: nil, service_ports: false)
145
- 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])
146
- env_params = env.map { |v| { e: v } }
147
- volume_params = volumes.map { |v| { v: v } }
148
- run!('run', o, *env_params, *volume_params, service, cmd)
149
- end
150
-
151
- def restart(*services, timeout:10)
152
- o = opts(timeout: [timeout, 10])
153
- run!('restart', o, *services)
154
- end
155
-
156
- # Pause running services.
157
- # @param [Array] services list of String service names to run
158
- def pause(*services)
159
- run!('pause', *services)
160
- end
161
-
162
- # Unpause running services.
163
- # @param [Array] services list of String service names to run
164
- def unpause(*services)
165
- run!('unpause', *services)
166
- end
167
-
168
51
  # Stop running services.
169
52
  # @param [Array] services list of String service names to stop
170
53
  # @param [Integer] timeout how long to wait for each service to stop
171
- # @raise [Error] if command fails
172
- def stop(*services, timeout: 10)
173
- o = opts(timeout: [timeout, 10])
174
- run!('stop', o, services)
175
- end
176
-
177
- # Forcibly stop running services.
178
- # @param [Array] services list of String service names to stop
179
- # @param [String] name of murderous signal to use, default is 'KILL'
180
- # @see Signal.list for a list of acceptable signal names
181
- def kill(*services, signal: 'KILL')
182
- o = opts(signal: [signal, 'KILL'])
183
- run!('kill', o, services)
54
+ def stop(*services, timeout:10)
55
+ run!('stop', {timeout:timeout}, services)
184
56
  end
185
57
 
186
- # Figure out which interface(s) and port a given service port has been published to.
187
- #
188
- # **NOTE**: if Docker Compose is communicating with a remote Docker host, this method
189
- # returns IP addresses from the point of view of *that* host and its interfaces. If
190
- # you need to know the address as reachable from localhost, you probably want to use
191
- # `Mapper`.
192
- #
193
- # @see Docker::Compose::Mapper
194
- #
58
+ # Figure out which host a port a given service port has been published to.
195
59
  # @param [String] service name of service from docker-compose.yml
196
60
  # @param [Integer] port number of port
197
61
  # @param [String] protocol 'tcp' or 'udp'
198
62
  # @param [Integer] index of container (if multiple instances running)
199
- # @return [String,nil] an ip:port pair such as "0.0.0.0:32176" or nil if the service is not running
200
- # @raise [Error] if command fails
201
- def port(service, port, protocol: 'tcp', index: 1)
202
- inter = @shell.interactive
203
- @shell.interactive = false
204
-
205
- o = opts(protocol: [protocol, 'tcp'], index: [index, 1])
206
- s = strip_ansi(run!('port', o, service, port).strip)
207
- (!s.empty? && s) || nil
208
- rescue Error => e
209
- # Deal with docker-compose v1.11+
210
- if e.detail =~ /No container found/i
211
- nil
212
- else
213
- raise
214
- end
215
- ensure
216
- @shell.interactive = inter
63
+ def port(service, port, protocol:'tcp', index:1)
64
+ run!('port', {protocol:protocol, index:index}, service, port)
217
65
  end
218
66
 
219
67
  # Determine the installed version of docker-compose.
220
68
  # @param [Boolean] short whether to return terse version information
221
69
  # @return [String, Hash] if short==true, returns a version string;
222
- # otherwise, returns a Hash of component-name strings to version strings
223
- # @raise [Error] if command fails
224
- def version(short: false)
225
- o = opts(short: [short, false])
226
- result = run!('version', o, file: false, dir: false)
70
+ # otherwise, returns a Hash of component names to version strings
71
+ # @raise [RuntimeError] if command fails
72
+ def version(short:false)
73
+ result = run!('version', short:short, file:false, dir:false)
227
74
 
228
75
  if short
229
76
  result.strip
230
77
  else
231
- lines = result.split(/[\r\n]+/)
78
+ lines = result.split("\n")
232
79
  lines.inject({}) do |h, line|
233
80
  kv = line.split(/: +/, 2)
234
81
  h[kv.first] = kv.last
@@ -237,118 +84,30 @@ module Docker::Compose
237
84
  end
238
85
  end
239
86
 
240
- def build(*services, force_rm: false, no_cache: false, pull: false)
241
- o = opts(force_rm: [force_rm, false],
242
- no_cache: [no_cache, false],
243
- pull: [pull, false])
244
- result = run!('build', o, services)
245
- end
246
-
247
87
  # Run a docker-compose command without validating that the CLI parameters
248
88
  # make sense. Prepend project and file options if suitable.
249
89
  #
250
90
  # @see Docker::Compose::Shell#command
251
91
  #
252
- # @param [Array] args command-line arguments in the format accepted by
253
- # Backticks::Runner#command
92
+ # @param [Array] cmd subcommand words and options in the format accepted by
93
+ # Shell#command
254
94
  # @return [String] output of the command
255
- # @raise [Error] if command fails
256
- def run!(*args)
257
- project_name_args = if @project_name
258
- [{ project_name: @project_name }]
259
- else
260
- []
261
- end
262
- file_args = case @file
263
- when 'docker-compose.yml'
264
- []
265
- when Array
266
- # backticks sugar can't handle array values; build a list of hashes
267
- # IMPORTANT: preserve the order of the files so overrides work correctly
268
- file_args = @file.map { |filepath| { :file => filepath } }
269
- else
270
- # a single String (or Pathname, etc); use normal sugar to add it
271
- [{ file: @file.to_s }]
95
+ # @raise [RuntimeError] if command fails
96
+ def run!(*cmd)
97
+ project_opts = {
98
+ file: @file
99
+ }
100
+
101
+ Dir.chdir(@dir) do
102
+ result, output =
103
+ @shell.command('docker-compose', project_opts, *cmd)
104
+ (result == 0) || raise(RuntimeError,
105
+ "#{cmd.first} failed with status #{result}")
106
+ output
272
107
  end
273
-
274
- @shell.chdir = dir
275
- @last_command = @shell.run('docker-compose', *project_name_args, *file_args, *args).join
276
- status = @last_command.status
277
- out = @last_command.captured_output
278
- err = @last_command.captured_error
279
- status.success? || fail(Error.new(args.first, status, out+err))
280
- out
281
- end
282
-
283
- private
284
-
285
- def docker_ps(id)
286
- cmd = @shell.run('docker', 'ps', a: true, f: "id=#{id}", no_trunc: true, format: Container::PS_FMT).join
287
- status, out, err = cmd.status, cmd.captured_output, cmd.captured_error
288
- raise Error.new('docker ps', status, out+err) unless status.success?
289
- lines = out.split(/[\r\n]+/)
290
- return nil if lines.empty?
291
- l = strip_ansi(lines.shift)
292
- m = parse(l)
293
- raise Error.new('docker ps', status, "Cannot parse output: '#{l}'") unless m
294
- raise Error.new('docker ps', status, "Cannot parse output: '#{l}'") unless m.size == 7
295
- return Container.new(*m)
296
- end
297
-
298
- # strip default-valued options. the value of each kw should be a pair:
299
- # [0] is present value
300
- # [1] is default value
301
- def opts(**kws)
302
- res = {}
303
- kws.each_pair do |kw, v|
304
- res[kw] = v[0] unless v[0] == v[1]
305
- end
306
- res
307
- end
308
-
309
- # strip all ANSI escape sequences from str
310
- def strip_ansi(str)
311
- str.gsub(ANSI, '')
312
108
  end
313
109
 
314
- # Parse a string that consists of a sequence of values enclosed within parentheses.
315
- # Ignore any bytes that are outside of parentheses. Values may include nested parentheses.
316
- #
317
- # @param [String] str e.g. "(foo) ((bar)) ... (baz)"
318
- # @return [Array] e.g. ["foo", "bar", "baz"]
319
- def parse(str)
320
- fields = []
321
- nest = 0
322
- field = ''
323
- str.each_char do |ch|
324
- got = false
325
- if nest == 0
326
- if ch == '('
327
- nest += 1
328
- end
329
- else
330
- if ch == '('
331
- nest += 1
332
- field << ch
333
- elsif ch == ')'
334
- nest -= 1
335
- if nest == 0
336
- got = true
337
- else
338
- field << ch
339
- end
340
- else
341
- field << ch
342
- end
343
- end
344
-
345
- if got
346
- fields << field
347
- field = ''
348
- end
349
- end
350
-
351
- fields
352
- end
110
+ # Simulate behaviors from Docker 1.5
111
+ include Docker::Compose::Future::Session
353
112
  end
354
113
  end