Review: one local name is still a local name; a local name is unique; recovery knows it; recipes read as instructions; a tag before a digest; ask fails at once when nothing serves

A secrets object with one local name delivered no file. Two requirements could share
a local name. secret recover and the export could not tell two locals apart. The
recipe check missed continued lines and read heredoc bodies as bases. repo:tag@digest
kept the tag in the repository. ask now publishes mandatory, so a tool nothing serves
is said at once rather than after the wait.
This commit is contained in:
2026-09-21 21:03:22 +02:00
parent e81f352979
commit 9f3790dcda
13 changed files with 229 additions and 29 deletions
+58 -5
View File
@@ -831,11 +831,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) (bases, copies [
*out = append(*out, ref)
}
}
for _, raw := range strings.Split(recipe, "\n") {
line := strings.TrimSpace(raw)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
for _, line := range instructions(recipe) {
fields := strings.Fields(line)
switch strings.ToUpper(fields[0]) {
case "FROM":
@@ -860,7 +856,64 @@ func undeclaredFetches(recipe string, declared map[string]bool) (bases, copies [
note(strings.TrimPrefix(f, "--from="))
}
}
case "RUN":
// RUN --mount=type=bind,from=<image>,… reaches for an image exactly as COPY --from does.
out = &copies
for _, f := range fields[1:] {
if !strings.HasPrefix(f, "--mount=") {
continue
}
for _, opt := range strings.Split(strings.TrimPrefix(f, "--mount="), ",") {
if from, found := strings.CutPrefix(opt, "from="); found {
note(from)
}
}
}
}
}
return bases, copies
}
// instructions is a recipe as its instructions, one per line: continuations joined, comments and
// blank lines dropped, and heredoc bodies (`COPY <<EOF … EOF`) skipped — a Python file written into
// an image is not a list of images to fetch. The review found a `COPY \` continued onto the next
// line slip past the check, and a stage named on a continuation line refused as a fetch.
func instructions(recipe string) []string {
var out []string
var current strings.Builder
var heredoc string
flush := func() {
if line := strings.TrimSpace(current.String()); line != "" && !strings.HasPrefix(line, "#") {
out = append(out, line)
}
current.Reset()
}
for _, raw := range strings.Split(recipe, "\n") {
if heredoc != "" {
if strings.TrimSpace(raw) == heredoc {
heredoc = ""
}
continue
}
line := strings.TrimRight(raw, " \t")
if strings.HasPrefix(strings.TrimSpace(line), "#") && current.Len() == 0 {
continue
}
if strings.HasSuffix(line, "\\") {
current.WriteString(strings.TrimSuffix(line, "\\"))
current.WriteString(" ")
continue
}
current.WriteString(line)
if at := strings.Index(current.String(), "<<"); at >= 0 {
// `<<EOF`, `<<-EOF`, `<<'EOF'`, `<<"EOF"`: the body runs to a line that is the word.
word := strings.Fields(current.String()[at+2:])
if len(word) > 0 {
heredoc = strings.Trim(strings.TrimPrefix(word[0], "-"), `'"`)
}
}
flush()
}
flush()
return out
}