github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration/build/build_test.go (about)

     1  package build // import "github.com/docker/docker/integration/build"
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"io"
     9  	"io/ioutil"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/filters"
    16  	"github.com/docker/docker/api/types/versions"
    17  	"github.com/docker/docker/internal/test/fakecontext"
    18  	"github.com/docker/docker/pkg/jsonmessage"
    19  	"gotest.tools/assert"
    20  	is "gotest.tools/assert/cmp"
    21  	"gotest.tools/skip"
    22  )
    23  
    24  func TestBuildWithRemoveAndForceRemove(t *testing.T) {
    25  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
    26  	defer setupTest(t)()
    27  
    28  	cases := []struct {
    29  		name                           string
    30  		dockerfile                     string
    31  		numberOfIntermediateContainers int
    32  		rm                             bool
    33  		forceRm                        bool
    34  	}{
    35  		{
    36  			name: "successful build with no removal",
    37  			dockerfile: `FROM busybox
    38  			RUN exit 0
    39  			RUN exit 0`,
    40  			numberOfIntermediateContainers: 2,
    41  			rm:      false,
    42  			forceRm: false,
    43  		},
    44  		{
    45  			name: "successful build with remove",
    46  			dockerfile: `FROM busybox
    47  			RUN exit 0
    48  			RUN exit 0`,
    49  			numberOfIntermediateContainers: 0,
    50  			rm:      true,
    51  			forceRm: false,
    52  		},
    53  		{
    54  			name: "successful build with remove and force remove",
    55  			dockerfile: `FROM busybox
    56  			RUN exit 0
    57  			RUN exit 0`,
    58  			numberOfIntermediateContainers: 0,
    59  			rm:      true,
    60  			forceRm: true,
    61  		},
    62  		{
    63  			name: "failed build with no removal",
    64  			dockerfile: `FROM busybox
    65  			RUN exit 0
    66  			RUN exit 1`,
    67  			numberOfIntermediateContainers: 2,
    68  			rm:      false,
    69  			forceRm: false,
    70  		},
    71  		{
    72  			name: "failed build with remove",
    73  			dockerfile: `FROM busybox
    74  			RUN exit 0
    75  			RUN exit 1`,
    76  			numberOfIntermediateContainers: 1,
    77  			rm:      true,
    78  			forceRm: false,
    79  		},
    80  		{
    81  			name: "failed build with remove and force remove",
    82  			dockerfile: `FROM busybox
    83  			RUN exit 0
    84  			RUN exit 1`,
    85  			numberOfIntermediateContainers: 0,
    86  			rm:      true,
    87  			forceRm: true,
    88  		},
    89  	}
    90  
    91  	client := testEnv.APIClient()
    92  	ctx := context.Background()
    93  	for _, c := range cases {
    94  		t.Run(c.name, func(t *testing.T) {
    95  			t.Parallel()
    96  			dockerfile := []byte(c.dockerfile)
    97  
    98  			buff := bytes.NewBuffer(nil)
    99  			tw := tar.NewWriter(buff)
   100  			assert.NilError(t, tw.WriteHeader(&tar.Header{
   101  				Name: "Dockerfile",
   102  				Size: int64(len(dockerfile)),
   103  			}))
   104  			_, err := tw.Write(dockerfile)
   105  			assert.NilError(t, err)
   106  			assert.NilError(t, tw.Close())
   107  			resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
   108  			assert.NilError(t, err)
   109  			defer resp.Body.Close()
   110  			filter, err := buildContainerIdsFilter(resp.Body)
   111  			assert.NilError(t, err)
   112  			remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
   113  			assert.NilError(t, err)
   114  			assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
   115  		})
   116  	}
   117  }
   118  
   119  func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
   120  	const intermediateContainerPrefix = " ---> Running in "
   121  	filter := filters.NewArgs()
   122  
   123  	dec := json.NewDecoder(buildOutput)
   124  	for {
   125  		m := jsonmessage.JSONMessage{}
   126  		err := dec.Decode(&m)
   127  		if err == io.EOF {
   128  			return filter, nil
   129  		}
   130  		if err != nil {
   131  			return filter, err
   132  		}
   133  		if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
   134  			filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
   135  		}
   136  	}
   137  }
   138  
   139  func TestBuildMultiStageParentConfig(t *testing.T) {
   140  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
   141  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   142  	dockerfile := `
   143  		FROM busybox AS stage0
   144  		ENV WHO=parent
   145  		WORKDIR /foo
   146  
   147  		FROM stage0
   148  		ENV WHO=sibling1
   149  		WORKDIR sub1
   150  
   151  		FROM stage0
   152  		WORKDIR sub2
   153  	`
   154  	ctx := context.Background()
   155  	source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
   156  	defer source.Close()
   157  
   158  	apiclient := testEnv.APIClient()
   159  	resp, err := apiclient.ImageBuild(ctx,
   160  		source.AsTarReader(t),
   161  		types.ImageBuildOptions{
   162  			Remove:      true,
   163  			ForceRemove: true,
   164  			Tags:        []string{"build1"},
   165  		})
   166  	assert.NilError(t, err)
   167  	_, err = io.Copy(ioutil.Discard, resp.Body)
   168  	resp.Body.Close()
   169  	assert.NilError(t, err)
   170  
   171  	time.Sleep(30 * time.Second)
   172  
   173  	imgs, err := apiclient.ImageList(ctx, types.ImageListOptions{})
   174  	assert.NilError(t, err)
   175  	t.Log(imgs)
   176  
   177  	image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
   178  	assert.NilError(t, err)
   179  
   180  	expected := "/foo/sub2"
   181  	if testEnv.DaemonInfo.OSType == "windows" {
   182  		expected = `C:\foo\sub2`
   183  	}
   184  	assert.Check(t, is.Equal(expected, image.Config.WorkingDir))
   185  	assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
   186  }
   187  
   188  // Test cases in #36996
   189  func TestBuildLabelWithTargets(t *testing.T) {
   190  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38")
   191  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   192  	bldName := "build-a"
   193  	testLabels := map[string]string{
   194  		"foo":  "bar",
   195  		"dead": "beef",
   196  	}
   197  
   198  	dockerfile := `
   199  		FROM busybox AS target-a
   200  		CMD ["/dev"]
   201  		LABEL label-a=inline-a
   202  		FROM busybox AS target-b
   203  		CMD ["/dist"]
   204  		LABEL label-b=inline-b
   205  		`
   206  
   207  	ctx := context.Background()
   208  	source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
   209  	defer source.Close()
   210  
   211  	apiclient := testEnv.APIClient()
   212  	// For `target-a` build
   213  	resp, err := apiclient.ImageBuild(ctx,
   214  		source.AsTarReader(t),
   215  		types.ImageBuildOptions{
   216  			Remove:      true,
   217  			ForceRemove: true,
   218  			Tags:        []string{bldName},
   219  			Labels:      testLabels,
   220  			Target:      "target-a",
   221  		})
   222  	assert.NilError(t, err)
   223  	_, err = io.Copy(ioutil.Discard, resp.Body)
   224  	resp.Body.Close()
   225  	assert.NilError(t, err)
   226  
   227  	image, _, err := apiclient.ImageInspectWithRaw(ctx, bldName)
   228  	assert.NilError(t, err)
   229  
   230  	testLabels["label-a"] = "inline-a"
   231  	for k, v := range testLabels {
   232  		x, ok := image.Config.Labels[k]
   233  		assert.Assert(t, ok)
   234  		assert.Assert(t, x == v)
   235  	}
   236  
   237  	// For `target-b` build
   238  	bldName = "build-b"
   239  	delete(testLabels, "label-a")
   240  	resp, err = apiclient.ImageBuild(ctx,
   241  		source.AsTarReader(t),
   242  		types.ImageBuildOptions{
   243  			Remove:      true,
   244  			ForceRemove: true,
   245  			Tags:        []string{bldName},
   246  			Labels:      testLabels,
   247  			Target:      "target-b",
   248  		})
   249  	assert.NilError(t, err)
   250  	_, err = io.Copy(ioutil.Discard, resp.Body)
   251  	resp.Body.Close()
   252  	assert.NilError(t, err)
   253  
   254  	image, _, err = apiclient.ImageInspectWithRaw(ctx, bldName)
   255  	assert.NilError(t, err)
   256  
   257  	testLabels["label-b"] = "inline-b"
   258  	for k, v := range testLabels {
   259  		x, ok := image.Config.Labels[k]
   260  		assert.Assert(t, ok)
   261  		assert.Assert(t, x == v)
   262  	}
   263  }
   264  
   265  func TestBuildWithEmptyLayers(t *testing.T) {
   266  	dockerfile := `
   267  		FROM    busybox
   268  		COPY    1/ /target/
   269  		COPY    2/ /target/
   270  		COPY    3/ /target/
   271  	`
   272  	ctx := context.Background()
   273  	source := fakecontext.New(t, "",
   274  		fakecontext.WithDockerfile(dockerfile),
   275  		fakecontext.WithFile("1/a", "asdf"),
   276  		fakecontext.WithFile("2/a", "asdf"),
   277  		fakecontext.WithFile("3/a", "asdf"))
   278  	defer source.Close()
   279  
   280  	apiclient := testEnv.APIClient()
   281  	resp, err := apiclient.ImageBuild(ctx,
   282  		source.AsTarReader(t),
   283  		types.ImageBuildOptions{
   284  			Remove:      true,
   285  			ForceRemove: true,
   286  		})
   287  	assert.NilError(t, err)
   288  	_, err = io.Copy(ioutil.Discard, resp.Body)
   289  	resp.Body.Close()
   290  	assert.NilError(t, err)
   291  }
   292  
   293  // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
   294  // multiple subsequent stages
   295  // #35652
   296  func TestBuildMultiStageOnBuild(t *testing.T) {
   297  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
   298  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   299  	defer setupTest(t)()
   300  	// test both metadata and layer based commands as they may be implemented differently
   301  	dockerfile := `FROM busybox AS stage1
   302  ONBUILD RUN echo 'foo' >somefile
   303  ONBUILD ENV bar=baz
   304  
   305  FROM stage1
   306  # fails if ONBUILD RUN fails
   307  RUN cat somefile
   308  
   309  FROM stage1
   310  RUN cat somefile`
   311  
   312  	ctx := context.Background()
   313  	source := fakecontext.New(t, "",
   314  		fakecontext.WithDockerfile(dockerfile))
   315  	defer source.Close()
   316  
   317  	apiclient := testEnv.APIClient()
   318  	resp, err := apiclient.ImageBuild(ctx,
   319  		source.AsTarReader(t),
   320  		types.ImageBuildOptions{
   321  			Remove:      true,
   322  			ForceRemove: true,
   323  		})
   324  
   325  	out := bytes.NewBuffer(nil)
   326  	assert.NilError(t, err)
   327  	_, err = io.Copy(out, resp.Body)
   328  	resp.Body.Close()
   329  	assert.NilError(t, err)
   330  
   331  	assert.Check(t, is.Contains(out.String(), "Successfully built"))
   332  
   333  	imageIDs, err := getImageIDsFromBuild(out.Bytes())
   334  	assert.NilError(t, err)
   335  	assert.Check(t, is.Equal(3, len(imageIDs)))
   336  
   337  	image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
   338  	assert.NilError(t, err)
   339  	assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
   340  }
   341  
   342  // #35403 #36122
   343  func TestBuildUncleanTarFilenames(t *testing.T) {
   344  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
   345  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   346  
   347  	ctx := context.TODO()
   348  	defer setupTest(t)()
   349  
   350  	dockerfile := `FROM scratch
   351  COPY foo /
   352  FROM scratch
   353  COPY bar /`
   354  
   355  	buf := bytes.NewBuffer(nil)
   356  	w := tar.NewWriter(buf)
   357  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   358  	writeTarRecord(t, w, "../foo", "foocontents0")
   359  	writeTarRecord(t, w, "/bar", "barcontents0")
   360  	err := w.Close()
   361  	assert.NilError(t, err)
   362  
   363  	apiclient := testEnv.APIClient()
   364  	resp, err := apiclient.ImageBuild(ctx,
   365  		buf,
   366  		types.ImageBuildOptions{
   367  			Remove:      true,
   368  			ForceRemove: true,
   369  		})
   370  
   371  	out := bytes.NewBuffer(nil)
   372  	assert.NilError(t, err)
   373  	_, err = io.Copy(out, resp.Body)
   374  	resp.Body.Close()
   375  	assert.NilError(t, err)
   376  
   377  	// repeat with changed data should not cause cache hits
   378  
   379  	buf = bytes.NewBuffer(nil)
   380  	w = tar.NewWriter(buf)
   381  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   382  	writeTarRecord(t, w, "../foo", "foocontents1")
   383  	writeTarRecord(t, w, "/bar", "barcontents1")
   384  	err = w.Close()
   385  	assert.NilError(t, err)
   386  
   387  	resp, err = apiclient.ImageBuild(ctx,
   388  		buf,
   389  		types.ImageBuildOptions{
   390  			Remove:      true,
   391  			ForceRemove: true,
   392  		})
   393  
   394  	out = bytes.NewBuffer(nil)
   395  	assert.NilError(t, err)
   396  	_, err = io.Copy(out, resp.Body)
   397  	resp.Body.Close()
   398  	assert.NilError(t, err)
   399  	assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
   400  }
   401  
   402  // docker/for-linux#135
   403  // #35641
   404  func TestBuildMultiStageLayerLeak(t *testing.T) {
   405  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
   406  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
   407  	ctx := context.TODO()
   408  	defer setupTest(t)()
   409  
   410  	// all commands need to match until COPY
   411  	dockerfile := `FROM busybox
   412  WORKDIR /foo
   413  COPY foo .
   414  FROM busybox
   415  WORKDIR /foo
   416  COPY bar .
   417  RUN [ -f bar ]
   418  RUN [ ! -f foo ]
   419  `
   420  
   421  	source := fakecontext.New(t, "",
   422  		fakecontext.WithFile("foo", "0"),
   423  		fakecontext.WithFile("bar", "1"),
   424  		fakecontext.WithDockerfile(dockerfile))
   425  	defer source.Close()
   426  
   427  	apiclient := testEnv.APIClient()
   428  	resp, err := apiclient.ImageBuild(ctx,
   429  		source.AsTarReader(t),
   430  		types.ImageBuildOptions{
   431  			Remove:      true,
   432  			ForceRemove: true,
   433  		})
   434  
   435  	out := bytes.NewBuffer(nil)
   436  	assert.NilError(t, err)
   437  	_, err = io.Copy(out, resp.Body)
   438  	resp.Body.Close()
   439  	assert.NilError(t, err)
   440  
   441  	assert.Check(t, is.Contains(out.String(), "Successfully built"))
   442  }
   443  
   444  // #37581
   445  func TestBuildWithHugeFile(t *testing.T) {
   446  	skip.If(t, testEnv.OSType == "windows")
   447  	ctx := context.TODO()
   448  	defer setupTest(t)()
   449  
   450  	dockerfile := `FROM busybox
   451  # create a sparse file with size over 8GB
   452  RUN for g in $(seq 0 8); do dd if=/dev/urandom of=rnd bs=1K count=1 seek=$((1024*1024*g)) status=none; done && \
   453      ls -la rnd && du -sk rnd`
   454  
   455  	buf := bytes.NewBuffer(nil)
   456  	w := tar.NewWriter(buf)
   457  	writeTarRecord(t, w, "Dockerfile", dockerfile)
   458  	err := w.Close()
   459  	assert.NilError(t, err)
   460  
   461  	apiclient := testEnv.APIClient()
   462  	resp, err := apiclient.ImageBuild(ctx,
   463  		buf,
   464  		types.ImageBuildOptions{
   465  			Remove:      true,
   466  			ForceRemove: true,
   467  		})
   468  
   469  	out := bytes.NewBuffer(nil)
   470  	assert.NilError(t, err)
   471  	_, err = io.Copy(out, resp.Body)
   472  	resp.Body.Close()
   473  	assert.NilError(t, err)
   474  	assert.Check(t, is.Contains(out.String(), "Successfully built"))
   475  }
   476  
   477  func TestBuildWithEmptyDockerfile(t *testing.T) {
   478  	ctx := context.TODO()
   479  	defer setupTest(t)()
   480  
   481  	tests := []struct {
   482  		name        string
   483  		dockerfile  string
   484  		expectedErr string
   485  	}{
   486  		{
   487  			name:        "empty-dockerfile",
   488  			dockerfile:  "",
   489  			expectedErr: "cannot be empty",
   490  		},
   491  		{
   492  			name: "empty-lines-dockerfile",
   493  			dockerfile: `
   494  			
   495  			
   496  			
   497  			`,
   498  			expectedErr: "file with no instructions",
   499  		},
   500  		{
   501  			name:        "comment-only-dockerfile",
   502  			dockerfile:  `# this is a comment`,
   503  			expectedErr: "file with no instructions",
   504  		},
   505  	}
   506  
   507  	apiclient := testEnv.APIClient()
   508  
   509  	for _, tc := range tests {
   510  		tc := tc
   511  		t.Run(tc.name, func(t *testing.T) {
   512  			t.Parallel()
   513  
   514  			buf := bytes.NewBuffer(nil)
   515  			w := tar.NewWriter(buf)
   516  			writeTarRecord(t, w, "Dockerfile", tc.dockerfile)
   517  			err := w.Close()
   518  			assert.NilError(t, err)
   519  
   520  			_, err = apiclient.ImageBuild(ctx,
   521  				buf,
   522  				types.ImageBuildOptions{
   523  					Remove:      true,
   524  					ForceRemove: true,
   525  				})
   526  
   527  			assert.Check(t, is.Contains(err.Error(), tc.expectedErr))
   528  		})
   529  	}
   530  }
   531  
   532  func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
   533  	err := w.WriteHeader(&tar.Header{
   534  		Name:     fn,
   535  		Mode:     0600,
   536  		Size:     int64(len(contents)),
   537  		Typeflag: '0',
   538  	})
   539  	assert.NilError(t, err)
   540  	_, err = w.Write([]byte(contents))
   541  	assert.NilError(t, err)
   542  }
   543  
   544  type buildLine struct {
   545  	Stream string
   546  	Aux    struct {
   547  		ID string
   548  	}
   549  }
   550  
   551  func getImageIDsFromBuild(output []byte) ([]string, error) {
   552  	var ids []string
   553  	for _, line := range bytes.Split(output, []byte("\n")) {
   554  		if len(line) == 0 {
   555  			continue
   556  		}
   557  		entry := buildLine{}
   558  		if err := json.Unmarshal(line, &entry); err != nil {
   559  			return nil, err
   560  		}
   561  		if entry.Aux.ID != "" {
   562  			ids = append(ids, entry.Aux.ID)
   563  		}
   564  	}
   565  	return ids, nil
   566  }