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.
@@ -0,0 +1,67 @@
1
+ # This class represents all the messages either received by chunks from attach
2
+ class Docker::Messages
3
+
4
+ attr_accessor :buffer, :stdout_messages, :stderr_messages, :all_messages
5
+
6
+ def initialize(stdout_messages=[],
7
+ stderr_messages=[],
8
+ all_messages=[],
9
+ buffer="")
10
+ @stdout_messages = stdout_messages
11
+ @stderr_messages = stderr_messages
12
+ @all_messages = all_messages
13
+ @buffer = buffer
14
+ end
15
+
16
+ def add_message(source, message)
17
+ case source
18
+ when 1
19
+ stdout_messages << message
20
+ when 2
21
+ stderr_messages << message
22
+ end
23
+ all_messages << message
24
+ end
25
+
26
+ def get_message(raw_text)
27
+ header = raw_text.slice!(0,8)
28
+ if header.length < 8
29
+ @buffer = header
30
+ return
31
+ end
32
+ type, length = header.unpack("CxxxN")
33
+
34
+ message = raw_text.slice!(0,length)
35
+ if message.length < length
36
+ @buffer = header + message
37
+ else
38
+ add_message(type, message)
39
+ end
40
+ end
41
+
42
+ def append(messages)
43
+ @stdout_messages += messages.stdout_messages
44
+ @stderr_messages += messages.stderr_messages
45
+ @all_messages += messages.all_messages
46
+ messages.clear
47
+
48
+ @all_messages
49
+ end
50
+
51
+ def clear
52
+ stdout_messages.clear
53
+ stderr_messages.clear
54
+ all_messages.clear
55
+ end
56
+
57
+ # Method to break apart application/vnd.docker.raw-stream headers
58
+ def decipher_messages(body)
59
+ raw_text = buffer + body.dup
60
+ messages = Docker::Messages.new
61
+ while !raw_text.empty?
62
+ messages.get_message(raw_text)
63
+ end
64
+
65
+ messages
66
+ end
67
+ end
@@ -0,0 +1,25 @@
1
+ # This class represents a messages stack
2
+ class Docker::MessagesStack
3
+
4
+ attr_accessor :messages
5
+
6
+ # Initialize stack with optional size
7
+ #
8
+ # @param size [Integer]
9
+ def initialize(size = -1)
10
+ @messages = []
11
+ @size = size
12
+ end
13
+
14
+ # Append messages to stack
15
+ #
16
+ # @param messages [Docker::Messages]
17
+ def append(messages)
18
+ return if @size == 0
19
+
20
+ messages.all_messages.each do |msg|
21
+ @messages << msg
22
+ @messages.shift if @size > -1 && @messages.size > @size
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ # This class represents a Docker Network.
2
+ class Docker::Network
3
+ include Docker::Base
4
+
5
+ def connect(container, opts = {}, body_opts = {})
6
+ body = MultiJson.dump({ container: container }.merge(body_opts))
7
+ Docker::Util.parse_json(
8
+ connection.post(path_for('connect'), opts, body: body)
9
+ )
10
+ reload
11
+ end
12
+
13
+ def disconnect(container, opts = {})
14
+ body = MultiJson.dump(container: container)
15
+ Docker::Util.parse_json(
16
+ connection.post(path_for('disconnect'), opts, body: body)
17
+ )
18
+ reload
19
+ end
20
+
21
+ def remove(opts = {})
22
+ connection.delete(path_for, opts)
23
+ nil
24
+ end
25
+ alias_method :delete, :remove
26
+
27
+ def json(opts = {})
28
+ Docker::Util.parse_json(connection.get(path_for, opts))
29
+ end
30
+
31
+ def to_s
32
+ "Docker::Network { :id => #{id}, :info => #{info.inspect}, "\
33
+ ":connection => #{connection} }"
34
+ end
35
+
36
+ def reload
37
+ network_json = @connection.get("/networks/#{@id}")
38
+ hash = Docker::Util.parse_json(network_json) || {}
39
+ @info = hash
40
+ end
41
+
42
+ class << self
43
+ def create(name, opts = {}, conn = Docker.connection)
44
+ default_opts = MultiJson.dump({
45
+ 'Name' => name,
46
+ 'CheckDuplicate' => true
47
+ }.merge(opts))
48
+ resp = conn.post('/networks/create', {}, body: default_opts)
49
+ response_hash = Docker::Util.parse_json(resp) || {}
50
+ get(response_hash['Id'], {}, conn) || {}
51
+ end
52
+
53
+ def get(id, opts = {}, conn = Docker.connection)
54
+ network_json = conn.get("/networks/#{id}", opts)
55
+ hash = Docker::Util.parse_json(network_json) || {}
56
+ new(conn, hash)
57
+ end
58
+
59
+ def all(opts = {}, conn = Docker.connection)
60
+ hashes = Docker::Util.parse_json(conn.get('/networks', opts)) || []
61
+ hashes.map { |hash| new(conn, hash) }
62
+ end
63
+
64
+ def remove(id, opts = {}, conn = Docker.connection)
65
+ conn.delete("/networks/#{id}", opts)
66
+ nil
67
+ end
68
+ alias_method :delete, :remove
69
+
70
+ def prune(conn = Docker.connection)
71
+ conn.post("/networks/prune", {})
72
+ nil
73
+ end
74
+ end
75
+
76
+ # Convenience method to return the path for a particular resource.
77
+ def path_for(resource = nil)
78
+ ["/networks/#{id}", resource].compact.join('/')
79
+ end
80
+
81
+ private :path_for
82
+ end
@@ -0,0 +1,39 @@
1
+ # This class allows image-based tasks to be created.
2
+ class Docker::ImageTask < Rake::Task
3
+ def self.scope_name(_scope, task_name)
4
+ task_name
5
+ end
6
+
7
+ def needed?
8
+ !has_repo_tag?
9
+ end
10
+
11
+ private
12
+
13
+ def has_repo_tag?
14
+ images.any? { |image| image.info['RepoTags'].include?(repo_tag) }
15
+ end
16
+
17
+ def images
18
+ @images ||= Docker::Image.all(:all => true)
19
+ end
20
+
21
+ def repo
22
+ name.split(':')[0]
23
+ end
24
+
25
+ def tag
26
+ name.split(':')[1] || 'latest'
27
+ end
28
+
29
+ def repo_tag
30
+ "#{repo}:#{tag}"
31
+ end
32
+ end
33
+
34
+ # Monkeypatch Rake to add the `image` task.
35
+ module Rake::DSL
36
+ def image(*args, &block)
37
+ Docker::ImageTask.define_task(*args, &block)
38
+ end
39
+ end
@@ -0,0 +1,279 @@
1
+ # This module holds shared logic that doesn't really belong anywhere else in the
2
+ # gem.
3
+ module Docker::Util
4
+ # http://www.tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm#STANDARD-WILDCARDS
5
+ GLOB_WILDCARDS = /[\?\*\[\{]/
6
+
7
+ include Docker::Error
8
+
9
+ module_function
10
+
11
+ # Attaches to a HTTP stream
12
+ #
13
+ # @param block
14
+ # @param msg_stack [Docker::Messages]
15
+ # @param tty [boolean]
16
+ def attach_for(block, msg_stack, tty = false)
17
+ # If TTY is enabled expect raw data and append to stdout
18
+ if tty
19
+ attach_for_tty(block, msg_stack)
20
+ else
21
+ attach_for_multiplex(block, msg_stack)
22
+ end
23
+ end
24
+
25
+ def attach_for_tty(block, msg_stack)
26
+ messages = Docker::Messages.new
27
+ lambda do |c,r,t|
28
+ messages.stdout_messages << c
29
+ messages.all_messages << c
30
+ msg_stack.append(messages)
31
+
32
+ block.call c if block
33
+ end
34
+ end
35
+
36
+ def attach_for_multiplex(block, msg_stack)
37
+ messages = Docker::Messages.new
38
+ lambda do |c,r,t|
39
+ messages = messages.decipher_messages(c)
40
+
41
+ unless block.nil?
42
+ messages.stdout_messages.each do |msg|
43
+ block.call(:stdout, msg)
44
+ end
45
+ messages.stderr_messages.each do |msg|
46
+ block.call(:stderr, msg)
47
+ end
48
+ end
49
+
50
+ msg_stack.append(messages)
51
+ end
52
+ end
53
+
54
+ def debug(msg)
55
+ Docker.logger.debug(msg) if Docker.logger
56
+ end
57
+
58
+ def hijack_for(stdin, block, msg_stack, tty)
59
+ attach_block = attach_for(block, msg_stack, tty)
60
+
61
+ lambda do |socket|
62
+ debug "hijack: hijacking the HTTP socket"
63
+ threads = []
64
+
65
+ debug "hijack: starting stdin copy thread"
66
+ threads << Thread.start do
67
+ debug "hijack: copying stdin => socket"
68
+ IO.copy_stream stdin, socket
69
+
70
+ debug "hijack: closing write end of hijacked socket"
71
+ close_write(socket)
72
+ end
73
+
74
+ debug "hijack: starting hijacked socket read thread"
75
+ threads << Thread.start do
76
+ debug "hijack: reading from hijacked socket"
77
+
78
+ begin
79
+ while chunk = socket.readpartial(512)
80
+ debug "hijack: got #{chunk.bytesize} bytes from hijacked socket"
81
+ attach_block.call chunk, nil, nil
82
+ end
83
+ rescue EOFError
84
+ end
85
+
86
+ debug "hijack: killing stdin copy thread"
87
+ threads.first.kill
88
+ end
89
+
90
+ threads.each(&:join)
91
+ end
92
+ end
93
+
94
+ def close_write(socket)
95
+ if socket.respond_to?(:close_write)
96
+ socket.close_write
97
+ elsif socket.respond_to?(:io)
98
+ socket.io.close_write
99
+ else
100
+ raise IOError, 'Cannot close socket'
101
+ end
102
+ end
103
+
104
+ def parse_json(body)
105
+ MultiJson.load(body) unless body.nil? || body.empty? || (body == 'null')
106
+ rescue MultiJson::ParseError => ex
107
+ raise UnexpectedResponseError, ex.message
108
+ end
109
+
110
+ def parse_repo_tag(str)
111
+ if match = str.match(/\A(.*):([^:]*)\z/)
112
+ match.captures
113
+ else
114
+ [str, '']
115
+ end
116
+ end
117
+
118
+ def fix_json(body)
119
+ parse_json("[#{body.gsub(/}\s*{/, '},{')}]")
120
+ end
121
+
122
+ def create_tar(hash = {})
123
+ output = StringIO.new
124
+ Gem::Package::TarWriter.new(output) do |tar|
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
131
+ end
132
+ end
133
+ output.tap(&:rewind).string
134
+ end
135
+
136
+ def create_dir_tar(directory)
137
+ tempfile = create_temp_file
138
+ directory += '/' unless directory.end_with?('/')
139
+
140
+ create_relative_dir_tar(directory, tempfile)
141
+
142
+ File.new(tempfile.path, 'r')
143
+ end
144
+
145
+ def create_relative_dir_tar(directory, output)
146
+ Gem::Package::TarWriter.new(output) do |tar|
147
+ files = glob_all_files(File.join(directory, "**/*"))
148
+ remove_ignored_files!(directory, files)
149
+
150
+ files.each do |prefixed_file_name|
151
+ stat = File.stat(prefixed_file_name)
152
+ next unless stat.file?
153
+
154
+ unprefixed_file_name = prefixed_file_name[directory.length..-1]
155
+ add_file_to_tar(
156
+ tar, unprefixed_file_name, stat.mode, stat.size, stat.mtime
157
+ ) do |tar_file|
158
+ IO.copy_stream(File.open(prefixed_file_name, 'rb'), tar_file)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def add_file_to_tar(tar, name, mode, size, mtime)
165
+ tar.check_closed
166
+
167
+ io = tar.instance_variable_get(:@io)
168
+
169
+ name, prefix = tar.split_name(name)
170
+
171
+ header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
172
+ :size => size, :prefix => prefix,
173
+ :mtime => mtime).to_s
174
+
175
+ io.write header
176
+ os = Gem::Package::TarWriter::BoundedStream.new io, size
177
+
178
+ yield os if block_given?
179
+
180
+ min_padding = size - os.written
181
+ io.write("\0" * min_padding)
182
+
183
+ remainder = (512 - (size % 512)) % 512
184
+ io.write("\0" * remainder)
185
+
186
+ tar
187
+ end
188
+
189
+ def create_temp_file
190
+ tempfile_name = Dir::Tmpname.create('out') {}
191
+ File.open(tempfile_name, 'wb+')
192
+ end
193
+
194
+ def extract_id(body)
195
+ body.lines.reverse_each do |line|
196
+ if (id = line.match(/Successfully built ([a-f0-9]+)/)) && !id[1].empty?
197
+ return id[1]
198
+ end
199
+ end
200
+ raise UnexpectedResponseError, "Couldn't find id: #{body}"
201
+ end
202
+
203
+ # Convenience method to get the file hash corresponding to an array of
204
+ # local paths.
205
+ def file_hash_from_paths(local_paths)
206
+ local_paths.each_with_object({}) do |local_path, file_hash|
207
+ unless File.exist?(local_path)
208
+ raise ArgumentError, "#{local_path} does not exist."
209
+ end
210
+
211
+ basename = File.basename(local_path)
212
+ if File.directory?(local_path)
213
+ tar = create_dir_tar(local_path)
214
+ file_hash[basename] = {
215
+ content: tar.read,
216
+ permissions: filesystem_permissions(local_path)
217
+ }
218
+ tar.close
219
+ FileUtils.rm(tar.path)
220
+ else
221
+ file_hash[basename] = {
222
+ content: File.read(local_path, mode: 'rb'),
223
+ permissions: filesystem_permissions(local_path)
224
+ }
225
+ end
226
+ end
227
+ end
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
+
234
+ def build_auth_header(credentials)
235
+ credentials = MultiJson.dump(credentials) if credentials.is_a?(Hash)
236
+ encoded_creds = Base64.urlsafe_encode64(credentials)
237
+ {
238
+ 'X-Registry-Auth' => encoded_creds
239
+ }
240
+ end
241
+
242
+ def build_config_header(credentials)
243
+ if credentials.is_a?(String)
244
+ credentials = MultiJson.load(credentials, symbolize_keys: true)
245
+ end
246
+
247
+ header = MultiJson.dump(
248
+ credentials[:serveraddress].to_s => {
249
+ 'username' => credentials[:username].to_s,
250
+ 'password' => credentials[:password].to_s,
251
+ 'email' => credentials[:email].to_s
252
+ }
253
+ )
254
+
255
+ encoded_header = Base64.urlsafe_encode64(header)
256
+
257
+ {
258
+ 'X-Registry-Config' => encoded_header
259
+ }
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
279
+ end