2018-02-05 16:05:59 -05:00
|
|
|
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
|
2014-08-05 16:17:40 -04:00
|
|
|
|
2014-08-07 01:56:44 -04:00
|
|
|
// internals for handling commands. Covers many areas and a lot of
|
|
|
|
// non-contiguous functionality. Please read the comments.
|
|
|
|
|
2014-08-05 18:41:09 -04:00
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
2017-08-03 20:22:00 -04:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path"
|
2017-07-26 12:05:55 -04:00
|
|
|
"path/filepath"
|
2018-01-03 18:59:06 -05:00
|
|
|
"runtime"
|
2014-08-05 18:41:09 -04:00
|
|
|
"strings"
|
|
|
|
|
2016-09-06 14:18:12 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-03-16 19:07:41 -04:00
|
|
|
"github.com/docker/docker/api/types/backend"
|
2016-09-06 14:18:12 -04:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2018-02-16 16:50:57 -05:00
|
|
|
"github.com/docker/docker/builder"
|
2017-05-14 14:18:48 -04:00
|
|
|
"github.com/docker/docker/image"
|
2017-08-03 20:22:00 -04:00
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
|
|
"github.com/docker/docker/pkg/chrootarchive"
|
|
|
|
"github.com/docker/docker/pkg/containerfs"
|
2017-07-26 12:05:55 -04:00
|
|
|
"github.com/docker/docker/pkg/idtools"
|
2015-03-24 07:25:26 -04:00
|
|
|
"github.com/docker/docker/pkg/stringid"
|
2017-08-03 20:22:00 -04:00
|
|
|
"github.com/docker/docker/pkg/system"
|
2017-11-09 16:20:51 -05:00
|
|
|
"github.com/docker/go-connections/nat"
|
2019-04-26 18:12:43 -04:00
|
|
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
2017-03-15 18:28:06 -04:00
|
|
|
"github.com/pkg/errors"
|
2018-06-01 03:21:29 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2014-08-05 18:41:09 -04:00
|
|
|
)
|
|
|
|
|
2017-08-03 20:22:00 -04:00
|
|
|
// Archiver defines an interface for copying files from one destination to
|
|
|
|
// another using Tar/Untar.
|
|
|
|
type Archiver interface {
|
|
|
|
TarUntar(src, dst string) error
|
|
|
|
UntarPath(src, dst string) error
|
|
|
|
CopyWithTar(src, dst string) error
|
|
|
|
CopyFileWithTar(src, dst string) error
|
2017-11-16 01:20:33 -05:00
|
|
|
IdentityMapping() *idtools.IdentityMapping
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// The builder will use the following interfaces if the container fs implements
|
|
|
|
// these for optimized copies to and from the container.
|
|
|
|
type extractor interface {
|
|
|
|
ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type archiver interface {
|
|
|
|
ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper functions to get tar/untar func
|
|
|
|
func untarFunc(i interface{}) containerfs.UntarFunc {
|
|
|
|
if ea, ok := i.(extractor); ok {
|
|
|
|
return ea.ExtractArchive
|
|
|
|
}
|
|
|
|
return chrootarchive.Untar
|
|
|
|
}
|
|
|
|
|
|
|
|
func tarFunc(i interface{}) containerfs.TarFunc {
|
|
|
|
if ap, ok := i.(archiver); ok {
|
|
|
|
return ap.ArchivePath
|
|
|
|
}
|
|
|
|
return archive.TarWithOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Builder) getArchiver(src, dst containerfs.Driver) Archiver {
|
|
|
|
t, u := tarFunc(src), untarFunc(dst)
|
|
|
|
return &containerfs.Archiver{
|
2017-11-16 01:20:33 -05:00
|
|
|
SrcDriver: src,
|
|
|
|
DstDriver: dst,
|
|
|
|
Tar: t,
|
|
|
|
Untar: u,
|
|
|
|
IDMapping: b.idMapping,
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-26 17:45:16 -04:00
|
|
|
func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
|
2015-01-09 16:07:00 -05:00
|
|
|
if b.disableCommit {
|
|
|
|
return nil
|
|
|
|
}
|
2017-04-26 17:45:16 -04:00
|
|
|
if !dispatchState.hasFromImage() {
|
2016-12-25 01:37:31 -05:00
|
|
|
return errors.New("Please provide a source image with `from` prior to commit")
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
2016-03-16 17:52:34 -04:00
|
|
|
|
2018-02-05 17:41:45 -05:00
|
|
|
runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, dispatchState.operatingSystem))
|
2018-06-01 03:21:29 -04:00
|
|
|
id, err := b.probeAndCreate(dispatchState, runConfigWithCommentCmd)
|
|
|
|
if err != nil || id == "" {
|
2017-04-21 14:11:21 -04:00
|
|
|
return err
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
2015-09-06 13:26:40 -04:00
|
|
|
|
2017-04-26 17:45:16 -04:00
|
|
|
return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
|
2017-04-21 14:11:21 -04:00
|
|
|
}
|
|
|
|
|
2017-04-26 17:45:16 -04:00
|
|
|
func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
|
2017-04-21 14:11:21 -04:00
|
|
|
if b.disableCommit {
|
|
|
|
return nil
|
|
|
|
}
|
2014-08-30 07:34:09 -04:00
|
|
|
|
2018-02-06 13:27:55 -05:00
|
|
|
commitCfg := backend.CommitConfig{
|
|
|
|
Author: dispatchState.maintainer,
|
|
|
|
// TODO: this copy should be done by Commit()
|
|
|
|
Config: copyRunConfig(dispatchState.runConfig),
|
2017-04-21 15:08:11 -04:00
|
|
|
ContainerConfig: containerConfig,
|
2018-02-06 13:27:55 -05:00
|
|
|
ContainerID: id,
|
2015-06-20 06:40:37 -04:00
|
|
|
}
|
|
|
|
|
2018-02-06 13:27:55 -05:00
|
|
|
imageID, err := b.docker.CommitBuildStep(commitCfg)
|
|
|
|
dispatchState.imageID = string(imageID)
|
|
|
|
return err
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
2018-02-16 16:50:57 -05:00
|
|
|
func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
|
|
|
|
newLayer, err := layer.Commit()
|
2017-05-25 17:03:29 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-02-16 16:50:57 -05:00
|
|
|
parentImage, ok := parent.(*image.Image)
|
2017-05-25 17:03:29 -04:00
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("unexpected image type")
|
|
|
|
}
|
|
|
|
|
2019-04-26 18:12:43 -04:00
|
|
|
platform := &specs.Platform{
|
|
|
|
OS: parentImage.OS,
|
|
|
|
Architecture: parentImage.Architecture,
|
|
|
|
Variant: parentImage.Variant,
|
|
|
|
}
|
|
|
|
|
|
|
|
// add an image mount without an image so the layer is properly unmounted
|
|
|
|
// if there is an error before we can add the full mount with image
|
|
|
|
b.imageSources.Add(newImageMount(nil, newLayer), platform)
|
|
|
|
|
2017-05-25 17:03:29 -04:00
|
|
|
newImage := image.NewChildImage(parentImage, image.ChildConfig{
|
|
|
|
Author: state.maintainer,
|
|
|
|
ContainerConfig: runConfig,
|
|
|
|
DiffID: newLayer.DiffID(),
|
|
|
|
Config: copyRunConfig(state.runConfig),
|
2017-05-17 15:01:43 -04:00
|
|
|
}, parentImage.OS)
|
2017-05-25 17:03:29 -04:00
|
|
|
|
|
|
|
// TODO: it seems strange to marshal this here instead of just passing in the
|
|
|
|
// image struct
|
|
|
|
config, err := newImage.MarshalJSON()
|
2017-05-14 14:18:48 -04:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to encode image config")
|
|
|
|
}
|
|
|
|
|
2017-08-24 14:48:16 -04:00
|
|
|
exportedImage, err := b.docker.CreateImage(config, state.imageID)
|
2017-05-25 17:03:29 -04:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to export image")
|
|
|
|
}
|
|
|
|
|
|
|
|
state.imageID = exportedImage.ImageID()
|
2019-04-26 18:12:43 -04:00
|
|
|
b.imageSources.Add(newImageMount(exportedImage, newLayer), platform)
|
2017-05-25 17:03:29 -04:00
|
|
|
return nil
|
2017-05-14 14:18:48 -04:00
|
|
|
}
|
|
|
|
|
2018-06-26 17:49:33 -04:00
|
|
|
func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
|
|
|
|
state := req.state
|
2017-05-07 14:37:46 -04:00
|
|
|
srcHash := getSourceHashFromInfos(inst.infos)
|
2014-08-05 16:17:40 -04:00
|
|
|
|
2017-07-26 12:05:55 -04:00
|
|
|
var chownComment string
|
|
|
|
if inst.chownStr != "" {
|
|
|
|
chownComment = fmt.Sprintf("--chown=%s", inst.chownStr)
|
|
|
|
}
|
|
|
|
commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
|
|
|
|
|
2017-04-21 14:11:21 -04:00
|
|
|
// TODO: should this have been using origPaths instead of srcHash in the comment?
|
2017-04-21 15:08:11 -04:00
|
|
|
runConfigWithCommentCmd := copyRunConfig(
|
2017-05-07 14:37:46 -04:00
|
|
|
state.runConfig,
|
2018-02-05 17:41:45 -05:00
|
|
|
withCmdCommentString(commentStr, state.operatingSystem))
|
2017-05-25 17:03:29 -04:00
|
|
|
hit, err := b.probeCache(state, runConfigWithCommentCmd)
|
|
|
|
if err != nil || hit {
|
2016-05-02 21:33:59 -04:00
|
|
|
return err
|
2014-09-16 12:58:20 -04:00
|
|
|
}
|
|
|
|
|
2018-07-02 22:31:05 -04:00
|
|
|
imageMount, err := b.imageSources.Get(state.imageID, true, req.builder.platform)
|
2017-05-14 14:18:48 -04:00
|
|
|
if err != nil {
|
2017-05-25 17:03:29 -04:00
|
|
|
return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
|
2017-05-14 14:18:48 -04:00
|
|
|
}
|
2017-08-03 20:22:00 -04:00
|
|
|
|
2018-02-16 16:50:57 -05:00
|
|
|
rwLayer, err := imageMount.NewRWLayer()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rwLayer.Release()
|
|
|
|
|
2018-02-05 17:41:45 -05:00
|
|
|
destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, state.operatingSystem)
|
2017-05-14 14:18:48 -04:00
|
|
|
if err != nil {
|
2017-05-25 17:03:29 -04:00
|
|
|
return err
|
2017-05-14 14:18:48 -04:00
|
|
|
}
|
|
|
|
|
2017-11-16 01:20:33 -05:00
|
|
|
identity := b.idMapping.RootPair()
|
2017-07-26 12:05:55 -04:00
|
|
|
// if a chown was requested, perform the steps to get the uid, gid
|
|
|
|
// translated (if necessary because of user namespaces), and replace
|
|
|
|
// the root pair with the chown pair for copy operations
|
|
|
|
if inst.chownStr != "" {
|
2017-11-16 01:20:33 -05:00
|
|
|
identity, err = parseChownFlag(b, state, inst.chownStr, destInfo.root.Path(), b.idMapping)
|
2017-07-26 12:05:55 -04:00
|
|
|
if err != nil {
|
2017-11-16 01:20:33 -05:00
|
|
|
if b.options.Platform != "windows" {
|
|
|
|
return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrapf(err, "unable to map container user account name to SID")
|
2017-07-26 12:05:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-07 14:37:46 -04:00
|
|
|
for _, info := range inst.infos {
|
2017-08-03 20:22:00 -04:00
|
|
|
opts := copyFileOptions{
|
|
|
|
decompress: inst.allowLocalDecompression,
|
|
|
|
archiver: b.getArchiver(info.root, destInfo.root),
|
2018-12-18 16:30:08 -05:00
|
|
|
}
|
|
|
|
if !inst.preserveOwnership {
|
|
|
|
opts.identity = &identity
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
2017-05-25 17:03:29 -04:00
|
|
|
if err := performCopyForInfo(destInfo, info, opts); err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to copy files")
|
2014-09-16 12:58:20 -04:00
|
|
|
}
|
|
|
|
}
|
2018-02-16 16:50:57 -05:00
|
|
|
return b.exportImage(state, rwLayer, imageMount.Image(), runConfigWithCommentCmd)
|
2017-05-25 17:03:29 -04:00
|
|
|
}
|
2017-05-14 14:18:48 -04:00
|
|
|
|
2018-02-16 16:50:57 -05:00
|
|
|
func createDestInfo(workingDir string, inst copyInstruction, rwLayer builder.RWLayer, platform string) (copyInfo, error) {
|
2017-05-25 17:03:29 -04:00
|
|
|
// Twiddle the destination when it's a relative path - meaning, make it
|
|
|
|
// relative to the WORKINGDIR
|
2017-08-03 20:22:00 -04:00
|
|
|
dest, err := normalizeDest(workingDir, inst.dest, platform)
|
2017-05-25 17:03:29 -04:00
|
|
|
if err != nil {
|
|
|
|
return copyInfo{}, errors.Wrapf(err, "invalid %s", inst.cmdName)
|
|
|
|
}
|
|
|
|
|
2018-02-16 16:50:57 -05:00
|
|
|
return copyInfo{root: rwLayer.Root(), path: dest}, nil
|
2017-05-07 14:37:46 -04:00
|
|
|
}
|
2014-09-16 12:58:20 -04:00
|
|
|
|
2017-08-03 20:22:00 -04:00
|
|
|
// normalizeDest normalises the destination of a COPY/ADD command in a
|
|
|
|
// platform semantically consistent way.
|
|
|
|
func normalizeDest(workingDir, requested string, platform string) (string, error) {
|
|
|
|
dest := fromSlash(requested, platform)
|
|
|
|
endsInSlash := strings.HasSuffix(dest, string(separator(platform)))
|
|
|
|
|
|
|
|
if platform != "windows" {
|
|
|
|
if !path.IsAbs(requested) {
|
|
|
|
dest = path.Join("/", filepath.ToSlash(workingDir), dest)
|
|
|
|
// Make sure we preserve any trailing slash
|
|
|
|
if endsInSlash {
|
|
|
|
dest += "/"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dest, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are guaranteed that the working directory is already consistent,
|
|
|
|
// However, Windows also has, for now, the limitation that ADD/COPY can
|
|
|
|
// only be done to the system drive, not any drives that might be present
|
|
|
|
// as a result of a bind mount.
|
|
|
|
//
|
|
|
|
// So... if the path requested is Linux-style absolute (/foo or \\foo),
|
|
|
|
// we assume it is the system drive. If it is a Windows-style absolute
|
|
|
|
// (DRIVE:\\foo), error if DRIVE is not C. And finally, ensure we
|
|
|
|
// strip any configured working directories drive letter so that it
|
|
|
|
// can be subsequently legitimately converted to a Windows volume-style
|
|
|
|
// pathname.
|
|
|
|
|
|
|
|
// Not a typo - filepath.IsAbs, not system.IsAbs on this next check as
|
|
|
|
// we only want to validate where the DriveColon part has been supplied.
|
|
|
|
if filepath.IsAbs(dest) {
|
|
|
|
if strings.ToUpper(string(dest[0])) != "C" {
|
|
|
|
return "", fmt.Errorf("Windows does not support destinations not on the system drive (C:)")
|
|
|
|
}
|
|
|
|
dest = dest[2:] // Strip the drive letter
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cannot handle relative where WorkingDir is not the system drive.
|
|
|
|
if len(workingDir) > 0 {
|
|
|
|
if ((len(workingDir) > 1) && !system.IsAbs(workingDir[2:])) || (len(workingDir) == 1) {
|
|
|
|
return "", fmt.Errorf("Current WorkingDir %s is not platform consistent", workingDir)
|
|
|
|
}
|
|
|
|
if !system.IsAbs(dest) {
|
|
|
|
if string(workingDir[0]) != "C" {
|
|
|
|
return "", fmt.Errorf("Windows does not support relative paths when WORKDIR is not the system drive")
|
|
|
|
}
|
|
|
|
dest = filepath.Join(string(os.PathSeparator), workingDir[2:], dest)
|
|
|
|
// Make sure we preserve any trailing slash
|
|
|
|
if endsInSlash {
|
|
|
|
dest += string(os.PathSeparator)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dest, nil
|
|
|
|
}
|
|
|
|
|
2017-05-07 14:37:46 -04:00
|
|
|
// For backwards compat, if there's just one info then use it as the
|
|
|
|
// cache look-up string, otherwise hash 'em all into one
|
|
|
|
func getSourceHashFromInfos(infos []copyInfo) string {
|
|
|
|
if len(infos) == 1 {
|
|
|
|
return infos[0].hash
|
|
|
|
}
|
|
|
|
var hashs []string
|
|
|
|
for _, info := range infos {
|
|
|
|
hashs = append(hashs, info.hash)
|
|
|
|
}
|
|
|
|
return hashStringSlice("multi", hashs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func hashStringSlice(prefix string, slice []string) string {
|
|
|
|
hasher := sha256.New()
|
|
|
|
hasher.Write([]byte(strings.Join(slice, ",")))
|
|
|
|
return prefix + ":" + hex.EncodeToString(hasher.Sum(nil))
|
2017-04-21 14:11:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type runConfigModifier func(*container.Config)
|
|
|
|
|
|
|
|
func withCmd(cmd []string) runConfigModifier {
|
|
|
|
return func(runConfig *container.Config) {
|
|
|
|
runConfig.Cmd = cmd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Windows: (WCOW) Generate OCI spec that remote runtime can escape
Signed-off-by: John Howard <jhoward@microsoft.com>
Also fixes https://github.com/moby/moby/issues/22874
This commit is a pre-requisite to moving moby/moby on Windows to using
Containerd for its runtime.
The reason for this is that the interface between moby and containerd
for the runtime is an OCI spec which must be unambigious.
It is the responsibility of the runtime (runhcs in the case of
containerd on Windows) to ensure that arguments are escaped prior
to calling into HCS and onwards to the Win32 CreateProcess call.
Previously, the builder was always escaping arguments which has
led to several bugs in moby. Because the local runtime in
libcontainerd had context of whether or not arguments were escaped,
it was possible to hack around in daemon/oci_windows.go with
knowledge of the context of the call (from builder or not).
With a remote runtime, this is not possible as there's rightly
no context of the caller passed across in the OCI spec. Put another
way, as I put above, the OCI spec must be unambigious.
The other previous limitation (which leads to various subtle bugs)
is that moby is coded entirely from a Linux-centric point of view.
Unfortunately, Windows != Linux. Windows CreateProcess uses a
command line, not an array of arguments. And it has very specific
rules about how to escape a command line. Some interesting reading
links about this are:
https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess
https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017
For this reason, the OCI spec has recently been updated to cater
for more natural syntax by including a CommandLine option in
Process.
What does this commit do?
Primary objective is to ensure that the built OCI spec is unambigious.
It changes the builder so that `ArgsEscaped` as commited in a
layer is only controlled by the use of CMD or ENTRYPOINT.
Subsequently, when calling in to create a container from the builder,
if follows a different path to both `docker run` and `docker create`
using the added `ContainerCreateIgnoreImagesArgsEscaped`. This allows
a RUN from the builder to control how to escape in the OCI spec.
It changes the builder so that when shell form is used for RUN,
CMD or ENTRYPOINT, it builds (for WCOW) a more natural command line
using the original as put by the user in the dockerfile, not
the parsed version as a set of args which loses fidelity.
This command line is put into args[0] and `ArgsEscaped` is set
to true for CMD or ENTRYPOINT. A RUN statement does not commit
`ArgsEscaped` to the commited layer regardless or whether shell
or exec form were used.
2019-01-17 19:03:29 -05:00
|
|
|
func withArgsEscaped(argsEscaped bool) runConfigModifier {
|
|
|
|
return func(runConfig *container.Config) {
|
|
|
|
runConfig.ArgsEscaped = argsEscaped
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-21 15:08:11 -04:00
|
|
|
// withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
|
|
|
|
// why there are two almost identical versions of this.
|
2017-06-20 18:08:58 -04:00
|
|
|
func withCmdComment(comment string, platform string) runConfigModifier {
|
2017-04-21 14:11:21 -04:00
|
|
|
return func(runConfig *container.Config) {
|
2017-06-20 18:08:58 -04:00
|
|
|
runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) ", comment)
|
2017-04-21 14:11:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-21 15:08:11 -04:00
|
|
|
// withCmdCommentString exists to maintain compatibility with older versions.
|
|
|
|
// A few instructions (workdir, copy, add) used a nop comment that is a single arg
|
|
|
|
// where as all the other instructions used a two arg comment string. This
|
|
|
|
// function implements the single arg version.
|
2017-06-20 18:08:58 -04:00
|
|
|
func withCmdCommentString(comment string, platform string) runConfigModifier {
|
2017-04-21 15:08:11 -04:00
|
|
|
return func(runConfig *container.Config) {
|
2017-06-20 18:08:58 -04:00
|
|
|
runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) "+comment)
|
2017-04-21 15:08:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-21 14:11:21 -04:00
|
|
|
func withEnv(env []string) runConfigModifier {
|
|
|
|
return func(runConfig *container.Config) {
|
|
|
|
runConfig.Env = env
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-25 12:21:43 -04:00
|
|
|
// withEntrypointOverride sets an entrypoint on runConfig if the command is
|
|
|
|
// not empty. The entrypoint is left unmodified if command is empty.
|
|
|
|
//
|
|
|
|
// The dockerfile RUN instruction expect to run without an entrypoint
|
|
|
|
// so the runConfig entrypoint needs to be modified accordingly. ContainerCreate
|
|
|
|
// will change a []string{""} entrypoint to nil, so we probe the cache with the
|
|
|
|
// nil entrypoint.
|
|
|
|
func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier {
|
|
|
|
return func(runConfig *container.Config) {
|
|
|
|
if len(cmd) > 0 {
|
|
|
|
runConfig.Entrypoint = entrypoint
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-07 20:36:50 -04:00
|
|
|
// withoutHealthcheck disables healthcheck.
|
|
|
|
//
|
|
|
|
// The dockerfile RUN instruction expect to run without healthcheck
|
|
|
|
// so the runConfig Healthcheck needs to be disabled.
|
|
|
|
func withoutHealthcheck() runConfigModifier {
|
|
|
|
return func(runConfig *container.Config) {
|
|
|
|
runConfig.Healthcheck = &container.HealthConfig{
|
|
|
|
Test: []string{"NONE"},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-09 16:20:51 -05:00
|
|
|
func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
|
|
|
|
copy := *runConfig
|
|
|
|
copy.Cmd = copyStringSlice(runConfig.Cmd)
|
|
|
|
copy.Env = copyStringSlice(runConfig.Env)
|
|
|
|
copy.Entrypoint = copyStringSlice(runConfig.Entrypoint)
|
|
|
|
copy.OnBuild = copyStringSlice(runConfig.OnBuild)
|
|
|
|
copy.Shell = copyStringSlice(runConfig.Shell)
|
|
|
|
|
|
|
|
if copy.Volumes != nil {
|
|
|
|
copy.Volumes = make(map[string]struct{}, len(runConfig.Volumes))
|
|
|
|
for k, v := range runConfig.Volumes {
|
|
|
|
copy.Volumes[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if copy.ExposedPorts != nil {
|
|
|
|
copy.ExposedPorts = make(nat.PortSet, len(runConfig.ExposedPorts))
|
|
|
|
for k, v := range runConfig.ExposedPorts {
|
|
|
|
copy.ExposedPorts[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if copy.Labels != nil {
|
|
|
|
copy.Labels = make(map[string]string, len(runConfig.Labels))
|
|
|
|
for k, v := range runConfig.Labels {
|
|
|
|
copy.Labels[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, modifier := range modifiers {
|
|
|
|
modifier(©)
|
|
|
|
}
|
|
|
|
return ©
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyStringSlice(orig []string) []string {
|
|
|
|
if orig == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return append([]string{}, orig...)
|
|
|
|
}
|
|
|
|
|
2017-04-21 14:11:21 -04:00
|
|
|
// getShell is a helper function which gets the right shell for prefixing the
|
|
|
|
// shell-form of RUN, ENTRYPOINT and CMD instructions
|
2017-08-08 15:43:48 -04:00
|
|
|
func getShell(c *container.Config, os string) []string {
|
2017-04-21 14:11:21 -04:00
|
|
|
if 0 == len(c.Shell) {
|
2017-08-08 15:43:48 -04:00
|
|
|
return append([]string{}, defaultShellForOS(os)[:]...)
|
2017-04-21 14:11:21 -04:00
|
|
|
}
|
|
|
|
return append([]string{}, c.Shell[:]...)
|
2014-09-16 12:58:20 -04:00
|
|
|
}
|
|
|
|
|
2017-04-26 17:45:16 -04:00
|
|
|
func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
|
2017-04-13 18:44:36 -04:00
|
|
|
cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig)
|
|
|
|
if cachedID == "" || err != nil {
|
2015-02-25 13:27:32 -05:00
|
|
|
return false, err
|
|
|
|
}
|
2016-12-25 01:37:31 -05:00
|
|
|
fmt.Fprint(b.Stdout, " ---> Using cache\n")
|
2015-09-06 13:26:40 -04:00
|
|
|
|
2017-08-24 13:11:44 -04:00
|
|
|
dispatchState.imageID = cachedID
|
2015-02-25 13:27:32 -05:00
|
|
|
return true, nil
|
2014-08-05 18:41:09 -04:00
|
|
|
}
|
|
|
|
|
2017-04-13 18:44:36 -04:00
|
|
|
var defaultLogConfig = container.LogConfig{Type: "none"}
|
2015-11-18 14:03:08 -05:00
|
|
|
|
2017-04-13 18:44:36 -04:00
|
|
|
func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *container.Config) (string, error) {
|
|
|
|
if hit, err := b.probeCache(dispatchState, runConfig); err != nil || hit {
|
2015-12-10 09:35:53 -05:00
|
|
|
return "", err
|
2014-08-05 18:41:09 -04:00
|
|
|
}
|
2018-06-01 03:21:29 -04:00
|
|
|
return b.create(runConfig)
|
2014-08-05 18:41:09 -04:00
|
|
|
}
|
|
|
|
|
2017-04-13 18:44:36 -04:00
|
|
|
func (b *Builder) create(runConfig *container.Config) (string, error) {
|
2018-06-01 03:21:29 -04:00
|
|
|
logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd)
|
2018-07-02 22:31:05 -04:00
|
|
|
|
|
|
|
isWCOW := runtime.GOOS == "windows" && b.platform != nil && b.platform.OS == "windows"
|
|
|
|
hostConfig := hostConfigFromOptions(b.options, isWCOW)
|
2017-09-19 15:14:46 -04:00
|
|
|
container, err := b.containerManager.Create(runConfig, hostConfig)
|
2017-03-30 16:52:40 -04:00
|
|
|
if err != nil {
|
2017-04-13 18:44:36 -04:00
|
|
|
return "", err
|
2015-10-16 05:18:10 -04:00
|
|
|
}
|
2017-04-13 18:44:36 -04:00
|
|
|
// TODO: could this be moved into containerManager.Create() ?
|
|
|
|
for _, warning := range container.Warnings {
|
|
|
|
fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning)
|
2015-10-16 05:18:10 -04:00
|
|
|
}
|
2017-04-13 18:44:36 -04:00
|
|
|
fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(container.ID))
|
|
|
|
return container.ID, nil
|
2015-10-16 05:18:10 -04:00
|
|
|
}
|
|
|
|
|
2018-07-02 22:31:05 -04:00
|
|
|
func hostConfigFromOptions(options *types.ImageBuildOptions, isWCOW bool) *container.HostConfig {
|
2017-04-13 18:44:36 -04:00
|
|
|
resources := container.Resources{
|
|
|
|
CgroupParent: options.CgroupParent,
|
|
|
|
CPUShares: options.CPUShares,
|
|
|
|
CPUPeriod: options.CPUPeriod,
|
|
|
|
CPUQuota: options.CPUQuota,
|
|
|
|
CpusetCpus: options.CPUSetCPUs,
|
|
|
|
CpusetMems: options.CPUSetMems,
|
|
|
|
Memory: options.Memory,
|
|
|
|
MemorySwap: options.MemorySwap,
|
|
|
|
Ulimits: options.Ulimits,
|
|
|
|
}
|
|
|
|
|
2018-01-03 18:59:06 -05:00
|
|
|
hc := &container.HostConfig{
|
2017-04-13 18:44:36 -04:00
|
|
|
SecurityOpt: options.SecurityOpt,
|
|
|
|
Isolation: options.Isolation,
|
|
|
|
ShmSize: options.ShmSize,
|
|
|
|
Resources: resources,
|
|
|
|
NetworkMode: container.NetworkMode(options.NetworkMode),
|
|
|
|
// Set a log config to override any default value set on the daemon
|
|
|
|
LogConfig: defaultLogConfig,
|
|
|
|
ExtraHosts: options.ExtraHosts,
|
2014-08-05 18:41:09 -04:00
|
|
|
}
|
2018-01-03 18:59:06 -05:00
|
|
|
|
|
|
|
// For WCOW, the default of 20GB hard-coded in the platform
|
|
|
|
// is too small for builder scenarios where many users are
|
|
|
|
// using RUN statements to install large amounts of data.
|
|
|
|
// Use 127GB as that's the default size of a VHD in Hyper-V.
|
2018-07-02 22:31:05 -04:00
|
|
|
if isWCOW {
|
2018-01-03 18:59:06 -05:00
|
|
|
hc.StorageOpt = make(map[string]string)
|
|
|
|
hc.StorageOpt["size"] = "127GB"
|
|
|
|
}
|
|
|
|
|
|
|
|
return hc
|
2014-08-05 18:41:09 -04:00
|
|
|
}
|
2017-08-03 20:22:00 -04:00
|
|
|
|
|
|
|
// fromSlash works like filepath.FromSlash but with a given OS platform field
|
|
|
|
func fromSlash(path, platform string) string {
|
|
|
|
if platform == "windows" {
|
|
|
|
return strings.Replace(path, "/", "\\", -1)
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
|
|
|
// separator returns a OS path separator for the given OS platform
|
|
|
|
func separator(platform string) byte {
|
|
|
|
if platform == "windows" {
|
|
|
|
return '\\'
|
|
|
|
}
|
|
|
|
return '/'
|
|
|
|
}
|