Build a module from a repository and a path within it
The builder cloned a repository and read the manifest at its root, which means one repository per module. Nothing we have is shaped that way, so the builder could be asked to build nothing that exists (novox/hq ADR 0069). The path travels the whole way — named when asking, carried in the request, used to read the manifest and as the context everything is produced from, echoed back in the result, and recorded as part of where a module came from. Without that last part the mesh could notice a module was behind its source and then be unable to rebuild it, which is the worst of both. A path climbing out of the clone is refused: a machine whose job is building other people's repositories must not read whatever else is on its disk. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -108,7 +108,7 @@ func TestABuildProducesAManifestThePinsAreIn(t *testing.T) {
|
||||
r, workspace := aRepository(t, withBoth, map[string]string{
|
||||
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
|
||||
})
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", workspace)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func TestTwoBuildsOfOneCommitProduceOneDigest(t *testing.T) {
|
||||
})
|
||||
// A year apart, so a packer carrying timestamps cannot accidentally agree.
|
||||
r.stamped = time.Date(2020+i, time.March, 3, 4, 5, 6, 0, time.UTC)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", workspace)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -154,7 +154,7 @@ func TestNothingIsPublishedUntilEverythingIsBuilt(t *testing.T) {
|
||||
// unreferenced, and indistinguishable from something in use.
|
||||
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch"})
|
||||
// `files` is missing, so packing the archive fails — after the image would have been pushed.
|
||||
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", workspace)
|
||||
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace)
|
||||
if err == nil {
|
||||
t.Fatal("a build with a missing input succeeded")
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func TestNothingIsPublishedUntilEverythingIsBuilt(t *testing.T) {
|
||||
func TestARepositoryWithNoManifestSaysSo(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
r := &recorded{contents: map[string]string{"README.md": "nothing to see"}}
|
||||
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", workspace)
|
||||
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace)
|
||||
if err == nil {
|
||||
t.Fatal("a repository with nothing saying what it is was built")
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func TestAModuleThatBuildsNothingStillProducesAManifest(t *testing.T) {
|
||||
// Most of what a person installs is configuration.
|
||||
r, workspace := aRepository(t, `{"module":"shell","version":"1","resources":[
|
||||
{"id":"rc","type":"file","path":"/etc/zsh/zshrc","content":"setopt"}]}`, nil)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/shell.git", "", workspace)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/shell.git", "", "", workspace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func TestTheTreeIsFreshEveryTime(t *testing.T) {
|
||||
if err := os.WriteFile(leftover, []byte("stale"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", workspace); err != nil {
|
||||
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(leftover); err == nil {
|
||||
@@ -222,7 +222,7 @@ func TestABuildThatCannotPushFails(t *testing.T) {
|
||||
"Dockerfile": "FROM scratch", "files/a": "b",
|
||||
})
|
||||
r.failPush = true
|
||||
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", workspace); err == nil {
|
||||
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace); err == nil {
|
||||
t.Fatal("a build that could publish nothing reported success")
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@ func TestAnUpstreamImageIsMirroredRatherThanBuilt(t *testing.T) {
|
||||
"resources":[{"id":"db","type":"container","name":"mesh-postgres","artifact":"store"}]}`
|
||||
|
||||
r, workspace := aRepository(t, mirrors, nil)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/postgres.git", "", workspace)
|
||||
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/postgres.git", "", "", workspace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -287,3 +287,47 @@ func TestAnUpstreamReferenceIsNotAPathInTheRepository(t *testing.T) {
|
||||
t.Fatalf("a perfectly ordinary upstream reference was refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// **A module is a repository and a path within it** (novox/hq ADR 0069). The catalogue holds its
|
||||
// modules one to a directory and the system this replaces has always built one that way, so a
|
||||
// builder that could only read a repository's root could build none of what exists.
|
||||
func TestAModuleIsBuiltFromItsPathWithinTheRepository(t *testing.T) {
|
||||
r := &recorded{contents: map[string]string{
|
||||
"README.md": "this repository holds several modules",
|
||||
"modules/shell/" + ManifestName: withBoth,
|
||||
"modules/shell/Dockerfile": "FROM scratch",
|
||||
"modules/shell/files/theme.conf": "dark",
|
||||
// A second module beside it, so what is built is chosen by the path rather than by
|
||||
// happening to be the only manifest in the clone.
|
||||
"modules/other/" + ManifestName: `{"module":"other","version":"1"}`,
|
||||
}}
|
||||
got, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/catalogue.git", "modules/shell", "", t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Manifest.Module != "meshboard" {
|
||||
t.Fatalf("built %q, which is not the module at the path asked for", got.Manifest.Module)
|
||||
}
|
||||
if got.Manifest.Resources[0]["image"] == nil {
|
||||
t.Fatalf("the image was not pinned: %v", got.Manifest.Resources[0])
|
||||
}
|
||||
}
|
||||
|
||||
// A build reads only its own tree. A path climbing out of the clone would otherwise let a build
|
||||
// read — and an archive artifact publish — whatever the build machine happens to hold, which is
|
||||
// the one thing a machine that builds other people's repositories must not do.
|
||||
func TestAPathThatLeavesTheRepositoryIsRefused(t *testing.T) {
|
||||
for _, escaping := range []string{"../../etc", "/etc"} {
|
||||
r := &recorded{contents: map[string]string{ManifestName: withBoth}}
|
||||
_, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/x.git", escaping, "", t.TempDir())
|
||||
if err == nil {
|
||||
t.Fatalf("%q was accepted as a module's path", escaping)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "leaves the repository") &&
|
||||
!strings.Contains(err.Error(), "absolute path") {
|
||||
t.Fatalf("the refusal of %q does not say why: %v", escaping, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user