module Docker::Util
This module holds shared logic that doesn’t really belong anywhere else in the gem.
Constants
Public Instance Methods
add_file_to_tar(tar, name, mode, size, mtime) { |os| ... }
click to toggle source
# File lib/docker/util.rb, line 193 def add_file_to_tar(tar, name, mode, size, mtime) tar.check_closed io = tar.instance_variable_get(:@io) name, prefix = tar.split_name(name) header = Gem::Package::TarHeader.new(:name => name, :mode => mode, :size => size, :prefix => prefix, :mtime => mtime).to_s io.write header os = Gem::Package::TarWriter::BoundedStream.new io, size yield os if block_given? min_padding = size - os.written io.write("\0" * min_padding) remainder = (512 - (size % 512)) % 512 io.write("\0" * remainder) tar end
attach_for(block, msg_stack, tty = false)
click to toggle source
Attaches to a HTTP stream
@param block @param msg_stack [Docker::Messages] @param tty [boolean]
# File lib/docker/util.rb, line 20 def attach_for(block, msg_stack, tty = false) # If TTY is enabled expect raw data and append to stdout if tty attach_for_tty(block, msg_stack) else attach_for_multiplex(block, msg_stack) end end
attach_for_multiplex(block, msg_stack)
click to toggle source
# File lib/docker/util.rb, line 40 def attach_for_multiplex(block, msg_stack) messages = Docker::Messages.new lambda do |c,r,t| messages = messages.decipher_messages(c) unless block.nil? messages.stdout_messages.each do |msg| block.call(:stdout, msg) end messages.stderr_messages.each do |msg| block.call(:stderr, msg) end end msg_stack.append(messages) end end
attach_for_tty(block, msg_stack)
click to toggle source
# File lib/docker/util.rb, line 29 def attach_for_tty(block, msg_stack) messages = Docker::Messages.new lambda do |c,r,t| messages.stdout_messages << c messages.all_messages << c msg_stack.append(messages) block.call c if block end end
build_auth_header(credentials)
click to toggle source
# File lib/docker/util.rb, line 263 def build_auth_header(credentials) credentials = MultiJson.dump(credentials) if credentials.is_a?(Hash) encoded_creds = Base64.urlsafe_encode64(credentials) { 'X-Registry-Auth' => encoded_creds } end
build_config_header(credentials)
click to toggle source
# File lib/docker/util.rb, line 271 def build_config_header(credentials) if credentials.is_a?(String) credentials = MultiJson.load(credentials, symbolize_keys: true) end header = MultiJson.dump( credentials[:serveraddress].to_s => { 'username' => credentials[:username].to_s, 'password' => credentials[:password].to_s, 'email' => credentials[:email].to_s } ) encoded_header = Base64.urlsafe_encode64(header) { 'X-Registry-Config' => encoded_header } end
close_write(socket)
click to toggle source
# File lib/docker/util.rb, line 98 def close_write(socket) if socket.respond_to?(:close_write) socket.close_write elsif socket.respond_to?(:io) socket.io.close_write else raise IOError, 'Cannot close socket' end end
create_dir_tar(directory)
click to toggle source
# File lib/docker/util.rb, line 140 def create_dir_tar(directory) tempfile = create_temp_file directory += '/' unless directory.end_with?('/') create_relative_dir_tar(directory, tempfile) File.new(tempfile.path, 'r') end
create_relative_dir_tar(directory, output)
click to toggle source
# File lib/docker/util.rb, line 175 def create_relative_dir_tar(directory, output) Gem::Package::TarWriter.new(output) do |tar| files = docker_context(directory) files.each do |prefixed_file_name| stat = File.stat(prefixed_file_name) next unless stat.file? unprefixed_file_name = prefixed_file_name[directory.length..-1] add_file_to_tar( tar, unprefixed_file_name, stat.mode, stat.size, stat.mtime ) do |tar_file| IO.copy_stream(File.open(prefixed_file_name, 'rb'), tar_file) end end end end
create_tar(hash = {})
click to toggle source
# File lib/docker/util.rb, line 126 def create_tar(hash = {}) output = StringIO.new Gem::Package::TarWriter.new(output) do |tar| hash.each do |file_name, file_details| permissions = file_details.is_a?(Hash) ? file_details[:permissions] : 0640 tar.add_file(file_name, permissions) do |tar_file| content = file_details.is_a?(Hash) ? file_details[:content] : file_details tar_file.write(content) end end end output.tap(&:rewind).string end
create_temp_file()
click to toggle source
# File lib/docker/util.rb, line 218 def create_temp_file tempfile_name = Dir::Tmpname.create('out') {} File.open(tempfile_name, 'wb+') end
debug(msg)
click to toggle source
# File lib/docker/util.rb, line 58 def debug(msg) Docker.logger.debug(msg) if Docker.logger end
docker_context(directory)
click to toggle source
return the set of files that form the docker context implement this logic docs.docker.com/engine/reference/builder/#dockerignore-file
# File lib/docker/util.rb, line 152 def docker_context(directory) all_files = glob_all_files(File.join(directory, "**/*")) dockerignore = File.join(directory, '.dockerignore') return all_files unless all_files.include?(dockerignore) # Iterate over valid lines, starting with the initial glob as working set File .read(dockerignore) # https://docs.docker.com/engine/reference/builder/#dockerignore-file .each_line # "a newline-separated list of patterns" .map(&:strip) # "A preprocessing step removes leading and trailing whitespace" .reject(&:empty?) # "Lines that are blank after preprocessing are ignored" .reject { |p| p.start_with?('#') } # "if [a line starts with `#`], then this line is considered as a comment" .each_with_object(Set.new(all_files)) do |p, working_set| # determine the pattern (p) and whether it is to be added or removed from context add = p.start_with?("!") # strip leading "!" from pattern p, then prepend the base directory matches = dockerignore_compatible_glob(File.join(directory, add ? p[1..-1] : p)) # add or remove the matched items as indicated in the ignore file add ? working_set.merge(matches) : working_set.replace(working_set.difference(matches)) end .to_a end
dockerignore_compatible_glob(pattern)
click to toggle source
do a directory glob that matches .dockerignore behavior specifically: matched directories are considered a recursive match
# File lib/docker/util.rb, line 293 def dockerignore_compatible_glob(pattern) begin some_dirs, some_files = glob_all_files(pattern).partition { |f| File.directory?(f) } # since all directories will be re-processed with a /**/* glob, we can preemptively # eliminate any whose parent directory is already in this set. This saves significant time. some_files + some_dirs.reject { |d| some_dirs.any? { |pd| d.start_with?(pd) && d != pd } } end.each_with_object(Set.new) do |f, acc| # expand any directories by globbing; flatten results acc.merge(File.directory?(f) ? glob_all_files("#{f}/**/*") : [f]) end end
extract_id(body)
click to toggle source
# File lib/docker/util.rb, line 223 def extract_id(body) body.lines.reverse_each do |line| if (id = line.match(/Successfully built ([a-f0-9]+)/)) && !id[1].empty? return id[1] end end raise UnexpectedResponseError, "Couldn't find id: #{body}" end
file_hash_from_paths(local_paths)
click to toggle source
Convenience method to get the file hash corresponding to an array of local paths.
# File lib/docker/util.rb, line 234 def file_hash_from_paths(local_paths) local_paths.each_with_object({}) do |local_path, file_hash| unless File.exist?(local_path) raise ArgumentError, "#{local_path} does not exist." end basename = File.basename(local_path) if File.directory?(local_path) tar = create_dir_tar(local_path) file_hash[basename] = { content: tar.read, permissions: filesystem_permissions(local_path) } tar.close FileUtils.rm(tar.path) else file_hash[basename] = { content: File.read(local_path, mode: 'rb'), permissions: filesystem_permissions(local_path) } end end end
filesystem_permissions(path)
click to toggle source
# File lib/docker/util.rb, line 258 def filesystem_permissions(path) mode = sprintf("%o", File.stat(path).mode) mode[(mode.length - 3)...mode.length].to_i(8) end
fix_json(body)
click to toggle source
# File lib/docker/util.rb, line 122 def fix_json(body) parse_json("[#{body.gsub(/}\s*{/, '},{')}]") end
glob_all_files(pattern)
click to toggle source
# File lib/docker/util.rb, line 305 def glob_all_files(pattern) # globs of "a_dir/**/*" can return "a_dir/.", so explicitly reject those (Dir.glob(pattern, File::FNM_DOTMATCH) - ['..', '.']).reject { |p| p.end_with?("/.") } end
hijack_for(stdin, block, msg_stack, tty)
click to toggle source
# File lib/docker/util.rb, line 62 def hijack_for(stdin, block, msg_stack, tty) attach_block = attach_for(block, msg_stack, tty) lambda do |socket| debug "hijack: hijacking the HTTP socket" threads = [] debug "hijack: starting stdin copy thread" threads << Thread.start do debug "hijack: copying stdin => socket" IO.copy_stream stdin, socket debug "hijack: closing write end of hijacked socket" close_write(socket) end debug "hijack: starting hijacked socket read thread" threads << Thread.start do debug "hijack: reading from hijacked socket" begin while chunk = socket.readpartial(512) debug "hijack: got #{chunk.bytesize} bytes from hijacked socket" attach_block.call chunk, nil, nil end rescue EOFError end debug "hijack: killing stdin copy thread" threads.first.kill end threads.each(&:join) end end
parse_json(body)
click to toggle source
# File lib/docker/util.rb, line 108 def parse_json(body) MultiJson.load(body) unless body.nil? || body.empty? || (body == 'null') rescue MultiJson::ParseError => ex raise UnexpectedResponseError, ex.message end
parse_repo_tag(str)
click to toggle source
# File lib/docker/util.rb, line 114 def parse_repo_tag(str) if match = str.match(/\A(.*):([^:]*)\z/) match.captures else [str, ''] end end