Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / docker / cli / cli / command / image / build.go
1 package image
2
3 import (
4         "archive/tar"
5         "bufio"
6         "bytes"
7         "encoding/json"
8         "fmt"
9         "io"
10         "io/ioutil"
11         "os"
12         "regexp"
13         "runtime"
14
15         "github.com/docker/cli/cli"
16         "github.com/docker/cli/cli/command"
17         "github.com/docker/cli/cli/command/image/build"
18         "github.com/docker/cli/opts"
19         "github.com/docker/distribution/reference"
20         "github.com/docker/docker/api"
21         "github.com/docker/docker/api/types"
22         "github.com/docker/docker/api/types/container"
23         "github.com/docker/docker/pkg/archive"
24         "github.com/docker/docker/pkg/jsonmessage"
25         "github.com/docker/docker/pkg/progress"
26         "github.com/docker/docker/pkg/streamformatter"
27         "github.com/docker/docker/pkg/urlutil"
28         units "github.com/docker/go-units"
29         "github.com/pkg/errors"
30         "github.com/spf13/cobra"
31         "golang.org/x/net/context"
32 )
33
34 type buildOptions struct {
35         context        string
36         dockerfileName string
37         tags           opts.ListOpts
38         labels         opts.ListOpts
39         buildArgs      opts.ListOpts
40         extraHosts     opts.ListOpts
41         ulimits        *opts.UlimitOpt
42         memory         opts.MemBytes
43         memorySwap     opts.MemSwapBytes
44         shmSize        opts.MemBytes
45         cpuShares      int64
46         cpuPeriod      int64
47         cpuQuota       int64
48         cpuSetCpus     string
49         cpuSetMems     string
50         cgroupParent   string
51         isolation      string
52         quiet          bool
53         noCache        bool
54         rm             bool
55         forceRm        bool
56         pull           bool
57         cacheFrom      []string
58         compress       bool
59         securityOpt    []string
60         networkMode    string
61         squash         bool
62         target         string
63         imageIDFile    string
64         volumes        opts.ListOpts
65 }
66
67 // dockerfileFromStdin returns true when the user specified that the Dockerfile
68 // should be read from stdin instead of a file
69 func (o buildOptions) dockerfileFromStdin() bool {
70         return o.dockerfileName == "-"
71 }
72
73 // contextFromStdin returns true when the user specified that the build context
74 // should be read from stdin
75 func (o buildOptions) contextFromStdin() bool {
76         return o.context == "-"
77 }
78
79 // NewBuildCommand creates a new `docker build` command
80 func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
81         ulimits := make(map[string]*units.Ulimit)
82         options := buildOptions{
83                 tags:       opts.NewListOpts(validateTag),
84                 buildArgs:  opts.NewListOpts(opts.ValidateEnv),
85                 ulimits:    opts.NewUlimitOpt(&ulimits),
86                 labels:     opts.NewListOpts(opts.ValidateEnv),
87                 extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
88                 volumes:    opts.NewListOpts(nil),
89         }
90
91         cmd := &cobra.Command{
92                 Use:   "build [OPTIONS] PATH | URL | -",
93                 Short: "Build an image from a Dockerfile",
94                 Args:  cli.ExactArgs(1),
95                 RunE: func(cmd *cobra.Command, args []string) error {
96                         options.context = args[0]
97                         return runBuild(dockerCli, options)
98                 },
99         }
100
101         flags := cmd.Flags()
102
103         flags.VarP(&options.tags, "tag", "t", "Name and optionally a tag in the 'name:tag' format")
104         flags.Var(&options.buildArgs, "build-arg", "Set build-time variables")
105         flags.Var(options.ulimits, "ulimit", "Ulimit options")
106         flags.StringVarP(&options.dockerfileName, "file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
107         flags.VarP(&options.memory, "memory", "m", "Memory limit")
108         flags.Var(&options.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
109         flags.Var(&options.shmSize, "shm-size", "Size of /dev/shm")
110         flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
111         flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
112         flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
113         flags.StringVar(&options.cpuSetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
114         flags.StringVar(&options.cpuSetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
115         flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
116         flags.StringVar(&options.isolation, "isolation", "", "Container isolation technology")
117         flags.Var(&options.labels, "label", "Set metadata for an image")
118         flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
119         flags.BoolVar(&options.rm, "rm", true, "Remove intermediate containers after a successful build")
120         flags.BoolVar(&options.forceRm, "force-rm", false, "Always remove intermediate containers")
121         flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
122         flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
123         flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
124         flags.BoolVar(&options.compress, "compress", false, "Compress the build context using gzip")
125         flags.StringSliceVar(&options.securityOpt, "security-opt", []string{}, "Security options")
126         flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
127         flags.SetAnnotation("network", "version", []string{"1.25"})
128         flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
129         flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
130         flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
131
132         command.AddTrustVerificationFlags(flags)
133
134         flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
135         flags.SetAnnotation("squash", "experimental", nil)
136         flags.SetAnnotation("squash", "version", []string{"1.25"})
137         flags.VarP(&options.volumes, "volume", "v", "Bind mount a volume")
138
139         return cmd
140 }
141
142 // lastProgressOutput is the same as progress.Output except
143 // that it only output with the last update. It is used in
144 // non terminal scenarios to suppress verbose messages
145 type lastProgressOutput struct {
146         output progress.Output
147 }
148
149 // WriteProgress formats progress information from a ProgressReader.
150 func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
151         if !prog.LastUpdate {
152                 return nil
153         }
154
155         return out.output.WriteProgress(prog)
156 }
157
158 // nolint: gocyclo
159 func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
160         var (
161                 buildCtx      io.ReadCloser
162                 dockerfileCtx io.ReadCloser
163                 err           error
164                 contextDir    string
165                 tempDir       string
166                 relDockerfile string
167                 progBuff      io.Writer
168                 buildBuff     io.Writer
169         )
170
171         if options.dockerfileFromStdin() {
172                 if options.contextFromStdin() {
173                         return errors.New("invalid argument: can't use stdin for both build context and dockerfile")
174                 }
175                 dockerfileCtx = dockerCli.In()
176         }
177
178         specifiedContext := options.context
179         progBuff = dockerCli.Out()
180         buildBuff = dockerCli.Out()
181         if options.quiet {
182                 progBuff = bytes.NewBuffer(nil)
183                 buildBuff = bytes.NewBuffer(nil)
184         }
185         if options.imageIDFile != "" {
186                 // Avoid leaving a stale file if we eventually fail
187                 if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
188                         return errors.Wrap(err, "Removing image ID file")
189                 }
190         }
191
192         switch {
193         case options.contextFromStdin():
194                 buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
195         case isLocalDir(specifiedContext):
196                 contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
197         case urlutil.IsGitURL(specifiedContext):
198                 tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName)
199         case urlutil.IsURL(specifiedContext):
200                 buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
201         default:
202                 return errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
203         }
204
205         if err != nil {
206                 if options.quiet && urlutil.IsURL(specifiedContext) {
207                         fmt.Fprintln(dockerCli.Err(), progBuff)
208                 }
209                 return errors.Errorf("unable to prepare context: %s", err)
210         }
211
212         if tempDir != "" {
213                 defer os.RemoveAll(tempDir)
214                 contextDir = tempDir
215         }
216
217         if buildCtx == nil {
218                 excludes, err := build.ReadDockerignore(contextDir)
219                 if err != nil {
220                         return err
221                 }
222
223                 if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
224                         return errors.Errorf("error checking context: '%s'.", err)
225                 }
226
227                 // And canonicalize dockerfile name to a platform-independent one
228                 relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
229                 if err != nil {
230                         return errors.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
231                 }
232
233                 excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
234
235                 compression := archive.Uncompressed
236                 if options.compress {
237                         compression = archive.Gzip
238                 }
239                 buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
240                         Compression:     compression,
241                         ExcludePatterns: excludes,
242                 })
243                 if err != nil {
244                         return err
245                 }
246         }
247
248         // replace Dockerfile if added dynamically
249         if dockerfileCtx != nil {
250                 buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
251                 if err != nil {
252                         return err
253                 }
254         }
255
256         ctx := context.Background()
257
258         var resolvedTags []*resolvedTag
259         if command.IsTrusted() {
260                 translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
261                         return TrustedReference(ctx, dockerCli, ref, nil)
262                 }
263                 // Wrap the tar archive to replace the Dockerfile entry with the rewritten
264                 // Dockerfile which uses trusted pulls.
265                 buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
266         }
267
268         // Setup an upload progress bar
269         progressOutput := streamformatter.NewProgressOutput(progBuff)
270         if !dockerCli.Out().IsTerminal() {
271                 progressOutput = &lastProgressOutput{output: progressOutput}
272         }
273
274         var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
275
276         authConfigs, _ := dockerCli.GetAllCredentials()
277         buildOptions := types.ImageBuildOptions{
278                 Memory:         options.memory.Value(),
279                 MemorySwap:     options.memorySwap.Value(),
280                 Tags:           options.tags.GetAll(),
281                 SuppressOutput: options.quiet,
282                 NoCache:        options.noCache,
283                 Remove:         options.rm,
284                 ForceRemove:    options.forceRm,
285                 PullParent:     options.pull,
286                 Isolation:      container.Isolation(options.isolation),
287                 CPUSetCPUs:     options.cpuSetCpus,
288                 CPUSetMems:     options.cpuSetMems,
289                 CPUShares:      options.cpuShares,
290                 CPUQuota:       options.cpuQuota,
291                 CPUPeriod:      options.cpuPeriod,
292                 CgroupParent:   options.cgroupParent,
293                 Dockerfile:     relDockerfile,
294                 ShmSize:        options.shmSize.Value(),
295                 Ulimits:        options.ulimits.GetList(),
296                 BuildArgs:      dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
297                 AuthConfigs:    authConfigs,
298                 Labels:         opts.ConvertKVStringsToMap(options.labels.GetAll()),
299                 CacheFrom:      options.cacheFrom,
300                 SecurityOpt:    options.securityOpt,
301                 NetworkMode:    options.networkMode,
302                 Squash:         options.squash,
303                 ExtraHosts:     options.extraHosts.GetAll(),
304                 Target:         options.target,
305                 Volumes:        options.volumes.GetAll(),
306         }
307
308         response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
309         if err != nil {
310                 if options.quiet {
311                         fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
312                 }
313                 return err
314         }
315         defer response.Body.Close()
316
317         imageID := ""
318         aux := func(auxJSON *json.RawMessage) {
319                 var result types.BuildResult
320                 if err := json.Unmarshal(*auxJSON, &result); err != nil {
321                         fmt.Fprintf(dockerCli.Err(), "Failed to parse aux message: %s", err)
322                 } else {
323                         imageID = result.ID
324                 }
325         }
326
327         err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), aux)
328         if err != nil {
329                 if jerr, ok := err.(*jsonmessage.JSONError); ok {
330                         // If no error code is set, default to 1
331                         if jerr.Code == 0 {
332                                 jerr.Code = 1
333                         }
334                         if options.quiet {
335                                 fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff)
336                         }
337                         return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
338                 }
339                 return err
340         }
341
342         // Windows: show error message about modified file permissions if the
343         // daemon isn't running Windows.
344         if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
345                 fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+
346                         "image from Windows against a non-Windows Docker host. All files and "+
347                         "directories added to build context will have '-rwxr-xr-x' permissions. "+
348                         "It is recommended to double check and reset permissions for sensitive "+
349                         "files and directories.")
350         }
351
352         // Everything worked so if -q was provided the output from the daemon
353         // should be just the image ID and we'll print that to stdout.
354         if options.quiet {
355                 imageID = fmt.Sprintf("%s", buildBuff)
356                 fmt.Fprintf(dockerCli.Out(), imageID)
357         }
358
359         if options.imageIDFile != "" {
360                 if imageID == "" {
361                         return errors.Errorf("Server did not provide an image ID. Cannot write %s", options.imageIDFile)
362                 }
363                 if err := ioutil.WriteFile(options.imageIDFile, []byte(imageID), 0666); err != nil {
364                         return err
365                 }
366         }
367         if command.IsTrusted() {
368                 // Since the build was successful, now we must tag any of the resolved
369                 // images from the above Dockerfile rewrite.
370                 for _, resolved := range resolvedTags {
371                         if err := TagTrusted(ctx, dockerCli, resolved.digestRef, resolved.tagRef); err != nil {
372                                 return err
373                         }
374                 }
375         }
376
377         return nil
378 }
379
380 func isLocalDir(c string) bool {
381         _, err := os.Stat(c)
382         return err == nil
383 }
384
385 type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
386
387 // validateTag checks if the given image name can be resolved.
388 func validateTag(rawRepo string) (string, error) {
389         _, err := reference.ParseNormalizedNamed(rawRepo)
390         if err != nil {
391                 return "", err
392         }
393
394         return rawRepo, nil
395 }
396
397 var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
398
399 // resolvedTag records the repository, tag, and resolved digest reference
400 // from a Dockerfile rewrite.
401 type resolvedTag struct {
402         digestRef reference.Canonical
403         tagRef    reference.NamedTagged
404 }
405
406 // rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
407 // "FROM <image>" instructions to a digest reference. `translator` is a
408 // function that takes a repository name and tag reference and returns a
409 // trusted digest reference.
410 func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
411         scanner := bufio.NewScanner(dockerfile)
412         buf := bytes.NewBuffer(nil)
413
414         // Scan the lines of the Dockerfile, looking for a "FROM" line.
415         for scanner.Scan() {
416                 line := scanner.Text()
417
418                 matches := dockerfileFromLinePattern.FindStringSubmatch(line)
419                 if matches != nil && matches[1] != api.NoBaseImageSpecifier {
420                         // Replace the line with a resolved "FROM repo@digest"
421                         var ref reference.Named
422                         ref, err = reference.ParseNormalizedNamed(matches[1])
423                         if err != nil {
424                                 return nil, nil, err
425                         }
426                         ref = reference.TagNameOnly(ref)
427                         if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() {
428                                 trustedRef, err := translator(ctx, ref)
429                                 if err != nil {
430                                         return nil, nil, err
431                                 }
432
433                                 line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef)))
434                                 resolvedTags = append(resolvedTags, &resolvedTag{
435                                         digestRef: trustedRef,
436                                         tagRef:    ref,
437                                 })
438                         }
439                 }
440
441                 _, err := fmt.Fprintln(buf, line)
442                 if err != nil {
443                         return nil, nil, err
444                 }
445         }
446
447         return buf.Bytes(), resolvedTags, scanner.Err()
448 }
449
450 // replaceDockerfileTarWrapper wraps the given input tar archive stream and
451 // replaces the entry with the given Dockerfile name with the contents of the
452 // new Dockerfile. Returns a new tar archive stream with the replaced
453 // Dockerfile.
454 func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
455         pipeReader, pipeWriter := io.Pipe()
456         go func() {
457                 tarReader := tar.NewReader(inputTarStream)
458                 tarWriter := tar.NewWriter(pipeWriter)
459
460                 defer inputTarStream.Close()
461
462                 for {
463                         hdr, err := tarReader.Next()
464                         if err == io.EOF {
465                                 // Signals end of archive.
466                                 tarWriter.Close()
467                                 pipeWriter.Close()
468                                 return
469                         }
470                         if err != nil {
471                                 pipeWriter.CloseWithError(err)
472                                 return
473                         }
474
475                         content := io.Reader(tarReader)
476                         if hdr.Name == dockerfileName {
477                                 // This entry is the Dockerfile. Since the tar archive was
478                                 // generated from a directory on the local filesystem, the
479                                 // Dockerfile will only appear once in the archive.
480                                 var newDockerfile []byte
481                                 newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator)
482                                 if err != nil {
483                                         pipeWriter.CloseWithError(err)
484                                         return
485                                 }
486                                 hdr.Size = int64(len(newDockerfile))
487                                 content = bytes.NewBuffer(newDockerfile)
488                         }
489
490                         if err := tarWriter.WriteHeader(hdr); err != nil {
491                                 pipeWriter.CloseWithError(err)
492                                 return
493                         }
494
495                         if _, err := io.Copy(tarWriter, content); err != nil {
496                                 pipeWriter.CloseWithError(err)
497                                 return
498                         }
499                 }
500         }()
501
502         return pipeReader
503 }