@@ -5,11 +5,13 @@ import (
55 "context"
66 "encoding/json"
77 "fmt"
8+ "io"
89 "io/ioutil"
910 "os"
1011 "path/filepath"
1112 "strconv"
1213 "strings"
14+ "sync"
1315
1416 "github.com/deislabs/cnab-go/bundle"
1517 cnab "github.com/deislabs/cnab-go/driver"
@@ -24,6 +26,7 @@ import (
2426 "github.com/docker/cli/cli/command"
2527 compose "github.com/docker/cli/cli/compose/types"
2628 "github.com/docker/cli/cli/streams"
29+ cliOpts "github.com/docker/cli/opts"
2730 "github.com/docker/cnab-to-oci/remotes"
2831 "github.com/docker/distribution/reference"
2932 "github.com/moby/buildkit/client"
@@ -39,19 +42,26 @@ type buildOptions struct {
3942 noCache bool
4043 progress string
4144 pull bool
42- tag string
45+ tags cliOpts. ListOpts
4346 folder string
4447 imageIDFile string
45- args [] string
48+ args cliOpts. ListOpts
4649 quiet bool
4750 noResolveImage bool
4851}
4952
5053const 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+ }
5262
5363func Cmd (dockerCli command.Cli ) * cobra.Command {
54- var opts buildOptions
64+ opts := newBuildOptions ()
5565 cmd := & cobra.Command {
5666 Use : "build [OPTIONS] BUILD_PATH" ,
5767 Short : "Build an App image from an App definition (.dockerapp)" ,
@@ -66,10 +76,10 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
6676 flags .BoolVar (& opts .noCache , "no-cache" , false , "Do not use cache when building the App image" )
6777 flags .StringVar (& opts .progress , "progress" , "auto" , "Set type of progress output (auto, plain, tty). Use plain to show container output" )
6878 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" )
7080 flags .StringVarP (& opts .folder , "file" , "f" , "" , "App definition as a .dockerapp directory" )
7181 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" )
7383 flags .BoolVarP (& opts .quiet , "quiet" , "q" , false , "Suppress the build output and print App image ID on success" )
7484 flags .StringVar (& opts .imageIDFile , "iidfile" , "" , "Write the App image ID to the file" )
7585
@@ -128,37 +138,65 @@ func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) error
128138 }
129139 defer app .Cleanup ()
130140
131- bundle , err := buildImageUsingBuildx (app , contextPath , opt , dockerCli )
141+ bndl , err := buildImageUsingBuildx (app , contextPath , opt , dockerCli )
132142 if err != nil {
133143 return err
134144 }
135145
136- var ref reference.Reference
137- ref , err = packager .GetNamedTagged (opt .tag )
146+ out , err := getOutputFile (dockerCli .Out (), opt .quiet )
138147 if err != nil {
139148 return err
140149 }
141150
142- id , err := packager . PersistInBundleStore ( ref , bundle )
151+ id , err := persistTags ( bndl , opt . tags , opt . imageIDFile , out , dockerCli . Err () )
143152 if err != nil {
144153 return err
145154 }
146155
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 ())
151158 }
159+ return err
160+ }
152161
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+ }
156182 }
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
160190 }
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
162200}
163201
164202func 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) {
350388 }
351389}
352390
353- func checkBuildArgsUniqueness (args [] string ) error {
391+ func checkBuildArgsUniqueness (args cliOpts. ListOpts ) error {
354392 set := make (map [string ]bool )
355- for _ , value := range args {
393+ for _ , value := range args . GetAllOrEmpty () {
356394 key := strings .Split (value , "=" )[0 ]
357395 if _ , ok := set [key ]; ok {
358396 return fmt .Errorf ("'--build-arg %s' is defined twice" , key )
@@ -361,3 +399,12 @@ func checkBuildArgsUniqueness(args []string) error {
361399 }
362400 return nil
363401}
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