docker-api 2.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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