An undeclared COPY --from is refused; an undeclared FROM is said, not yet refused
The mesh's own images start FROM a public base — the control plane's, the builder's, the tool runtime's — and refusing those refuses genesis. They declare their bases next; until then the base is named every build, with the remedy.
This commit is contained in:
+22
-10
@@ -387,13 +387,23 @@ func one(ctx context.Context, run Runner, publish Publisher,
|
|||||||
declared[strings.SplitN(args[i+1], "=", 2)[0]] = true
|
declared[strings.SplitN(args[i+1], "=", 2)[0]] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fetches := undeclaredFetches(string(recipe), declared); len(fetches) > 0 {
|
bases, copies := undeclaredFetches(string(recipe), declared)
|
||||||
|
if len(copies) > 0 {
|
||||||
return catalogue.Built{}, fmt.Errorf(
|
return catalogue.Built{}, fmt.Errorf(
|
||||||
"%s: the recipe %s fetches %s, which the manifest does not declare. A build "+
|
"%s: the recipe %s copies out of %s, which the manifest does not declare. A build "+
|
||||||
"reaching a public registry on its own works only when that registry answers; "+
|
"reaching a public registry on its own works only when that registry answers; "+
|
||||||
"declare it under build.on as {\"arg\": \"<NAME>\", \"image\": \"<image>@sha256:…\"} "+
|
"declare it under build.on as {\"arg\": \"<NAME>\", \"image\": \"<image>@sha256:…\"} "+
|
||||||
"and read it from that argument (novox/hq ADR 0097)",
|
"and read it from that argument (novox/hq ADR 0097)",
|
||||||
module, a.From, strings.Join(fetches, ", "))
|
module, a.From, strings.Join(copies, ", "))
|
||||||
|
}
|
||||||
|
if len(bases) > 0 {
|
||||||
|
// Said, not yet refused: the mesh's own images start FROM a public base — the control
|
||||||
|
// plane's, the builder's, the tool runtime's — and refusing those refuses genesis.
|
||||||
|
// They declare their bases next; until then a base fetched on its own is named here,
|
||||||
|
// with the remedy, every build.
|
||||||
|
say("recipe", "UNDECLARED base(s) %s in %s — declare each under build.on as "+
|
||||||
|
"{arg, image@sha256:…} and read it from that argument (novox/hq ADR 0097)",
|
||||||
|
strings.Join(bases, ", "), a.From)
|
||||||
}
|
}
|
||||||
invocation := append([]string{"build", "-f", a.From, "-t", local}, args...)
|
invocation := append([]string{"build", "-f", a.From, "-t", local}, args...)
|
||||||
if a.Target != "" {
|
if a.Target != "" {
|
||||||
@@ -788,12 +798,12 @@ func timeNow() time.Time { return time.Now() }
|
|||||||
func since(t time.Time) string { return time.Since(t).Round(time.Millisecond).String() }
|
func since(t time.Time) string { return time.Since(t).Round(time.Millisecond).String() }
|
||||||
|
|
||||||
// undeclaredFetches is every image a recipe reaches for that is neither a declared build argument
|
// undeclaredFetches is every image a recipe reaches for that is neither a declared build argument
|
||||||
// nor one of its own stages nor `scratch`: a `FROM` or a `COPY --from` naming somebody else's
|
// nor one of its own stages nor `scratch`, in two lists: the bases it starts `FROM`, and the images
|
||||||
// registry directly.
|
// it `COPY --from`s out of — a vendor's tool, the case novox/hq 04-ISSUES/064 is about.
|
||||||
func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
func undeclaredFetches(recipe string, declared map[string]bool) (bases, copies []string) {
|
||||||
stages := map[string]bool{}
|
stages := map[string]bool{}
|
||||||
var out []string
|
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
|
var out *[]string
|
||||||
note := func(ref string) {
|
note := func(ref string) {
|
||||||
ref = strings.TrimSpace(ref)
|
ref = strings.TrimSpace(ref)
|
||||||
switch {
|
switch {
|
||||||
@@ -807,7 +817,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
|||||||
if !declared[name] {
|
if !declared[name] {
|
||||||
if !seen[ref] {
|
if !seen[ref] {
|
||||||
seen[ref] = true
|
seen[ref] = true
|
||||||
out = append(out, ref+" (a build argument the manifest does not declare)")
|
*out = append(*out, ref+" (a build argument the manifest does not declare)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -818,7 +828,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
|||||||
}
|
}
|
||||||
if !seen[ref] {
|
if !seen[ref] {
|
||||||
seen[ref] = true
|
seen[ref] = true
|
||||||
out = append(out, ref)
|
*out = append(*out, ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, raw := range strings.Split(recipe, "\n") {
|
for _, raw := range strings.Split(recipe, "\n") {
|
||||||
@@ -830,6 +840,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
|||||||
switch strings.ToUpper(fields[0]) {
|
switch strings.ToUpper(fields[0]) {
|
||||||
case "FROM":
|
case "FROM":
|
||||||
// FROM [--platform=…] <ref> [AS <name>]
|
// FROM [--platform=…] <ref> [AS <name>]
|
||||||
|
out = &bases
|
||||||
var ref string
|
var ref string
|
||||||
for i := 1; i < len(fields); i++ {
|
for i := 1; i < len(fields); i++ {
|
||||||
if strings.HasPrefix(fields[i], "--") {
|
if strings.HasPrefix(fields[i], "--") {
|
||||||
@@ -843,6 +854,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
|||||||
}
|
}
|
||||||
note(ref)
|
note(ref)
|
||||||
case "COPY", "ADD":
|
case "COPY", "ADD":
|
||||||
|
out = &copies
|
||||||
for _, f := range fields[1:] {
|
for _, f := range fields[1:] {
|
||||||
if strings.HasPrefix(f, "--from=") {
|
if strings.HasPrefix(f, "--from=") {
|
||||||
note(strings.TrimPrefix(f, "--from="))
|
note(strings.TrimPrefix(f, "--from="))
|
||||||
@@ -850,5 +862,5 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return bases, copies
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,12 +120,16 @@ FROM scratch
|
|||||||
COPY --from=vendor/tool:latest /tool /tool
|
COPY --from=vendor/tool:latest /tool /tool
|
||||||
FROM golang:1.25-alpine AS go
|
FROM golang:1.25-alpine AS go
|
||||||
`
|
`
|
||||||
got := undeclaredFetches(recipe, map[string]bool{"RUNTIME_BASE": true})
|
bases, copies := undeclaredFetches(recipe, map[string]bool{"RUNTIME_BASE": true})
|
||||||
want := []string{"${MC_BASE} (a build argument the manifest does not declare)", "vendor/tool:latest", "golang:1.25-alpine"}
|
if strings.Join(copies, "|") != "${MC_BASE} (a build argument the manifest does not declare)|vendor/tool:latest" {
|
||||||
if strings.Join(got, "|") != strings.Join(want, "|") {
|
t.Fatalf("copies out of undeclared images: %v", copies)
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
|
||||||
}
|
}
|
||||||
if got := undeclaredFetches(recipe, map[string]bool{"RUNTIME_BASE": true, "MC_BASE": true}); len(got) != 2 {
|
// A base fetched on its own is named apart: the mesh's own images still start FROM one, so
|
||||||
t.Fatalf("declared arguments are not fetches: %v", got)
|
// it is said rather than refused until they declare theirs.
|
||||||
|
if strings.Join(bases, "|") != "golang:1.25-alpine" {
|
||||||
|
t.Fatalf("undeclared bases: %v", bases)
|
||||||
|
}
|
||||||
|
if _, copies := undeclaredFetches(recipe, map[string]bool{"RUNTIME_BASE": true, "MC_BASE": true}); len(copies) != 1 {
|
||||||
|
t.Fatalf("declared arguments are not fetches: %v", copies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user