Multiple fixes: several secrets per module (069), ask a module's tool (049), copy an upstream image (046), declare a vendor image (064) #38
+22
-10
@@ -387,13 +387,23 @@ func one(ctx context.Context, run Runner, publish Publisher,
|
||||
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(
|
||||
"%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; "+
|
||||
"declare it under build.on as {\"arg\": \"<NAME>\", \"image\": \"<image>@sha256:…\"} "+
|
||||
"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...)
|
||||
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() }
|
||||
|
||||
// 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
|
||||
// registry directly.
|
||||
func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
||||
// nor one of its own stages nor `scratch`, in two lists: the bases it starts `FROM`, and the images
|
||||
// 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) (bases, copies []string) {
|
||||
stages := map[string]bool{}
|
||||
var out []string
|
||||
seen := map[string]bool{}
|
||||
var out *[]string
|
||||
note := func(ref string) {
|
||||
ref = strings.TrimSpace(ref)
|
||||
switch {
|
||||
@@ -807,7 +817,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
||||
if !declared[name] {
|
||||
if !seen[ref] {
|
||||
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
|
||||
@@ -818,7 +828,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
||||
}
|
||||
if !seen[ref] {
|
||||
seen[ref] = true
|
||||
out = append(out, ref)
|
||||
*out = append(*out, ref)
|
||||
}
|
||||
}
|
||||
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]) {
|
||||
case "FROM":
|
||||
// FROM [--platform=…] <ref> [AS <name>]
|
||||
out = &bases
|
||||
var ref string
|
||||
for i := 1; i < len(fields); i++ {
|
||||
if strings.HasPrefix(fields[i], "--") {
|
||||
@@ -843,6 +854,7 @@ func undeclaredFetches(recipe string, declared map[string]bool) []string {
|
||||
}
|
||||
note(ref)
|
||||
case "COPY", "ADD":
|
||||
out = &copies
|
||||
for _, f := range fields[1:] {
|
||||
if strings.HasPrefix(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
|
||||
FROM golang:1.25-alpine AS go
|
||||
`
|
||||
got := 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(got, "|") != strings.Join(want, "|") {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
bases, copies := undeclaredFetches(recipe, map[string]bool{"RUNTIME_BASE": true})
|
||||
if strings.Join(copies, "|") != "${MC_BASE} (a build argument the manifest does not declare)|vendor/tool:latest" {
|
||||
t.Fatalf("copies out of undeclared images: %v", copies)
|
||||
}
|
||||
if got := undeclaredFetches(recipe, map[string]bool{"RUNTIME_BASE": true, "MC_BASE": true}); len(got) != 2 {
|
||||
t.Fatalf("declared arguments are not fetches: %v", got)
|
||||
// A base fetched on its own is named apart: the mesh's own images still start FROM one, so
|
||||
// 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