Class: Dash::Dockerfile::Context
- Inherits:
-
Object
- Object
- Dash::Dockerfile::Context
- Defined in:
- lib/dash/dockerfile/context.rb
Overview
Everything a rule is allowed to look at: the parsed Dockerfile, the build context on disk, the builder's configuration, and — when the advice is printed next to a build that actually ran — what that build measured.
The shared predicates live here rather than in each rule so that "what counts as a dependency install" has one answer, and so the measured half of a rule can find the buildx vertex that corresponds to a Dockerfile line.
Constant Summary collapse
- DEPENDENCY_INSTALLS =
The package managers whose install step is worth protecting from cache busting, and the cache directory each conventionally wants mounted.
[ [ /\bbundle\s+(?:_[\d._]+_\s+)?install\b/, "/usr/local/bundle/cache" ], [ /\bnpm\s+(?:ci|install)\b/, "/root/.npm" ], [ /\byarn\s+install\b/, "/usr/local/share/.cache/yarn" ], [ /\bpnpm\s+install\b/, "/root/.local/share/pnpm/store" ], [ /\bbun\s+install\b/, "/root/.bun/install/cache" ], [ /\bpip3?\s+install\b/, "/root/.cache/pip" ], [ /\bpoetry\s+install\b/, "/root/.cache/pypoetry" ], [ /\bgo\s+mod\s+download\b/, "/go/pkg/mod" ], [ /\bcargo\s+(?:build|fetch)\b/, "/usr/local/cargo/registry" ], [ /\bcomposer\s+install\b/, "/root/.composer/cache" ], [ /\bmix\s+deps\.get\b/, "/root/.hex" ], [ /\bdotnet\s+restore\b/, "/root/.nuget/packages" ] ].freeze
- APT_OPTIONS =
apt takes its options before or after the verb (
apt-get -y install,apt-get -t bookworm-backports install), so the verb is found past any of them. A shell separator is never an option or its value, soapt-get -y && installis not an apt install. /(?:-[^\s;&|]+(?:\s+[^-\s;&|][^\s;&|]*)?\s+)*/- APT_INSTALL =
[ /\bapt-get\s+#{APT_OPTIONS.source}install\b/, "/var/cache/apt" ].freeze
- BROAD_SOURCES =
A copy that ships the whole tree, so every commit invalidates it and everything layered on top of it.
[ ".", "./", "*", "/" ].freeze
- MINIMUM_STEP_MATCH =
buildx expands ARG and ENV references in the vertex name it prints, so a step's text and the Dockerfile line it came from stop agreeing at the first $…. Match on the longest shared prefix instead, and require enough of it that two unrelated RUNs cannot be confused for each other.
12
Instance Attribute Summary collapse
-
#build ⇒ Object
readonly
Returns the value of attribute build.
-
#builder ⇒ Object
readonly
Returns the value of attribute builder.
-
#context_dir ⇒ Object
readonly
Returns the value of attribute context_dir.
-
#dockerignore ⇒ Object
readonly
Returns the value of attribute dockerignore.
-
#document ⇒ Object
readonly
Returns the value of attribute document.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Instance Method Summary collapse
- #apt_install?(instruction) ⇒ Boolean
- #broad_copy?(instruction) ⇒ Boolean
-
#build_step_for(instruction) ⇒ Object
buildx labels a step with its stage name except in a single-stage build, where it prints none.
-
#busted_by_broad_copy?(instruction) ⇒ Boolean
A dependency install directly after a broad copy is invalidated by every commit, which is a finding of its own — rules that would otherwise report the same slow step twice defer to it.
- #context_entries(name) ⇒ Object
- #dependency_install?(instruction) ⇒ Boolean
-
#initialize(document:, context_dir: nil, dockerignore: nil, build: nil, builder: nil, path: "Dockerfile") ⇒ Context
constructor
A new instance of Context.
- #install_cache_target(instruction) ⇒ Object
-
#install_command(instruction) ⇒ Object
The command that made it a dependency install ("bundle install"), for advice that names what the operator wrote rather than the whole RUN.
- #location_for(instruction) ⇒ Object
Constructor Details
#initialize(document:, context_dir: nil, dockerignore: nil, build: nil, builder: nil, path: "Dockerfile") ⇒ Context
Returns a new instance of Context.
45 46 47 48 49 50 51 52 |
# File 'lib/dash/dockerfile/context.rb', line 45 def initialize(document:, context_dir: nil, dockerignore: nil, build: nil, builder: nil, path: "Dockerfile") @document = document @context_dir = context_dir @dockerignore = dockerignore @build = build @builder = builder @path = path end |
Instance Attribute Details
#build ⇒ Object (readonly)
Returns the value of attribute build.
43 44 45 |
# File 'lib/dash/dockerfile/context.rb', line 43 def build @build end |
#builder ⇒ Object (readonly)
Returns the value of attribute builder.
43 44 45 |
# File 'lib/dash/dockerfile/context.rb', line 43 def builder @builder end |
#context_dir ⇒ Object (readonly)
Returns the value of attribute context_dir.
43 44 45 |
# File 'lib/dash/dockerfile/context.rb', line 43 def context_dir @context_dir end |
#dockerignore ⇒ Object (readonly)
Returns the value of attribute dockerignore.
43 44 45 |
# File 'lib/dash/dockerfile/context.rb', line 43 def dockerignore @dockerignore end |
#document ⇒ Object (readonly)
Returns the value of attribute document.
43 44 45 |
# File 'lib/dash/dockerfile/context.rb', line 43 def document @document end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
43 44 45 |
# File 'lib/dash/dockerfile/context.rb', line 43 def path @path end |
Instance Method Details
#apt_install?(instruction) ⇒ Boolean
72 73 74 |
# File 'lib/dash/dockerfile/context.rb', line 72 def apt_install?(instruction) instruction.name == "RUN" && instruction.shell_command.match?(APT_INSTALL.first) end |
#broad_copy?(instruction) ⇒ Boolean
76 77 78 79 80 81 |
# File 'lib/dash/dockerfile/context.rb', line 76 def broad_copy?(instruction) return false unless %w[ COPY ADD ].include?(instruction.name) return false if instruction.flag?("from") sources(instruction).any? { |source| BROAD_SOURCES.include?(source) } end |
#build_step_for(instruction) ⇒ Object
buildx labels a step with its stage name except in a single-stage build, where it prints none. A multi-platform build reports the same step once per platform; the slowest one is the number worth quoting.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/dash/dockerfile/context.rb', line 95 def build_step_for(instruction) return unless build text = normalize(instruction.to_s) candidates = build.instruction_steps.select { |step| same_stage?(step, instruction) } best = candidates.max_by do |step| matched = normalize(step.instruction) [ matched == text ? 1 : 0, shared_prefix(text, matched), step.seconds.to_f ] end return unless best matched = normalize(best.instruction) best if matched == text || shared_prefix(text, matched) >= MINIMUM_STEP_MATCH end |
#busted_by_broad_copy?(instruction) ⇒ Boolean
A dependency install directly after a broad copy is invalidated by every commit, which is a finding of its own — rules that would otherwise report the same slow step twice defer to it.
86 87 88 89 90 |
# File 'lib/dash/dockerfile/context.rb', line 86 def busted_by_broad_copy?(instruction) stage = instruction.stage or return false stage.instructions.any? { |other| other.line < instruction.line && broad_copy?(other) } end |
#context_entries(name) ⇒ Object
111 112 113 114 115 |
# File 'lib/dash/dockerfile/context.rb', line 111 def context_entries(name) return [] unless context_dir Dir.glob(name, base: context_dir, flags: ::File::FNM_DOTMATCH) end |
#dependency_install?(instruction) ⇒ Boolean
58 59 60 |
# File 'lib/dash/dockerfile/context.rb', line 58 def dependency_install?(instruction) !install_match(instruction).nil? end |
#install_cache_target(instruction) ⇒ Object
68 69 70 |
# File 'lib/dash/dockerfile/context.rb', line 68 def install_cache_target(instruction) install_match(instruction)&.last end |
#install_command(instruction) ⇒ Object
The command that made it a dependency install ("bundle install"), for advice that names what the operator wrote rather than the whole RUN.
64 65 66 |
# File 'lib/dash/dockerfile/context.rb', line 64 def install_command(instruction) install_match(instruction)&.first end |
#location_for(instruction) ⇒ Object
54 55 56 |
# File 'lib/dash/dockerfile/context.rb', line 54 def location_for(instruction) "#{path}:#{instruction.line}" end |