The builder resolves the SDK from the mesh registry, and can publish packages
A new 'package' artifact kind builds a module's own code on a public base image and publishes it to the mesh's package registry by version (hq ADR 0076) — the SDK above all, which the toolchain is built from and so cannot be built in the toolchain. The credential a build needs to resolve or publish packages is rendered as an .npmrc (basic auth, hq ADR 0048) and given to an image build as a buildkit secret, never a layer, so a token is not baked into the toolchain image. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -86,6 +86,12 @@ func (m Manifest) Resolve(built []Built) (Manifest, error) {
|
||||
}
|
||||
delete(filled, "artifact")
|
||||
switch artifact.Kind {
|
||||
case ArtifactPackage:
|
||||
// A package is not a resource on any machine; it is consumed by other builds. A
|
||||
// resource that names one is a manifest error, named here rather than shipped.
|
||||
return Manifest{}, fmt.Errorf(
|
||||
"%s: %v uses %q, which is a package — a build input, not a resource a machine runs",
|
||||
m.Module, r["id"], named)
|
||||
case ArtifactImage, ArtifactUpstream:
|
||||
filled["image"] = artifact.Reference
|
||||
case ArtifactArchive, ArtifactBundle:
|
||||
@@ -123,19 +129,19 @@ func (b *Build) problems(module string) []string {
|
||||
}
|
||||
seen[a.Name] = true
|
||||
switch a.Kind {
|
||||
case ArtifactImage, ArtifactArchive, ArtifactUpstream, ArtifactBundle:
|
||||
case ArtifactImage, ArtifactArchive, ArtifactUpstream, ArtifactBundle, ArtifactPackage:
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q is a %q, and an artifact is %q, %q, %q or %q",
|
||||
module, a.Name, a.Kind, ArtifactImage, ArtifactArchive, ArtifactUpstream,
|
||||
ArtifactBundle))
|
||||
ArtifactBundle+", "+ArtifactPackage))
|
||||
}
|
||||
// **A bundle is built from the module itself, so it says a language instead.** Everything
|
||||
// else names what it is built from: a Dockerfile, a directory, somebody else's reference.
|
||||
// A bundle's source is the module's own directory by definition, and what it needs to say
|
||||
// is which compiler — because the mesh chooses that, and cannot choose for a module that
|
||||
// has not said.
|
||||
if a.Kind == ArtifactBundle {
|
||||
if a.Kind == ArtifactBundle || a.Kind == ArtifactPackage {
|
||||
if a.From != "" {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q is a bundle and names what it is built from (%q). A bundle is built "+
|
||||
|
||||
@@ -142,3 +142,41 @@ func TestAnArtifactOfAnUnknownKindIsRefused(t *testing.T) {
|
||||
t.Fatal("an artifact of an unknown kind was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPackageParsesLikeABundleAndNeedsALanguage(t *testing.T) {
|
||||
// The SDK's shape: a package built from the module's own directory, naming a language.
|
||||
m, err := ParseManifest([]byte(`{"module":"mesh-sdk","version":"1","slug":"sdk",
|
||||
"build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript"}]},
|
||||
"resources":[]}`))
|
||||
if err != nil {
|
||||
t.Fatalf("the SDK's package manifest did not parse: %v", err)
|
||||
}
|
||||
if m.Build.Artifacts[0].Kind != ArtifactPackage {
|
||||
t.Fatalf("expected a package artifact, got %q", m.Build.Artifacts[0].Kind)
|
||||
}
|
||||
|
||||
// A package that names what it is built from is refused, exactly as a bundle is: it is built
|
||||
// from the module's own directory.
|
||||
if _, err := ParseManifest([]byte(`{"module":"a","version":"1","build":{"artifacts":[
|
||||
{"name":"lib","kind":"package","language":"typescript","from":"Dockerfile"}]}}`)); err == nil {
|
||||
t.Fatal("a package naming a source was accepted")
|
||||
}
|
||||
// A package with no language cannot choose a toolchain.
|
||||
if _, err := ParseManifest([]byte(`{"module":"a","version":"1","build":{"artifacts":[
|
||||
{"name":"lib","kind":"package"}]}}`)); err == nil {
|
||||
t.Fatal("a package with no language was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAResourceNamingAPackageIsRefused(t *testing.T) {
|
||||
m, err := ParseManifest([]byte(`{"module":"a","version":"1","slug":"a",
|
||||
"build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript"}]},
|
||||
"resources":[{"id":"svc","type":"container","name":"a","artifact":"lib"}]}`))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
_, err = m.Resolve([]Built{{Name: "lib", Kind: ArtifactPackage, Reference: "@novox/a@1.0.0"}})
|
||||
if err == nil {
|
||||
t.Fatal("a resource backed by a package was accepted; a package is not a resource")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,6 +463,13 @@ const (
|
||||
// Mirroring is what the bootstrap already does by hand: the lab stocks upstream images into
|
||||
// the registry a first node pulls from. This makes that a thing a module can say.
|
||||
ArtifactUpstream = "upstream"
|
||||
|
||||
// ArtifactPackage is this module's own code, compiled and published to the mesh's package
|
||||
// registry by version, for other modules to consume when they are built — the SDK above all
|
||||
// (novox/hq ADR 0076). Like a bundle it is built from the module's own directory and names a
|
||||
// language; unlike a bundle it is not a resource on any machine, it is a build input. It is
|
||||
// compiled on a PUBLIC base, never the mesh toolchain, because the toolchain is built from it.
|
||||
ArtifactPackage = "package"
|
||||
)
|
||||
|
||||
// ArtifactStoreProvision is the name a module offers when it is the mesh's store for what modules
|
||||
|
||||
Reference in New Issue
Block a user