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/error.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# This module holds the Errors for the gem.
|
2
|
+
module Docker::Error
|
3
|
+
|
4
|
+
# The default error. It's never actually raised, but can be used to catch all
|
5
|
+
# gem-specific errors that are thrown as they all subclass from this.
|
6
|
+
class DockerError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when invalid arguments are passed to a method.
|
9
|
+
class ArgumentError < DockerError; end
|
10
|
+
|
11
|
+
# Raised when a request returns a 400.
|
12
|
+
class ClientError < DockerError; end
|
13
|
+
|
14
|
+
# Raised when a request returns a 401.
|
15
|
+
class UnauthorizedError < DockerError; end
|
16
|
+
|
17
|
+
# Raised when a request returns a 404.
|
18
|
+
class NotFoundError < DockerError; end
|
19
|
+
|
20
|
+
# Raised when a request returns a 409.
|
21
|
+
class ConflictError < DockerError; end
|
22
|
+
|
23
|
+
# Raised when a request returns a 500.
|
24
|
+
class ServerError < DockerError; end
|
25
|
+
|
26
|
+
# Raised when there is an unexpected response code / body.
|
27
|
+
class UnexpectedResponseError < DockerError; end
|
28
|
+
|
29
|
+
# Raised when there is an incompatible version of Docker.
|
30
|
+
class VersionError < DockerError; end
|
31
|
+
|
32
|
+
# Raised when a request times out.
|
33
|
+
class TimeoutError < DockerError; end
|
34
|
+
|
35
|
+
# Raised when login fails.
|
36
|
+
class AuthenticationError < DockerError; end
|
37
|
+
|
38
|
+
# Raised when an IO action fails.
|
39
|
+
class IOError < DockerError; end
|
40
|
+
end
|
data/lib/docker/event.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# This class represents a Docker Event.
|
2
|
+
class Docker::Event
|
3
|
+
include Docker::Error
|
4
|
+
|
5
|
+
# Represents the actor object nested within an event
|
6
|
+
class Actor
|
7
|
+
attr_accessor :ID, :Attributes
|
8
|
+
|
9
|
+
def initialize(actor_attributes = {})
|
10
|
+
[:ID, :Attributes].each do |sym|
|
11
|
+
value = actor_attributes[sym]
|
12
|
+
if value.nil?
|
13
|
+
value = actor_attributes[sym.to_s]
|
14
|
+
end
|
15
|
+
send("#{sym}=", value)
|
16
|
+
end
|
17
|
+
|
18
|
+
if self.Attributes.nil?
|
19
|
+
self.Attributes = {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :id, :ID
|
24
|
+
alias_method :attributes, :Attributes
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
include Docker::Error
|
29
|
+
|
30
|
+
def stream(opts = {}, conn = Docker.connection, &block)
|
31
|
+
conn.get('/events', opts, :response_block => lambda { |b, r, t|
|
32
|
+
b.each_line do |line|
|
33
|
+
block.call(new_event(line, r, t))
|
34
|
+
end
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
def since(since, opts = {}, conn = Docker.connection, &block)
|
39
|
+
stream(opts.merge(:since => since), conn, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_event(body, remaining, total)
|
43
|
+
return if body.nil? || body.empty?
|
44
|
+
json = Docker::Util.parse_json(body)
|
45
|
+
Docker::Event.new(json)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :Type, :Action, :time, :timeNano
|
50
|
+
attr_reader :Actor
|
51
|
+
# Deprecated interface
|
52
|
+
attr_accessor :status, :from
|
53
|
+
|
54
|
+
def initialize(event_attributes = {})
|
55
|
+
[:Type, :Action, :Actor, :time, :timeNano, :status, :from].each do |sym|
|
56
|
+
value = event_attributes[sym]
|
57
|
+
if value.nil?
|
58
|
+
value = event_attributes[sym.to_s]
|
59
|
+
end
|
60
|
+
send("#{sym}=", value)
|
61
|
+
end
|
62
|
+
|
63
|
+
if @Actor.nil?
|
64
|
+
value = event_attributes[:id]
|
65
|
+
if value.nil?
|
66
|
+
value = event_attributes['id']
|
67
|
+
end
|
68
|
+
self.Actor = Actor.new(ID: value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ID
|
73
|
+
self.actor.ID
|
74
|
+
end
|
75
|
+
|
76
|
+
def Actor=(actor)
|
77
|
+
return if actor.nil?
|
78
|
+
if actor.is_a? Actor
|
79
|
+
@Actor = actor
|
80
|
+
else
|
81
|
+
@Actor = Actor.new(actor)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_method :type, :Type
|
86
|
+
alias_method :action, :Action
|
87
|
+
alias_method :actor, :Actor
|
88
|
+
alias_method :time_nano, :timeNano
|
89
|
+
alias_method :id, :ID
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
if type.nil? && action.nil?
|
93
|
+
to_s_legacy
|
94
|
+
else
|
95
|
+
to_s_actor_style
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def to_s_legacy
|
102
|
+
attributes = []
|
103
|
+
attributes << "from=#{from}" unless from.nil?
|
104
|
+
|
105
|
+
unless attributes.empty?
|
106
|
+
attribute_string = "(#{attributes.join(', ')}) "
|
107
|
+
end
|
108
|
+
|
109
|
+
"Docker::Event { #{time} #{status} #{id} #{attribute_string}}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s_actor_style
|
113
|
+
most_accurate_time = time_nano || time
|
114
|
+
|
115
|
+
attributes = []
|
116
|
+
actor.attributes.each do |attribute, value|
|
117
|
+
attributes << "#{attribute}=#{value}"
|
118
|
+
end
|
119
|
+
|
120
|
+
unless attributes.empty?
|
121
|
+
attribute_string = "(#{attributes.join(', ')}) "
|
122
|
+
end
|
123
|
+
|
124
|
+
"Docker::Event { #{most_accurate_time} #{type} #{action} #{actor.id} #{attribute_string}}"
|
125
|
+
end
|
126
|
+
end
|
data/lib/docker/exec.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# This class represents a Docker Exec Instance.
|
2
|
+
class Docker::Exec
|
3
|
+
include Docker::Base
|
4
|
+
|
5
|
+
# Convert details about the object into a string
|
6
|
+
#
|
7
|
+
# @return [String] String representation of the Exec instance object
|
8
|
+
def to_s
|
9
|
+
"Docker::Exec { :id => #{self.id}, :connection => #{self.connection} }"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Create a new Exec instance in a running container. Please note, this does
|
13
|
+
# NOT execute the instance - you must run #start. Also, each instance is
|
14
|
+
# one-time use only.
|
15
|
+
#
|
16
|
+
# @param options [Hash] Parameters to pass in to the API.
|
17
|
+
# @param conn [Docker::Connection] Connection to Docker Remote API
|
18
|
+
#
|
19
|
+
# @return [Docker::Exec] self
|
20
|
+
def self.create(options = {}, conn = Docker.connection)
|
21
|
+
container = options.delete('Container')
|
22
|
+
resp = conn.post("/containers/#{container}/exec", {},
|
23
|
+
body: MultiJson.dump(options))
|
24
|
+
hash = Docker::Util.parse_json(resp) || {}
|
25
|
+
new(conn, hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get info about the Exec instance
|
29
|
+
#
|
30
|
+
def json
|
31
|
+
Docker::Util.parse_json(connection.get(path_for(:json), {}))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Start the Exec instance. The Exec instance is deleted after this so this
|
35
|
+
# command can only be run once.
|
36
|
+
#
|
37
|
+
# @param options [Hash] Options to dictate behavior of the instance
|
38
|
+
# @option options [Object] :stdin (nil) The object to pass to STDIN.
|
39
|
+
# @option options [TrueClass, FalseClass] :detach (false) Whether to attach
|
40
|
+
# to STDOUT/STDERR.
|
41
|
+
# @option options [TrueClass, FalseClass] :tty (false) Whether to attach using
|
42
|
+
# a pseudo-TTY.
|
43
|
+
#
|
44
|
+
# @return [Array, Array, Int] The STDOUT, STDERR and exit code
|
45
|
+
def start!(options = {}, &block)
|
46
|
+
|
47
|
+
# Parse the Options
|
48
|
+
tty = !!options.delete(:tty)
|
49
|
+
detached = !!options.delete(:detach)
|
50
|
+
stdin = options[:stdin]
|
51
|
+
read_timeout = options[:wait]
|
52
|
+
|
53
|
+
# Create API Request Body
|
54
|
+
body = MultiJson.dump(
|
55
|
+
'Tty' => tty,
|
56
|
+
'Detach' => detached
|
57
|
+
)
|
58
|
+
excon_params = { body: body }
|
59
|
+
|
60
|
+
msgs = Docker::Messages.new
|
61
|
+
unless detached
|
62
|
+
if stdin
|
63
|
+
excon_params[:hijack_block] = Docker::Util.hijack_for(stdin, block,
|
64
|
+
msgs, tty)
|
65
|
+
else
|
66
|
+
excon_params[:response_block] = Docker::Util.attach_for(block,
|
67
|
+
msgs, tty)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
excon_params[:read_timeout] = read_timeout unless read_timeout.nil?
|
72
|
+
|
73
|
+
connection.post(path_for(:start), nil, excon_params)
|
74
|
+
[msgs.stdout_messages, msgs.stderr_messages, self.json['ExitCode']]
|
75
|
+
end
|
76
|
+
|
77
|
+
# #start! performs the associated action and returns the output.
|
78
|
+
# #start does the same, but rescues from ServerErrors.
|
79
|
+
[:start].each do |method|
|
80
|
+
define_method(method) do |*args|
|
81
|
+
begin; public_send(:"#{method}!", *args); rescue ServerError; self end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Resize the TTY associated with the Exec instance
|
86
|
+
#
|
87
|
+
# @param query [Hash] API query parameters
|
88
|
+
# @option query [Fixnum] h Height of the TTY
|
89
|
+
# @option query [Fixnum] w Width of the TTY
|
90
|
+
#
|
91
|
+
# @return [Docker::Exec] self
|
92
|
+
def resize(query = {})
|
93
|
+
connection.post(path_for(:resize), query)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the request URI for the given endpoint
|
98
|
+
#
|
99
|
+
# @param endpoint [Symbol] The endpoint to grab
|
100
|
+
# @return [String] The full Remote API endpoint with ID
|
101
|
+
def path_for(endpoint)
|
102
|
+
"/exec/#{self.id}/#{endpoint}"
|
103
|
+
end
|
104
|
+
|
105
|
+
private :path_for
|
106
|
+
private_class_method :new
|
107
|
+
end
|
data/lib/docker/image.rb
ADDED
@@ -0,0 +1,356 @@
|
|
1
|
+
# This class represents a Docker Image.
|
2
|
+
class Docker::Image
|
3
|
+
include Docker::Base
|
4
|
+
|
5
|
+
# Given a command and optional list of streams to attach to, run a command on
|
6
|
+
# an Image. This will not modify the Image, but rather create a new Container
|
7
|
+
# to run the Image. If the image has an embedded config, no command is
|
8
|
+
# necessary, but it will fail with 500 if no config is saved with the image
|
9
|
+
def run(cmd = nil, options = {})
|
10
|
+
opts = {'Image' => self.id}.merge(options)
|
11
|
+
opts["Cmd"] = cmd.is_a?(String) ? cmd.split(/\s+/) : cmd
|
12
|
+
begin
|
13
|
+
Docker::Container.create(opts, connection)
|
14
|
+
.tap(&:start!)
|
15
|
+
rescue ServerError, ClientError => ex
|
16
|
+
if cmd
|
17
|
+
raise ex
|
18
|
+
else
|
19
|
+
raise ex, "No command specified."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Push the Image to the Docker registry.
|
25
|
+
def push(creds = nil, options = {}, &block)
|
26
|
+
repo_tag = options.delete(:repo_tag) || ensure_repo_tags.first
|
27
|
+
raise ArgumentError, "Image is untagged" if repo_tag.nil?
|
28
|
+
repo, tag = Docker::Util.parse_repo_tag(repo_tag)
|
29
|
+
raise ArgumentError, "Image does not have a name to push." if repo.nil?
|
30
|
+
|
31
|
+
body = ""
|
32
|
+
credentials = creds || Docker.creds || {}
|
33
|
+
headers = Docker::Util.build_auth_header(credentials)
|
34
|
+
opts = {:tag => tag}.merge(options)
|
35
|
+
connection.post("/images/#{repo}/push", opts, :headers => headers,
|
36
|
+
:response_block => self.class.response_block(body, &block))
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Tag the Image.
|
41
|
+
def tag(opts = {})
|
42
|
+
self.info['RepoTags'] ||= []
|
43
|
+
connection.post(path_for(:tag), opts)
|
44
|
+
repo = opts['repo'] || opts[:repo]
|
45
|
+
tag = opts['tag'] || opts[:tag] || 'latest'
|
46
|
+
self.info['RepoTags'] << "#{repo}:#{tag}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Given a path of a local file and the path it should be inserted, creates
|
50
|
+
# a new Image that has that file.
|
51
|
+
def insert_local(opts = {})
|
52
|
+
local_paths = opts.delete('localPath')
|
53
|
+
output_path = opts.delete('outputPath')
|
54
|
+
|
55
|
+
local_paths = [ local_paths ] unless local_paths.is_a?(Array)
|
56
|
+
|
57
|
+
file_hash = Docker::Util.file_hash_from_paths(local_paths)
|
58
|
+
|
59
|
+
file_hash['Dockerfile'] = dockerfile_for(file_hash, output_path)
|
60
|
+
|
61
|
+
tar = Docker::Util.create_tar(file_hash)
|
62
|
+
body = connection.post('/build', opts, :body => tar)
|
63
|
+
self.class.send(:new, connection, 'id' => Docker::Util.extract_id(body))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Remove the Image from the server.
|
67
|
+
def remove(opts = {})
|
68
|
+
name = opts.delete(:name) || self.id
|
69
|
+
connection.delete("/images/#{name}", opts)
|
70
|
+
end
|
71
|
+
alias_method :delete, :remove
|
72
|
+
|
73
|
+
# Return a String representation of the Image.
|
74
|
+
def to_s
|
75
|
+
"Docker::Image { :id => #{self.id}, :info => #{self.info.inspect}, "\
|
76
|
+
":connection => #{self.connection} }"
|
77
|
+
end
|
78
|
+
|
79
|
+
# #json returns extra information about an Image, #history returns its
|
80
|
+
# history.
|
81
|
+
[:json, :history].each do |method|
|
82
|
+
define_method(method) do |opts = {}|
|
83
|
+
Docker::Util.parse_json(connection.get(path_for(method), opts))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Save the image as a tarball
|
88
|
+
def save(filename = nil)
|
89
|
+
self.class.save(self.id, filename, connection)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Save the image as a tarball to an IO object.
|
93
|
+
def save_stream(opts = {}, &block)
|
94
|
+
self.class.save_stream(self.id, opts, connection, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Update the @info hash, which is the only mutable state in this object.
|
98
|
+
def refresh!
|
99
|
+
img = Docker::Image.all({:all => true}, connection).find { |image|
|
100
|
+
image.id.start_with?(self.id) || self.id.start_with?(image.id)
|
101
|
+
}
|
102
|
+
info.merge!(self.json)
|
103
|
+
img && info.merge!(img.info)
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
class << self
|
108
|
+
|
109
|
+
# Create a new Image.
|
110
|
+
def create(opts = {}, creds = nil, conn = Docker.connection, &block)
|
111
|
+
credentials = creds.nil? ? Docker.creds : MultiJson.dump(creds)
|
112
|
+
headers = credentials && Docker::Util.build_auth_header(credentials) || {}
|
113
|
+
body = ''
|
114
|
+
conn.post(
|
115
|
+
'/images/create',
|
116
|
+
opts,
|
117
|
+
:headers => headers,
|
118
|
+
:response_block => response_block(body, &block)
|
119
|
+
)
|
120
|
+
# NOTE: see associated tests for why we're looking at image#end_with?
|
121
|
+
image = opts['fromImage'] || opts[:fromImage]
|
122
|
+
tag = opts['tag'] || opts[:tag]
|
123
|
+
image = "#{image}:#{tag}" if tag && !image.end_with?(":#{tag}")
|
124
|
+
get(image, {}, conn)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return a specific image.
|
128
|
+
def get(id, opts = {}, conn = Docker.connection)
|
129
|
+
image_json = conn.get("/images/#{id}/json", opts)
|
130
|
+
hash = Docker::Util.parse_json(image_json) || {}
|
131
|
+
new(conn, hash)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Delete a specific image
|
135
|
+
def remove(id, opts = {}, conn = Docker.connection)
|
136
|
+
conn.delete("/images/#{id}", opts)
|
137
|
+
end
|
138
|
+
alias_method :delete, :remove
|
139
|
+
|
140
|
+
# Prune images
|
141
|
+
def prune(conn = Docker.connection)
|
142
|
+
conn.post("/images/prune", {})
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# Save the raw binary representation or one or more Docker images
|
147
|
+
#
|
148
|
+
# @param names [String, Array#String] The image(s) you wish to save
|
149
|
+
# @param filename [String] The file to export the data to.
|
150
|
+
# @param conn [Docker::Connection] The Docker connection to use
|
151
|
+
#
|
152
|
+
# @return [NilClass, String] If filename is nil, return the string
|
153
|
+
# representation of the binary data. If the filename is not nil, then
|
154
|
+
# return nil.
|
155
|
+
def save(names, filename = nil, conn = Docker.connection)
|
156
|
+
if filename
|
157
|
+
File.open(filename, 'wb') do |file|
|
158
|
+
save_stream(names, {}, conn, &response_block_for_save(file))
|
159
|
+
end
|
160
|
+
nil
|
161
|
+
else
|
162
|
+
string = ''
|
163
|
+
save_stream(names, {}, conn, &response_block_for_save(string))
|
164
|
+
string
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Stream the contents of Docker image(s) to a block.
|
169
|
+
#
|
170
|
+
# @param names [String, Array#String] The image(s) you wish to save
|
171
|
+
# @param conn [Docker::Connection] The Docker connection to use
|
172
|
+
# @yield chunk [String] a chunk of the Docker image(s).
|
173
|
+
def save_stream(names, opts = {}, conn = Docker.connection, &block)
|
174
|
+
# By using compare_by_identity we can create a Hash that has
|
175
|
+
# the same key multiple times.
|
176
|
+
query = {}.tap(&:compare_by_identity)
|
177
|
+
Array(names).each { |name| query['names'.dup] = name }
|
178
|
+
conn.get(
|
179
|
+
'/images/get',
|
180
|
+
query,
|
181
|
+
opts.merge(:response_block => block)
|
182
|
+
)
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
|
186
|
+
# Load a tar Image
|
187
|
+
def load(tar, opts = {}, conn = Docker.connection, creds = nil, &block)
|
188
|
+
headers = build_headers(creds)
|
189
|
+
io = tar.is_a?(String) ? File.open(tar, 'rb') : tar
|
190
|
+
body = ""
|
191
|
+
conn.post(
|
192
|
+
'/images/load',
|
193
|
+
opts,
|
194
|
+
:headers => headers,
|
195
|
+
:response_block => response_block(body, &block)
|
196
|
+
) { io.read(Excon.defaults[:chunk_size]).to_s }
|
197
|
+
end
|
198
|
+
|
199
|
+
# Check if an image exists.
|
200
|
+
def exist?(id, opts = {}, conn = Docker.connection)
|
201
|
+
get(id, opts, conn)
|
202
|
+
true
|
203
|
+
rescue Docker::Error::NotFoundError
|
204
|
+
false
|
205
|
+
end
|
206
|
+
|
207
|
+
# Return every Image.
|
208
|
+
def all(opts = {}, conn = Docker.connection)
|
209
|
+
hashes = Docker::Util.parse_json(conn.get('/images/json', opts)) || []
|
210
|
+
hashes.map { |hash| new(conn, hash) }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Given a query like `{ :term => 'sshd' }`, queries the Docker Registry for
|
214
|
+
# a corresponding Image.
|
215
|
+
def search(query = {}, connection = Docker.connection, creds = nil)
|
216
|
+
credentials = creds.nil? ? Docker.creds : creds.to_json
|
217
|
+
headers = credentials && Docker::Util.build_auth_header(credentials) || {}
|
218
|
+
body = connection.get(
|
219
|
+
'/images/search',
|
220
|
+
query,
|
221
|
+
:headers => headers,
|
222
|
+
)
|
223
|
+
hashes = Docker::Util.parse_json(body) || []
|
224
|
+
hashes.map { |hash| new(connection, 'id' => hash['name']) }
|
225
|
+
end
|
226
|
+
|
227
|
+
# Import an Image from the output of Docker::Container#export. The first
|
228
|
+
# argument may either be a File or URI.
|
229
|
+
def import(imp, opts = {}, conn = Docker.connection)
|
230
|
+
open(imp) do |io|
|
231
|
+
import_stream(opts, conn) do
|
232
|
+
io.read(Excon.defaults[:chunk_size]).to_s
|
233
|
+
end
|
234
|
+
end
|
235
|
+
rescue StandardError
|
236
|
+
raise Docker::Error::IOError, "Could not import '#{imp}'"
|
237
|
+
end
|
238
|
+
|
239
|
+
def import_stream(options = {}, connection = Docker.connection, &block)
|
240
|
+
body = connection.post(
|
241
|
+
'/images/create',
|
242
|
+
options.merge('fromSrc' => '-'),
|
243
|
+
:headers => { 'Content-Type' => 'application/tar',
|
244
|
+
'Transfer-Encoding' => 'chunked' },
|
245
|
+
&block
|
246
|
+
)
|
247
|
+
new(connection, 'id'=> Docker::Util.parse_json(body)['status'])
|
248
|
+
end
|
249
|
+
|
250
|
+
# Given a Dockerfile as a string, builds an Image.
|
251
|
+
def build(commands, opts = {}, connection = Docker.connection, &block)
|
252
|
+
body = ""
|
253
|
+
connection.post(
|
254
|
+
'/build', opts,
|
255
|
+
:body => Docker::Util.create_tar('Dockerfile' => commands),
|
256
|
+
:response_block => response_block(body, &block)
|
257
|
+
)
|
258
|
+
new(connection, 'id' => Docker::Util.extract_id(body))
|
259
|
+
rescue Docker::Error::ServerError
|
260
|
+
raise Docker::Error::UnexpectedResponseError
|
261
|
+
end
|
262
|
+
|
263
|
+
# Given File like object containing a tar file, builds an Image.
|
264
|
+
#
|
265
|
+
# If a block is passed, chunks of output produced by Docker will be passed
|
266
|
+
# to that block.
|
267
|
+
def build_from_tar(tar, opts = {}, connection = Docker.connection,
|
268
|
+
creds = nil, &block)
|
269
|
+
|
270
|
+
headers = build_headers(creds)
|
271
|
+
|
272
|
+
# The response_block passed to Excon will build up this body variable.
|
273
|
+
body = ""
|
274
|
+
connection.post(
|
275
|
+
'/build', opts,
|
276
|
+
:headers => headers,
|
277
|
+
:response_block => response_block(body, &block)
|
278
|
+
) { tar.read(Excon.defaults[:chunk_size]).to_s }
|
279
|
+
|
280
|
+
new(connection,
|
281
|
+
'id' => Docker::Util.extract_id(body),
|
282
|
+
:headers => headers)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Given a directory that contains a Dockerfile, builds an Image.
|
286
|
+
#
|
287
|
+
# If a block is passed, chunks of output produced by Docker will be passed
|
288
|
+
# to that block.
|
289
|
+
def build_from_dir(dir, opts = {}, connection = Docker.connection,
|
290
|
+
creds = nil, &block)
|
291
|
+
|
292
|
+
tar = Docker::Util.create_dir_tar(dir)
|
293
|
+
build_from_tar tar, opts, connection, creds, &block
|
294
|
+
ensure
|
295
|
+
unless tar.nil?
|
296
|
+
tar.close
|
297
|
+
FileUtils.rm(tar.path, force: true)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
private
|
303
|
+
|
304
|
+
# A method to build the config header and merge it into the
|
305
|
+
# headers sent by build_from_dir.
|
306
|
+
def self.build_headers(creds=nil)
|
307
|
+
credentials = creds || Docker.creds || {}
|
308
|
+
config_header = Docker::Util.build_config_header(credentials)
|
309
|
+
|
310
|
+
headers = { 'Content-Type' => 'application/tar',
|
311
|
+
'Transfer-Encoding' => 'chunked' }
|
312
|
+
headers = headers.merge(config_header) if config_header
|
313
|
+
headers
|
314
|
+
end
|
315
|
+
|
316
|
+
# Convenience method to return the path for a particular resource.
|
317
|
+
def path_for(resource)
|
318
|
+
"/images/#{self.id}/#{resource}"
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
# Convience method to get the Dockerfile for a file hash and a path to
|
323
|
+
# output to.
|
324
|
+
def dockerfile_for(file_hash, output_path)
|
325
|
+
dockerfile = "from #{self.id}\n"
|
326
|
+
|
327
|
+
file_hash.keys.each do |basename|
|
328
|
+
dockerfile << "add #{basename} #{output_path}\n"
|
329
|
+
end
|
330
|
+
|
331
|
+
dockerfile
|
332
|
+
end
|
333
|
+
|
334
|
+
def ensure_repo_tags
|
335
|
+
refresh! unless info.has_key?('RepoTags')
|
336
|
+
info['RepoTags']
|
337
|
+
end
|
338
|
+
|
339
|
+
# Generates the block to be passed as a reponse block to Excon. The returned
|
340
|
+
# lambda will append Docker output to the first argument, and yield output to
|
341
|
+
# the passed block, if a block is given.
|
342
|
+
def self.response_block(body)
|
343
|
+
lambda do |chunk, remaining, total|
|
344
|
+
body << chunk
|
345
|
+
yield chunk if block_given?
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Generates the block to be passed in to the save request. This lambda will
|
350
|
+
# append the streaming data to the file provided.
|
351
|
+
def self.response_block_for_save(file)
|
352
|
+
lambda do |chunk, remianing, total|
|
353
|
+
file << chunk
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|