mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
a061b1e2d8
This fix was not yet included in Docker 17.05, so API version v1.37 was not the right selector (Docker 18.03, 18.04 and 18.05 all support API v1.37). We should change these checks for engine versions, or use a different method to skip tests when running against older engines. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
460 lines
11 KiB
Go
460 lines
11 KiB
Go
package build // import "github.com/docker/docker/integration/build"
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/versions"
|
|
"github.com/docker/docker/internal/test/fakecontext"
|
|
"github.com/docker/docker/internal/test/request"
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
|
"github.com/gotestyourself/gotestyourself/assert"
|
|
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
|
"github.com/gotestyourself/gotestyourself/skip"
|
|
)
|
|
|
|
func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
|
defer setupTest(t)()
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
dockerfile string
|
|
numberOfIntermediateContainers int
|
|
rm bool
|
|
forceRm bool
|
|
}{
|
|
{
|
|
name: "successful build with no removal",
|
|
dockerfile: `FROM busybox
|
|
RUN exit 0
|
|
RUN exit 0`,
|
|
numberOfIntermediateContainers: 2,
|
|
rm: false,
|
|
forceRm: false,
|
|
},
|
|
{
|
|
name: "successful build with remove",
|
|
dockerfile: `FROM busybox
|
|
RUN exit 0
|
|
RUN exit 0`,
|
|
numberOfIntermediateContainers: 0,
|
|
rm: true,
|
|
forceRm: false,
|
|
},
|
|
{
|
|
name: "successful build with remove and force remove",
|
|
dockerfile: `FROM busybox
|
|
RUN exit 0
|
|
RUN exit 0`,
|
|
numberOfIntermediateContainers: 0,
|
|
rm: true,
|
|
forceRm: true,
|
|
},
|
|
{
|
|
name: "failed build with no removal",
|
|
dockerfile: `FROM busybox
|
|
RUN exit 0
|
|
RUN exit 1`,
|
|
numberOfIntermediateContainers: 2,
|
|
rm: false,
|
|
forceRm: false,
|
|
},
|
|
{
|
|
name: "failed build with remove",
|
|
dockerfile: `FROM busybox
|
|
RUN exit 0
|
|
RUN exit 1`,
|
|
numberOfIntermediateContainers: 1,
|
|
rm: true,
|
|
forceRm: false,
|
|
},
|
|
{
|
|
name: "failed build with remove and force remove",
|
|
dockerfile: `FROM busybox
|
|
RUN exit 0
|
|
RUN exit 1`,
|
|
numberOfIntermediateContainers: 0,
|
|
rm: true,
|
|
forceRm: true,
|
|
},
|
|
}
|
|
|
|
client := request.NewAPIClient(t)
|
|
ctx := context.Background()
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
dockerfile := []byte(c.dockerfile)
|
|
|
|
buff := bytes.NewBuffer(nil)
|
|
tw := tar.NewWriter(buff)
|
|
assert.NilError(t, tw.WriteHeader(&tar.Header{
|
|
Name: "Dockerfile",
|
|
Size: int64(len(dockerfile)),
|
|
}))
|
|
_, err := tw.Write(dockerfile)
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, tw.Close())
|
|
resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
|
|
assert.NilError(t, err)
|
|
defer resp.Body.Close()
|
|
filter, err := buildContainerIdsFilter(resp.Body)
|
|
assert.NilError(t, err)
|
|
remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
|
|
})
|
|
}
|
|
}
|
|
|
|
func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
|
|
const intermediateContainerPrefix = " ---> Running in "
|
|
filter := filters.NewArgs()
|
|
|
|
dec := json.NewDecoder(buildOutput)
|
|
for {
|
|
m := jsonmessage.JSONMessage{}
|
|
err := dec.Decode(&m)
|
|
if err == io.EOF {
|
|
return filter, nil
|
|
}
|
|
if err != nil {
|
|
return filter, err
|
|
}
|
|
if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
|
|
filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildMultiStageParentConfig(t *testing.T) {
|
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
|
|
dockerfile := `
|
|
FROM busybox AS stage0
|
|
ENV WHO=parent
|
|
WORKDIR /foo
|
|
|
|
FROM stage0
|
|
ENV WHO=sibling1
|
|
WORKDIR sub1
|
|
|
|
FROM stage0
|
|
WORKDIR sub2
|
|
`
|
|
ctx := context.Background()
|
|
source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
|
|
defer source.Close()
|
|
|
|
apiclient := testEnv.APIClient()
|
|
resp, err := apiclient.ImageBuild(ctx,
|
|
source.AsTarReader(t),
|
|
types.ImageBuildOptions{
|
|
Remove: true,
|
|
ForceRemove: true,
|
|
Tags: []string{"build1"},
|
|
})
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(ioutil.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
assert.NilError(t, err)
|
|
|
|
image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal("/foo/sub2", image.Config.WorkingDir))
|
|
assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
|
|
}
|
|
|
|
// Test cases in #36996
|
|
func TestBuildLabelWithTargets(t *testing.T) {
|
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38")
|
|
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
|
|
COPY 1/ /target/
|
|
COPY 2/ /target/
|
|
COPY 3/ /target/
|
|
`
|
|
ctx := context.Background()
|
|
source := fakecontext.New(t, "",
|
|
fakecontext.WithDockerfile(dockerfile),
|
|
fakecontext.WithFile("1/a", "asdf"),
|
|
fakecontext.WithFile("2/a", "asdf"),
|
|
fakecontext.WithFile("3/a", "asdf"))
|
|
defer source.Close()
|
|
|
|
apiclient := testEnv.APIClient()
|
|
resp, err := apiclient.ImageBuild(ctx,
|
|
source.AsTarReader(t),
|
|
types.ImageBuildOptions{
|
|
Remove: true,
|
|
ForceRemove: true,
|
|
})
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(ioutil.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
// TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
|
|
// multiple subsequent stages
|
|
// #35652
|
|
func TestBuildMultiStageOnBuild(t *testing.T) {
|
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
|
|
defer setupTest(t)()
|
|
// test both metadata and layer based commands as they may be implemented differently
|
|
dockerfile := `FROM busybox AS stage1
|
|
ONBUILD RUN echo 'foo' >somefile
|
|
ONBUILD ENV bar=baz
|
|
|
|
FROM stage1
|
|
RUN cat somefile # fails if ONBUILD RUN fails
|
|
|
|
FROM stage1
|
|
RUN cat somefile`
|
|
|
|
ctx := context.Background()
|
|
source := fakecontext.New(t, "",
|
|
fakecontext.WithDockerfile(dockerfile))
|
|
defer source.Close()
|
|
|
|
apiclient := testEnv.APIClient()
|
|
resp, err := apiclient.ImageBuild(ctx,
|
|
source.AsTarReader(t),
|
|
types.ImageBuildOptions{
|
|
Remove: true,
|
|
ForceRemove: true,
|
|
})
|
|
|
|
out := bytes.NewBuffer(nil)
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(out, resp.Body)
|
|
resp.Body.Close()
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Contains(out.String(), "Successfully built"))
|
|
|
|
imageIDs, err := getImageIDsFromBuild(out.Bytes())
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(3, len(imageIDs)))
|
|
|
|
image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
|
|
}
|
|
|
|
// #35403 #36122
|
|
func TestBuildUncleanTarFilenames(t *testing.T) {
|
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
|
|
ctx := context.TODO()
|
|
defer setupTest(t)()
|
|
|
|
dockerfile := `FROM scratch
|
|
COPY foo /
|
|
FROM scratch
|
|
COPY bar /`
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
w := tar.NewWriter(buf)
|
|
writeTarRecord(t, w, "Dockerfile", dockerfile)
|
|
writeTarRecord(t, w, "../foo", "foocontents0")
|
|
writeTarRecord(t, w, "/bar", "barcontents0")
|
|
err := w.Close()
|
|
assert.NilError(t, err)
|
|
|
|
apiclient := testEnv.APIClient()
|
|
resp, err := apiclient.ImageBuild(ctx,
|
|
buf,
|
|
types.ImageBuildOptions{
|
|
Remove: true,
|
|
ForceRemove: true,
|
|
})
|
|
|
|
out := bytes.NewBuffer(nil)
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(out, resp.Body)
|
|
resp.Body.Close()
|
|
assert.NilError(t, err)
|
|
|
|
// repeat with changed data should not cause cache hits
|
|
|
|
buf = bytes.NewBuffer(nil)
|
|
w = tar.NewWriter(buf)
|
|
writeTarRecord(t, w, "Dockerfile", dockerfile)
|
|
writeTarRecord(t, w, "../foo", "foocontents1")
|
|
writeTarRecord(t, w, "/bar", "barcontents1")
|
|
err = w.Close()
|
|
assert.NilError(t, err)
|
|
|
|
resp, err = apiclient.ImageBuild(ctx,
|
|
buf,
|
|
types.ImageBuildOptions{
|
|
Remove: true,
|
|
ForceRemove: true,
|
|
})
|
|
|
|
out = bytes.NewBuffer(nil)
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(out, resp.Body)
|
|
resp.Body.Close()
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
|
|
}
|
|
|
|
// docker/for-linux#135
|
|
// #35641
|
|
func TestBuildMultiStageLayerLeak(t *testing.T) {
|
|
skip.IfCondition(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
|
|
ctx := context.TODO()
|
|
defer setupTest(t)()
|
|
|
|
// all commands need to match until COPY
|
|
dockerfile := `FROM busybox
|
|
WORKDIR /foo
|
|
COPY foo .
|
|
FROM busybox
|
|
WORKDIR /foo
|
|
COPY bar .
|
|
RUN [ -f bar ]
|
|
RUN [ ! -f foo ]
|
|
`
|
|
|
|
source := fakecontext.New(t, "",
|
|
fakecontext.WithFile("foo", "0"),
|
|
fakecontext.WithFile("bar", "1"),
|
|
fakecontext.WithDockerfile(dockerfile))
|
|
defer source.Close()
|
|
|
|
apiclient := testEnv.APIClient()
|
|
resp, err := apiclient.ImageBuild(ctx,
|
|
source.AsTarReader(t),
|
|
types.ImageBuildOptions{
|
|
Remove: true,
|
|
ForceRemove: true,
|
|
})
|
|
|
|
out := bytes.NewBuffer(nil)
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(out, resp.Body)
|
|
resp.Body.Close()
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Contains(out.String(), "Successfully built"))
|
|
}
|
|
|
|
func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
|
|
err := w.WriteHeader(&tar.Header{
|
|
Name: fn,
|
|
Mode: 0600,
|
|
Size: int64(len(contents)),
|
|
Typeflag: '0',
|
|
})
|
|
assert.NilError(t, err)
|
|
_, err = w.Write([]byte(contents))
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
type buildLine struct {
|
|
Stream string
|
|
Aux struct {
|
|
ID string
|
|
}
|
|
}
|
|
|
|
func getImageIDsFromBuild(output []byte) ([]string, error) {
|
|
var ids []string
|
|
for _, line := range bytes.Split(output, []byte("\n")) {
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
entry := buildLine{}
|
|
if err := json.Unmarshal(line, &entry); err != nil {
|
|
return nil, err
|
|
}
|
|
if entry.Aux.ID != "" {
|
|
ids = append(ids, entry.Aux.ID)
|
|
}
|
|
}
|
|
return ids, nil
|
|
}
|