Merge component 'engine' from git@github.com:moby/moby master
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -208,13 +209,26 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
|
||||
return b
|
||||
}
|
||||
|
||||
// Build 'LABEL' command(s) from '--label' options and add to the last stage
|
||||
func buildLabelOptions(labels map[string]string, stages []instructions.Stage) {
|
||||
keys := []string{}
|
||||
for key := range labels {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// Sort the label to have a repeatable order
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
value := labels[key]
|
||||
stages[len(stages)-1].AddCommand(instructions.NewLabelCommand(key, value, true))
|
||||
}
|
||||
}
|
||||
|
||||
// Build runs the Dockerfile builder by parsing the Dockerfile and executing
|
||||
// the instructions from the file.
|
||||
func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
|
||||
defer b.imageSources.Unmount()
|
||||
|
||||
addNodesForLabelOption(dockerfile.AST, b.options.Labels)
|
||||
|
||||
stages, metaArgs, err := instructions.Parse(dockerfile.AST)
|
||||
if err != nil {
|
||||
if instructions.IsUnknownInstruction(err) {
|
||||
@@ -231,6 +245,9 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil
|
||||
stages = stages[:targetIx+1]
|
||||
}
|
||||
|
||||
// Add 'LABEL' command specified by '--label' option to the last stage
|
||||
buildLabelOptions(b.options.Labels, stages)
|
||||
|
||||
dockerfile.PrintWarnings(b.Stderr)
|
||||
dispatchState, err := b.dispatchDockerfileWithCancellation(stages, metaArgs, dockerfile.EscapeToken, source)
|
||||
if err != nil {
|
||||
@@ -333,15 +350,6 @@ func (b *Builder) dispatchDockerfileWithCancellation(parseResult []instructions.
|
||||
return dispatchRequest.state, nil
|
||||
}
|
||||
|
||||
func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
|
||||
if len(labels) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
node := parser.NodeFromLabels(labels)
|
||||
dockerfile.Children = append(dockerfile.Children, node)
|
||||
}
|
||||
|
||||
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
|
||||
// It will:
|
||||
// - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/builder/dockerfile/parser"
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
func TestAddNodesForLabelOption(t *testing.T) {
|
||||
dockerfile := "FROM scratch"
|
||||
result, err := parser.Parse(strings.NewReader(dockerfile))
|
||||
assert.Check(t, err)
|
||||
|
||||
labels := map[string]string{
|
||||
"org.e": "cli-e",
|
||||
"org.d": "cli-d",
|
||||
"org.c": "cli-c",
|
||||
"org.b": "cli-b",
|
||||
"org.a": "cli-a",
|
||||
}
|
||||
nodes := result.AST
|
||||
addNodesForLabelOption(nodes, labels)
|
||||
|
||||
expected := []string{
|
||||
"FROM scratch",
|
||||
`LABEL "org.a"='cli-a' "org.b"='cli-b' "org.c"='cli-c' "org.d"='cli-d' "org.e"='cli-e'`,
|
||||
}
|
||||
assert.Check(t, is.Len(nodes.Children, 2))
|
||||
for i, v := range nodes.Children {
|
||||
assert.Check(t, is.Equal(expected[i], v.Original))
|
||||
}
|
||||
}
|
||||
@@ -110,17 +110,37 @@ type MaintainerCommand struct {
|
||||
Maintainer string
|
||||
}
|
||||
|
||||
// NewLabelCommand creates a new 'LABEL' command
|
||||
func NewLabelCommand(k string, v string, NoExp bool) *LabelCommand {
|
||||
kvp := KeyValuePair{Key: k, Value: v}
|
||||
c := "LABEL "
|
||||
c += kvp.String()
|
||||
nc := withNameAndCode{code: c, name: "label"}
|
||||
cmd := &LabelCommand{
|
||||
withNameAndCode: nc,
|
||||
Labels: KeyValuePairs{
|
||||
kvp,
|
||||
},
|
||||
noExpand: NoExp,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// LabelCommand : LABEL some json data describing the image
|
||||
//
|
||||
// Sets the Label variable foo to bar,
|
||||
//
|
||||
type LabelCommand struct {
|
||||
withNameAndCode
|
||||
Labels KeyValuePairs // kvp slice instead of map to preserve ordering
|
||||
Labels KeyValuePairs // kvp slice instead of map to preserve ordering
|
||||
noExpand bool
|
||||
}
|
||||
|
||||
// Expand variables
|
||||
func (c *LabelCommand) Expand(expander SingleWordExpander) error {
|
||||
if c.noExpand {
|
||||
return nil
|
||||
}
|
||||
return expandKvpsInPlace(c.Labels, expander)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/docker/docker/builder/dockerfile/command"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -205,34 +202,6 @@ func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) {
|
||||
return node, nil, err
|
||||
}
|
||||
|
||||
// NodeFromLabels returns a Node for the injected labels
|
||||
func NodeFromLabels(labels map[string]string) *Node {
|
||||
keys := []string{}
|
||||
for key := range labels {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
// Sort the label to have a repeatable order
|
||||
sort.Strings(keys)
|
||||
|
||||
labelPairs := []string{}
|
||||
var rootNode *Node
|
||||
var prevNode *Node
|
||||
for _, key := range keys {
|
||||
value := labels[key]
|
||||
labelPairs = append(labelPairs, fmt.Sprintf("%q='%s'", key, value))
|
||||
// Value must be single quoted to prevent env variable expansion
|
||||
// See https://github.com/docker/docker/issues/26027
|
||||
node := newKeyValueNode(key, "'"+value+"'")
|
||||
rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
|
||||
}
|
||||
|
||||
return &Node{
|
||||
Value: command.Label,
|
||||
Original: commandLabel + " " + strings.Join(labelPairs, " "),
|
||||
Next: rootNode,
|
||||
}
|
||||
}
|
||||
|
||||
// parses a statement containing one or more keyword definition(s) and/or
|
||||
// value assignments, like `name1 name2= name3="" name4=value`.
|
||||
// Note that this is a stricter format than the old format of assignment,
|
||||
|
||||
@@ -42,32 +42,6 @@ func TestParseNameValNewFormat(t *testing.T) {
|
||||
assert.DeepEqual(t, expected, node, cmpNodeOpt)
|
||||
}
|
||||
|
||||
func TestNodeFromLabels(t *testing.T) {
|
||||
labels := map[string]string{
|
||||
"foo": "bar",
|
||||
"weird": "first' second",
|
||||
}
|
||||
expected := &Node{
|
||||
Value: "label",
|
||||
Original: `LABEL "foo"='bar' "weird"='first' second'`,
|
||||
Next: &Node{
|
||||
Value: "foo",
|
||||
Next: &Node{
|
||||
Value: "'bar'",
|
||||
Next: &Node{
|
||||
Value: "weird",
|
||||
Next: &Node{
|
||||
Value: "'first' second'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
node := NodeFromLabels(labels)
|
||||
assert.DeepEqual(t, expected, node, cmpNodeOpt)
|
||||
}
|
||||
|
||||
func TestParseNameValWithoutVal(t *testing.T) {
|
||||
directive := Directive{}
|
||||
// In Config.Env, a variable without `=` is removed from the environment. (#31634)
|
||||
|
||||
@@ -62,6 +62,8 @@ var (
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
|
||||
logger = logrus.WithField("storage-driver", "aufs")
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -84,9 +86,9 @@ type Driver struct {
|
||||
// Init returns a new AUFS driver.
|
||||
// An error is returned if AUFS is not supported.
|
||||
func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
|
||||
|
||||
// Try to load the aufs kernel module
|
||||
if err := supportsAufs(); err != nil {
|
||||
logger.Error(err)
|
||||
return nil, graphdriver.ErrNotSupported
|
||||
}
|
||||
|
||||
@@ -109,7 +111,7 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
|
||||
switch fsMagic {
|
||||
case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicEcryptfs:
|
||||
logrus.WithField("storage-driver", "aufs").Errorf("AUFS is not supported over %s", backingFs)
|
||||
logger.Errorf("AUFS is not supported over %s", backingFs)
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
}
|
||||
|
||||
@@ -143,7 +145,6 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
logger := logrus.WithField("storage-driver", "aufs")
|
||||
|
||||
for _, path := range []string{"mnt", "diff"} {
|
||||
p := filepath.Join(root, path)
|
||||
@@ -306,10 +307,7 @@ func (a *Driver) Remove(id string) error {
|
||||
mountpoint = a.getMountpoint(id)
|
||||
}
|
||||
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"storage-driver": "aufs",
|
||||
"layer": id,
|
||||
})
|
||||
logger := logger.WithField("layer", id)
|
||||
|
||||
var retries int
|
||||
for {
|
||||
@@ -439,7 +437,7 @@ func (a *Driver) Put(id string) error {
|
||||
|
||||
err := a.unmount(m)
|
||||
if err != nil {
|
||||
logrus.WithField("storage-driver", "aufs").Debugf("Failed to unmount %s aufs: %v", id, err)
|
||||
logger.Debugf("Failed to unmount %s aufs: %v", id, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -597,7 +595,7 @@ func (a *Driver) Cleanup() error {
|
||||
|
||||
for _, m := range dirs {
|
||||
if err := a.unmount(m); err != nil {
|
||||
logrus.WithField("storage-driver", "aufs").Debugf("error unmounting %s: %s", m, err)
|
||||
logger.Debugf("error unmounting %s: %s", m, err)
|
||||
}
|
||||
}
|
||||
return mountpk.RecursiveUnmount(a.root)
|
||||
@@ -652,7 +650,6 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
logger := logrus.WithField("storage-driver", "aufs")
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := ioutil.TempDir("", "docker-aufs-base")
|
||||
if err != nil {
|
||||
|
||||
@@ -5,14 +5,13 @@ package aufs // import "github.com/docker/docker/daemon/graphdriver/aufs"
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Unmount the target specified.
|
||||
func Unmount(target string) error {
|
||||
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
||||
logrus.WithField("storage-driver", "aufs").Warnf("Couldn't run auplink before unmount %s: %s", target, err)
|
||||
logger.WithError(err).Warnf("Couldn't run auplink before unmount %s", target)
|
||||
}
|
||||
return unix.Unmount(target, 0)
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ ORIG_BUILDFLAGS+=( $REBUILD_FLAG )
|
||||
BUILDFLAGS=( $BUILDFLAGS "${ORIG_BUILDFLAGS[@]}" )
|
||||
|
||||
# Test timeout.
|
||||
if [ "${DOCKER_ENGINE_GOARCH}" == "arm" ]; then
|
||||
if [ "${DOCKER_ENGINE_GOARCH}" == "arm64" ] || [ "${DOCKER_ENGINE_GOARCH}" == "arm" ]; then
|
||||
: ${TIMEOUT:=10m}
|
||||
elif [ "${DOCKER_ENGINE_GOARCH}" == "windows" ]; then
|
||||
: ${TIMEOUT:=8m}
|
||||
|
||||
@@ -25,7 +25,10 @@ func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
go io.Copy(b, p)
|
||||
go func() {
|
||||
io.Copy(b, p)
|
||||
p.Close()
|
||||
}()
|
||||
|
||||
ch := make(chan error)
|
||||
go func() { ch <- cmd.Wait() }()
|
||||
@@ -33,9 +36,7 @@ func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) {
|
||||
select {
|
||||
case err := <-ch:
|
||||
c.Assert(err, checker.IsNil)
|
||||
bs := b.Bytes()
|
||||
bs = bytes.Trim(bs, "\x00")
|
||||
output := string(bs[:])
|
||||
output := b.String()
|
||||
c.Assert(strings.TrimSpace(output), checker.Equals, "hello")
|
||||
case <-time.After(5 * time.Second):
|
||||
c.Fatal("timed out running docker exec")
|
||||
|
||||
@@ -173,6 +173,81 @@ func TestBuildMultiStageParentConfig(t *testing.T) {
|
||||
assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
|
||||
}
|
||||
|
||||
// Test cases in #36996
|
||||
func TestBuildLabelWithTargets(t *testing.T) {
|
||||
bldName := "build-a"
|
||||
testLabels := map[string]string{
|
||||
"foo": "bar",
|
||||
"dead": "beef",
|
||||
}
|
||||
|
||||
dockerfile := `
|
||||
FROM busybox AS target-a
|
||||
CMD ["/dev"]
|
||||
LABEL label-a=inline-a
|
||||
FROM busybox AS target-b
|
||||
CMD ["/dist"]
|
||||
LABEL label-b=inline-b
|
||||
`
|
||||
|
||||
ctx := context.Background()
|
||||
source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
|
||||
defer source.Close()
|
||||
|
||||
apiclient := testEnv.APIClient()
|
||||
// For `target-a` build
|
||||
resp, err := apiclient.ImageBuild(ctx,
|
||||
source.AsTarReader(t),
|
||||
types.ImageBuildOptions{
|
||||
Remove: true,
|
||||
ForceRemove: true,
|
||||
Tags: []string{bldName},
|
||||
Labels: testLabels,
|
||||
Target: "target-a",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
_, err = io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
assert.NilError(t, err)
|
||||
|
||||
image, _, err := apiclient.ImageInspectWithRaw(ctx, bldName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
testLabels["label-a"] = "inline-a"
|
||||
for k, v := range testLabels {
|
||||
x, ok := image.Config.Labels[k]
|
||||
assert.Assert(t, ok)
|
||||
assert.Assert(t, x == v)
|
||||
}
|
||||
|
||||
// For `target-b` build
|
||||
bldName = "build-b"
|
||||
delete(testLabels, "label-a")
|
||||
resp, err = apiclient.ImageBuild(ctx,
|
||||
source.AsTarReader(t),
|
||||
types.ImageBuildOptions{
|
||||
Remove: true,
|
||||
ForceRemove: true,
|
||||
Tags: []string{bldName},
|
||||
Labels: testLabels,
|
||||
Target: "target-b",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
_, err = io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
assert.NilError(t, err)
|
||||
|
||||
image, _, err = apiclient.ImageInspectWithRaw(ctx, bldName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
testLabels["label-b"] = "inline-b"
|
||||
for k, v := range testLabels {
|
||||
x, ok := image.Config.Labels[k]
|
||||
assert.Assert(t, ok)
|
||||
assert.Assert(t, x == v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWithEmptyLayers(t *testing.T) {
|
||||
dockerfile := `
|
||||
FROM busybox
|
||||
|
||||
@@ -22,7 +22,7 @@ func ServicePoll(config *poll.Settings) {
|
||||
config.Timeout = 30 * time.Second
|
||||
config.Delay = 100 * time.Millisecond
|
||||
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
|
||||
config.Timeout = 1 * time.Minute
|
||||
config.Timeout = 90 * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ type logT interface {
|
||||
// Clean the environment, preserving protected objects (images, containers, ...)
|
||||
// and removing everything else. It's meant to run after any tests so that they don't
|
||||
// depend on each others.
|
||||
func (e *Execution) Clean(t testingT) {
|
||||
func (e *Execution) Clean(t assert.TestingT) {
|
||||
if ht, ok := t.(test.HelperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func getAllContainers(ctx context.Context, t assert.TestingT, client client.Cont
|
||||
return containers
|
||||
}
|
||||
|
||||
func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
|
||||
func deleteAllImages(t assert.TestingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
|
||||
if ht, ok := t.(test.HelperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
@@ -123,15 +123,12 @@ func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImage
|
||||
for _, image := range images {
|
||||
tags := tagsFromImageSummary(image)
|
||||
if len(tags) == 0 {
|
||||
t.Logf("Removing image %s", image.ID)
|
||||
removeImage(ctx, t, apiclient, image.ID)
|
||||
continue
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if _, ok := protectedImages[tag]; !ok {
|
||||
t.Logf("Removing image %s", tag)
|
||||
removeImage(ctx, t, apiclient, tag)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user