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:
@@ -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