Skip to content

Commit beeff50

Browse files
authored
Merge pull request #1219 from manugupt1/fallback-containerfile
Fallback to Containerfile when Dockerfile is not present.
2 parents e8aec64 + 357fdfd commit beeff50

File tree

4 files changed

+273
-6
lines changed

4 files changed

+273
-6
lines changed

cmd/nerdctl/build.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ import (
4141

4242
func newBuildCommand() *cobra.Command {
4343
var buildCommand = &cobra.Command{
44-
Use: "build",
45-
Short: "Build an image from a Dockerfile. Needs buildkitd to be running.",
44+
Use: "build",
45+
Short: "Build an image from a Dockerfile. Needs buildkitd to be running.",
46+
Long: `Build an image from a Dockerfile. Needs buildkitd to be running.
47+
If Dockerfile is not present and -f is not specified, it will look for Containerfile and build with it. `,
4648
RunE: buildAction,
4749
SilenceUsage: true,
4850
SilenceErrors: true,
@@ -355,13 +357,11 @@ func generateBuildctlArgs(cmd *cobra.Command, buildkitHost string, platform, arg
355357
dir = "."
356358
}
357359
}
358-
absDir, err := filepath.Abs(dir)
360+
dir, file, err = buildkitutil.BuildKitFile(dir, file)
359361
if err != nil {
360362
return "", nil, false, "", nil, nil, err
361363
}
362-
if _, err := os.Lstat(filepath.Join(absDir, file)); err != nil {
363-
return "", nil, false, "", nil, nil, err
364-
}
364+
365365
buildctlArgs = append(buildctlArgs, "--local=dockerfile="+dir)
366366
buildctlArgs = append(buildctlArgs, "--opt=filename="+file)
367367

cmd/nerdctl/build_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,54 @@ CMD ["echo", "nerdctl-build-test-string"]
287287
base.Cmd("run", "--rm", imgWithNoTag).AssertOutExactly("nerdctl-build-test-string\n")
288288
base.Cmd("run", "--rm", imgWithCustomTag).AssertOutExactly("nerdctl-build-test-string\n")
289289
}
290+
291+
func TestBuildWithContainerfile(t *testing.T) {
292+
testutil.RequiresBuild(t)
293+
testutil.DockerIncompatible(t)
294+
base := testutil.NewBase(t)
295+
defer base.Cmd("builder", "prune").Run()
296+
imageName := testutil.Identifier(t)
297+
defer base.Cmd("rmi", imageName).Run()
298+
299+
containerfile := fmt.Sprintf(`FROM %s
300+
CMD ["echo", "nerdctl-build-test-string"]
301+
`, testutil.CommonImage)
302+
303+
buildCtx := t.TempDir()
304+
305+
var err = os.WriteFile(filepath.Join(buildCtx, "Containerfile"), []byte(containerfile), 0644)
306+
assert.NilError(t, err)
307+
base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
308+
base.Cmd("run", "--rm", imageName).AssertOutExactly("nerdctl-build-test-string\n")
309+
}
310+
311+
func TestBuildWithDockerFileAndContainerfile(t *testing.T) {
312+
testutil.RequiresBuild(t)
313+
base := testutil.NewBase(t)
314+
defer base.Cmd("builder", "prune").Run()
315+
imageName := testutil.Identifier(t)
316+
defer base.Cmd("rmi", imageName).Run()
317+
318+
dockerfile := fmt.Sprintf(`FROM %s
319+
CMD ["echo", "dockerfile"]
320+
`, testutil.CommonImage)
321+
322+
containerfile := fmt.Sprintf(`FROM %s
323+
CMD ["echo", "containerfile"]
324+
`, testutil.CommonImage)
325+
326+
tmpDir := t.TempDir()
327+
328+
var err = os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644)
329+
assert.NilError(t, err)
330+
331+
err = os.WriteFile(filepath.Join(tmpDir, "Containerfile"), []byte(containerfile), 0644)
332+
assert.NilError(t, err)
333+
334+
buildCtx, err := createBuildContext(dockerfile)
335+
assert.NilError(t, err)
336+
defer os.RemoveAll(buildCtx)
337+
338+
base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
339+
base.Cmd("run", "--rm", imageName).AssertOutExactly("dockerfile\n")
340+
}

