Skip to content

Commit aab5de3

Browse files
authored
Merge pull request #399 from AkihiroSuda/fix-build-output
build: support --output=(oci|tar|docker|image), --quiet, --cache-from, --cache-to
2 parents d8d4894 + 6f68f89 commit aab5de3

File tree

4 files changed

+96
-37
lines changed

4 files changed

+96
-37
lines changed

README.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -593,11 +593,20 @@ Flags:
593593
- :whale: `--target`: Set the target build stage to build
594594
- :whale: `--build-arg`: Set build-time variables
595595
- :whale: `--no-cache`: Do not use cache when building the image
596+
- :whale: `--output=OUTPUT`: Output destination (format: type=local,dest=path)
597+
- :whale: `type=local,dest=path/to/output-dir`: Local directory
598+
- :whale: `type=oci[,dest=path/to/output.tar]`: Docker/OCI dual-format tar ball (compatible with `docker buildx build`)
599+
- :whale: `type=docker[,dest=path/to/output.tar]`: Docker format tar ball (compatible with `docker buildx build`)
600+
- :whale: `type=tar[,dest=path/to/output.tar]`: Raw tar ball
601+
- :whale: `type=image,name=example.com/image,push=true`: Push to a registry (see [`buildctl build`](https://github.com/moby/buildkit/tree/v0.9.0#imageregistry) documentation)
596602
- :whale: `--progress=(auto|plain|tty)`: Set type of progress output (auto, plain, tty). Use plain to show container output
597603
- :whale: `--secret`: Secret file to expose to the build: id=mysecret,src=/local/secret
598604
- :whale: `--ssh`: SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)
605+
- :whale: `-q, --quiet`: Suppress the build output and print image ID on success
606+
- :whale: `--cache-from=CACHE`: External cache sources (eg. user/app:cache, type=local,src=path/to/dir) (compatible with `docker buildx build`)
607+
- :whale: `--cache-to=CACHE`: Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir) (compatible with `docker buildx build`)
599608

600-
Unimplemented `docker build` flags: `--add-host`, `--cache-from`, `--iidfile`, `--label`, `--network`, `--platform`, `--quiet`, `--squash`
609+
Unimplemented `docker build` flags: `--add-host`, `--iidfile`, `--label`, `--network`, `--platform`, `--squash`
601610

602611
### :whale: nerdctl commit
603612
Create a new image from a container's changes

cmd/nerdctl/build.go

+71-31
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
package main
1818

