github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/image/build_test.go (about) 1 package image 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "context" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "sort" 14 "testing" 15 16 "github.com/docker/cli/cli/command" 17 "github.com/docker/cli/internal/test" 18 "github.com/docker/docker/api/types" 19 "github.com/docker/docker/pkg/archive" 20 "github.com/google/go-cmp/cmp" 21 "github.com/moby/buildkit/session/secrets/secretsprovider" 22 "gotest.tools/assert" 23 "gotest.tools/fs" 24 "gotest.tools/skip" 25 ) 26 27 func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) { 28 buffer := new(bytes.Buffer) 29 fakeBuild := newFakeBuild() 30 fakeImageBuild := func(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { 31 tee := io.TeeReader(context, buffer) 32 gzipReader, err := gzip.NewReader(tee) 33 assert.NilError(t, err) 34 return fakeBuild.build(ctx, gzipReader, options) 35 } 36 37 cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild}) 38 dockerfile := bytes.NewBufferString(` 39 FROM alpine:3.6 40 COPY foo / 41 `) 42 cli.SetIn(command.NewInStream(ioutil.NopCloser(dockerfile))) 43 44 dir := fs.NewDir(t, t.Name(), 45 fs.WithFile("foo", "some content")) 46 defer dir.Remove() 47 48 options := newBuildOptions() 49 options.compress = true 50 options.dockerfileName = "-" 51 options.context = dir.Path() 52 options.untrusted = true 53 assert.NilError(t, runBuild(cli, options)) 54 55 expected := []string{fakeBuild.options.Dockerfile, ".dockerignore", "foo"} 56 assert.DeepEqual(t, expected, fakeBuild.filenames(t)) 57 58 header := buffer.Bytes()[:10] 59 assert.Equal(t, archive.Gzip, archive.DetectCompression(header)) 60 } 61 62 func TestRunBuildResetsUidAndGidInContext(t *testing.T) { 63 skip.If(t, os.Getuid() != 0, "root is required to chown files") 64 fakeBuild := newFakeBuild() 65 cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeBuild.build}) 66 67 dir := fs.NewDir(t, "test-build-context", 68 fs.WithFile("foo", "some content", fs.AsUser(65534, 65534)), 69 fs.WithFile("Dockerfile", ` 70 FROM alpine:3.6 71 COPY foo bar / 72 `), 73 ) 74 defer dir.Remove() 75 76 options := newBuildOptions() 77 options.context = dir.Path() 78 options.untrusted = true 79 assert.NilError(t, runBuild(cli, options)) 80 81 headers := fakeBuild.headers(t) 82 expected := []*tar.Header{ 83 {Name: "Dockerfile"}, 84 {Name: "foo"}, 85 } 86 var cmpTarHeaderNameAndOwner = cmp.Comparer(func(x, y tar.Header) bool { 87 return x.Name == y.Name && x.Uid == y.Uid && x.Gid == y.Gid 88 }) 89 assert.DeepEqual(t, expected, headers, cmpTarHeaderNameAndOwner) 90 } 91 92 func TestRunBuildDockerfileOutsideContext(t *testing.T) { 93 dir := fs.NewDir(t, t.Name(), 94 fs.WithFile("data", "data file")) 95 defer dir.Remove() 96 97 // Dockerfile outside of build-context 98 df := fs.NewFile(t, t.Name(), 99 fs.WithContent(` 100 FROM FOOBAR 101 COPY data /data 102 `), 103 ) 104 defer df.Remove() 105 106 fakeBuild := newFakeBuild() 107 cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeBuild.build}) 108 109 options := newBuildOptions() 110 options.context = dir.Path() 111 options.dockerfileName = df.Path() 112 options.untrusted = true 113 assert.NilError(t, runBuild(cli, options)) 114 115 expected := []string{fakeBuild.options.Dockerfile, ".dockerignore", "data"} 116 assert.DeepEqual(t, expected, fakeBuild.filenames(t)) 117 } 118 119 // TestRunBuildFromLocalGitHubDirNonExistingRepo tests that build contexts 120 // starting with `github.com/` are special-cased, and the build command attempts 121 // to clone the remote repo. 122 // TODO: test "context selection" logic directly when runBuild is refactored 123 // to support testing (ex: docker/cli#294) 124 func TestRunBuildFromGitHubSpecialCase(t *testing.T) { 125 cmd := NewBuildCommand(test.NewFakeCli(nil)) 126 // Clone a small repo that exists so git doesn't prompt for credentials 127 cmd.SetArgs([]string{"github.com/docker/for-win"}) 128 cmd.SetOutput(ioutil.Discard) 129 err := cmd.Execute() 130 assert.ErrorContains(t, err, "unable to prepare context") 131 assert.ErrorContains(t, err, "docker-build-git") 132 } 133 134 // TestRunBuildFromLocalGitHubDirNonExistingRepo tests that a local directory 135 // starting with `github.com` takes precedence over the `github.com` special 136 // case. 137 func TestRunBuildFromLocalGitHubDir(t *testing.T) { 138 tmpDir, err := ioutil.TempDir("", "docker-build-from-local-dir-") 139 assert.NilError(t, err) 140 defer os.RemoveAll(tmpDir) 141 142 buildDir := filepath.Join(tmpDir, "github.com", "docker", "no-such-repository") 143 err = os.MkdirAll(buildDir, 0777) 144 assert.NilError(t, err) 145 err = ioutil.WriteFile(filepath.Join(buildDir, "Dockerfile"), []byte("FROM busybox\n"), 0644) 146 assert.NilError(t, err) 147 148 client := test.NewFakeCli(&fakeClient{}) 149 cmd := NewBuildCommand(client) 150 cmd.SetArgs([]string{buildDir}) 151 cmd.SetOutput(ioutil.Discard) 152 err = cmd.Execute() 153 assert.NilError(t, err) 154 } 155 156 func TestRunBuildWithSymlinkedContext(t *testing.T) { 157 dockerfile := ` 158 FROM alpine:3.6 159 RUN echo hello world 160 ` 161 162 tmpDir := fs.NewDir(t, t.Name(), 163 fs.WithDir("context", 164 fs.WithFile("Dockerfile", dockerfile)), 165 fs.WithSymlink("context-link", "context")) 166 defer tmpDir.Remove() 167 168 fakeBuild := newFakeBuild() 169 cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeBuild.build}) 170 options := newBuildOptions() 171 options.context = tmpDir.Join("context-link") 172 options.untrusted = true 173 assert.NilError(t, runBuild(cli, options)) 174 175 assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"}) 176 } 177 178 func TestParseSecret(t *testing.T) { 179 type testcase struct { 180 value string 181 errExpected bool 182 errMatch string 183 filesource *secretsprovider.FileSource 184 } 185 var testcases = []testcase{ 186 { 187 value: "", 188 errExpected: true, 189 }, { 190 value: "foobar", 191 errExpected: true, 192 errMatch: "must be a key=value pair", 193 }, { 194 value: "foo,bar", 195 errExpected: true, 196 errMatch: "must be a key=value pair", 197 }, { 198 value: "foo=bar", 199 errExpected: true, 200 errMatch: "unexpected key", 201 }, { 202 value: "src=somefile", 203 filesource: &secretsprovider.FileSource{FilePath: "somefile"}, 204 }, { 205 value: "source=somefile", 206 filesource: &secretsprovider.FileSource{FilePath: "somefile"}, 207 }, { 208 value: "id=mysecret", 209 filesource: &secretsprovider.FileSource{ID: "mysecret"}, 210 }, { 211 value: "id=mysecret,src=somefile", 212 filesource: &secretsprovider.FileSource{ID: "mysecret", FilePath: "somefile"}, 213 }, { 214 value: "id=mysecret,source=somefile,type=file", 215 filesource: &secretsprovider.FileSource{ID: "mysecret", FilePath: "somefile"}, 216 }, { 217 value: "id=mysecret,src=somefile,src=othersecretfile", 218 filesource: &secretsprovider.FileSource{ID: "mysecret", FilePath: "othersecretfile"}, 219 }, { 220 value: "type=invalid", 221 errExpected: true, 222 errMatch: "unsupported secret type", 223 }, 224 } 225 226 for _, tc := range testcases { 227 t.Run(tc.value, func(t *testing.T) { 228 secret, err := parseSecret(tc.value) 229 assert.Equal(t, err != nil, tc.errExpected, fmt.Sprintf("err=%v errExpected=%t", err, tc.errExpected)) 230 if tc.errMatch != "" { 231 assert.ErrorContains(t, err, tc.errMatch) 232 } 233 assert.DeepEqual(t, secret, tc.filesource) 234 }) 235 } 236 } 237 238 type fakeBuild struct { 239 context *tar.Reader 240 options types.ImageBuildOptions 241 } 242 243 func newFakeBuild() *fakeBuild { 244 return &fakeBuild{} 245 } 246 247 func (f *fakeBuild) build(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { 248 f.context = tar.NewReader(context) 249 f.options = options 250 body := new(bytes.Buffer) 251 return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil 252 } 253 254 func (f *fakeBuild) headers(t *testing.T) []*tar.Header { 255 t.Helper() 256 headers := []*tar.Header{} 257 for { 258 hdr, err := f.context.Next() 259 switch err { 260 case io.EOF: 261 return headers 262 case nil: 263 headers = append(headers, hdr) 264 default: 265 assert.NilError(t, err) 266 } 267 } 268 } 269 270 func (f *fakeBuild) filenames(t *testing.T) []string { 271 t.Helper() 272 names := []string{} 273 for _, header := range f.headers(t) { 274 names = append(names, header.Name) 275 } 276 sort.Strings(names) 277 return names 278 }