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/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
|