pkg/buildkitutil/buildkitutil.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
package buildkitutil
2525

2626
import (
27+
"bytes"
2728
"encoding/json"
2829
"errors"
2930
"fmt"
3031
"io"
32+
"io/fs"
3133
"os"
3234
"os/exec"
3335
"path/filepath"
@@ -41,6 +43,7 @@ import (
4143
const (
4244
// DefaultDockerfileName is the Default filename, read by nerdctl build
4345
DefaultDockerfileName string = "Dockerfile"
46+
ContainerfileName string = "Containerfile"
4447

4548
TempDockerfileName string = "docker-build-tempdockerfile-"
4649
)
@@ -181,3 +184,51 @@ func WriteTempDockerfile(rc io.Reader) (dockerfileDir string, err error) {
181184
}
182185
return dockerfileDir, nil
183186
}
187+
188+
// Buildkit file returns the values for the following buildctl args
189+
// --localfilename=dockerfile={absDir}
190+
// --opt=filename={file}
191+
func BuildKitFile(dir, inputfile string) (absDir string, file string, err error) {
192+
file = inputfile
193+
if file == "" || file == "." {
194+
file = DefaultDockerfileName
195+
}
196+
absDir, err = filepath.Abs(dir)
197+
if err != nil {
198+
return "", "", err
199+
}
200+
if file != DefaultDockerfileName {
201+
if _, err := os.Lstat(filepath.Join(absDir, file)); err != nil {
202+
return "", "", err
203+
}
204+
} else {
205+
_, dErr := os.Lstat(filepath.Join(absDir, file))
206+
_, cErr := os.Lstat(filepath.Join(absDir, ContainerfileName))
207+
if dErr == nil && cErr == nil {
208+
// both files exist, prefer Dockerfile.
209+
dockerfile, err := os.ReadFile(filepath.Join(absDir, DefaultDockerfileName))
210+
if err != nil {
211+
return "", "", err
212+
}
213+
containerfile, err := os.ReadFile(filepath.Join(absDir, ContainerfileName))
214+
if err != nil {
215+
return "", "", err
216+
}
217+
if !bytes.Equal(dockerfile, containerfile) {
218+
logrus.Warnf("%s and %s have different contents, building with %s", DefaultDockerfileName, ContainerfileName, DefaultDockerfileName)
219+
}
220+
}
221+
if dErr != nil {
222+
if errors.Is(dErr, fs.ErrNotExist) {
223+
logrus.Warnf("%s, using %s as fallback", dErr, ContainerfileName)
224+
file = ContainerfileName
225+
} else {
226+
return "", "", dErr
227+
}
228+
if cErr != nil {
229+
return "", "", cErr
230+
}
231+
}
232+
}
233+
return absDir, file, nil
234+
}

pkg/buildkitutil/buildkitutil_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*
18+
Portions from https://github.com/docker/cli/blob/v20.10.9/cli/command/image/build/context.go
19+
Copyright (C) Docker authors.
20+
Licensed under the Apache License, Version 2.0
21+
NOTICE: https://github.com/docker/cli/blob/v20.10.9/NOTICE
22+
*/
23+
24+
package buildkitutil
25+
26+
import (
27+
"os"
28+
"path/filepath"
29+
"testing"
30+
31+
"gotest.tools/v3/assert"
32+
)
33+
34+
func TestBuildKitFile(t *testing.T) {
35+
var tmp = t.TempDir()
36+
var wd, err = os.Getwd()
37+
assert.NilError(t, err)
38+
err = os.Chdir(tmp)
39+
assert.NilError(t, err)
40+
defer os.Chdir(wd)
41+
type args struct {
42+
dir string
43+
inputfile string
44+
}
45+
tests := []struct {
46+
name string
47+
args args
48+
prepare func(t *testing.T) error
49+
wantAbsDir string
50+
wantFile string
51+
wantErr bool
52+
}{
53+
{
54+
name: "only Dockerfile is present",
55+
prepare: func(t *testing.T) error {
56+
return os.WriteFile(filepath.Join(tmp, DefaultDockerfileName), []byte{}, 0644)
57+
},
58+
args: args{".", ""},
59+
wantAbsDir: tmp,
60+
wantFile: DefaultDockerfileName,
61+
wantErr: false,
62+
},
63+
{
64+
name: "only Containerfile is present",
65+
prepare: func(t *testing.T) error {
66+
return os.WriteFile(filepath.Join(tmp, "Containerfile"), []byte{}, 0644)
67+
},
68+
args: args{".", ""},
69+
wantAbsDir: tmp,
70+
wantFile: ContainerfileName,
71+
wantErr: false,
72+
},
73+
{
74+
name: "both Dockerfile and Containerfile are present",
75+
prepare: func(t *testing.T) error {
76+
var err = os.WriteFile(filepath.Join(tmp, "Dockerfile"), []byte{}, 0644)
77+
if err != nil {
78+
return err
79+
}
80+
return os.WriteFile(filepath.Join(tmp, "Containerfile"), []byte{}, 0644)
81+
},
82+
args: args{".", ""},
83+
wantAbsDir: tmp,
84+
wantFile: DefaultDockerfileName,
85+
wantErr: false,
86+
},
87+
{
88+
name: "Dockerfile and Containerfile have different contents",
89+
prepare: func(t *testing.T) error {
90+
var err = os.WriteFile(filepath.Join(tmp, "Dockerfile"), []byte{'d'}, 0644)
91+
if err != nil {
92+
return err
93+
}
94+
return os.WriteFile(filepath.Join(tmp, "Containerfile"), []byte{'c'}, 0644)
95+
},
96+
args: args{".", ""},
97+
wantAbsDir: tmp,
98+
wantFile: DefaultDockerfileName,
99+
wantErr: false,
100+
},
101+
{
102+
name: "Custom file is specfied",
103+
prepare: func(t *testing.T) error {
104+
return os.WriteFile(filepath.Join(tmp, "CustomFile"), []byte{}, 0644)
105+
},
106+
args: args{".", "CustomFile"},
107+
wantAbsDir: tmp,
108+
wantFile: "CustomFile",
109+
wantErr: false,
110+
},
111+
{
112+
name: "Absolute path is specified along with custom file",
113+
prepare: func(t *testing.T) error {
114+
return os.WriteFile(filepath.Join(tmp, "CustomFile"), []byte{}, 0644)
115+
},
116+
args: args{tmp, "CustomFile"},
117+
wantAbsDir: tmp,
118+
wantFile: "CustomFile",
119+
wantErr: false,
120+
},
121+
{
122+
name: "Absolute path is specified along with Docker file",
123+
prepare: func(t *testing.T) error {
124+
return os.WriteFile(filepath.Join(tmp, "Dockerfile"), []byte{}, 0644)
125+
},
126+
args: args{tmp, "."},
127+
wantAbsDir: tmp,
128+
wantFile: DefaultDockerfileName,
129+
wantErr: false,
130+
},
131+
{
132+
name: "Absolute path is specified with Container file in the path",
133+
prepare: func(t *testing.T) error {
134+
return os.WriteFile(filepath.Join(tmp, ContainerfileName), []byte{}, 0644)
135+
},
136+
args: args{tmp, "."},
137+
wantAbsDir: tmp,
138+
wantFile: ContainerfileName,
139+
wantErr: false,
140+
},
141+
}
142+
for _, tt := range tests {
143+
t.Run(tt.name, func(t *testing.T) {
144+
tt.prepare(t)
145+
gotAbsDir, gotFile, err := BuildKitFile(tt.args.dir, tt.args.inputfile)
146+
if (err != nil) != tt.wantErr {
147+
t.Errorf("BuildKitFile() error = %v, wantErr %v", err, tt.wantErr)
148+
return
149+
}
150+
if gotAbsDir != tt.wantAbsDir {
151+
t.Errorf("BuildKitFile() gotAbsDir = %v, want %v", gotAbsDir, tt.wantAbsDir)
152+
}
153+
if gotFile != tt.wantFile {
154+
t.Errorf("BuildKitFile() gotFile = %v, want %v", gotFile, tt.wantFile)
155+
}
156+
157+
entry, err := os.ReadDir(tmp)
158+
assert.NilError(t, err)
159+
for _, f := range entry {
160+
err = os.Remove(f.Name())
161+
assert.NilError(t, err)
162+
}
163+
})
164+
}
165+
}

0 commit comments

Comments
 (0)