github.com/SAP/jenkins-library@v1.362.0/cmd/kanikoExecute_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package cmd 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "net/http" 11 "path/filepath" 12 "strings" 13 "testing" 14 15 piperhttp "github.com/SAP/jenkins-library/pkg/http" 16 "github.com/SAP/jenkins-library/pkg/mock" 17 "github.com/SAP/jenkins-library/pkg/telemetry" 18 "github.com/jarcoal/httpmock" 19 "github.com/stretchr/testify/assert" 20 ) 21 22 type kanikoMockClient struct { 23 httpMethod string 24 httpStatusCode int 25 urlsCalled []string 26 requestBody io.Reader 27 responseBody string 28 errorMessage string 29 } 30 31 func (c *kanikoMockClient) SetOptions(opts piperhttp.ClientOptions) {} 32 33 func (c *kanikoMockClient) SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) { 34 c.httpMethod = method 35 c.urlsCalled = append(c.urlsCalled, url) 36 c.requestBody = body 37 if len(c.errorMessage) > 0 { 38 return nil, fmt.Errorf(c.errorMessage) 39 } 40 return &http.Response{StatusCode: c.httpStatusCode, Body: io.NopCloser(bytes.NewReader([]byte(c.responseBody)))}, nil 41 } 42 func (c *kanikoMockClient) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error { 43 if len(c.errorMessage) > 0 { 44 return fmt.Errorf(c.errorMessage) 45 } 46 return nil 47 } 48 49 func TestRunKanikoExecute(t *testing.T) { 50 51 // required due to config resolution during build settings retrieval 52 // ToDo: proper mocking 53 openFileBak := configOptions.OpenFile 54 defer func() { 55 configOptions.OpenFile = openFileBak 56 }() 57 58 configOptions.OpenFile = configOpenFileMock 59 60 t.Run("success case", func(t *testing.T) { 61 config := &kanikoExecuteOptions{ 62 BuildOptions: []string{"--skip-tls-verify-pull"}, 63 ContainerImage: "myImage:tag", 64 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 65 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 66 DockerfilePath: "Dockerfile", 67 DockerConfigJSON: "path/to/docker/config.json", 68 BuildSettingsInfo: `{"mavenExecuteBuild":[{"dockerImage":"maven"}]}`, 69 } 70 71 execRunner := &mock.ExecMockRunner{} 72 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 73 74 certClient := &kanikoMockClient{ 75 responseBody: "testCert", 76 } 77 fileUtils := &mock.FilesMock{} 78 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 79 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 80 81 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 82 83 assert.NoError(t, err) 84 85 assert.Equal(t, "rm", execRunner.Calls[0].Exec) 86 assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params) 87 88 assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled) 89 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 90 assert.NoError(t, err) 91 assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c)) 92 93 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 94 cwd, _ := fileUtils.Getwd() 95 assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params) 96 97 assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`) 98 assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`) 99 100 assert.Equal(t, "myImage:tag", commonPipelineEnvironment.container.imageNameTag) 101 assert.Equal(t, "https://index.docker.io", commonPipelineEnvironment.container.registryURL) 102 assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) 103 assert.Equal(t, []string{"myImage:tag"}, commonPipelineEnvironment.container.imageNameTags) 104 105 assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest) 106 assert.Empty(t, commonPipelineEnvironment.container.imageDigests) 107 }) 108 109 t.Run("success case - pass image digest to cpe if activated", func(t *testing.T) { 110 config := &kanikoExecuteOptions{ 111 BuildOptions: []string{"--skip-tls-verify-pull"}, 112 ContainerImage: "myImage:tag", 113 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 114 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 115 DockerfilePath: "Dockerfile", 116 DockerConfigJSON: "path/to/docker/config.json", 117 BuildSettingsInfo: `{"mavenExecuteBuild":[{"dockerImage":"maven"}]}`, 118 ReadImageDigest: true, 119 } 120 121 execRunner := &mock.ExecMockRunner{} 122 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 123 124 certClient := &kanikoMockClient{ 125 responseBody: "testCert", 126 } 127 fileUtils := &mock.FilesMock{} 128 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 129 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 130 fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) 131 132 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 133 134 assert.NoError(t, err) 135 136 assert.Equal(t, "rm", execRunner.Calls[0].Exec) 137 assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params) 138 139 assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled) 140 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 141 assert.NoError(t, err) 142 assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c)) 143 144 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 145 cwd, _ := fileUtils.Getwd() 146 assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt"}, execRunner.Calls[1].Params) 147 148 assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`) 149 assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`) 150 151 assert.Equal(t, "myImage:tag", commonPipelineEnvironment.container.imageNameTag) 152 assert.Equal(t, "https://index.docker.io", commonPipelineEnvironment.container.registryURL) 153 assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) 154 assert.Equal(t, []string{"myImage:tag"}, commonPipelineEnvironment.container.imageNameTags) 155 156 assert.Equal(t, "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0", commonPipelineEnvironment.container.imageDigest) 157 assert.Equal(t, []string{"sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0"}, commonPipelineEnvironment.container.imageDigests) 158 }) 159 160 t.Run("success case - image params", func(t *testing.T) { 161 config := &kanikoExecuteOptions{ 162 BuildOptions: []string{"--skip-tls-verify-pull"}, 163 ContainerImageName: "myImage", 164 ContainerImageTag: "1.2.3-a+x", 165 ContainerRegistryURL: "https://my.registry.com:50000", 166 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 167 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 168 DockerfilePath: "Dockerfile", 169 DockerConfigJSON: "path/to/docker/config.json", 170 } 171 172 execRunner := &mock.ExecMockRunner{} 173 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 174 175 certClient := &kanikoMockClient{ 176 responseBody: "testCert", 177 } 178 fileUtils := &mock.FilesMock{} 179 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 180 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 181 182 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 183 184 assert.NoError(t, err) 185 186 assert.Equal(t, "rm", execRunner.Calls[0].Exec) 187 assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params) 188 189 assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled) 190 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 191 assert.NoError(t, err) 192 assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c)) 193 194 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 195 cwd, _ := fileUtils.Getwd() 196 assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, execRunner.Calls[1].Params) 197 198 assert.Equal(t, "myImage:1.2.3-a-x", commonPipelineEnvironment.container.imageNameTag) 199 assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL) 200 assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) 201 assert.Equal(t, []string{"myImage:1.2.3-a-x"}, commonPipelineEnvironment.container.imageNameTags) 202 203 assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest) 204 assert.Empty(t, commonPipelineEnvironment.container.imageDigests) 205 }) 206 207 t.Run("success case - image params with custom destination", func(t *testing.T) { 208 config := &kanikoExecuteOptions{ 209 BuildOptions: []string{"--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, 210 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 211 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 212 DockerfilePath: "Dockerfile", 213 DockerConfigJSON: "path/to/docker/config.json", 214 } 215 216 execRunner := &mock.ExecMockRunner{} 217 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 218 219 certClient := &kanikoMockClient{ 220 responseBody: "testCert", 221 } 222 fileUtils := &mock.FilesMock{} 223 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 224 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 225 226 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 227 228 assert.NoError(t, err) 229 230 assert.Equal(t, "rm", execRunner.Calls[0].Exec) 231 assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params) 232 233 assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled) 234 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 235 assert.NoError(t, err) 236 assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c)) 237 238 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 239 cwd, _ := fileUtils.Getwd() 240 assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, execRunner.Calls[1].Params) 241 242 assert.Equal(t, "myImage:3.2.1-a-x", commonPipelineEnvironment.container.imageNameTag) 243 assert.Equal(t, "https://my.other.registry.com:50000", commonPipelineEnvironment.container.registryURL) 244 assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) 245 assert.Equal(t, []string{"myImage:3.2.1-a-x"}, commonPipelineEnvironment.container.imageNameTags) 246 247 assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest) 248 assert.Empty(t, []string{}, commonPipelineEnvironment.container.imageDigests) 249 }) 250 251 t.Run("no error case - when cert update skipped", func(t *testing.T) { 252 config := &kanikoExecuteOptions{ 253 BuildOptions: []string{"--skip-tls-verify-pull"}, 254 ContainerImageName: "myImage", 255 ContainerImageTag: "1.2.3-a+x", 256 ContainerRegistryURL: "https://my.registry.com:50000", 257 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 258 CustomTLSCertificateLinks: []string{}, 259 DockerfilePath: "Dockerfile", 260 DockerConfigJSON: "path/to/docker/config.json", 261 } 262 263 execRunner := &mock.ExecMockRunner{} 264 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 265 266 certClient := &kanikoMockClient{} 267 fileUtils := &mock.FilesMock{} 268 fileUtils.AddFile("path/to/docker/config.json", []byte(``)) 269 fileUtils.FileReadErrors = map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")} 270 271 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 272 273 assert.NoErrorf(t, err, "failed to update certificates: failed to load file '/kaniko/ssl/certs/ca-certificates.crt': read error") 274 }) 275 276 t.Run("success case - no push, no docker config.json", func(t *testing.T) { 277 config := &kanikoExecuteOptions{ 278 ContainerBuildOptions: "--skip-tls-verify-pull", 279 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 280 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 281 DockerfilePath: "Dockerfile", 282 } 283 284 execRunner := &mock.ExecMockRunner{} 285 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 286 287 certClient := &kanikoMockClient{ 288 responseBody: "testCert", 289 } 290 fileUtils := &mock.FilesMock{} 291 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 292 293 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 294 295 assert.NoError(t, err) 296 297 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 298 assert.NoError(t, err) 299 assert.Equal(t, `{"auths":{}}`, string(c)) 300 301 cwd, _ := fileUtils.Getwd() 302 assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--no-push"}, execRunner.Calls[1].Params) 303 }) 304 305 t.Run("success case - backward compatibility", func(t *testing.T) { 306 config := &kanikoExecuteOptions{ 307 ContainerBuildOptions: "--skip-tls-verify-pull", 308 ContainerImage: "myImage:tag", 309 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 310 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 311 DockerfilePath: "Dockerfile", 312 DockerConfigJSON: "path/to/docker/config.json", 313 } 314 315 execRunner := &mock.ExecMockRunner{} 316 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 317 318 certClient := &kanikoMockClient{ 319 responseBody: "testCert", 320 } 321 fileUtils := &mock.FilesMock{} 322 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 323 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 324 325 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 326 327 assert.NoError(t, err) 328 cwd, _ := fileUtils.Getwd() 329 assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params) 330 }) 331 332 t.Run("success case - createBOM", func(t *testing.T) { 333 config := &kanikoExecuteOptions{ 334 ContainerImage: "myImage:tag", 335 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 336 DockerfilePath: "Dockerfile", 337 DockerConfigJSON: "path/to/docker/config.json", 338 CreateBOM: true, 339 SyftDownloadURL: "http://test-syft-url.io", 340 } 341 342 execRunner := &mock.ExecMockRunner{} 343 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 344 fileUtils := &mock.FilesMock{} 345 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 346 347 httpmock.Activate() 348 defer httpmock.DeactivateAndReset() 349 fakeArchive, err := fileUtils.CreateArchive(map[string][]byte{"syft": []byte("test")}) 350 assert.NoError(t, err) 351 352 httpmock.RegisterResponder(http.MethodGet, "http://test-syft-url.io", httpmock.NewBytesResponder(http.StatusOK, fakeArchive)) 353 client := &piperhttp.Client{} 354 client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true}) 355 356 err = runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, client, fileUtils) 357 assert.NoError(t, err) 358 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 359 assert.Equal(t, "myImage:tag", commonPipelineEnvironment.container.imageNameTag) 360 assert.Equal(t, "https://index.docker.io", commonPipelineEnvironment.container.registryURL) 361 362 assert.Equal(t, "/tmp/syfttest/syft", execRunner.Calls[2].Exec) 363 assert.Equal(t, []string{"packages", "registry:index.docker.io/myImage:tag", "-o", "cyclonedx-xml", "--file", "bom-docker-0.xml", "-q"}, execRunner.Calls[2].Params) 364 }) 365 366 t.Run("success case - multi image build with root image", func(t *testing.T) { 367 config := &kanikoExecuteOptions{ 368 ContainerImageName: "myImage", 369 ContainerImageTag: "myTag", 370 ContainerRegistryURL: "https://my.registry.com:50000", 371 ContainerMultiImageBuild: true, 372 } 373 374 execRunner := &mock.ExecMockRunner{} 375 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 376 377 fileUtils := &mock.FilesMock{} 378 fileUtils.AddFile("Dockerfile", []byte("some content")) 379 fileUtils.AddFile("sub1/Dockerfile", []byte("some content")) 380 fileUtils.AddFile("sub2/Dockerfile", []byte("some content")) 381 382 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, nil, fileUtils) 383 384 assert.NoError(t, err) 385 386 assert.Equal(t, 3, len(execRunner.Calls)) 387 assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec) 388 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 389 assert.Equal(t, "/kaniko/executor", execRunner.Calls[2].Exec) 390 391 cwd, _ := fileUtils.Getwd() 392 expectedParams := [][]string{ 393 {"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage:myTag"}, 394 {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, 395 {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, 396 } 397 // need to go this way since we cannot count on the correct order 398 for _, call := range execRunner.Calls { 399 found := false 400 for _, expected := range expectedParams { 401 if strings.Join(call.Params, " ") == strings.Join(expected, " ") { 402 found = true 403 break 404 } 405 } 406 assert.True(t, found, fmt.Sprintf("%v not found", call.Params)) 407 } 408 409 assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL) 410 assert.Equal(t, "myImage:myTag", commonPipelineEnvironment.container.imageNameTag) 411 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage") 412 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub1") 413 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub2") 414 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage:myTag") 415 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub1:myTag") 416 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub2:myTag") 417 418 assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest) 419 assert.Empty(t, commonPipelineEnvironment.container.imageDigests) 420 }) 421 422 t.Run("success case - multi image build excluding root image", func(t *testing.T) { 423 config := &kanikoExecuteOptions{ 424 ContainerImageName: "myImage", 425 ContainerImageTag: "myTag", 426 ContainerRegistryURL: "https://my.registry.com:50000", 427 ContainerMultiImageBuild: true, 428 ContainerMultiImageBuildExcludes: []string{"Dockerfile"}, 429 } 430 431 execRunner := &mock.ExecMockRunner{} 432 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 433 434 fileUtils := &mock.FilesMock{} 435 fileUtils.AddFile("Dockerfile", []byte("some content")) 436 fileUtils.AddFile("sub1/Dockerfile", []byte("some content")) 437 fileUtils.AddFile("sub2/Dockerfile", []byte("some content")) 438 439 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, nil, fileUtils) 440 441 assert.NoError(t, err) 442 443 assert.Equal(t, 2, len(execRunner.Calls)) 444 assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec) 445 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 446 447 cwd, _ := fileUtils.Getwd() 448 expectedParams := [][]string{ 449 {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, 450 {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, 451 } 452 // need to go this way since we cannot count on the correct order 453 for _, call := range execRunner.Calls { 454 found := false 455 for _, expected := range expectedParams { 456 if strings.Join(call.Params, " ") == strings.Join(expected, " ") { 457 found = true 458 break 459 } 460 } 461 assert.True(t, found, fmt.Sprintf("%v not found", call.Params)) 462 } 463 464 assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL) 465 assert.Equal(t, "", commonPipelineEnvironment.container.imageNameTag) 466 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub1") 467 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub2") 468 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub1:myTag") 469 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub2:myTag") 470 }) 471 472 t.Run("success case - multi image build with CreateBOM", func(t *testing.T) { 473 config := &kanikoExecuteOptions{ 474 ContainerImageName: "myImage", 475 ContainerImageTag: "myTag", 476 ContainerRegistryURL: "https://my.registry.com:50000", 477 ContainerMultiImageBuild: true, 478 DockerConfigJSON: "path/to/docker/config.json", 479 CreateBOM: true, 480 SyftDownloadURL: "http://test-syft-url.io", 481 } 482 execRunner := &mock.ExecMockRunner{} 483 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 484 fileUtils := &mock.FilesMock{} 485 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 486 fileUtils.AddFile("Dockerfile", []byte("some content")) 487 fileUtils.AddFile("sub1/Dockerfile", []byte("some content")) 488 fileUtils.AddFile("sub2/Dockerfile", []byte("some content")) 489 490 httpmock.Activate() 491 defer httpmock.DeactivateAndReset() 492 fakeArchive, err := fileUtils.CreateArchive(map[string][]byte{"syft": []byte("test")}) 493 assert.NoError(t, err) 494 495 httpmock.RegisterResponder(http.MethodGet, "http://test-syft-url.io", httpmock.NewBytesResponder(http.StatusOK, fakeArchive)) 496 client := &piperhttp.Client{} 497 client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true}) 498 499 err = runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, client, fileUtils) 500 assert.NoError(t, err) 501 502 assert.Equal(t, 6, len(execRunner.Calls)) 503 assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec) 504 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 505 assert.Equal(t, "/kaniko/executor", execRunner.Calls[2].Exec) 506 507 cwd, _ := fileUtils.Getwd() 508 expectedParams := [][]string{ 509 {"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage:myTag"}, 510 {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, 511 {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, 512 {"packages", "registry:my.registry.com:50000/myImage:myTag", "-o", "cyclonedx-xml", "--file"}, 513 {"packages", "registry:my.registry.com:50000/myImage-sub1:myTag", "-o", "cyclonedx-xml", "--file"}, 514 {"packages", "registry:my.registry.com:50000/myImage-sub2:myTag", "-o", "cyclonedx-xml", "--file"}, 515 } 516 // need to go this way since we cannot count on the correct order 517 for index, call := range execRunner.Calls { 518 found := false 519 for _, expected := range expectedParams { 520 if expected[0] == "packages" { 521 expected = append(expected, fmt.Sprintf("bom-docker-%d.xml", index-3), "-q") 522 } 523 if strings.Join(call.Params, " ") == strings.Join(expected, " ") { 524 found = true 525 break 526 } 527 } 528 assert.True(t, found, fmt.Sprintf("%v not found", call.Params)) 529 } 530 531 assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL) 532 assert.Equal(t, "myImage:myTag", commonPipelineEnvironment.container.imageNameTag) 533 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage") 534 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub1") 535 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub2") 536 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage:myTag") 537 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub1:myTag") 538 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub2:myTag") 539 540 assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest) 541 assert.Empty(t, commonPipelineEnvironment.container.imageDigests) 542 }) 543 544 t.Run("success case - updating an existing docker config json with addtional credentials", func(t *testing.T) { 545 config := &kanikoExecuteOptions{ 546 BuildOptions: []string{"--skip-tls-verify-pull"}, 547 ContainerImageName: "myImage", 548 ContainerImageTag: "1.2.3-a+x", 549 ContainerRegistryURL: "https://my.registry.com:50000", 550 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 551 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 552 DockerfilePath: "Dockerfile", 553 DockerConfigJSON: "path/to/docker/config.json", 554 ContainerRegistryUser: "dummyUser", 555 ContainerRegistryPassword: "dummyPassword", 556 } 557 558 execRunner := &mock.ExecMockRunner{} 559 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 560 561 certClient := &kanikoMockClient{ 562 responseBody: "testCert", 563 } 564 fileUtils := &mock.FilesMock{} 565 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths": {"dummyUrl": {"auth": "XXXXXXX"}}}`)) 566 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 567 568 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 569 570 assert.NoError(t, err) 571 572 assert.Equal(t, "rm", execRunner.Calls[0].Exec) 573 assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params) 574 575 assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled) 576 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 577 assert.NoError(t, err) 578 assert.Equal(t, `{"auths":{"dummyUrl":{"auth":"XXXXXXX"},"https://my.registry.com:50000":{"auth":"ZHVtbXlVc2VyOmR1bW15UGFzc3dvcmQ="}}}`, string(c)) 579 }) 580 581 t.Run("success case - creating new docker config json with provided container credentials", func(t *testing.T) { 582 config := &kanikoExecuteOptions{ 583 BuildOptions: []string{"--skip-tls-verify-pull"}, 584 ContainerImageName: "myImage", 585 ContainerImageTag: "1.2.3-a+x", 586 ContainerRegistryURL: "https://my.registry.com:50000", 587 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 588 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 589 DockerfilePath: "Dockerfile", 590 ContainerRegistryUser: "dummyUser", 591 ContainerRegistryPassword: "dummyPassword", 592 } 593 594 execRunner := &mock.ExecMockRunner{} 595 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 596 597 certClient := &kanikoMockClient{ 598 responseBody: "testCert", 599 } 600 fileUtils := &mock.FilesMock{} 601 fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) 602 603 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 604 605 assert.NoError(t, err) 606 607 assert.Equal(t, "rm", execRunner.Calls[0].Exec) 608 assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params) 609 610 assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled) 611 c, err := fileUtils.FileRead("/kaniko/.docker/config.json") 612 assert.NoError(t, err) 613 assert.Equal(t, `{"auths":{"https://my.registry.com:50000":{"auth":"ZHVtbXlVc2VyOmR1bW15UGFzc3dvcmQ="}}}`, string(c)) 614 }) 615 616 t.Run("success case - multi context build with CreateBOM", func(t *testing.T) { 617 config := &kanikoExecuteOptions{ 618 ContainerImageName: "myImage", 619 ContainerImageTag: "myTag", 620 ContainerRegistryURL: "https://my.registry.com:50000", 621 DockerConfigJSON: "path/to/docker/config.json", 622 DockerfilePath: "Dockerfile", 623 CreateBOM: true, 624 SyftDownloadURL: "http://test-syft-url.io", 625 MultipleImages: []map[string]interface{}{ 626 { 627 "contextSubPath": "/test1", 628 "containerImageName": "myImageOne", 629 }, 630 { 631 "contextSubPath": "/test2", 632 "containerImageName": "myImageTwo", 633 "containerImageTag": "myTagTwo", 634 }, 635 }, 636 } 637 execRunner := &mock.ExecMockRunner{} 638 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 639 fileUtils := &mock.FilesMock{} 640 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 641 fileUtils.AddFile("Dockerfile", []byte("some content")) 642 fileUtils.AddFile("test1/test", []byte("some content test1")) 643 fileUtils.AddFile("test2/test", []byte("some content test2")) 644 645 httpmock.Activate() 646 defer httpmock.DeactivateAndReset() 647 fakeArchive, err := fileUtils.CreateArchive(map[string][]byte{"syft": []byte("test")}) 648 assert.NoError(t, err) 649 650 httpmock.RegisterResponder(http.MethodGet, "http://test-syft-url.io", httpmock.NewBytesResponder(http.StatusOK, fakeArchive)) 651 client := &piperhttp.Client{} 652 client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true}) 653 654 err = runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, client, fileUtils) 655 assert.NoError(t, err) 656 657 assert.Equal(t, 4, len(execRunner.Calls)) 658 assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec) 659 assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec) 660 661 cwd, _ := fileUtils.Getwd() 662 expectedParams := [][]string{ 663 {"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--context-sub-path", "/test1", "--destination", "my.registry.com:50000/myImageOne:myTag"}, 664 {"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--context-sub-path", "/test2", "--destination", "my.registry.com:50000/myImageTwo:myTagTwo"}, 665 {"packages", "registry:my.registry.com:50000/myImageOne:myTag", "-o", "cyclonedx-xml", "--file"}, 666 {"packages", "registry:my.registry.com:50000/myImageTwo:myTagTwo", "-o", "cyclonedx-xml", "--file"}, 667 } 668 // need to go this way since we cannot count on the correct order 669 for index, call := range execRunner.Calls { 670 found := false 671 for _, expected := range expectedParams { 672 if expected[0] == "packages" { 673 expected = append(expected, fmt.Sprintf("bom-docker-%d.xml", index-2), "-q") 674 } 675 if strings.Join(call.Params, " ") == strings.Join(expected, " ") { 676 found = true 677 break 678 } 679 } 680 assert.True(t, found, fmt.Sprintf("%v not found", call.Params)) 681 } 682 683 assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL) 684 assert.Equal(t, "myImage:myTag", commonPipelineEnvironment.container.imageNameTag) 685 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImageOne") 686 assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImageTwo") 687 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImageOne:myTag") 688 assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImageTwo:myTagTwo") 689 690 assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest) 691 assert.Empty(t, commonPipelineEnvironment.container.imageDigests) 692 }) 693 694 t.Run("error case - multi image build: no docker files", func(t *testing.T) { 695 config := &kanikoExecuteOptions{ 696 ContainerImageName: "myImage", 697 ContainerImageTag: "myTag", 698 ContainerRegistryURL: "https://my.registry.com:50000", 699 ContainerMultiImageBuild: true, 700 } 701 702 cpe := kanikoExecuteCommonPipelineEnvironment{} 703 execRunner := &mock.ExecMockRunner{} 704 fileUtils := &mock.FilesMock{} 705 706 err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, nil, fileUtils) 707 708 assert.Error(t, err) 709 assert.Contains(t, fmt.Sprint(err), "failed to identify image list for multi image build") 710 }) 711 712 t.Run("error case - multi image build: no docker files to process", func(t *testing.T) { 713 config := &kanikoExecuteOptions{ 714 ContainerImageName: "myImage", 715 ContainerImageTag: "myTag", 716 ContainerRegistryURL: "https://my.registry.com:50000", 717 ContainerMultiImageBuild: true, 718 ContainerMultiImageBuildExcludes: []string{"Dockerfile"}, 719 } 720 721 cpe := kanikoExecuteCommonPipelineEnvironment{} 722 execRunner := &mock.ExecMockRunner{} 723 724 fileUtils := &mock.FilesMock{} 725 fileUtils.AddFile("Dockerfile", []byte("some content")) 726 727 err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, nil, fileUtils) 728 729 assert.Error(t, err) 730 assert.Contains(t, fmt.Sprint(err), "no docker files to process, please check exclude list") 731 }) 732 733 t.Run("error case - multi image build: build failed", func(t *testing.T) { 734 config := &kanikoExecuteOptions{ 735 ContainerImageName: "myImage", 736 ContainerImageTag: "myTag", 737 ContainerRegistryURL: "https://my.registry.com:50000", 738 ContainerMultiImageBuild: true, 739 } 740 741 cpe := kanikoExecuteCommonPipelineEnvironment{} 742 execRunner := &mock.ExecMockRunner{} 743 744 execRunner.ShouldFailOnCommand = map[string]error{"/kaniko/executor": fmt.Errorf("execution failed")} 745 746 fileUtils := &mock.FilesMock{} 747 fileUtils.AddFile("Dockerfile", []byte("some content")) 748 749 err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, nil, fileUtils) 750 751 assert.Error(t, err) 752 assert.Contains(t, fmt.Sprint(err), "failed to build image") 753 }) 754 755 t.Run("error case - Kaniko init failed", func(t *testing.T) { 756 config := &kanikoExecuteOptions{ 757 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 758 } 759 execRunner := &mock.ExecMockRunner{ 760 ShouldFailOnCommand: map[string]error{"rm": fmt.Errorf("rm failed")}, 761 } 762 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 763 764 certClient := &kanikoMockClient{} 765 fileUtils := &mock.FilesMock{} 766 767 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 768 769 assert.EqualError(t, err, "failed to initialize Kaniko container: rm failed") 770 }) 771 772 t.Run("error case - Kaniko execution failed", func(t *testing.T) { 773 config := &kanikoExecuteOptions{} 774 execRunner := &mock.ExecMockRunner{ 775 ShouldFailOnCommand: map[string]error{"/kaniko/executor": fmt.Errorf("kaniko run failed")}, 776 } 777 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 778 779 certClient := &kanikoMockClient{} 780 fileUtils := &mock.FilesMock{} 781 782 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 783 784 assert.EqualError(t, err, "execution of '/kaniko/executor' failed: kaniko run failed") 785 }) 786 787 t.Run("error case - cert update failed", func(t *testing.T) { 788 config := &kanikoExecuteOptions{ 789 BuildOptions: []string{"--skip-tls-verify-pull"}, 790 ContainerImageName: "myImage", 791 ContainerImageTag: "1.2.3-a+x", 792 ContainerRegistryURL: "https://my.registry.com:50000", 793 ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", 794 CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"}, 795 DockerfilePath: "Dockerfile", 796 DockerConfigJSON: "path/to/docker/config.json", 797 } 798 execRunner := &mock.ExecMockRunner{} 799 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 800 801 certClient := &kanikoMockClient{} 802 fileUtils := &mock.FilesMock{} 803 fileUtils.FileReadErrors = map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")} 804 805 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 806 807 assert.EqualError(t, err, "failed to update certificates: failed to load file '/kaniko/ssl/certs/ca-certificates.crt': read error") 808 }) 809 810 t.Run("error case - dockerconfig read failed", func(t *testing.T) { 811 config := &kanikoExecuteOptions{ 812 DockerConfigJSON: "path/to/docker/config.json", 813 } 814 execRunner := &mock.ExecMockRunner{} 815 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 816 817 certClient := &kanikoMockClient{} 818 fileUtils := &mock.FilesMock{} 819 fileUtils.FileReadErrors = map[string]error{"path/to/docker/config.json": fmt.Errorf("read error")} 820 821 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 822 823 assert.EqualError(t, err, "failed to read existing docker config json at 'path/to/docker/config.json': read error") 824 }) 825 826 t.Run("error case - dockerconfig write failed", func(t *testing.T) { 827 config := &kanikoExecuteOptions{ 828 DockerConfigJSON: "path/to/docker/config.json", 829 } 830 execRunner := &mock.ExecMockRunner{} 831 commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} 832 833 certClient := &kanikoMockClient{} 834 fileUtils := &mock.FilesMock{} 835 fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) 836 fileUtils.FileWriteErrors = map[string]error{"/kaniko/.docker/config.json": fmt.Errorf("write error")} 837 838 err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, certClient, fileUtils) 839 840 assert.EqualError(t, err, "failed to write file '/kaniko/.docker/config.json': write error") 841 }) 842 843 t.Run("error case - multi context build: no subcontext provided", func(t *testing.T) { 844 config := &kanikoExecuteOptions{ 845 ContainerImageName: "myImage", 846 ContainerImageTag: "myTag", 847 ContainerRegistryURL: "https://my.registry.com:50000", 848 MultipleImages: []map[string]interface{}{ 849 {"containerImageName": "myImageOne"}, 850 {"containerImageName": "myImageTwo"}, 851 }, 852 } 853 854 cpe := kanikoExecuteCommonPipelineEnvironment{} 855 execRunner := &mock.ExecMockRunner{} 856 857 fileUtils := &mock.FilesMock{} 858 fileUtils.AddFile("Dockerfile", []byte("some content")) 859 860 err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, nil, fileUtils) 861 862 assert.Error(t, err) 863 assert.Contains(t, fmt.Sprint(err), "multipleImages: empty contextSubPath") 864 }) 865 }