Merge pull request 'The image build context is the module's own directory, so a Dockerfile in a subdirectory builds (issue 1703)' (#512) from fix/1703-image-context into main
This commit was merged in pull request #512.
This commit is contained in:
@@ -24,8 +24,9 @@ The Dockerfile's steps may use the network (ADR 0487 §6); nothing a step can re
|
||||
|
||||
- `build_image_fetch` fetches a commit over SSH into a workspace (a base commit makes the merge preview, as the other
|
||||
build modules' fetch). Long: answers a step id and ends on its key in `steps`.
|
||||
- `build_image_build` builds one image artifact: `buildah bud` with the Dockerfile of the module's directory as file
|
||||
and context, `--target`, and the images `build.on` pins as `--build-arg`. Answers the image's digest. With
|
||||
- `build_image_build` builds one image artifact: `buildah bud` with the Dockerfile `from` names (in the module's
|
||||
directory or a subdirectory of it) as file and the module's own directory as context, so a COPY names paths from the
|
||||
module's root (issue 1703), `--target`, and the images `build.on` pins as `--build-arg`. Answers the image's digest. With
|
||||
`publish: true` (a merged trunk commit only) the module's own process then pushes it to the registry under
|
||||
`<module>/<artifact>:<tag of its own>` (the first 12 hex digits of the digest unless the call gives one), waiting out
|
||||
a registry held still, and the key carries the digest the registry serves and the reference pinned by it. Without
|
||||
|
||||
@@ -55,18 +55,18 @@ func (m *Module) buildTool() StepTool {
|
||||
}
|
||||
|
||||
// unsupportedImage says why this module does not build the artifact, or "" when it does. It is the one place that
|
||||
// knows which image forms are out: ADR 0487 §1 takes the Dockerfile's directory as the build context and the base
|
||||
// knows which image forms are out: ADR 0487 §1 builds from the module's own directory (the build context) and the base
|
||||
// from the Dockerfile or an image pinned in `build.on`, so a context that is another repository and a base that is
|
||||
// another module's artifact are not built (the lead's decision for R4c-9, which retires them with build-agent at R4g).
|
||||
func unsupportedImage(manifest catalogue.Manifest, a catalogue.Artifact) string {
|
||||
if a.Context != nil {
|
||||
return fmt.Sprintf("the image %s of %s builds with the repository %s as its context; mesh-build-image takes the Dockerfile's directory as the build context (ADR 0487 §1) and does not build a whole repository",
|
||||
return fmt.Sprintf("the image %s of %s builds with the repository %s as its context; mesh-build-image takes the module's own directory as the build context (ADR 0487 §1) and does not build another repository",
|
||||
a.Name, manifest.Module, a.Context.Repository)
|
||||
}
|
||||
if manifest.Build != nil {
|
||||
for _, on := range manifest.Build.On {
|
||||
if on.Module != "" {
|
||||
return fmt.Sprintf("the image %s of %s stands on the artifact %s of the module %s through the build argument %s; mesh-build-image builds only from the Dockerfile's directory and an image pinned in build.on (ADR 0487 §1)",
|
||||
return fmt.Sprintf("the image %s of %s stands on the artifact %s of the module %s through the build argument %s; mesh-build-image builds only from the module's own directory and an image pinned in build.on (ADR 0487 §1)",
|
||||
a.Name, manifest.Module, on.Artifact, on.Module, on.Arg)
|
||||
}
|
||||
}
|
||||
@@ -183,8 +183,14 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) {
|
||||
return m.push(ctx, workspace, manifest.Module, local, res, tag)
|
||||
}
|
||||
|
||||
// dockerfileOf is the Dockerfile an artifact's `from` names and the build context: the directory it lies in. It must
|
||||
// be a regular file inside the module's own directory (checks/imageplace holds the catalogue to the same).
|
||||
// dockerfileOf is the Dockerfile an artifact's `from` names and the build context: the module's own directory, the
|
||||
// one its manifest lies in, whichever subdirectory the Dockerfile sits in. `from` is a path relative to that
|
||||
// directory, and so is every COPY of the Dockerfile: a recipe in a subdirectory (photos' admin-client/Dockerfile
|
||||
// copies admin-client/... and client-server/serve.mjs) builds as build-agent built it, and as the fingerprint reads
|
||||
// the module's whole directory as its build source. Taking the Dockerfile's own directory instead failed every such
|
||||
// COPY with `copier: stat ... no such file or directory` (issue 1703, 2026-10-26). The context never leaves the module's
|
||||
// directory, so a module's build reads no other module's files. The Dockerfile must be a regular file inside that
|
||||
// directory (checks/imageplace holds the catalogue to the same).
|
||||
func dockerfileOf(moduleDir, from string) (dockerfile, contextDir string, err error) {
|
||||
clean := filepath.Clean(filepath.FromSlash(strings.TrimSpace(from)))
|
||||
if from == "" || filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
|
||||
@@ -195,7 +201,7 @@ func dockerfileOf(moduleDir, from string) (dockerfile, contextDir string, err er
|
||||
if err != nil || !info.Mode().IsRegular() {
|
||||
return "", "", fmt.Errorf("%q is not a file in the module's directory", from)
|
||||
}
|
||||
return dockerfile, filepath.Dir(dockerfile), nil
|
||||
return dockerfile, moduleDir, nil
|
||||
}
|
||||
|
||||
// localName names the image in buildah's store: one name for each artifact of a workspace.
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if !strings.HasSuffix(bud[0].line, " "+dir) {
|
||||
t.Errorf("the context is not the Dockerfile's directory:\n%s", bud[0].line)
|
||||
t.Errorf("the context is not the module's directory:\n%s", bud[0].line)
|
||||
}
|
||||
for _, c := range f.calls {
|
||||
if c.name != "buildah" {
|
||||
@@ -122,6 +122,43 @@ func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1703, 2026-10-26: a Dockerfile in a subdirectory of the module builds with the module's directory as its
|
||||
// context, so its COPY of a path from the module's root (photos' admin-client/Dockerfile copies admin-client/... and
|
||||
// client-server/serve.mjs) finds it; the Dockerfile's own directory as the context failed with `copier: stat
|
||||
// "/admin-client/package.json": no such file or directory`.
|
||||
func TestBuildContextIsTheModulesDirectoryForADockerfileInASubdirectory(t *testing.T) {
|
||||
m, f, states := setup(t, func(s string) string {
|
||||
return strings.Replace(s, `"from": "Dockerfile"`, `"from": "admin-client/Dockerfile"`, 1)
|
||||
})
|
||||
dir := filepath.Join(m.Work, "ws1", "tree", "modules", "demo")
|
||||
for name, body := range map[string]string{
|
||||
"admin-client/Dockerfile": "ARG DEMO_BASE\nFROM ${DEMO_BASE} AS final\nCOPY admin-client/package.json client-server/serve.mjs /\n",
|
||||
"admin-client/package.json": "{}\n",
|
||||
"client-server/serve.mjs": "\n",
|
||||
} {
|
||||
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, st := runBuild(t, m, states, nil)
|
||||
if st.State != delivery.StepPassed {
|
||||
t.Fatalf("step = %+v", st)
|
||||
}
|
||||
bud := f.called("bud")
|
||||
if len(bud) != 1 {
|
||||
t.Fatalf("buildah bud ran %d times", len(bud))
|
||||
}
|
||||
if !strings.Contains(bud[0].line, "--file "+dir+"/admin-client/Dockerfile ") {
|
||||
t.Errorf("the Dockerfile is not the subdirectory's:\n%s", bud[0].line)
|
||||
}
|
||||
if !strings.HasSuffix(bud[0].line, " "+dir) {
|
||||
t.Errorf("the context is not the module's directory, so admin-client/ and client-server/ are out of reach:\n%s", bud[0].line)
|
||||
}
|
||||
}
|
||||
|
||||
// 1703c, issue 1703: a failing Dockerfile step fails the step, its reason saying buildah's error from stderr, never the
|
||||
// last STEP line buildah writes on stdout; nothing is pushed.
|
||||
func TestBuildFailsWithBuildahsError(t *testing.T) {
|
||||
@@ -178,7 +215,7 @@ func TestBuildRefusesAnImageWithARepositoryContextOrAModuleBase(t *testing.T) {
|
||||
}{
|
||||
"context": {func(s string) string {
|
||||
return strings.Replace(s, `"target": "final"`, `"target": "final", "context": {"seat": "git", "repository": "novox/mesh-controller"}`, 1)
|
||||
}, []string{"novox/mesh-controller", "Dockerfile's directory", "ADR 0487"}},
|
||||
}, []string{"novox/mesh-controller", "module's own directory", "ADR 0487"}},
|
||||
"module base": {func(s string) string {
|
||||
return strings.Replace(s, `"image": "docker.io/library/busybox@sha256:0000000000000000000000000000000000000000000000000000000000000001"`, `"module": "mesh-sdk", "artifact": "lib"`, 1)
|
||||
}, []string{"artifact lib of the module mesh-sdk", "DEMO_BASE"}},
|
||||
|
||||
Reference in New Issue
Block a user