@@ -5,11 +5,13 @@ import (
5
5
"context"
6
6
"encoding/json"
7
7
"fmt"
8
+ "io"
8
9
"io/ioutil"
9
10
"os"
10
11
"path/filepath"
11
12
"strconv"
12
13
"strings"
14
+ "sync"
13
15
14
16
"github.com/deislabs/cnab-go/bundle"
15
17
cnab "github.com/deislabs/cnab-go/driver"
@@ -24,6 +26,7 @@ import (
24
26
"github.com/docker/cli/cli/command"
25
27
compose "github.com/docker/cli/cli/compose/types"
26
28
"github.com/docker/cli/cli/streams"
29
+ cliOpts "github.com/docker/cli/opts"
27
30
"github.com/docker/cnab-to-oci/remotes"
28
31
"github.com/docker/distribution/reference"
29
32
"github.com/moby/buildkit/client"
@@ -39,19 +42,26 @@ type buildOptions struct {
39
42
noCache bool
40
43
progress string
41
44
pull bool
42
- tag string
45
+ tags cliOpts. ListOpts
43
46
folder string
44
47
imageIDFile string
45
- args [] string
48
+ args cliOpts. ListOpts
46
49
quiet bool
47
50
noResolveImage bool
48
51
}
49
52
50
53
const buildExample = `- $ docker app build .
51
- - $ docker app build --file myapp.dockerapp --tag myrepo/myapp:1.0.0 .`
54
+ - $ docker app build --file myapp.dockerapp --tag myrepo/myapp:1.0.0 --tag myrepo/myapp:latest .`
55
+
56
+ func newBuildOptions () buildOptions {
57
+ return buildOptions {
58
+ tags : cliOpts .NewListOpts (validateTag ),
59
+ args : cliOpts .NewListOpts (nil ),
60
+ }
61
+ }
52
62
53
63
func Cmd (dockerCli command.Cli ) * cobra.Command {
54
- var opts buildOptions
64
+ opts := newBuildOptions ()
55
65
cmd := & cobra.Command {
56
66
Use : "build [OPTIONS] BUILD_PATH" ,
57
67
Short : "Build an App image from an App definition (.dockerapp)" ,
@@ -66,10 +76,10 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
66
76
flags .BoolVar (& opts .noCache , "no-cache" , false , "Do not use cache when building the App image" )
67
77
flags .StringVar (& opts .progress , "progress" , "auto" , "Set type of progress output (auto, plain, tty). Use plain to show container output" )
68
78
flags .BoolVar (& opts .noResolveImage , "no-resolve-image" , false , "Do not query the registry to resolve image digest" )
69
- flags .StringVarP (& opts .tag , "tag" , "t" , "" , "App image tag, optionally in the 'repo :tag' format" )
79
+ flags .VarP (& opts .tags , "tag" , "t" , "Name and optionally a tag in the 'name :tag' format" )
70
80
flags .StringVarP (& opts .folder , "file" , "f" , "" , "App definition as a .dockerapp directory" )
71
81
flags .BoolVar (& opts .pull , "pull" , false , "Always attempt to pull a newer version of the App image" )
72
- flags .StringArrayVar (& opts .args , "build-arg" , [] string {} , "Set build-time variables" )
82
+ flags .Var (& opts .args , "build-arg" , "Set build-time variables" )
73
83
flags .BoolVarP (& opts .quiet , "quiet" , "q" , false , "Suppress the build output and print App image ID on success" )
74
84
flags .StringVar (& opts .imageIDFile , "iidfile" , "" , "Write the App image ID to the file" )
75
85
@@ -128,37 +138,65 @@ func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) error
128
138
}
129
139
defer app .Cleanup ()
130
140
131
- bundle , err := buildImageUsingBuildx (app , contextPath , opt , dockerCli )
141
+ bndl , err := buildImageUsingBuildx (app , contextPath , opt , dockerCli )
132
142
if err != nil {
133
143
return err
134
144
}
135
145
136
- var ref reference.Reference
137
- ref , err = packager .GetNamedTagged (opt .tag )
146
+ out , err := getOutputFile (dockerCli .Out (), opt .quiet )
138
147
if err != nil {
139
148
return err
140
149
}
141
150
142
- id , err := packager . PersistInBundleStore ( ref , bundle )
151
+ id , err := persistTags ( bndl , opt . tags , opt . imageIDFile , out , dockerCli . Err () )
143
152
if err != nil {
144
153
return err
145
154
}
146
155
147
- if opt .imageIDFile != "" {
148
- if err = ioutil .WriteFile (opt .imageIDFile , []byte (id .Digest ().String ()), 0644 ); err != nil {
149
- fmt .Fprintf (dockerCli .Err (), "Failed to write App image ID in %s: %s" , opt .imageIDFile , err )
150
- }
156
+ if opt .quiet {
157
+ _ , err = fmt .Fprintln (dockerCli .Out (), id .Digest ().String ())
151
158
}
159
+ return err
160
+ }
152
161
153
- if opt .quiet {
154
- fmt .Fprintln (dockerCli .Out (), id .Digest ().String ())
155
- return err
162
+ func persistTags (bndl * bundle.Bundle , tags cliOpts.ListOpts , iidFile string , outWriter io.Writer , errWriter io.Writer ) (reference.Digested , error ) {
163
+ var (
164
+ id reference.Digested
165
+ onceWriteIIDFile sync.Once
166
+ )
167
+ if tags .Len () == 0 {
168
+ return persistInBundleStore (& onceWriteIIDFile , outWriter , errWriter , bndl , nil , iidFile )
169
+ }
170
+ for _ , tag := range tags .GetAll () {
171
+ ref , err := packager .GetNamedTagged (tag )
172
+ if err != nil {
173
+ return nil , err
174
+ }
175
+ id , err = persistInBundleStore (& onceWriteIIDFile , outWriter , errWriter , bndl , ref , iidFile )
176
+ if err != nil {
177
+ return nil , err
178
+ }
179
+ if tag != "" {
180
+ fmt .Fprintf (outWriter , "Successfully tagged app image %s\n " , ref .String ())
181
+ }
156
182
}
157
- fmt .Fprintf (dockerCli .Out (), "Successfully built %s\n " , id .String ())
158
- if ref != nil {
159
- fmt .Fprintf (dockerCli .Out (), "Successfully tagged %s\n " , ref .String ())
183
+ return id , nil
184
+ }
185
+
186
+ func persistInBundleStore (once * sync.Once , outWriter io.Writer , errWriter io.Writer , b * bundle.Bundle , ref reference.Reference , iidFileName string ) (reference.Digested , error ) {
187
+ id , err := packager .PersistInBundleStore (ref , b )
188
+ if err != nil {
189
+ return nil , err
160
190
}
161
- return err
191
+ once .Do (func () {
192
+ fmt .Fprintf (outWriter , "Successfully built app image %s\n " , id .String ())
193
+ if iidFileName != "" {
194
+ if err := ioutil .WriteFile (iidFileName , []byte (id .Digest ().String ()), 0644 ); err != nil {
195
+ fmt .Fprintf (errWriter , "Failed to write App image ID in %s: %s" , iidFileName , err )
196
+ }
197
+ }
198
+ })
199
+ return id , nil
162
200
}
163
201
164
202
func buildImageUsingBuildx (app * types.App , contextPath string , opt buildOptions , dockerCli command.Cli ) (* bundle.Bundle , error ) {
@@ -350,9 +388,9 @@ func debugSolveResponses(resp map[string]*client.SolveResponse) {
350
388
}
351
389
}
352
390
353
- func checkBuildArgsUniqueness (args [] string ) error {
391
+ func checkBuildArgsUniqueness (args cliOpts. ListOpts ) error {
354
392
set := make (map [string ]bool )
355
- for _ , value := range args {
393
+ for _ , value := range args . GetAllOrEmpty () {
356
394
key := strings .Split (value , "=" )[0 ]
357
395
if _ , ok := set [key ]; ok {
358
396
return fmt .Errorf ("'--build-arg %s' is defined twice" , key )
@@ -361,3 +399,12 @@ func checkBuildArgsUniqueness(args []string) error {
361
399
}
362
400
return nil
363
401
}
402
+
403
+ // validateTag checks if the given image name can be resolved.
404
+ func validateTag (rawRepo string ) (string , error ) {
405
+ _ , err := reference .ParseNormalizedNamed (rawRepo )
406
+ if err != nil {
407
+ return "" , err
408
+ }
409
+ return rawRepo , nil
410
+ }
0 commit comments