123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- package 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/integration-cli/cli/build/fakecontext"
- "github.com/docker/docker/integration/util/request"
- "github.com/docker/docker/pkg/jsonmessage"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- 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)
- require.NoError(t, tw.WriteHeader(&tar.Header{
- Name: "Dockerfile",
- Size: int64(len(dockerfile)),
- }))
- _, err := tw.Write(dockerfile)
- require.NoError(t, err)
- require.NoError(t, tw.Close())
- resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
- require.NoError(t, err)
- defer resp.Body.Close()
- filter, err := buildContainerIdsFilter(resp.Body)
- require.NoError(t, err)
- remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
- require.NoError(t, err)
- require.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) {
- 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"},
- })
- require.NoError(t, err)
- _, err = io.Copy(ioutil.Discard, resp.Body)
- resp.Body.Close()
- require.NoError(t, err)
- image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
- require.NoError(t, err)
- assert.Equal(t, "/foo/sub2", image.Config.WorkingDir)
- assert.Contains(t, image.Config.Env, "WHO=parent")
- }
- 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,
- })
- require.NoError(t, err)
- _, err = io.Copy(ioutil.Discard, resp.Body)
- resp.Body.Close()
- require.NoError(t, err)
- }
- // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
- // multiple subsequent stages
- // #35652
- func TestBuildMultiStageOnBuild(t *testing.T) {
- 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)
- require.NoError(t, err)
- _, err = io.Copy(out, resp.Body)
- resp.Body.Close()
- require.NoError(t, err)
- assert.Contains(t, out.String(), "Successfully built")
- imageIDs, err := getImageIDsFromBuild(out.Bytes())
- require.NoError(t, err)
- assert.Equal(t, 3, len(imageIDs))
- image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
- require.NoError(t, err)
- assert.Contains(t, image.Config.Env, "bar=baz")
- }
- type buildLine struct {
- Stream string
- Aux struct {
- ID string
- }
- }
- func getImageIDsFromBuild(output []byte) ([]string, error) {
- 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
- }
|