docker-api 2.0.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|