2016-09-08 13:11:39 -04:00
|
|
|
package image
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/tar"
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-04-06 08:33:56 -04:00
|
|
|
"encoding/json"
|
2016-09-08 13:11:39 -04:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
2018-02-17 19:40:55 -05:00
|
|
|
"path/filepath"
|
2016-09-08 13:11:39 -04:00
|
|
|
"regexp"
|
|
|
|
"runtime"
|
2018-02-17 19:40:55 -05:00
|
|
|
"strings"
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
|
|
|
"github.com/docker/cli/cli/command/image/build"
|
2017-05-15 08:45:19 -04:00
|
|
|
"github.com/docker/cli/opts"
|
2017-01-11 16:54:52 -05:00
|
|
|
"github.com/docker/distribution/reference"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/api"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
2023-02-07 20:31:59 -05:00
|
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
2022-04-29 13:26:50 -04:00
|
|
|
"github.com/docker/docker/builder/remotecontext/urlutil"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/pkg/archive"
|
2017-09-08 13:45:19 -04:00
|
|
|
"github.com/docker/docker/pkg/idtools"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
|
|
|
"github.com/docker/docker/pkg/progress"
|
|
|
|
"github.com/docker/docker/pkg/streamformatter"
|
2016-12-22 16:25:02 -05:00
|
|
|
units "github.com/docker/go-units"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2018-05-22 19:23:15 -04:00
|
|
|
var errStdinConflict = errors.New("invalid argument: can't use stdin for both build context and dockerfile")
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
type buildOptions struct {
|
|
|
|
context string
|
|
|
|
dockerfileName string
|
|
|
|
tags opts.ListOpts
|
2016-09-29 17:59:52 -04:00
|
|
|
labels opts.ListOpts
|
2016-09-08 13:11:39 -04:00
|
|
|
buildArgs opts.ListOpts
|
2017-01-13 10:01:58 -05:00
|
|
|
extraHosts opts.ListOpts
|
2016-12-23 14:09:12 -05:00
|
|
|
ulimits *opts.UlimitOpt
|
2017-03-06 16:01:04 -05:00
|
|
|
memory opts.MemBytes
|
|
|
|
memorySwap opts.MemSwapBytes
|
2016-12-28 17:44:07 -05:00
|
|
|
shmSize opts.MemBytes
|
2016-09-08 13:11:39 -04:00
|
|
|
cpuShares int64
|
|
|
|
cpuPeriod int64
|
|
|
|
cpuQuota int64
|
|
|
|
cpuSetCpus string
|
|
|
|
cpuSetMems string
|
|
|
|
cgroupParent string
|
|
|
|
isolation string
|
|
|
|
quiet bool
|
|
|
|
noCache bool
|
|
|
|
rm bool
|
|
|
|
forceRm bool
|
|
|
|
pull bool
|
2016-09-22 17:38:00 -04:00
|
|
|
cacheFrom []string
|
2016-08-18 04:35:23 -04:00
|
|
|
compress bool
|
2016-06-07 15:15:50 -04:00
|
|
|
securityOpt []string
|
2016-03-06 07:29:23 -05:00
|
|
|
networkMode string
|
2016-04-21 12:08:37 -04:00
|
|
|
squash bool
|
2017-04-10 18:27:42 -04:00
|
|
|
target string
|
2017-04-06 08:33:56 -04:00
|
|
|
imageIDFile string
|
2017-08-02 20:31:32 -04:00
|
|
|
platform string
|
2018-03-08 08:35:17 -05:00
|
|
|
untrusted bool
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-04-06 15:36:15 -04:00
|
|
|
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
|
|
|
// should be read from stdin instead of a file
|
|
|
|
func (o buildOptions) dockerfileFromStdin() bool {
|
|
|
|
return o.dockerfileName == "-"
|
|
|
|
}
|
|
|
|
|
|
|
|
// contextFromStdin returns true when the user specified that the build context
|
|
|
|
// should be read from stdin
|
|
|
|
func (o buildOptions) contextFromStdin() bool {
|
|
|
|
return o.context == "-"
|
|
|
|
}
|
|
|
|
|
2017-06-21 15:13:44 -04:00
|
|
|
func newBuildOptions() buildOptions {
|
2016-09-08 13:11:39 -04:00
|
|
|
ulimits := make(map[string]*units.Ulimit)
|
2017-06-21 15:13:44 -04:00
|
|
|
return buildOptions{
|
2017-01-13 10:01:58 -05:00
|
|
|
tags: opts.NewListOpts(validateTag),
|
|
|
|
buildArgs: opts.NewListOpts(opts.ValidateEnv),
|
|
|
|
ulimits: opts.NewUlimitOpt(&ulimits),
|
Fix labels copying value from environment variables
This patch fixes a bug where labels use the same behavior as `--env`, resulting
in a value to be copied from environment variables with the same name as the
label if no value is set (i.e. a simple key, no `=` sign, no value).
An earlier pull request addressed similar cases for `docker run`;
2b17f4c8a8caad552025edb05a73db683fb8a5c6, but this did not address the
same situation for (e.g.) `docker service create`.
Digging in history for this bug, I found that use of the `ValidateEnv`
function for labels was added in the original implementation of the labels feature in
https://github.com/docker/docker/commit/abb5e9a0777469e64fe2c7ecfa66ea01083d2071#diff-ae476143d40e21ac0918630f7365ed3cR34
However, the design never intended it to expand environment variables,
and use of this function was either due to either a "copy/paste" of the
equivalent `--env` flags, or a misunderstanding (the name `ValidateEnv` does
not communicate that it also expands environment variables), and the existing
`ValidateLabel` was designed for _engine_ labels (which required a value to
be set).
Following the initial implementation, other parts of the code followed
the same (incorrect) approach, therefore leading the bug to be introduced
in services as well.
This patch:
- updates the `ValidateLabel` to match the expected validation
rules (this function is no longer used since 31dc5c0a9a8bdc11c7ad335aebb753ed527caa5a),
and the daemon has its own implementation)
- corrects various locations in the code where `ValidateEnv` was used instead of `ValidateLabel`.
Before this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker service inspect --format '{{json .Spec.Labels}}' test
{"SOME_ENV_VAR":"I_AM_SOME_ENV_VAR"}
```
After this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker container inspect --format '{{json .Config.Labels}}' test
{"SOME_ENV_VAR":""}
```
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-02-13 10:47:30 -05:00
|
|
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
2017-01-13 10:01:58 -05:00
|
|
|
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-06-21 15:13:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewBuildCommand creates a new `docker build` command
|
|
|
|
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
|
|
|
options := newBuildOptions()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "build [OPTIONS] PATH | URL | -",
|
|
|
|
Short: "Build an image from a Dockerfile",
|
|
|
|
Args: cli.ExactArgs(1),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
options.context = args[0]
|
|
|
|
return runBuild(dockerCli, options)
|
|
|
|
},
|
2022-03-30 03:37:08 -04:00
|
|
|
Annotations: map[string]string{
|
|
|
|
"category-top": "4",
|
cli: use custom annotation for aliases
Cobra allows for aliases to be defined for a command, but only allows these
to be defined at the same level (for example, `docker image ls` as alias for
`docker image list`). Our CLI has some commands that are available both as a
top-level shorthand as well as `docker <object> <verb>` subcommands. For example,
`docker ps` is a shorthand for `docker container ps` / `docker container ls`.
This patch introduces a custom "aliases" annotation that can be used to print
all available aliases for a command. While this requires these aliases to be
defined manually, in practice the list of aliases rarely changes, so maintenance
should be minimal.
As a convention, we could consider the first command in this list to be the
canonical command, so that we can use this information to add redirects in
our documentation in future.
Before this patch:
docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Options:
-a, --all Show all images (default hides intermediate images)
...
With this patch:
docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Aliases:
docker image ls, docker image list, docker images
Options:
-a, --all Show all images (default hides intermediate images)
...
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-06-28 04:52:25 -04:00
|
|
|
"aliases": "docker image build, docker build, docker buildx build, docker builder build",
|
2022-03-30 03:37:08 -04:00
|
|
|
},
|
2022-03-30 09:27:25 -04:00
|
|
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
return nil, cobra.ShellCompDirectiveFilterDirs
|
|
|
|
},
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.VarP(&options.tags, "tag", "t", `Name and optionally a tag in the "name:tag" format`)
|
2016-09-08 13:11:39 -04:00
|
|
|
flags.Var(&options.buildArgs, "build-arg", "Set build-time variables")
|
|
|
|
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVarP(&options.dockerfileName, "file", "f", "", `Name of the Dockerfile (Default is "PATH/Dockerfile")`)
|
2017-03-06 16:01:04 -05:00
|
|
|
flags.VarP(&options.memory, "memory", "m", "Memory limit")
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.Var(&options.memorySwap, "memory-swap", `Swap limit equal to memory plus swap: -1 to enable unlimited swap`)
|
|
|
|
flags.Var(&options.shmSize, "shm-size", `Size of "/dev/shm"`)
|
2016-09-08 13:11:39 -04:00
|
|
|
flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
|
|
|
flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
|
|
|
flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
|
|
|
flags.StringVar(&options.cpuSetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
|
|
flags.StringVar(&options.cpuSetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
|
|
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
|
|
|
|
flags.StringVar(&options.isolation, "isolation", "", "Container isolation technology")
|
2016-09-29 17:59:52 -04:00
|
|
|
flags.Var(&options.labels, "label", "Set metadata for an image")
|
2016-09-08 13:11:39 -04:00
|
|
|
flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
|
|
|
|
flags.BoolVar(&options.rm, "rm", true, "Remove intermediate containers after a successful build")
|
|
|
|
flags.BoolVar(&options.forceRm, "force-rm", false, "Always remove intermediate containers")
|
|
|
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
|
|
|
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
2016-09-22 17:38:00 -04:00
|
|
|
flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
|
2016-08-18 04:35:23 -04:00
|
|
|
flags.BoolVar(&options.compress, "compress", false, "Compress the build context using gzip")
|
2016-06-07 15:15:50 -04:00
|
|
|
flags.StringSliceVar(&options.securityOpt, "security-opt", []string{}, "Security options")
|
2016-12-09 10:15:26 -05:00
|
|
|
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
|
2017-01-16 11:57:26 -05:00
|
|
|
flags.SetAnnotation("network", "version", []string{"1.25"})
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.Var(&options.extraHosts, "add-host", `Add a custom host-to-IP mapping ("host:ip")`)
|
2017-04-10 18:27:42 -04:00
|
|
|
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
|
2017-04-06 08:33:56 -04:00
|
|
|
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2018-03-08 14:56:56 -05:00
|
|
|
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
2019-03-15 22:09:39 -04:00
|
|
|
|
|
|
|
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
2020-04-07 17:24:20 -04:00
|
|
|
flags.SetAnnotation("platform", "version", []string{"1.38"})
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2016-11-02 20:43:32 -04:00
|
|
|
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
|
|
|
|
flags.SetAnnotation("squash", "experimental", nil)
|
2016-11-02 20:43:32 -04:00
|
|
|
flags.SetAnnotation("squash", "version", []string{"1.25"})
|
2016-04-21 12:08:37 -04:00
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
// lastProgressOutput is the same as progress.Output except
|
|
|
|
// that it only output with the last update. It is used in
|
2017-02-16 10:56:53 -05:00
|
|
|
// non terminal scenarios to suppress verbose messages
|
2016-09-08 13:11:39 -04:00
|
|
|
type lastProgressOutput struct {
|
|
|
|
output progress.Output
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteProgress formats progress information from a ProgressReader.
|
|
|
|
func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
|
|
|
|
if !prog.LastUpdate {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return out.output.WriteProgress(prog)
|
|
|
|
}
|
|
|
|
|
2022-07-13 06:29:49 -04:00
|
|
|
//nolint:gocyclo
|
2017-06-21 17:20:49 -04:00
|
|
|
func runBuild(dockerCli command.Cli, options buildOptions) error {
|
2016-09-08 13:11:39 -04:00
|
|
|
var (
|
2022-02-03 04:37:55 -05:00
|
|
|
err error
|
2016-11-07 03:17:04 -05:00
|
|
|
buildCtx io.ReadCloser
|
2017-02-21 15:07:45 -05:00
|
|
|
dockerfileCtx io.ReadCloser
|
2016-09-08 13:11:39 -04:00
|
|
|
contextDir string
|
|
|
|
tempDir string
|
|
|
|
relDockerfile string
|
|
|
|
progBuff io.Writer
|
|
|
|
buildBuff io.Writer
|
2017-05-15 17:14:31 -04:00
|
|
|
remote string
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
2016-11-02 20:43:32 -04:00
|
|
|
|
2017-04-06 15:36:15 -04:00
|
|
|
if options.dockerfileFromStdin() {
|
|
|
|
if options.contextFromStdin() {
|
2018-05-22 19:23:15 -04:00
|
|
|
return errStdinConflict
|
2017-04-06 15:36:15 -04:00
|
|
|
}
|
|
|
|
dockerfileCtx = dockerCli.In()
|
|
|
|
}
|
|
|
|
|
2016-11-07 03:17:04 -05:00
|
|
|
specifiedContext := options.context
|
2016-09-08 13:11:39 -04:00
|
|
|
progBuff = dockerCli.Out()
|
|
|
|
buildBuff = dockerCli.Out()
|
|
|
|
if options.quiet {
|
|
|
|
progBuff = bytes.NewBuffer(nil)
|
|
|
|
buildBuff = bytes.NewBuffer(nil)
|
|
|
|
}
|
2017-04-06 08:33:56 -04:00
|
|
|
if options.imageIDFile != "" {
|
|
|
|
// Avoid leaving a stale file if we eventually fail
|
|
|
|
if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
|
|
|
|
return errors.Wrap(err, "Removing image ID file")
|
|
|
|
}
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
switch {
|
2017-04-06 15:36:15 -04:00
|
|
|
case options.contextFromStdin():
|
2017-05-15 17:14:31 -04:00
|
|
|
// buildCtx is tar archive. if stdin was dockerfile then it is wrapped
|
2016-12-22 16:25:02 -05:00
|
|
|
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
|
2017-01-24 08:19:31 -05:00
|
|
|
case isLocalDir(specifiedContext):
|
|
|
|
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
|
2018-02-17 19:40:55 -05:00
|
|
|
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
|
|
|
|
// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
|
|
|
|
dockerfileCtx, err = os.Open(options.dockerfileName)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Errorf("unable to open Dockerfile: %v", err)
|
|
|
|
}
|
|
|
|
defer dockerfileCtx.Close()
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
case urlutil.IsGitURL(specifiedContext):
|
2016-12-22 16:25:02 -05:00
|
|
|
tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName)
|
2016-09-08 13:11:39 -04:00
|
|
|
case urlutil.IsURL(specifiedContext):
|
2016-12-22 16:25:02 -05:00
|
|
|
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
|
2016-09-08 13:11:39 -04:00
|
|
|
default:
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if options.quiet && urlutil.IsURL(specifiedContext) {
|
|
|
|
fmt.Fprintln(dockerCli.Err(), progBuff)
|
|
|
|
}
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("unable to prepare context: %s", err)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if tempDir != "" {
|
|
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
contextDir = tempDir
|
|
|
|
}
|
|
|
|
|
2017-05-15 17:14:31 -04:00
|
|
|
// read from a directory into tar archive
|
2019-09-24 17:24:46 -04:00
|
|
|
if buildCtx == nil {
|
2017-04-16 15:01:15 -04:00
|
|
|
excludes, err := build.ReadDockerignore(contextDir)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-12-22 16:25:02 -05:00
|
|
|
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
|
linting: fix incorrectly formatted errors (revive)
cli/compose/interpolation/interpolation.go:102:4: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.",
^
cli/command/stack/loader/loader.go:30:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n",
^
cli/command/formatter/formatter.go:76:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return tmpl, errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/formatter/formatter.go:97:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/image/build.go:257:25: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("error checking context: '%s'.", err)
^
cli/command/volume/create.go:35:27: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
^
cli/command/container/create.go:160:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-27 15:13:03 -04:00
|
|
|
return errors.Wrap(err, "error checking context")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-04-06 15:36:15 -04:00
|
|
|
// And canonicalize dockerfile name to a platform-independent one
|
2022-08-31 11:50:47 -04:00
|
|
|
relDockerfile = filepath.ToSlash(relDockerfile)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-04-16 15:01:15 -04:00
|
|
|
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
|
2016-09-08 13:11:39 -04:00
|
|
|
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
|
|
|
ExcludePatterns: excludes,
|
2018-08-06 17:17:03 -04:00
|
|
|
ChownOpts: &idtools.Identity{UID: 0, GID: 0},
|
2016-09-08 13:11:39 -04:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-17 19:40:55 -05:00
|
|
|
// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
|
2017-05-15 17:14:31 -04:00
|
|
|
if dockerfileCtx != nil && buildCtx != nil {
|
2017-04-16 16:06:16 -04:00
|
|
|
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
2017-02-21 15:07:45 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-15 17:14:31 -04:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
var resolvedTags []*resolvedTag
|
2018-03-08 14:56:56 -05:00
|
|
|
if !options.untrusted {
|
2016-08-29 14:45:29 -04:00
|
|
|
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
|
2023-03-15 11:21:02 -04:00
|
|
|
return TrustedReference(ctx, dockerCli, ref)
|
2016-08-29 14:45:29 -04:00
|
|
|
}
|
2017-05-15 17:14:31 -04:00
|
|
|
// if there is a tar wrapper, the dockerfile needs to be replaced inside it
|
|
|
|
if buildCtx != nil {
|
|
|
|
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
|
|
|
|
// Dockerfile which uses trusted pulls.
|
2018-03-08 14:56:56 -05:00
|
|
|
buildCtx = replaceDockerfileForContentTrust(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
|
2017-05-15 17:14:31 -04:00
|
|
|
} else if dockerfileCtx != nil {
|
|
|
|
// if there was not archive context still do the possible replacements in Dockerfile
|
2018-03-14 12:36:23 -04:00
|
|
|
newDockerfile, _, err := rewriteDockerfileFromForContentTrust(ctx, dockerfileCtx, translator)
|
2017-05-15 17:14:31 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-25 07:10:34 -05:00
|
|
|
dockerfileCtx = io.NopCloser(bytes.NewBuffer(newDockerfile))
|
2017-05-15 17:14:31 -04:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-06-21 15:13:44 -04:00
|
|
|
if options.compress {
|
|
|
|
buildCtx, err = build.Compress(buildCtx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
// Setup an upload progress bar
|
2017-05-01 14:54:56 -04:00
|
|
|
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
2016-09-08 13:11:39 -04:00
|
|
|
if !dockerCli.Out().IsTerminal() {
|
|
|
|
progressOutput = &lastProgressOutput{output: progressOutput}
|
|
|
|
}
|
|
|
|
|
2017-10-31 06:21:09 -04:00
|
|
|
// if up to this point nothing has set the context then we must have another
|
|
|
|
// way for sending it(streaming) and set the context to the Dockerfile
|
2017-05-15 17:14:31 -04:00
|
|
|
if dockerfileCtx != nil && buildCtx == nil {
|
|
|
|
buildCtx = dockerfileCtx
|
|
|
|
}
|
|
|
|
|
|
|
|
var body io.Reader
|
2019-09-24 17:24:46 -04:00
|
|
|
if buildCtx != nil {
|
2017-05-15 17:14:31 -04:00
|
|
|
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
|
|
|
}
|
|
|
|
|
2017-06-21 17:20:49 -04:00
|
|
|
configFile := dockerCli.ConfigFile()
|
2017-10-15 15:39:56 -04:00
|
|
|
creds, _ := configFile.GetAllCredentials()
|
2023-02-07 20:31:59 -05:00
|
|
|
authConfigs := make(map[string]registrytypes.AuthConfig, len(creds))
|
2017-10-15 15:39:56 -04:00
|
|
|
for k, auth := range creds {
|
2023-02-07 20:31:59 -05:00
|
|
|
authConfigs[k] = registrytypes.AuthConfig(auth)
|
2017-10-15 15:39:56 -04:00
|
|
|
}
|
2018-06-09 13:25:32 -04:00
|
|
|
buildOptions := imageBuildOptions(dockerCli, options)
|
|
|
|
buildOptions.Version = types.BuilderV1
|
|
|
|
buildOptions.Dockerfile = relDockerfile
|
|
|
|
buildOptions.AuthConfigs = authConfigs
|
|
|
|
buildOptions.RemoteContext = remote
|
2017-05-15 17:14:31 -04:00
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
|
|
|
|
if err != nil {
|
2016-09-13 00:06:04 -04:00
|
|
|
if options.quiet {
|
|
|
|
fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
|
|
|
|
}
|
2017-05-15 17:14:31 -04:00
|
|
|
cancel()
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
|
2017-04-06 08:33:56 -04:00
|
|
|
imageID := ""
|
2018-04-19 13:07:27 -04:00
|
|
|
aux := func(msg jsonmessage.JSONMessage) {
|
2017-04-06 08:33:56 -04:00
|
|
|
var result types.BuildResult
|
2018-04-19 13:07:27 -04:00
|
|
|
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
2017-04-06 08:33:56 -04:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to parse aux message: %s", err)
|
|
|
|
} else {
|
|
|
|
imageID = result.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), aux)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
|
|
|
// If no error code is set, default to 1
|
|
|
|
if jerr.Code == 0 {
|
|
|
|
jerr.Code = 1
|
|
|
|
}
|
|
|
|
if options.quiet {
|
|
|
|
fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff)
|
|
|
|
}
|
|
|
|
return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
|
|
}
|
2017-03-21 22:09:02 -04:00
|
|
|
return err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Windows: show error message about modified file permissions if the
|
|
|
|
// daemon isn't running Windows.
|
|
|
|
if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
2017-03-06 15:07:20 -05:00
|
|
|
fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+
|
|
|
|
"image from Windows against a non-Windows Docker host. All files and "+
|
|
|
|
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
|
|
|
"It is recommended to double check and reset permissions for sensitive "+
|
|
|
|
"files and directories.")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Everything worked so if -q was provided the output from the daemon
|
|
|
|
// should be just the image ID and we'll print that to stdout.
|
|
|
|
if options.quiet {
|
2017-04-06 08:33:56 -04:00
|
|
|
imageID = fmt.Sprintf("%s", buildBuff)
|
2019-10-29 05:21:20 -04:00
|
|
|
_, _ = fmt.Fprint(dockerCli.Out(), imageID)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-04-06 08:33:56 -04:00
|
|
|
if options.imageIDFile != "" {
|
|
|
|
if imageID == "" {
|
|
|
|
return errors.Errorf("Server did not provide an image ID. Cannot write %s", options.imageIDFile)
|
|
|
|
}
|
2022-09-30 13:13:22 -04:00
|
|
|
if err := os.WriteFile(options.imageIDFile, []byte(imageID), 0o666); err != nil {
|
2017-04-06 08:33:56 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-03-08 14:56:56 -05:00
|
|
|
if !options.untrusted {
|
2016-09-08 13:11:39 -04:00
|
|
|
// Since the build was successful, now we must tag any of the resolved
|
|
|
|
// images from the above Dockerfile rewrite.
|
|
|
|
for _, resolved := range resolvedTags {
|
2016-08-29 14:45:29 -04:00
|
|
|
if err := TagTrusted(ctx, dockerCli, resolved.digestRef, resolved.tagRef); err != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-01-24 08:19:31 -05:00
|
|
|
func isLocalDir(c string) bool {
|
|
|
|
_, err := os.Stat(c)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
|
|
|
|
|
|
|
|
// validateTag checks if the given image name can be resolved.
|
|
|
|
func validateTag(rawRepo string) (string, error) {
|
2017-01-11 16:54:52 -05:00
|
|
|
_, err := reference.ParseNormalizedNamed(rawRepo)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return rawRepo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
|
|
|
|
|
|
|
|
// resolvedTag records the repository, tag, and resolved digest reference
|
|
|
|
// from a Dockerfile rewrite.
|
|
|
|
type resolvedTag struct {
|
|
|
|
digestRef reference.Canonical
|
|
|
|
tagRef reference.NamedTagged
|
|
|
|
}
|
|
|
|
|
2018-03-14 12:36:23 -04:00
|
|
|
// rewriteDockerfileFromForContentTrust rewrites the given Dockerfile by resolving images in
|
2016-09-08 13:11:39 -04:00
|
|
|
// "FROM <image>" instructions to a digest reference. `translator` is a
|
|
|
|
// function that takes a repository name and tag reference and returns a
|
|
|
|
// trusted digest reference.
|
2018-03-14 12:36:23 -04:00
|
|
|
// This should be called *only* when content trust is enabled
|
|
|
|
func rewriteDockerfileFromForContentTrust(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
|
2016-09-08 13:11:39 -04:00
|
|
|
scanner := bufio.NewScanner(dockerfile)
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
|
|
|
|
// Scan the lines of the Dockerfile, looking for a "FROM" line.
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
|
|
|
|
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
|
|
|
|
if matches != nil && matches[1] != api.NoBaseImageSpecifier {
|
|
|
|
// Replace the line with a resolved "FROM repo@digest"
|
2017-01-11 16:54:52 -05:00
|
|
|
var ref reference.Named
|
|
|
|
ref, err = reference.ParseNormalizedNamed(matches[1])
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2017-01-25 19:54:18 -05:00
|
|
|
ref = reference.TagNameOnly(ref)
|
2018-03-14 12:36:23 -04:00
|
|
|
if ref, ok := ref.(reference.NamedTagged); ok {
|
2016-09-08 13:11:39 -04:00
|
|
|
trustedRef, err := translator(ctx, ref)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2017-01-11 16:54:52 -05:00
|
|
|
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef)))
|
2016-09-08 13:11:39 -04:00
|
|
|
resolvedTags = append(resolvedTags, &resolvedTag{
|
|
|
|
digestRef: trustedRef,
|
|
|
|
tagRef: ref,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := fmt.Fprintln(buf, line)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.Bytes(), resolvedTags, scanner.Err()
|
|
|
|
}
|
|
|
|
|
2018-03-08 14:56:56 -05:00
|
|
|
// replaceDockerfileForContentTrust wraps the given input tar archive stream and
|
|
|
|
// uses the translator to replace the Dockerfile which uses a trusted reference.
|
|
|
|
// Returns a new tar archive stream with the replaced Dockerfile.
|
|
|
|
func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
|
2016-09-08 13:11:39 -04:00
|
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
|
|
go func() {
|
|
|
|
tarReader := tar.NewReader(inputTarStream)
|
|
|
|
tarWriter := tar.NewWriter(pipeWriter)
|
|
|
|
|
|
|
|
defer inputTarStream.Close()
|
|
|
|
|
|
|
|
for {
|
|
|
|
hdr, err := tarReader.Next()
|
|
|
|
if err == io.EOF {
|
|
|
|
// Signals end of archive.
|
|
|
|
tarWriter.Close()
|
|
|
|
pipeWriter.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
pipeWriter.CloseWithError(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
content := io.Reader(tarReader)
|
|
|
|
if hdr.Name == dockerfileName {
|
|
|
|
// This entry is the Dockerfile. Since the tar archive was
|
|
|
|
// generated from a directory on the local filesystem, the
|
|
|
|
// Dockerfile will only appear once in the archive.
|
|
|
|
var newDockerfile []byte
|
2018-03-14 12:36:23 -04:00
|
|
|
newDockerfile, *resolvedTags, err = rewriteDockerfileFromForContentTrust(ctx, content, translator)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
pipeWriter.CloseWithError(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hdr.Size = int64(len(newDockerfile))
|
|
|
|
content = bytes.NewBuffer(newDockerfile)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := tarWriter.WriteHeader(hdr); err != nil {
|
|
|
|
pipeWriter.CloseWithError(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := io.Copy(tarWriter, content); err != nil {
|
|
|
|
pipeWriter.CloseWithError(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return pipeReader
|
|
|
|
}
|
2018-06-09 13:25:32 -04:00
|
|
|
|
|
|
|
func imageBuildOptions(dockerCli command.Cli, options buildOptions) types.ImageBuildOptions {
|
|
|
|
configFile := dockerCli.ConfigFile()
|
|
|
|
return types.ImageBuildOptions{
|
|
|
|
Memory: options.memory.Value(),
|
|
|
|
MemorySwap: options.memorySwap.Value(),
|
|
|
|
Tags: options.tags.GetAll(),
|
|
|
|
SuppressOutput: options.quiet,
|
|
|
|
NoCache: options.noCache,
|
|
|
|
Remove: options.rm,
|
|
|
|
ForceRemove: options.forceRm,
|
|
|
|
PullParent: options.pull,
|
|
|
|
Isolation: container.Isolation(options.isolation),
|
|
|
|
CPUSetCPUs: options.cpuSetCpus,
|
|
|
|
CPUSetMems: options.cpuSetMems,
|
|
|
|
CPUShares: options.cpuShares,
|
|
|
|
CPUQuota: options.cpuQuota,
|
|
|
|
CPUPeriod: options.cpuPeriod,
|
|
|
|
CgroupParent: options.cgroupParent,
|
|
|
|
ShmSize: options.shmSize.Value(),
|
|
|
|
Ulimits: options.ulimits.GetList(),
|
2017-10-15 15:39:56 -04:00
|
|
|
BuildArgs: configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll())),
|
2018-06-09 13:25:32 -04:00
|
|
|
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
|
|
|
|
CacheFrom: options.cacheFrom,
|
|
|
|
SecurityOpt: options.securityOpt,
|
|
|
|
NetworkMode: options.networkMode,
|
|
|
|
Squash: options.squash,
|
|
|
|
ExtraHosts: options.extraHosts.GetAll(),
|
|
|
|
Target: options.target,
|
|
|
|
Platform: options.platform,
|
|
|
|
}
|
|
|
|
}
|