Module: Docker::API::Tar
- Defined in:
- lib/docker/api/tar.rb
Overview
Packs a build context into the tar archive the daemon expects.
RubyGems ships Gem::Package::TarWriter with every Ruby, so building a
context needs no dependency and no shelling out to tar -- which also
means the behaviour is identical on Windows, where there may be no tar
to shell out to.
Class Method Summary collapse
- .add_entry(tar, absolute, relative) ⇒ void private
- .each_entry(root, patterns) ⇒ void private
-
.ignored?(relative, patterns) ⇒ Boolean
Docker's ignore rules allow a later "!" pattern to re-include something an earlier pattern excluded, so the last matching rule wins rather than the first.
- .matches?(relative, pattern) ⇒ Boolean
-
.pack_directory(directory, ignore: nil) ⇒ File
Pack a directory into an uncompressed tar archive.
-
.pack_dockerfile(contents, files: {}) ⇒ StringIO
Pack a single in-memory Dockerfile into a context of its own.
-
.read_dockerignore(root) ⇒ Array<String>
Patterns from .dockerignore, or an empty list.
- .write_file(tar, path, contents, mode) ⇒ void private
Class Method Details
.add_entry(tar, absolute, relative) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/docker/api/tar.rb', line 135 def add_entry(tar, absolute, relative) stat = File.lstat(absolute) if stat.directory? tar.mkdir(relative, stat.mode) elsif stat.symlink? tar.add_symlink(relative, File.readlink(absolute), stat.mode) elsif stat.file? tar.add_file_simple(relative, stat.mode, stat.size) do |entry| File.open(absolute, "rb") { |file| IO.copy_stream(file, entry) } end end end |
.each_entry(root, patterns) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
124 125 126 127 128 129 130 131 |
# File 'lib/docker/api/tar.rb', line 124 def each_entry(root, patterns) Dir.glob("**/*", File::FNM_DOTMATCH, base: root).sort.each do |relative| next if [".", ".."].include?(File.basename(relative)) next if ignored?(relative, patterns) yield File.join(root, relative), relative end end |
.ignored?(relative, patterns) ⇒ Boolean
Docker's ignore rules allow a later "!" pattern to re-include something an earlier pattern excluded, so the last matching rule wins rather than the first.
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/docker/api/tar.rb', line 98 def ignored?(relative, patterns) decision = false patterns.each do |pattern| negated = pattern.start_with?("!") candidate = negated ? pattern[1..] : pattern decision = !negated if matches?(relative, candidate) end decision end |
.matches?(relative, pattern) ⇒ Boolean
113 114 115 116 117 118 119 120 |
# File 'lib/docker/api/tar.rb', line 113 def matches?(relative, pattern) pattern = pattern.chomp("/") return true if File.fnmatch?(pattern, relative, File::FNM_PATHNAME) # A directory pattern excludes everything beneath it. File.fnmatch?("#{pattern}/**", relative, File::FNM_PATHNAME) || relative.start_with?("#{pattern}/") end |
.pack_directory(directory, ignore: nil) ⇒ File
Pack a directory into an uncompressed tar archive.
Written to a temporary file rather than a String. A build context is whatever the caller points at -- a Rails application with its assets, a monorepo subtree, a directory holding a model checkpoint -- and holding the whole archive in memory to send it costs its full size again on top of what the daemon is about to receive. The connection layer streams an IO body chunked, so the archive never has to be resident at all.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/docker/api/tar.rb', line 36 def pack_directory(directory, ignore: nil) root = File.(directory) raise ArgumentError, "build context #{directory} is not a directory" unless File.directory?(root) patterns = ignore || read_dockerignore(root) buffer = Tempfile.new(["docker-api-ng-context", ".tar"]) buffer.binmode begin Gem::Package::TarWriter.new(buffer) do |tar| each_entry(root, patterns) do |absolute, relative| add_entry(tar, absolute, relative) end end rescue StandardError buffer.close! raise end buffer.rewind buffer end |
.pack_dockerfile(contents, files: {}) ⇒ StringIO
Pack a single in-memory Dockerfile into a context of its own.
Useful for the common case of a short generated Dockerfile with no accompanying files, where writing a temporary directory first would be ceremony for its own sake.
68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/docker/api/tar.rb', line 68 def pack_dockerfile(contents, files: {}) buffer = StringIO.new(+"".b) Gem::Package::TarWriter.new(buffer) do |tar| write_file(tar, "Dockerfile", contents, 0o644) files.each { |path, body| write_file(tar, path, body, 0o644) } end buffer.rewind buffer end |
.read_dockerignore(root) ⇒ Array<String>
Returns patterns from .dockerignore, or an empty list.
82 83 84 85 86 87 88 89 |
# File 'lib/docker/api/tar.rb', line 82 def read_dockerignore(root) path = File.join(root, ".dockerignore") return [] unless File.readable?(path) File.readlines(path, chomp: true) .map(&:strip) .reject { |line| line.empty? || line.start_with?("#") } end |
.write_file(tar, path, contents, mode) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
151 152 153 154 |
# File 'lib/docker/api/tar.rb', line 151 def write_file(tar, path, contents, mode) bytes = contents.to_s.b tar.add_file_simple(path, mode, bytes.bytesize) { |entry| entry.write(bytes) } end |