docker-api 2.0.0.pre.1
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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +633 -0
- data/lib/docker-api.rb +1 -0
- data/lib/docker.rb +136 -0
- data/lib/docker/base.rb +25 -0
- data/lib/docker/connection.rb +93 -0
- data/lib/docker/container.rb +360 -0
- data/lib/docker/error.rb +40 -0
- data/lib/docker/event.rb +126 -0
- data/lib/docker/exec.rb +107 -0
- data/lib/docker/image.rb +356 -0
- data/lib/docker/messages.rb +67 -0
- data/lib/docker/messages_stack.rb +25 -0
- data/lib/docker/network.rb +82 -0
- data/lib/docker/rake_task.rb +39 -0
- data/lib/docker/util.rb +279 -0
- data/lib/docker/version.rb +4 -0
- data/lib/docker/volume.rb +44 -0
- data/lib/excon/middlewares/hijack.rb +49 -0
- metadata +201 -0
data/lib/docker-api.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'docker'
|
data/lib/docker.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'excon'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'base64'
|
6
|
+
require 'find'
|
7
|
+
require 'rubygems/package'
|
8
|
+
require 'uri'
|
9
|
+
require 'open-uri'
|
10
|
+
|
11
|
+
# Add the Hijack middleware at the top of the middleware stack so it can
|
12
|
+
# potentially hijack HTTP sockets (when attaching to stdin) before other
|
13
|
+
# middlewares try and parse the response.
|
14
|
+
require 'excon/middlewares/hijack'
|
15
|
+
Excon.defaults[:middlewares].unshift Excon::Middleware::Hijack
|
16
|
+
|
17
|
+
Excon.defaults[:middlewares] << Excon::Middleware::RedirectFollower
|
18
|
+
|
19
|
+
# The top-level module for this gem. Its purpose is to hold global
|
20
|
+
# configuration variables that are used as defaults in other classes.
|
21
|
+
module Docker
|
22
|
+
attr_accessor :creds, :logger
|
23
|
+
|
24
|
+
require 'docker/error'
|
25
|
+
require 'docker/connection'
|
26
|
+
require 'docker/base'
|
27
|
+
require 'docker/container'
|
28
|
+
require 'docker/network'
|
29
|
+
require 'docker/event'
|
30
|
+
require 'docker/exec'
|
31
|
+
require 'docker/image'
|
32
|
+
require 'docker/messages_stack'
|
33
|
+
require 'docker/messages'
|
34
|
+
require 'docker/util'
|
35
|
+
require 'docker/version'
|
36
|
+
require 'docker/volume'
|
37
|
+
require 'docker/rake_task' if defined?(Rake::Task)
|
38
|
+
|
39
|
+
def default_socket_url
|
40
|
+
'unix:///var/run/docker.sock'
|
41
|
+
end
|
42
|
+
|
43
|
+
def env_url
|
44
|
+
ENV['DOCKER_URL'] || ENV['DOCKER_HOST']
|
45
|
+
end
|
46
|
+
|
47
|
+
def env_options
|
48
|
+
if cert_path = ENV['DOCKER_CERT_PATH']
|
49
|
+
{
|
50
|
+
client_cert: File.join(cert_path, 'cert.pem'),
|
51
|
+
client_key: File.join(cert_path, 'key.pem'),
|
52
|
+
ssl_ca_file: File.join(cert_path, 'ca.pem'),
|
53
|
+
scheme: 'https'
|
54
|
+
}.merge(ssl_options)
|
55
|
+
else
|
56
|
+
{}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def ssl_options
|
61
|
+
if ENV['DOCKER_SSL_VERIFY'] == 'false'
|
62
|
+
{
|
63
|
+
ssl_verify_peer: false
|
64
|
+
}
|
65
|
+
else
|
66
|
+
{}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def url
|
71
|
+
@url ||= env_url || default_socket_url
|
72
|
+
# docker uses a default notation tcp:// which means tcp://localhost:2375
|
73
|
+
if @url == 'tcp://'
|
74
|
+
@url = 'tcp://localhost:2375'
|
75
|
+
end
|
76
|
+
@url
|
77
|
+
end
|
78
|
+
|
79
|
+
def options
|
80
|
+
@options ||= env_options
|
81
|
+
end
|
82
|
+
|
83
|
+
def url=(new_url)
|
84
|
+
@url = new_url
|
85
|
+
reset_connection!
|
86
|
+
end
|
87
|
+
|
88
|
+
def options=(new_options)
|
89
|
+
@options = env_options.merge(new_options || {})
|
90
|
+
reset_connection!
|
91
|
+
end
|
92
|
+
|
93
|
+
def connection
|
94
|
+
@connection ||= Connection.new(url, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def reset!
|
98
|
+
@url = nil
|
99
|
+
@options = nil
|
100
|
+
reset_connection!
|
101
|
+
end
|
102
|
+
|
103
|
+
def reset_connection!
|
104
|
+
@connection = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get the version of Go, Docker, and optionally the Git commit.
|
108
|
+
def version(connection = self.connection)
|
109
|
+
Util.parse_json(connection.get('/version'))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get more information about the Docker server.
|
113
|
+
def info(connection = self.connection)
|
114
|
+
Util.parse_json(connection.get('/info'))
|
115
|
+
end
|
116
|
+
|
117
|
+
# Ping the Docker server.
|
118
|
+
def ping(connection = self.connection)
|
119
|
+
connection.get('/_ping')
|
120
|
+
end
|
121
|
+
|
122
|
+
# Login to the Docker registry.
|
123
|
+
def authenticate!(options = {}, connection = self.connection)
|
124
|
+
creds = MultiJson.dump(options)
|
125
|
+
connection.post('/auth', {}, body: creds)
|
126
|
+
@creds = creds
|
127
|
+
true
|
128
|
+
rescue Docker::Error::ServerError, Docker::Error::UnauthorizedError
|
129
|
+
raise Docker::Error::AuthenticationError
|
130
|
+
end
|
131
|
+
|
132
|
+
module_function :default_socket_url, :env_url, :url, :url=, :env_options,
|
133
|
+
:options, :options=, :creds, :creds=, :logger, :logger=,
|
134
|
+
:connection, :reset!, :reset_connection!, :version, :info,
|
135
|
+
:ping, :authenticate!, :ssl_options
|
136
|
+
end
|
data/lib/docker/base.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# This class is a base class for Docker Container and Image.
|
2
|
+
# It is implementing accessor methods for the models attributes.
|
3
|
+
module Docker::Base
|
4
|
+
include Docker::Error
|
5
|
+
|
6
|
+
attr_accessor :connection, :info
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
# The private new method accepts a connection and a hash of options that must include an id.
|
10
|
+
def initialize(connection, hash={})
|
11
|
+
unless connection.is_a?(Docker::Connection)
|
12
|
+
raise ArgumentError, "Expected a Docker::Connection, got: #{connection}."
|
13
|
+
end
|
14
|
+
normalize_hash(hash)
|
15
|
+
@connection, @info, @id = connection, hash, hash['id']
|
16
|
+
raise ArgumentError, "Must have id, got: #{hash}" unless @id
|
17
|
+
end
|
18
|
+
|
19
|
+
# The docker-api will some time return "ID" other times it will return "Id"
|
20
|
+
# and other times it will return "id". This method normalize it to "id"
|
21
|
+
# The volumes endpoint returns Name instead of ID, added in the normalize function
|
22
|
+
def normalize_hash(hash)
|
23
|
+
hash["id"] ||= hash.delete("ID") || hash.delete("Id")
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# This class represents a Connection to a Docker server. The Connection is
|
2
|
+
# immutable in that once the url and options is set they cannot be changed.
|
3
|
+
class Docker::Connection
|
4
|
+
include Docker::Error
|
5
|
+
|
6
|
+
attr_reader :url, :options
|
7
|
+
|
8
|
+
# Create a new Connection. This method takes a url (String) and options
|
9
|
+
# (Hash). These are passed to Excon, so any options valid for `Excon.new`
|
10
|
+
# can be passed here.
|
11
|
+
def initialize(url, opts)
|
12
|
+
case
|
13
|
+
when !url.is_a?(String)
|
14
|
+
raise ArgumentError, "Expected a String, got: '#{url}'"
|
15
|
+
when !opts.is_a?(Hash)
|
16
|
+
raise ArgumentError, "Expected a Hash, got: '#{opts}'"
|
17
|
+
else
|
18
|
+
uri = URI.parse(url)
|
19
|
+
if uri.scheme == "unix"
|
20
|
+
@url, @options = 'unix:///', {:socket => uri.path}.merge(opts)
|
21
|
+
elsif uri.scheme =~ /^(https?|tcp)$/
|
22
|
+
@url, @options = url, opts
|
23
|
+
else
|
24
|
+
@url, @options = "http://#{uri}", opts
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# The actual client that sends HTTP methods to the Docker server. This value
|
30
|
+
# is not cached, since doing so may cause socket errors after bad requests.
|
31
|
+
def resource
|
32
|
+
Excon.new(url, options)
|
33
|
+
end
|
34
|
+
private :resource
|
35
|
+
|
36
|
+
# Send a request to the server with the `
|
37
|
+
def request(*args, &block)
|
38
|
+
request = compile_request_params(*args, &block)
|
39
|
+
log_request(request)
|
40
|
+
resource.request(request).body
|
41
|
+
rescue Excon::Errors::BadRequest => ex
|
42
|
+
raise ClientError, ex.response.body
|
43
|
+
rescue Excon::Errors::Unauthorized => ex
|
44
|
+
raise UnauthorizedError, ex.response.body
|
45
|
+
rescue Excon::Errors::NotFound => ex
|
46
|
+
raise NotFoundError, ex.response.body
|
47
|
+
rescue Excon::Errors::Conflict => ex
|
48
|
+
raise ConflictError, ex.response.body
|
49
|
+
rescue Excon::Errors::InternalServerError => ex
|
50
|
+
raise ServerError, ex.response.body
|
51
|
+
rescue Excon::Errors::Timeout => ex
|
52
|
+
raise TimeoutError, ex.message
|
53
|
+
end
|
54
|
+
|
55
|
+
def log_request(request)
|
56
|
+
if Docker.logger
|
57
|
+
Docker.logger.debug(
|
58
|
+
[request[:method], request[:path], request[:query], request[:body]]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Delegate all HTTP methods to the #request.
|
64
|
+
[:get, :put, :post, :delete].each do |method|
|
65
|
+
define_method(method) { |*args, &block| request(method, *args, &block) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
"Docker::Connection { :url => #{url}, :options => #{options} }"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
# Given an HTTP method, path, optional query, extra options, and block,
|
74
|
+
# compiles a request.
|
75
|
+
def compile_request_params(http_method, path, query = nil, opts = nil, &block)
|
76
|
+
query ||= {}
|
77
|
+
opts ||= {}
|
78
|
+
headers = opts.delete(:headers) || {}
|
79
|
+
content_type = opts[:body].nil? ? 'text/plain' : 'application/json'
|
80
|
+
user_agent = "Swipely/Docker-API #{Docker::VERSION}"
|
81
|
+
{
|
82
|
+
:method => http_method,
|
83
|
+
:path => path,
|
84
|
+
:query => query,
|
85
|
+
:headers => { 'Content-Type' => content_type,
|
86
|
+
'User-Agent' => user_agent,
|
87
|
+
}.merge(headers),
|
88
|
+
:expects => (200..204).to_a << 301 << 304,
|
89
|
+
:idempotent => http_method == :get,
|
90
|
+
:request_block => block,
|
91
|
+
}.merge(opts).reject { |_, v| v.nil? }
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
# This class represents a Docker Container. It's important to note that nothing
|
2
|
+
# is cached so that the information is always up to date.
|
3
|
+
class Docker::Container
|
4
|
+
include Docker::Base
|
5
|
+
|
6
|
+
# Update the @info hash, which is the only mutable state in this object.
|
7
|
+
# e.g. if you would like a live status from the #info hash, call #refresh! first.
|
8
|
+
def refresh!
|
9
|
+
other = Docker::Container.all({all: true}, connection).find { |c|
|
10
|
+
c.id.start_with?(self.id) || self.id.start_with?(c.id)
|
11
|
+
}
|
12
|
+
|
13
|
+
info.merge!(self.json)
|
14
|
+
other && info.merge!(other.info) { |key, info_value, other_value| info_value }
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return a List of Hashes that represents the top running processes.
|
19
|
+
def top(opts = {})
|
20
|
+
format = opts.delete(:format) { :array }
|
21
|
+
resp = Docker::Util.parse_json(connection.get(path_for(:top), opts))
|
22
|
+
if resp['Processes'].nil?
|
23
|
+
format == :array ? [] : {}
|
24
|
+
else
|
25
|
+
format == :array ? resp['Processes'].map { |ary| Hash[resp['Titles'].zip(ary)] } : resp
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Wait for the current command to finish executing. Default wait time is
|
30
|
+
# `Excon.options[:read_timeout]`.
|
31
|
+
def wait(time = nil)
|
32
|
+
excon_params = { :read_timeout => time }
|
33
|
+
resp = connection.post(path_for(:wait), nil, excon_params)
|
34
|
+
Docker::Util.parse_json(resp)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Given a command and an optional number of seconds to wait for the currently
|
38
|
+
# executing command, creates a new Container to run the specified command. If
|
39
|
+
# the command that is currently executing does not return a 0 status code, an
|
40
|
+
# UnexpectedResponseError is raised.
|
41
|
+
def run(cmd, time = 1000)
|
42
|
+
if (code = tap(&:start).wait(time)['StatusCode']).zero?
|
43
|
+
commit.run(cmd)
|
44
|
+
else
|
45
|
+
raise UnexpectedResponseError, "Command returned status code #{code}."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create an Exec instance inside the container
|
50
|
+
#
|
51
|
+
# @param command [String, Array] The command to run inside the Exec instance
|
52
|
+
# @param options [Hash] The options to pass to Docker::Exec
|
53
|
+
#
|
54
|
+
# @return [Docker::Exec] The Exec instance
|
55
|
+
def exec(command, options = {}, &block)
|
56
|
+
# Establish values
|
57
|
+
tty = options.delete(:tty) || false
|
58
|
+
detach = options.delete(:detach) || false
|
59
|
+
user = options.delete(:user)
|
60
|
+
stdin = options.delete(:stdin)
|
61
|
+
stdout = options.delete(:stdout) || !detach
|
62
|
+
stderr = options.delete(:stderr) || !detach
|
63
|
+
wait = options.delete(:wait)
|
64
|
+
|
65
|
+
opts = {
|
66
|
+
'Container' => self.id,
|
67
|
+
'User' => user,
|
68
|
+
'AttachStdin' => !!stdin,
|
69
|
+
'AttachStdout' => stdout,
|
70
|
+
'AttachStderr' => stderr,
|
71
|
+
'Tty' => tty,
|
72
|
+
'Cmd' => command
|
73
|
+
}.merge(options)
|
74
|
+
|
75
|
+
# Create Exec Instance
|
76
|
+
instance = Docker::Exec.create(
|
77
|
+
opts,
|
78
|
+
self.connection
|
79
|
+
)
|
80
|
+
|
81
|
+
start_opts = {
|
82
|
+
:tty => tty,
|
83
|
+
:stdin => stdin,
|
84
|
+
:detach => detach,
|
85
|
+
:wait => wait
|
86
|
+
}
|
87
|
+
|
88
|
+
if detach
|
89
|
+
instance.start!(start_opts)
|
90
|
+
return instance
|
91
|
+
else
|
92
|
+
instance.start!(start_opts, &block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Export the Container as a tar.
|
97
|
+
def export(&block)
|
98
|
+
connection.get(path_for(:export), {}, :response_block => block)
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Attach to a container's standard streams / logs.
|
103
|
+
def attach(options = {}, excon_params = {}, &block)
|
104
|
+
stdin = options.delete(:stdin)
|
105
|
+
tty = options.delete(:tty)
|
106
|
+
|
107
|
+
opts = {
|
108
|
+
:stream => true, :stdout => true, :stderr => true
|
109
|
+
}.merge(options)
|
110
|
+
# Creates list to store stdout and stderr messages
|
111
|
+
msgs = Docker::Messages.new
|
112
|
+
|
113
|
+
if stdin
|
114
|
+
# If attaching to stdin, we must hijack the underlying TCP connection
|
115
|
+
# so we can stream stdin to the remote Docker process
|
116
|
+
opts[:stdin] = true
|
117
|
+
excon_params[:hijack_block] = Docker::Util.hijack_for(stdin, block,
|
118
|
+
msgs, tty)
|
119
|
+
else
|
120
|
+
excon_params[:response_block] = Docker::Util.attach_for(block, msgs, tty)
|
121
|
+
end
|
122
|
+
|
123
|
+
connection.post(
|
124
|
+
path_for(:attach),
|
125
|
+
opts,
|
126
|
+
excon_params
|
127
|
+
)
|
128
|
+
[msgs.stdout_messages, msgs.stderr_messages]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Create an Image from a Container's change.s
|
132
|
+
def commit(options = {})
|
133
|
+
options.merge!('container' => self.id[0..7])
|
134
|
+
# [code](https://github.com/dotcloud/docker/blob/v0.6.3/commands.go#L1115)
|
135
|
+
# Based on the link, the config passed as run, needs to be passed as the
|
136
|
+
# body of the post so capture it, remove from the options, and pass it via
|
137
|
+
# the post body
|
138
|
+
config = MultiJson.dump(options.delete('run'))
|
139
|
+
hash = Docker::Util.parse_json(
|
140
|
+
connection.post('/commit', options, body: config)
|
141
|
+
)
|
142
|
+
Docker::Image.send(:new, self.connection, hash)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return a String representation of the Container.
|
146
|
+
def to_s
|
147
|
+
"Docker::Container { :id => #{self.id}, :connection => #{self.connection} }"
|
148
|
+
end
|
149
|
+
|
150
|
+
# #json returns information about the Container, #changes returns a list of
|
151
|
+
# the changes the Container has made to the filesystem.
|
152
|
+
[:json, :changes].each do |method|
|
153
|
+
define_method(method) do |opts = {}|
|
154
|
+
Docker::Util.parse_json(connection.get(path_for(method), opts))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def logs(opts = {})
|
159
|
+
connection.get(path_for(:logs), opts)
|
160
|
+
end
|
161
|
+
|
162
|
+
def stats(options = {})
|
163
|
+
if block_given?
|
164
|
+
options[:read_timeout] ||= 10
|
165
|
+
options[:idempotent] ||= false
|
166
|
+
parser = lambda do |chunk, remaining_bytes, total_bytes|
|
167
|
+
yield Docker::Util.parse_json(chunk)
|
168
|
+
end
|
169
|
+
begin
|
170
|
+
connection.get(path_for(:stats), nil, {response_block: parser}.merge(options))
|
171
|
+
rescue Docker::Error::TimeoutError
|
172
|
+
# If the container stops, the docker daemon will hold the connection
|
173
|
+
# open forever, but stop sending events.
|
174
|
+
# So this Timeout indicates the stream is over.
|
175
|
+
end
|
176
|
+
else
|
177
|
+
Docker::Util.parse_json(connection.get(path_for(:stats), {stream: 0}.merge(options)))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def rename(new_name)
|
182
|
+
query = {}
|
183
|
+
query['name'] = new_name
|
184
|
+
connection.post(path_for(:rename), query)
|
185
|
+
end
|
186
|
+
|
187
|
+
def update(opts)
|
188
|
+
connection.post(path_for(:update), {}, body: MultiJson.dump(opts))
|
189
|
+
end
|
190
|
+
|
191
|
+
def streaming_logs(opts = {}, &block)
|
192
|
+
stack_size = opts.delete('stack_size') || opts.delete(:stack_size) || -1
|
193
|
+
tty = opts.delete('tty') || opts.delete(:tty) || false
|
194
|
+
msgs = Docker::MessagesStack.new(stack_size)
|
195
|
+
excon_params = {response_block: Docker::Util.attach_for(block, msgs, tty), idempotent: false}
|
196
|
+
|
197
|
+
connection.get(path_for(:logs), opts, excon_params)
|
198
|
+
msgs.messages.join
|
199
|
+
end
|
200
|
+
|
201
|
+
def start!(opts = {})
|
202
|
+
connection.post(path_for(:start), {}, body: MultiJson.dump(opts))
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
def kill!(opts = {})
|
207
|
+
connection.post(path_for(:kill), opts)
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
# #start! and #kill! both perform the associated action and
|
212
|
+
# return the Container. #start and #kill do the same,
|
213
|
+
# but rescue from ServerErrors.
|
214
|
+
[:start, :kill].each do |method|
|
215
|
+
define_method(method) do |*args|
|
216
|
+
begin; public_send(:"#{method}!", *args); rescue ServerError; self end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# #stop! and #restart! both perform the associated action and
|
221
|
+
# return the Container. #stop and #restart do the same,
|
222
|
+
# but rescue from ServerErrors.
|
223
|
+
[:stop, :restart].each do |method|
|
224
|
+
define_method(:"#{method}!") do |opts = {}|
|
225
|
+
timeout = opts.delete('timeout')
|
226
|
+
query = {}
|
227
|
+
request_options = {
|
228
|
+
:body => MultiJson.dump(opts)
|
229
|
+
}
|
230
|
+
if timeout
|
231
|
+
query['t'] = timeout
|
232
|
+
# Ensure request does not timeout before Docker timeout
|
233
|
+
request_options.merge!(
|
234
|
+
read_timeout: timeout.to_i + 5,
|
235
|
+
write_timeout: timeout.to_i + 5
|
236
|
+
)
|
237
|
+
end
|
238
|
+
connection.post(path_for(method), query, request_options)
|
239
|
+
self
|
240
|
+
end
|
241
|
+
|
242
|
+
define_method(method) do |*args|
|
243
|
+
begin; public_send(:"#{method}!", *args); rescue ServerError; self end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# remove container
|
248
|
+
def remove(options = {})
|
249
|
+
connection.delete("/containers/#{self.id}", options)
|
250
|
+
nil
|
251
|
+
end
|
252
|
+
alias_method :delete, :remove
|
253
|
+
|
254
|
+
# pause and unpause containers
|
255
|
+
# #pause! and #unpause! both perform the associated action and
|
256
|
+
# return the Container. #pause and #unpause do the same,
|
257
|
+
# but rescue from ServerErrors.
|
258
|
+
[:pause, :unpause].each do |method|
|
259
|
+
define_method(:"#{method}!") do
|
260
|
+
connection.post path_for(method)
|
261
|
+
self
|
262
|
+
end
|
263
|
+
|
264
|
+
define_method(method) do
|
265
|
+
begin; public_send(:"#{method}!"); rescue ServerError; self; end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def archive_out(path, &block)
|
270
|
+
connection.get(
|
271
|
+
path_for(:archive),
|
272
|
+
{ 'path' => path },
|
273
|
+
:response_block => block
|
274
|
+
)
|
275
|
+
self
|
276
|
+
end
|
277
|
+
|
278
|
+
def archive_in(inputs, output_path, opts = {})
|
279
|
+
file_hash = Docker::Util.file_hash_from_paths([*inputs])
|
280
|
+
tar = StringIO.new(Docker::Util.create_tar(file_hash))
|
281
|
+
archive_in_stream(output_path, opts) do
|
282
|
+
tar.read(Excon.defaults[:chunk_size]).to_s
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def archive_in_stream(output_path, opts = {}, &block)
|
287
|
+
overwrite = opts[:overwrite] || opts['overwrite'] || false
|
288
|
+
|
289
|
+
connection.put(
|
290
|
+
path_for(:archive),
|
291
|
+
{ 'path' => output_path, 'noOverwriteDirNonDir' => !overwrite },
|
292
|
+
:headers => {
|
293
|
+
'Content-Type' => 'application/x-tar'
|
294
|
+
},
|
295
|
+
&block
|
296
|
+
)
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
def read_file(path)
|
301
|
+
content = StringIO.new
|
302
|
+
archive_out(path) do |chunk|
|
303
|
+
content.write chunk
|
304
|
+
end
|
305
|
+
|
306
|
+
content.rewind
|
307
|
+
|
308
|
+
Gem::Package::TarReader.new(content) do |tar|
|
309
|
+
tar.each do |tarfile|
|
310
|
+
return tarfile.read
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def store_file(path, file_content)
|
316
|
+
output_io = StringIO.new(
|
317
|
+
Docker::Util.create_tar(
|
318
|
+
path => file_content
|
319
|
+
)
|
320
|
+
)
|
321
|
+
|
322
|
+
archive_in_stream("/", overwrite: true) { output_io.read }
|
323
|
+
end
|
324
|
+
|
325
|
+
# Create a new Container.
|
326
|
+
def self.create(opts = {}, conn = Docker.connection)
|
327
|
+
query = opts.select {|key| ['name', :name].include?(key) }
|
328
|
+
clean_opts = opts.reject {|key| ['name', :name].include?(key) }
|
329
|
+
resp = conn.post('/containers/create', query, :body => MultiJson.dump(clean_opts))
|
330
|
+
hash = Docker::Util.parse_json(resp) || {}
|
331
|
+
new(conn, hash)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Return the container with specified ID
|
335
|
+
def self.get(id, opts = {}, conn = Docker.connection)
|
336
|
+
container_json = conn.get("/containers/#{id}/json", opts)
|
337
|
+
hash = Docker::Util.parse_json(container_json) || {}
|
338
|
+
new(conn, hash)
|
339
|
+
end
|
340
|
+
|
341
|
+
# Return all of the Containers.
|
342
|
+
def self.all(opts = {}, conn = Docker.connection)
|
343
|
+
hashes = Docker::Util.parse_json(conn.get('/containers/json', opts)) || []
|
344
|
+
hashes.map { |hash| new(conn, hash) }
|
345
|
+
end
|
346
|
+
|
347
|
+
# Prune images
|
348
|
+
def self.prune(conn = Docker.connection)
|
349
|
+
conn.post("/containers/prune", {})
|
350
|
+
nil
|
351
|
+
end
|
352
|
+
|
353
|
+
# Convenience method to return the path for a particular resource.
|
354
|
+
def path_for(resource)
|
355
|
+
"/containers/#{self.id}/#{resource}"
|
356
|
+
end
|
357
|
+
|
358
|
+
private :path_for
|
359
|
+
private_class_method :new
|
360
|
+
end
|