diff --git a/internal/builder/builder.go b/internal/builder/builder.go index a174d48..8dc0dcc 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -324,14 +324,27 @@ func one(ctx context.Context, run Runner, publish Publisher, switch a.Kind { case catalogue.ArtifactUpstream: - // Mirrored, not built. Pulled by the reference the module names and pushed under a name - // of the mesh's own, so what a machine fetches is pinned by a digest this registry - // assigned rather than by a tag somebody else can move. + // Mirrored, not built: copied under a name of the mesh's own, so what a machine fetches is + // pinned by a digest this registry assigned rather than by a tag somebody else can move. + // + // **Between registries, never through this machine's image store** (novox/hq + // 04-ISSUES/046, ADR 0096). A published image is an index over several architectures; + // pulled, the store keeps the index and refuses to push one platform out of it, and + // every variant of pull-then-push failed the same way. A copy moves what is there. + if mirror, can := publish.(Mirrorer); can { + say("mirror", "copying %s into the mesh's registry", a.From) + reference, err := mirror.MirrorImage(ctx, a.From, module+"/"+a.Name) + if err != nil { + return catalogue.Built{}, fmt.Errorf("%s: %w", module, err) + } + return catalogue.Built{Name: a.Name, Kind: a.Kind, Reference: reference}, nil + } + // Genesis has no registry to copy into: the image stays in this machine's store, named by + // its own id, as every artifact does before there is anywhere to publish. say("mirror", "pulling %s", a.From) if _, err := run(ctx, tree, "docker", "pull", a.From); err != nil { return catalogue.Built{}, fmt.Errorf("%s: cannot fetch %s: %w", module, a.From, err) } - say("mirror", "publishing under the mesh's own name") reference, err := publish.PublishImage(ctx, a.From, module+"/"+a.Name) if err != nil { return catalogue.Built{}, err diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index 7aae727..41910d9 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -347,9 +347,11 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) { continue } first = append(first, map[string]any{ - "id": GrantID(to, g.Consumer+"."+g.From), + // One file per holder — the consumer's module with its local name after it + // where it keeps several (ADR 0094); the lab found two files with one id. + "id": GrantID(to, g.Consumer+"."+holderAs(g.From, g.Local)), "type": "file", - "path": grantPath(m.Grants[to], g.Consumer, g.From), + "path": grantPath(m.Grants[to], g.Consumer, holderAs(g.From, g.Local)), "sealed": g.Sealed, }) } diff --git a/internal/catalogue/several_secrets_test.go b/internal/catalogue/several_secrets_test.go index 11872f4..60a0be4 100644 --- a/internal/catalogue/several_secrets_test.go +++ b/internal/catalogue/several_secrets_test.go @@ -134,3 +134,28 @@ func TestAProviderSeesEachLocalNameAsAHolderOfItsOwn(t *testing.T) { t.Fatalf("two holders share one file on the provider: %q", given[0].Secret) } } + +// And on the provider's machine, two files with two ids — the lab's first run had the declaration +// refused for two resources with one identity. +func TestAProviderKeepsOneFilePerHolder(t *testing.T) { + got, err := Resolve(vaultAndCA(), []string{"mesh-vault", "cache", "ca"}, workstation(), World{}) + if err != nil { + t.Fatal(err) + } + out, err := got.Declaration(Rendering{Grants: []Grant{ + {Provision: "secret", Consumer: "workstation", From: "ca", Local: "root-key", Sealed: "x"}, + {Provision: "secret", Consumer: "workstation", From: "ca", Local: "root-pass", Sealed: "y"}, + }}) + if err != nil { + t.Fatal(err) + } + ids := map[string]string{} + for _, r := range out { + if id, _ := r["id"].(string); strings.Contains(id, "grant-secret") { + ids[id] = r["path"].(string) + } + } + if len(ids) != 2 { + t.Fatalf("two holders are two grant files: %v", ids) + } +}