1919
import (
20-
"fmt"
20+
"io"
2121
"os"
2222
"os/exec"
23+
"strconv"
2324
"strings"
2425

2526
"path/filepath"
@@ -69,7 +70,7 @@ var buildCommand = &cli.Command{
6970
&cli.StringFlag{
7071
Name: "output",
7172
Aliases: []string{"o"},
72-
Value: "type=docker",
73+
Usage: "Output destination (format: type=local,dest=path)",
7374
},
7475
&cli.StringFlag{
7576
Name: "progress",
@@ -84,6 +85,19 @@ var buildCommand = &cli.Command{
8485
Name: "ssh",
8586
Usage: "SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])",
8687
},
88+
&cli.BoolFlag{
89+
Name: "quiet",
90+
Aliases: []string{"q"},
91+
Usage: "Suppress the build output and print image ID on success",
92+
},
93+
&cli.StringSliceFlag{
94+
Name: "cache-from",
95+
Usage: "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)",
96+
},
97+
&cli.StringSliceFlag{
98+
Name: "cache-to",
99+
Usage: "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)",
100+
},
87101
},
88102
}
89103

@@ -93,31 +107,37 @@ func buildAction(clicontext *cli.Context) error {
93107
return err
94108
}
95109

96-
buildctlBinary, buildctlArgs, err := generateBuildctlArgs(clicontext)
110+
buildctlBinary, buildctlArgs, needsLoading, err := generateBuildctlArgs(clicontext)
97111
if err != nil {
98112
return err
99113
}
100114

115+
quiet := clicontext.Bool("quiet")
116+
101117
logrus.Debugf("running %s %v", buildctlBinary, buildctlArgs)
102118
buildctlCmd := exec.Command(buildctlBinary, buildctlArgs...)
103119
buildctlCmd.Env = os.Environ()
104120

105-
buildctlStdout, err := buildctlCmd.StdoutPipe()
106-
if err != nil {
107-
return err
121+
var buildctlStdout io.Reader
122+
if needsLoading {
123+
buildctlStdout, err = buildctlCmd.StdoutPipe()
124+
if err != nil {
125+
return err
126+
}
127+
} else {
128+
buildctlCmd.Stdout = clicontext.App.Writer
108129
}
109-
buildctlCmd.Stderr = clicontext.App.ErrWriter
110130

111-
if err := buildctlCmd.Start(); err != nil {
112-
return err
131+
if !quiet {
132+
buildctlCmd.Stderr = clicontext.App.ErrWriter
113133
}
114134

115-
localBuild, err := isLocalBuild(clicontext)
116-
if err != nil {
135+
if err := buildctlCmd.Start(); err != nil {
117136
return err
118137
}
119-
if !localBuild {
120-
if err = loadImage(buildctlStdout, clicontext); err != nil {
138+
139+
if needsLoading {
140+
if err = loadImage(buildctlStdout, clicontext, quiet); err != nil {
121141
return err
122142
}
123143
}
@@ -129,24 +149,29 @@ func buildAction(clicontext *cli.Context) error {
129149
return nil
130150
}
131151

132-
func generateBuildctlArgs(clicontext *cli.Context) (string, []string, error) {
152+
func generateBuildctlArgs(clicontext *cli.Context) (string, []string, bool, error) {
153+
var needsLoading bool
133154
if clicontext.NArg() < 1 {
134-
return "", nil, errors.New("context needs to be specified")
155+
return "", nil, false, errors.New("context needs to be specified")
135156
}
136157
buildContext := clicontext.Args().First()
137158
if buildContext == "-" || strings.Contains(buildContext, "://") {
138-
return "", nil, errors.Errorf("unsupported build context: %q", buildContext)
159+
return "", nil, false, errors.Errorf("unsupported build context: %q", buildContext)
139160
}
140161

141162
buildctlBinary, err := buildkitutil.BuildctlBinary()
142163
if err != nil {
143-
return "", nil, err
164+
return "", nil, false, err
144165
}
145166

146-
output := fmt.Sprintf("--output=%s", clicontext.String("output"))
167+
output := clicontext.String("output")
168+
if output == "" {
169+
output = "type=docker"
170+
needsLoading = true
171+
}
147172
if tagSlice := strutil.DedupeStrSlice(clicontext.StringSlice("tag")); len(tagSlice) > 0 {
148173
if len(tagSlice) > 1 {
149-
return "", nil, errors.Errorf("specifying multiple -t is not supported yet")
174+
return "", nil, false, errors.Errorf("specifying multiple -t is not supported yet")
150175
}
151176
output += ",name=" + tagSlice[0]
152177
}
@@ -159,7 +184,7 @@ func generateBuildctlArgs(clicontext *cli.Context) (string, []string, error) {
159184
"--frontend=dockerfile.v0",
160185
"--local=context=" + buildContext,
161186
"--local=dockerfile=" + buildContext,
162-
output,
187+
"--output=" + output,
163188
}...)
164189

165190
if filename := clicontext.String("file"); filename != "" {
@@ -176,6 +201,20 @@ func generateBuildctlArgs(clicontext *cli.Context) (string, []string, error) {
176201

177202
for _, ba := range strutil.DedupeStrSlice(clicontext.StringSlice("build-arg")) {
178203
buildctlArgs = append(buildctlArgs, "--opt=build-arg:"+ba)
204+
205+
// Support `--build-arg BUILDKIT_INLINE_CACHE=1` for compatibility with `docker buildx build`
206+
// https://github.com/docker/buildx/blob/v0.6.3/docs/reference/buildx_build.md#-export-build-cache-to-an-external-cache-destination---cache-to
207+
if strings.HasPrefix(ba, "BUILDKIT_INLINE_CACHE=") {
208+
bic := strings.TrimPrefix(ba, "BUILDKIT_INLINE_CACHE=")
209+
bicParsed, err := strconv.ParseBool(bic)
210+
if err == nil {
211+
if bicParsed {
212+
buildctlArgs = append(buildctlArgs, "--export-cache=type=inline")
213+
}
214+
} else {
215+
logrus.WithError(err).Warnf("invalid BUILDKIT_INLINE_CACHE: %q", bic)
216+
}
217+
}
179218
}
180219

181220
if clicontext.Bool("no-cache") {
@@ -190,18 +229,19 @@ func generateBuildctlArgs(clicontext *cli.Context) (string, []string, error) {
190229
buildctlArgs = append(buildctlArgs, "--ssh="+s)
191230
}
192231

193-
return buildctlBinary, buildctlArgs, nil
194-
}
195-
196-
func isLocalBuild(clicontext *cli.Context) (bool, error) {
197-
opts, err := strutil.ParseCSVMap(clicontext.String("output"))
198-
if err != nil {
199-
return false, err
232+
for _, s := range strutil.DedupeStrSlice(clicontext.StringSlice("cache-from")) {
233+
if !strings.Contains(s, "type=") {
234+
s = "type=registry,ref=" + s
235+
}
236+
buildctlArgs = append(buildctlArgs, "--import-cache="+s)
200237
}
201-
if v, ok := opts["type"]; ok {
202-
if strings.TrimSpace(strings.ToLower(v)) == "local" {
203-
return true, nil
238+
239+
for _, s := range strutil.DedupeStrSlice(clicontext.StringSlice("cache-to")) {
240+
if !strings.Contains(s, "type=") {
241+
s = "type=registry,ref=" + s
204242
}
243+
buildctlArgs = append(buildctlArgs, "--export-cache="+s)
205244
}
206-
return false, nil
245+
246+
return buildctlBinary, buildctlArgs, needsLoading, nil
207247
}

cmd/nerdctl/load.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ func loadAction(clicontext *cli.Context) error {
5555
defer f.Close()
5656
in = f
5757
}
58-
return loadImage(in, clicontext)
58+
return loadImage(in, clicontext, false)
5959
}
6060

61-
func loadImage(in io.Reader, clicontext *cli.Context) error {
61+
func loadImage(in io.Reader, clicontext *cli.Context, quiet bool) error {
6262
client, ctx, cancel, err := newClient(clicontext, containerd.WithDefaultPlatform(platforms.DefaultStrict()))
6363
if err != nil {
6464
return err
@@ -74,12 +74,18 @@ func loadImage(in io.Reader, clicontext *cli.Context) error {
7474
image := containerd.NewImage(client, img)
7575

7676
// TODO: Show unpack status
77-
fmt.Fprintf(clicontext.App.Writer, "unpacking %s (%s)...", img.Name, img.Target.Digest)
77+
if !quiet {
78+
fmt.Fprintf(clicontext.App.Writer, "unpacking %s (%s)...", img.Name, img.Target.Digest)
79+
}
7880
err = image.Unpack(ctx, sn)
7981
if err != nil {
8082
return err
8183
}
82-
fmt.Fprintf(clicontext.App.Writer, "done\n")
84+
if quiet {
85+
fmt.Fprintln(clicontext.App.Writer, img.Target.Digest)
86+
} else {
87+
fmt.Fprintf(clicontext.App.Writer, "done\n")
88+
}
8389
}
8490

8591
return nil

pkg/composer/serviceparser/build.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929

3030
func parseBuildConfig(c *types.BuildConfig, project *types.Project, imageName string) (*Build, error) {
3131
if unknown := reflectutil.UnknownNonEmptyFields(c,
32-
"Context", "Dockerfile", "Args", "Target",
32+
"Context", "Dockerfile", "Args", "CacheFrom", "Target",
3333
); len(unknown) > 0 {
3434
logrus.Warnf("Ignoring: build: %+v", unknown)
3535
}
@@ -66,6 +66,10 @@ func parseBuildConfig(c *types.BuildConfig, project *types.Project, imageName st
6666
}
6767
}
6868

69+
for _, s := range c.CacheFrom {
70+
b.BuildArgs = append(b.BuildArgs, "--cache-from="+s)
71+
}
72+
6973
if c.Target != "" {
7074
b.BuildArgs = append(b.BuildArgs, "--target="+c.Target)
7175
}

0 commit comments

Comments
 (0)