docker-api 1.28.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,15 +2,26 @@
2
2
  class Docker::Event
3
3
  include Docker::Error
4
4
 
5
- attr_accessor :status, :id, :from, :time
5
+ # Represents the actor object nested within an event
6
+ class Actor
7
+ attr_accessor :ID, :Attributes
6
8
 
7
- def initialize(status, id, from, time)
8
- @status, @id, @from, @time = status, id, from, time
9
- end
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
- def to_s
12
- "Docker::Event { :status => #{self.status}, :id => #{self.id}, "\
13
- ":from => #{self.from}, :time => #{self.time} }"
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
- block.call(new_event(b, r, t))
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
@@ -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
- :body => options.to_json)
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
- "Tty" => tty,
56
- "Detach" => detached
57
- }
58
- excon_params = { :body => body.to_json }
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
@@ -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 = { 'Image' => self.id }
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 ServerError, "No command specified."
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.to_json
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/#{URI.encode(id)}/json", opts)
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] = URI.encode(name) }
177
+ Array(names).each { |name| query['names'.dup] = name }
163
178
  conn.get(
164
179
  '/images/get',
165
180
  query,
@@ -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
- body = connection.get('/images/search', query)
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
@@ -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/#{URI.encode(@id)}")
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/#{URI.encode(id)}", opts)
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/#{URI.encode(id)}", opts)
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.
@@ -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
- JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
103
- rescue JSON::ParserError => ex
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, input|
123
- tar.add_file(file_name, 0640) { |tar_file| tar_file.write(input) }
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
- Find.find(directory) do |prefixed_file_name|
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] = tar.read
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] = File.read(local_path)
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.to_json if credentials.is_a?(Hash)
215
- encoded_creds = Base64.encode64(credentials).gsub(/\n/, '')
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 = JSON.parse(credentials, symbolize_names: true)
244
+ credentials = MultiJson.load(credentials, symbolize_keys: true)
224
245
  end
225
- header = {
246
+
247
+ header = MultiJson.dump(
226
248
  credentials[:serveraddress].to_s => {
227
- "username" => credentials[:username].to_s,
228
- "password" => credentials[:password].to_s,
229
- "email" => credentials[:email].to_s
249
+ 'username' => credentials[:username].to_s,
250
+ 'password' => credentials[:password].to_s,
251
+ 'email' => credentials[:email].to_s
230
252
  }
231
- }.to_json
253
+ )
232
254
 
233
- encoded_header = Base64.encode64(header).gsub(/\n/, '')
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
@@ -1,7 +1,4 @@
1
1
  module Docker
2
2
  # The version of the docker-api gem.
3
- VERSION = '1.28.0'
4
-
5
- # The version of the compatible Docker remote API.
6
- API_VERSION = '1.16'
3
+ VERSION = '2.0.0'
7
4
  end