docker-api 1.27.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +110 -10
- data/lib/docker.rb +7 -14
- data/lib/docker/connection.rb +3 -3
- data/lib/docker/container.rb +103 -45
- data/lib/docker/event.rb +100 -14
- data/lib/docker/exec.rb +6 -6
- data/lib/docker/image.rb +32 -11
- data/lib/docker/network.rb +16 -12
- data/lib/docker/util.rb +56 -16
- data/lib/docker/version.rb +1 -4
- data/lib/docker/volume.rb +8 -4
- metadata +16 -66
- data/.cane +0 -2
- data/.gitignore +0 -6
- data/.rspec +0 -1
- data/.simplecov +0 -4
- data/.travis.yml +0 -25
- data/Dockerfile +0 -2
- data/Gemfile +0 -3
- data/Rakefile +0 -54
- data/TESTING.md +0 -49
- data/docker-api.gemspec +0 -28
- data/script/docker +0 -149
- data/script/docker.conf +0 -61
- data/script/install_docker.sh +0 -35
- data/spec/docker/connection_spec.rb +0 -123
- data/spec/docker/container_spec.rb +0 -801
- data/spec/docker/event_spec.rb +0 -89
- data/spec/docker/exec_spec.rb +0 -181
- data/spec/docker/image_spec.rb +0 -683
- data/spec/docker/messages_spec.rb +0 -97
- data/spec/docker/messages_stack.rb +0 -26
- data/spec/docker/network_spec.rb +0 -150
- data/spec/docker/util_spec.rb +0 -154
- data/spec/docker/volume_spec.rb +0 -46
- data/spec/docker_spec.rb +0 -258
- data/spec/fixtures/build_from_dir/Dockerfile +0 -2
- data/spec/fixtures/export.tar +0 -0
- data/spec/fixtures/load.tar +0 -0
- data/spec/fixtures/top/Dockerfile +0 -2
- data/spec/spec_helper.rb +0 -36
data/lib/docker/event.rb
CHANGED
@@ -2,15 +2,26 @@
|
|
2
2
|
class Docker::Event
|
3
3
|
include Docker::Error
|
4
4
|
|
5
|
-
|
5
|
+
# Represents the actor object nested within an event
|
6
|
+
class Actor
|
7
|
+
attr_accessor :ID, :Attributes
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
10
17
|
|
11
|
-
|
12
|
-
|
13
|
-
|
18
|
+
if self.Attributes.nil?
|
19
|
+
self.Attributes = {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :id, :ID
|
24
|
+
alias_method :attributes, :Attributes
|
14
25
|
end
|
15
26
|
|
16
27
|
class << self
|
@@ -18,7 +29,9 @@ class Docker::Event
|
|
18
29
|
|
19
30
|
def stream(opts = {}, conn = Docker.connection, &block)
|
20
31
|
conn.get('/events', opts, :response_block => lambda { |b, r, t|
|
21
|
-
|
32
|
+
b.each_line do |line|
|
33
|
+
block.call(new_event(line, r, t))
|
34
|
+
end
|
22
35
|
})
|
23
36
|
end
|
24
37
|
|
@@ -29,12 +42,85 @@ class Docker::Event
|
|
29
42
|
def new_event(body, remaining, total)
|
30
43
|
return if body.nil? || body.empty?
|
31
44
|
json = Docker::Util.parse_json(body)
|
32
|
-
Docker::Event.new(
|
33
|
-
json['status'],
|
34
|
-
json['id'],
|
35
|
-
json['from'],
|
36
|
-
json['time']
|
37
|
-
)
|
45
|
+
Docker::Event.new(json)
|
38
46
|
end
|
39
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
|
40
126
|
end
|
data/lib/docker/exec.rb
CHANGED
@@ -20,7 +20,7 @@ class Docker::Exec
|
|
20
20
|
def self.create(options = {}, conn = Docker.connection)
|
21
21
|
container = options.delete('Container')
|
22
22
|
resp = conn.post("/containers/#{container}/exec", {},
|
23
|
-
:
|
23
|
+
body: MultiJson.dump(options))
|
24
24
|
hash = Docker::Util.parse_json(resp) || {}
|
25
25
|
new(conn, hash)
|
26
26
|
end
|
@@ -51,11 +51,11 @@ class Docker::Exec
|
|
51
51
|
read_timeout = options[:wait]
|
52
52
|
|
53
53
|
# Create API Request Body
|
54
|
-
body =
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
excon_params = { :
|
54
|
+
body = MultiJson.dump(
|
55
|
+
'Tty' => tty,
|
56
|
+
'Detach' => detached
|
57
|
+
)
|
58
|
+
excon_params = { body: body }
|
59
59
|
|
60
60
|
msgs = Docker::Messages.new
|
61
61
|
unless detached
|
data/lib/docker/image.rb
CHANGED
@@ -6,17 +6,17 @@ class Docker::Image
|
|
6
6
|
# an Image. This will not modify the Image, but rather create a new Container
|
7
7
|
# to run the Image. If the image has an embedded config, no command is
|
8
8
|
# necessary, but it will fail with 500 if no config is saved with the image
|
9
|
-
def run(cmd=nil)
|
10
|
-
opts = {
|
9
|
+
def run(cmd = nil, options = {})
|
10
|
+
opts = {'Image' => self.id}.merge(options)
|
11
11
|
opts["Cmd"] = cmd.is_a?(String) ? cmd.split(/\s+/) : cmd
|
12
12
|
begin
|
13
13
|
Docker::Container.create(opts, connection)
|
14
14
|
.tap(&:start!)
|
15
|
-
rescue ServerError => ex
|
15
|
+
rescue ServerError, ClientError => ex
|
16
16
|
if cmd
|
17
17
|
raise ex
|
18
18
|
else
|
19
|
-
raise
|
19
|
+
raise ex, "No command specified."
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -108,7 +108,7 @@ class Docker::Image
|
|
108
108
|
|
109
109
|
# Create a new Image.
|
110
110
|
def create(opts = {}, creds = nil, conn = Docker.connection, &block)
|
111
|
-
credentials = creds.nil? ? Docker.creds : creds
|
111
|
+
credentials = creds.nil? ? Docker.creds : MultiJson.dump(creds)
|
112
112
|
headers = credentials && Docker::Util.build_auth_header(credentials) || {}
|
113
113
|
body = ''
|
114
114
|
conn.post(
|
@@ -117,17 +117,32 @@ class Docker::Image
|
|
117
117
|
:headers => headers,
|
118
118
|
:response_block => response_block(body, &block)
|
119
119
|
)
|
120
|
+
# NOTE: see associated tests for why we're looking at image#end_with?
|
120
121
|
image = opts['fromImage'] || opts[:fromImage]
|
122
|
+
tag = opts['tag'] || opts[:tag]
|
123
|
+
image = "#{image}:#{tag}" if tag && !image.end_with?(":#{tag}")
|
121
124
|
get(image, {}, conn)
|
122
125
|
end
|
123
126
|
|
124
127
|
# Return a specific image.
|
125
128
|
def get(id, opts = {}, conn = Docker.connection)
|
126
|
-
image_json = conn.get("/images/#{
|
129
|
+
image_json = conn.get("/images/#{id}/json", opts)
|
127
130
|
hash = Docker::Util.parse_json(image_json) || {}
|
128
131
|
new(conn, hash)
|
129
132
|
end
|
130
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
|
+
|
131
146
|
# Save the raw binary representation or one or more Docker images
|
132
147
|
#
|
133
148
|
# @param names [String, Array#String] The image(s) you wish to save
|
@@ -159,7 +174,7 @@ class Docker::Image
|
|
159
174
|
# By using compare_by_identity we can create a Hash that has
|
160
175
|
# the same key multiple times.
|
161
176
|
query = {}.tap(&:compare_by_identity)
|
162
|
-
Array(names).each { |name| query['names'.dup] =
|
177
|
+
Array(names).each { |name| query['names'.dup] = name }
|
163
178
|
conn.get(
|
164
179
|
'/images/get',
|
165
180
|
query,
|
@@ -171,14 +186,14 @@ class Docker::Image
|
|
171
186
|
# Load a tar Image
|
172
187
|
def load(tar, opts = {}, conn = Docker.connection, creds = nil, &block)
|
173
188
|
headers = build_headers(creds)
|
189
|
+
io = tar.is_a?(String) ? File.open(tar, 'rb') : tar
|
174
190
|
body = ""
|
175
|
-
f = File.open(tar,'rb')
|
176
191
|
conn.post(
|
177
192
|
'/images/load',
|
178
193
|
opts,
|
179
194
|
:headers => headers,
|
180
195
|
:response_block => response_block(body, &block)
|
181
|
-
) {
|
196
|
+
) { io.read(Excon.defaults[:chunk_size]).to_s }
|
182
197
|
end
|
183
198
|
|
184
199
|
# Check if an image exists.
|
@@ -197,8 +212,14 @@ class Docker::Image
|
|
197
212
|
|
198
213
|
# Given a query like `{ :term => 'sshd' }`, queries the Docker Registry for
|
199
214
|
# a corresponding Image.
|
200
|
-
def search(query = {}, connection = Docker.connection)
|
201
|
-
|
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
|
+
)
|
202
223
|
hashes = Docker::Util.parse_json(body) || []
|
203
224
|
hashes.map { |hash| new(connection, 'id' => hash['name']) }
|
204
225
|
end
|
data/lib/docker/network.rb
CHANGED
@@ -2,18 +2,18 @@
|
|
2
2
|
class Docker::Network
|
3
3
|
include Docker::Base
|
4
4
|
|
5
|
-
def connect(container, opts = {})
|
5
|
+
def connect(container, opts = {}, body_opts = {})
|
6
|
+
body = MultiJson.dump({ container: container }.merge(body_opts))
|
6
7
|
Docker::Util.parse_json(
|
7
|
-
connection.post(path_for('connect'), opts,
|
8
|
-
body: { container: container }.to_json)
|
8
|
+
connection.post(path_for('connect'), opts, body: body)
|
9
9
|
)
|
10
10
|
reload
|
11
11
|
end
|
12
12
|
|
13
13
|
def disconnect(container, opts = {})
|
14
|
+
body = MultiJson.dump(container: container)
|
14
15
|
Docker::Util.parse_json(
|
15
|
-
connection.post(path_for('disconnect'), opts,
|
16
|
-
body: { container: container }.to_json)
|
16
|
+
connection.post(path_for('disconnect'), opts, body: body)
|
17
17
|
)
|
18
18
|
reload
|
19
19
|
end
|
@@ -34,25 +34,24 @@ class Docker::Network
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def reload
|
37
|
-
network_json = @connection.get("/networks/#{
|
37
|
+
network_json = @connection.get("/networks/#{@id}")
|
38
38
|
hash = Docker::Util.parse_json(network_json) || {}
|
39
39
|
@info = hash
|
40
40
|
end
|
41
41
|
|
42
42
|
class << self
|
43
43
|
def create(name, opts = {}, conn = Docker.connection)
|
44
|
-
default_opts = {
|
44
|
+
default_opts = MultiJson.dump({
|
45
45
|
'Name' => name,
|
46
46
|
'CheckDuplicate' => true
|
47
|
-
}
|
48
|
-
resp = conn.post('/networks/create', {},
|
49
|
-
body: default_opts.merge(opts).to_json)
|
47
|
+
}.merge(opts))
|
48
|
+
resp = conn.post('/networks/create', {}, body: default_opts)
|
50
49
|
response_hash = Docker::Util.parse_json(resp) || {}
|
51
50
|
get(response_hash['Id'], {}, conn) || {}
|
52
51
|
end
|
53
52
|
|
54
53
|
def get(id, opts = {}, conn = Docker.connection)
|
55
|
-
network_json = conn.get("/networks/#{
|
54
|
+
network_json = conn.get("/networks/#{id}", opts)
|
56
55
|
hash = Docker::Util.parse_json(network_json) || {}
|
57
56
|
new(conn, hash)
|
58
57
|
end
|
@@ -63,10 +62,15 @@ class Docker::Network
|
|
63
62
|
end
|
64
63
|
|
65
64
|
def remove(id, opts = {}, conn = Docker.connection)
|
66
|
-
conn.delete("/networks/#{
|
65
|
+
conn.delete("/networks/#{id}", opts)
|
67
66
|
nil
|
68
67
|
end
|
69
68
|
alias_method :delete, :remove
|
69
|
+
|
70
|
+
def prune(conn = Docker.connection)
|
71
|
+
conn.post("/networks/prune", {})
|
72
|
+
nil
|
73
|
+
end
|
70
74
|
end
|
71
75
|
|
72
76
|
# Convenience method to return the path for a particular resource.
|
data/lib/docker/util.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# This module holds shared logic that doesn't really belong anywhere else in the
|
2
2
|
# gem.
|
3
3
|
module Docker::Util
|
4
|
+
# http://www.tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm#STANDARD-WILDCARDS
|
5
|
+
GLOB_WILDCARDS = /[\?\*\[\{]/
|
6
|
+
|
4
7
|
include Docker::Error
|
5
8
|
|
6
9
|
module_function
|
@@ -99,8 +102,8 @@ module Docker::Util
|
|
99
102
|
end
|
100
103
|
|
101
104
|
def parse_json(body)
|
102
|
-
|
103
|
-
rescue
|
105
|
+
MultiJson.load(body) unless body.nil? || body.empty? || (body == 'null')
|
106
|
+
rescue MultiJson::ParseError => ex
|
104
107
|
raise UnexpectedResponseError, ex.message
|
105
108
|
end
|
106
109
|
|
@@ -119,8 +122,12 @@ module Docker::Util
|
|
119
122
|
def create_tar(hash = {})
|
120
123
|
output = StringIO.new
|
121
124
|
Gem::Package::TarWriter.new(output) do |tar|
|
122
|
-
hash.each do |file_name,
|
123
|
-
|
125
|
+
hash.each do |file_name, file_details|
|
126
|
+
permissions = file_details.is_a?(Hash) ? file_details[:permissions] : 0640
|
127
|
+
tar.add_file(file_name, permissions) do |tar_file|
|
128
|
+
content = file_details.is_a?(Hash) ? file_details[:content] : file_details
|
129
|
+
tar_file.write(content)
|
130
|
+
end
|
124
131
|
end
|
125
132
|
end
|
126
133
|
output.tap(&:rewind).string
|
@@ -137,7 +144,10 @@ module Docker::Util
|
|
137
144
|
|
138
145
|
def create_relative_dir_tar(directory, output)
|
139
146
|
Gem::Package::TarWriter.new(output) do |tar|
|
140
|
-
|
147
|
+
files = glob_all_files(File.join(directory, "**/*"))
|
148
|
+
remove_ignored_files!(directory, files)
|
149
|
+
|
150
|
+
files.each do |prefixed_file_name|
|
141
151
|
stat = File.stat(prefixed_file_name)
|
142
152
|
next unless stat.file?
|
143
153
|
|
@@ -201,18 +211,29 @@ module Docker::Util
|
|
201
211
|
basename = File.basename(local_path)
|
202
212
|
if File.directory?(local_path)
|
203
213
|
tar = create_dir_tar(local_path)
|
204
|
-
file_hash[basename] =
|
214
|
+
file_hash[basename] = {
|
215
|
+
content: tar.read,
|
216
|
+
permissions: filesystem_permissions(local_path)
|
217
|
+
}
|
205
218
|
tar.close
|
206
219
|
FileUtils.rm(tar.path)
|
207
220
|
else
|
208
|
-
file_hash[basename] =
|
221
|
+
file_hash[basename] = {
|
222
|
+
content: File.read(local_path, mode: 'rb'),
|
223
|
+
permissions: filesystem_permissions(local_path)
|
224
|
+
}
|
209
225
|
end
|
210
226
|
end
|
211
227
|
end
|
212
228
|
|
229
|
+
def filesystem_permissions(path)
|
230
|
+
mode = sprintf("%o", File.stat(path).mode)
|
231
|
+
mode[(mode.length - 3)...mode.length].to_i(8)
|
232
|
+
end
|
233
|
+
|
213
234
|
def build_auth_header(credentials)
|
214
|
-
credentials = credentials
|
215
|
-
encoded_creds = Base64.
|
235
|
+
credentials = MultiJson.dump(credentials) if credentials.is_a?(Hash)
|
236
|
+
encoded_creds = Base64.urlsafe_encode64(credentials)
|
216
237
|
{
|
217
238
|
'X-Registry-Auth' => encoded_creds
|
218
239
|
}
|
@@ -220,20 +241,39 @@ module Docker::Util
|
|
220
241
|
|
221
242
|
def build_config_header(credentials)
|
222
243
|
if credentials.is_a?(String)
|
223
|
-
credentials =
|
244
|
+
credentials = MultiJson.load(credentials, symbolize_keys: true)
|
224
245
|
end
|
225
|
-
|
246
|
+
|
247
|
+
header = MultiJson.dump(
|
226
248
|
credentials[:serveraddress].to_s => {
|
227
|
-
|
228
|
-
|
229
|
-
|
249
|
+
'username' => credentials[:username].to_s,
|
250
|
+
'password' => credentials[:password].to_s,
|
251
|
+
'email' => credentials[:email].to_s
|
230
252
|
}
|
231
|
-
|
253
|
+
)
|
232
254
|
|
233
|
-
encoded_header = Base64.
|
255
|
+
encoded_header = Base64.urlsafe_encode64(header)
|
234
256
|
|
235
257
|
{
|
236
258
|
'X-Registry-Config' => encoded_header
|
237
259
|
}
|
238
260
|
end
|
261
|
+
|
262
|
+
def glob_all_files(pattern)
|
263
|
+
Dir.glob(pattern, File::FNM_DOTMATCH) - ['..', '.']
|
264
|
+
end
|
265
|
+
|
266
|
+
def remove_ignored_files!(directory, files)
|
267
|
+
ignore = File.join(directory, '.dockerignore')
|
268
|
+
return unless files.include?(ignore)
|
269
|
+
ignored_files(directory, ignore).each { |f| files.delete(f) }
|
270
|
+
end
|
271
|
+
|
272
|
+
def ignored_files(directory, ignore_file)
|
273
|
+
patterns = File.read(ignore_file).split("\n").each(&:strip!)
|
274
|
+
patterns.reject! { |p| p.empty? || p.start_with?('#') }
|
275
|
+
patterns.map! { |p| File.join(directory, p) }
|
276
|
+
patterns.map! { |p| File.directory?(p) ? "#{p}/**/*" : p }
|
277
|
+
patterns.flat_map { |p| p =~ GLOB_WILDCARDS ? glob_all_files(p) : p }
|
278
|
+
end
|
239
279
|
end
|