1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-09-16 09:26:22 +02:00

Introducing syft to generate SBOMS for kaniko builds (#4093)

* Update kanikoExecute.go
* Syft function

* Change installation directory to ease cleanup

* Add createBOM option

* Unit tests

* Refactor code

Co-authored-by: raman-susla-epam <104915202+raman-susla-epam@users.noreply.github.com>
This commit is contained in:
Ashly Mathew
2022-11-07 14:27:05 +01:00
committed by GitHub
parent d256c3a604
commit e2c710c035
4 changed files with 329 additions and 79 deletions

View File

@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"net/http"
"strings"
"github.com/SAP/jenkins-library/pkg/buildsettings"
@@ -16,6 +17,50 @@ import (
"github.com/SAP/jenkins-library/pkg/telemetry"
)
const syftURL = "https://raw.githubusercontent.com/anchore/syft/main/install.sh"
type kanikoHttpClient interface {
piperhttp.Sender
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
}
func installSyft(shellRunner command.ShellRunner, fileUtils piperutils.FileUtils, httpClient kanikoHttpClient) error {
installationScript := "./install.sh"
err := httpClient.DownloadFile(syftURL, installationScript, nil, nil)
if err != nil {
return fmt.Errorf("failed to download syft: %w", err)
}
err = fileUtils.Chmod(installationScript, 0777)
if err != nil {
return err
}
err = shellRunner.RunShell("/busybox/sh", "cat ./install.sh | sh -s -- -b .")
if err != nil {
return fmt.Errorf("failed to install syft: %w", err)
}
return nil
}
func generateSBOM(shellRunner command.ShellRunner, fileUtils piperutils.FileUtils, httpClient kanikoHttpClient, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) error {
shellRunner.AppendEnv([]string{"DOCKER_CONFIG", "/kaniko/.docker"})
syftInstallErr := installSyft(shellRunner, fileUtils, httpClient)
if syftInstallErr != nil {
return syftInstallErr
}
for index, eachImageTag := range commonPipelineEnvironment.container.imageNameTags {
// TrimPrefix needed as syft needs containerRegistry name only
syftRunErr := shellRunner.RunShell("/busybox/sh", fmt.Sprintf("./syft %s/%s -o cyclonedx-xml=bom-docker-%v.xml", strings.TrimPrefix(commonPipelineEnvironment.container.registryURL, "https://"), eachImageTag, index))
if syftRunErr != nil {
return fmt.Errorf("failed to generate SBOM: %w", syftRunErr)
}
}
return nil
}
func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) {
// for command execution use Command
c := command.Command{
@@ -34,13 +79,13 @@ func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomD
fileUtils := &piperutils.Files{}
err := runKanikoExecute(&config, telemetryData, commonPipelineEnvironment, &c, client, fileUtils)
err := runKanikoExecute(&config, telemetryData, commonPipelineEnvironment, &c, &c, client, fileUtils)
if err != nil {
log.Entry().WithError(err).Fatal("Kaniko execution failed")
}
}
func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment, execRunner command.ExecRunner, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error {
func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment, execRunner command.ExecRunner, shellRunner command.ShellRunner, httpClient kanikoHttpClient, fileUtils piperutils.FileUtils) error {
binfmtSupported, _ := docker.IsBinfmtMiscSupportedByHost(fileUtils)
if !binfmtSupported && len(config.TargetArchitectures) > 0 {
@@ -175,7 +220,10 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus
containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag)
commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag
}
if config.CreateBOM {
//Syft for multi image, generates bom-docker-(1/2/3).xml
return generateSBOM(shellRunner, fileUtils, httpClient, commonPipelineEnvironment)
}
return nil
} else {
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, config.ContainerImageName)
@@ -232,7 +280,15 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus
}
// no support for building multiple containers
return runKaniko(config.DockerfilePath, config.BuildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment)
kanikoErr := runKaniko(config.DockerfilePath, config.BuildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment)
if kanikoErr != nil {
return kanikoErr
}
if config.CreateBOM {
// Syft for single image, generates bom-docker-0.xml
return generateSBOM(shellRunner, fileUtils, httpClient, commonPipelineEnvironment)
}
return nil
}
func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, execRunner command.ExecRunner, fileUtils piperutils.FileUtils, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) error {

View File

@@ -36,6 +36,7 @@ type kanikoExecuteOptions struct {
DockerfilePath string `json:"dockerfilePath,omitempty"`
TargetArchitectures []string `json:"targetArchitectures,omitempty"`
ReadImageDigest bool `json:"readImageDigest,omitempty"`
CreateBOM bool `json:"createBOM,omitempty"`
}
type kanikoExecuteCommonPipelineEnvironment struct {
@@ -257,6 +258,7 @@ func addKanikoExecuteFlags(cmd *cobra.Command, stepConfig *kanikoExecuteOptions)
cmd.Flags().StringVar(&stepConfig.DockerfilePath, "dockerfilePath", `Dockerfile`, "Defines the location of the Dockerfile relative to the Jenkins workspace.")
cmd.Flags().StringSliceVar(&stepConfig.TargetArchitectures, "targetArchitectures", []string{``}, "Defines the target architectures for which the build should run using OS and architecture separated by a comma. (EXPERIMENTAL)")
cmd.Flags().BoolVar(&stepConfig.ReadImageDigest, "readImageDigest", false, "")
cmd.Flags().BoolVar(&stepConfig.CreateBOM, "createBOM", false, "Creates the bill of materials (BOM) using Syft and stored in a file of CycloneDX 1.4 format.")
}
@@ -472,6 +474,15 @@ func kanikoExecuteMetadata() config.StepData {
Aliases: []config.Alias{},
Default: false,
},
{
Name: "createBOM",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
},
},
Containers: []config.Container{

View File

@@ -36,6 +36,12 @@ func (c *kanikoMockClient) SendRequest(method, url string, body io.Reader, heade
}
return &http.Response{StatusCode: c.httpStatusCode, Body: ioutil.NopCloser(bytes.NewReader([]byte(c.responseBody)))}, nil
}
func (c *kanikoMockClient) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
if len(c.errorMessage) > 0 {
return fmt.Errorf(c.errorMessage)
}
return nil
}
func TestRunKanikoExecute(t *testing.T) {
@@ -59,7 +65,8 @@ func TestRunKanikoExecute(t *testing.T) {
BuildSettingsInfo: `{"mavenExecuteBuild":[{"dockerImage":"maven"}]}`,
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -69,21 +76,21 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, "rm", execRunner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
c, err := fileUtils.FileRead("/kaniko/.docker/config.json")
assert.NoError(t, err)
assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c))
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
cwd, _ := fileUtils.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, runner.Calls[1].Params)
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params)
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`)
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`)
@@ -109,7 +116,8 @@ func TestRunKanikoExecute(t *testing.T) {
ReadImageDigest: true,
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -120,21 +128,21 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, "rm", execRunner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
c, err := fileUtils.FileRead("/kaniko/.docker/config.json")
assert.NoError(t, err)
assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c))
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
cwd, _ := fileUtils.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt"}, runner.Calls[1].Params)
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt"}, execRunner.Calls[1].Params)
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`)
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`)
@@ -160,7 +168,8 @@ func TestRunKanikoExecute(t *testing.T) {
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -170,21 +179,21 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, "rm", execRunner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
c, err := fileUtils.FileRead("/kaniko/.docker/config.json")
assert.NoError(t, err)
assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c))
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
cwd, _ := fileUtils.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, runner.Calls[1].Params)
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, execRunner.Calls[1].Params)
assert.Equal(t, "myImage:1.2.3-a-x", commonPipelineEnvironment.container.imageNameTag)
assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL)
@@ -204,7 +213,8 @@ func TestRunKanikoExecute(t *testing.T) {
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -214,21 +224,21 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, "rm", execRunner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
c, err := fileUtils.FileRead("/kaniko/.docker/config.json")
assert.NoError(t, err)
assert.Equal(t, `{"auths":{"custom":"test"}}`, string(c))
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
cwd, _ := fileUtils.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, runner.Calls[1].Params)
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, execRunner.Calls[1].Params)
assert.Equal(t, "myImage:3.2.1-a-x", commonPipelineEnvironment.container.imageNameTag)
assert.Equal(t, "https://my.other.registry.com:50000", commonPipelineEnvironment.container.registryURL)
@@ -251,7 +261,8 @@ func TestRunKanikoExecute(t *testing.T) {
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{}
@@ -259,7 +270,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(``))
fileUtils.FileReadErrors = map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoErrorf(t, err, "failed to update certificates: failed to load file '/kaniko/ssl/certs/ca-certificates.crt': read error")
})
@@ -272,7 +283,8 @@ func TestRunKanikoExecute(t *testing.T) {
DockerfilePath: "Dockerfile",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -281,7 +293,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
@@ -290,7 +302,7 @@ func TestRunKanikoExecute(t *testing.T) {
assert.Equal(t, `{"auths":{}}`, string(c))
cwd, _ := fileUtils.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--no-push"}, runner.Calls[1].Params)
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--no-push"}, execRunner.Calls[1].Params)
})
t.Run("success case - backward compatibility", func(t *testing.T) {
@@ -303,7 +315,8 @@ func TestRunKanikoExecute(t *testing.T) {
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -313,11 +326,44 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
cwd, _ := fileUtils.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, runner.Calls[1].Params)
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params)
})
t.Run("success case - createBOM", func(t *testing.T) {
config := &kanikoExecuteOptions{
ContainerImage: "myImage:tag",
ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json",
DockerfilePath: "Dockerfile",
DockerConfigJSON: "path/to/docker/config.json",
CreateBOM: true,
}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
responseBody: "testCert",
}
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("install.sh", []byte(`echo syft`))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
assert.Equal(t, "myImage:tag", commonPipelineEnvironment.container.imageNameTag)
assert.Equal(t, "https://index.docker.io", commonPipelineEnvironment.container.registryURL)
//Syft install and call
assert.Contains(t, shellRunner.Calls[0], "cat ./install.sh | sh -s -- -b .")
assert.Contains(t, shellRunner.Calls[1], "./syft index.docker.io/myImage:tag -o cyclonedx-xml=bom-docker-0.xml")
})
t.Run("success case - multi image build with root image", func(t *testing.T) {
@@ -328,7 +374,8 @@ func TestRunKanikoExecute(t *testing.T) {
ContainerMultiImageBuild: true,
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
fileUtils := &mock.FilesMock{}
@@ -336,14 +383,14 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("sub1/Dockerfile", []byte("some content"))
fileUtils.AddFile("sub2/Dockerfile", []byte("some content"))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, nil, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, nil, fileUtils)
assert.NoError(t, err)
assert.Equal(t, 3, len(runner.Calls))
assert.Equal(t, "/kaniko/executor", runner.Calls[0].Exec)
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", runner.Calls[2].Exec)
assert.Equal(t, 3, len(execRunner.Calls))
assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[2].Exec)
cwd, _ := fileUtils.Getwd()
expectedParams := [][]string{
@@ -352,7 +399,7 @@ func TestRunKanikoExecute(t *testing.T) {
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
}
// need to go this way since we cannot count on the correct order
for _, call := range runner.Calls {
for _, call := range execRunner.Calls {
found := false
for _, expected := range expectedParams {
if strings.Join(call.Params, " ") == strings.Join(expected, " ") {
@@ -385,7 +432,8 @@ func TestRunKanikoExecute(t *testing.T) {
ContainerMultiImageBuildExcludes: []string{"Dockerfile"},
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
fileUtils := &mock.FilesMock{}
@@ -393,13 +441,13 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("sub1/Dockerfile", []byte("some content"))
fileUtils.AddFile("sub2/Dockerfile", []byte("some content"))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, nil, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, nil, fileUtils)
assert.NoError(t, err)
assert.Equal(t, 2, len(runner.Calls))
assert.Equal(t, "/kaniko/executor", runner.Calls[0].Exec)
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
assert.Equal(t, 2, len(execRunner.Calls))
assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
cwd, _ := fileUtils.Getwd()
expectedParams := [][]string{
@@ -407,7 +455,7 @@ func TestRunKanikoExecute(t *testing.T) {
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
}
// need to go this way since we cannot count on the correct order
for _, call := range runner.Calls {
for _, call := range execRunner.Calls {
found := false
for _, expected := range expectedParams {
if strings.Join(call.Params, " ") == strings.Join(expected, " ") {
@@ -426,6 +474,88 @@ func TestRunKanikoExecute(t *testing.T) {
assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub2:myTag")
})
t.Run("success case - multi image build with CreateBOM", func(t *testing.T) {
config := &kanikoExecuteOptions{
ContainerImageName: "myImage",
ContainerImageTag: "myTag",
ContainerRegistryURL: "https://my.registry.com:50000",
ContainerMultiImageBuild: true,
DockerConfigJSON: "path/to/docker/config.json",
CreateBOM: true,
}
certClient := &kanikoMockClient{
responseBody: "testCert",
}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("Dockerfile", []byte("some content"))
fileUtils.AddFile("sub1/Dockerfile", []byte("some content"))
fileUtils.AddFile("sub2/Dockerfile", []byte("some content"))
fileUtils.AddFile("install.sh", []byte(`echo syft`))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, 3, len(execRunner.Calls))
assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
assert.Equal(t, "/kaniko/executor", execRunner.Calls[2].Exec)
cwd, _ := fileUtils.Getwd()
expectedParams := [][]string{
{"--dockerfile", "Dockerfile", "--context", cwd, "--destination", "my.registry.com:50000/myImage:myTag"},
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
}
// need to go this way since we cannot count on the correct order
for _, call := range execRunner.Calls {
found := false
for _, expected := range expectedParams {
if strings.Join(call.Params, " ") == strings.Join(expected, " ") {
found = true
break
}
}
assert.True(t, found, fmt.Sprintf("%v not found", call.Params))
}
assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL)
assert.Equal(t, "myImage:myTag", commonPipelineEnvironment.container.imageNameTag)
assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage")
assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub1")
assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImage-sub2")
assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage:myTag")
assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub1:myTag")
assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub2:myTag")
assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest)
assert.Empty(t, commonPipelineEnvironment.container.imageDigests)
//Syft install and call, can we do it better without 2 for loops
expectedShellCalls := []string{
"cat ./install.sh | sh -s -- -b .",
"./syft my.registry.com:50000/myImage:myTag -o cyclonedx-xml=bom-docker-0.xml",
"./syft my.registry.com:50000/myImage-sub1:myTag -o cyclonedx-xml=bom-docker-1.xml",
"./syft my.registry.com:50000/myImage-sub2:myTag -o cyclonedx-xml=bom-docker-2.xml",
}
for _, call := range shellRunner.Calls {
found := false
for _, expected := range expectedShellCalls {
if strings.Contains(call, expected) {
found = true
break
}
}
assert.True(t, found)
}
})
t.Run("success case - updating an existing docker config json with addtional credentials", func(t *testing.T) {
config := &kanikoExecuteOptions{
BuildOptions: []string{"--skip-tls-verify-pull"},
@@ -440,7 +570,8 @@ func TestRunKanikoExecute(t *testing.T) {
ContainerRegistryPassword: "dummyPassword",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -450,12 +581,12 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths": {"dummyUrl": {"auth": "XXXXXXX"}}}`))
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, "rm", execRunner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
c, err := fileUtils.FileRead("/kaniko/.docker/config.json")
@@ -476,7 +607,8 @@ func TestRunKanikoExecute(t *testing.T) {
ContainerRegistryPassword: "dummyPassword",
}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
@@ -485,12 +617,12 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``))
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, "rm", execRunner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, execRunner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
c, err := fileUtils.FileRead("/kaniko/.docker/config.json")
@@ -507,11 +639,12 @@ func TestRunKanikoExecute(t *testing.T) {
}
cpe := kanikoExecuteCommonPipelineEnvironment{}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
fileUtils := &mock.FilesMock{}
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, runner, nil, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, shellRunner, nil, fileUtils)
assert.Error(t, err)
assert.Contains(t, fmt.Sprint(err), "failed to identify image list for multi image build")
@@ -527,12 +660,13 @@ func TestRunKanikoExecute(t *testing.T) {
}
cpe := kanikoExecuteCommonPipelineEnvironment{}
runner := &mock.ExecMockRunner{}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("Dockerfile", []byte("some content"))
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, runner, nil, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, shellRunner, nil, fileUtils)
assert.Error(t, err)
assert.Contains(t, fmt.Sprint(err), "no docker files to process, please check exclude list")
@@ -547,13 +681,15 @@ func TestRunKanikoExecute(t *testing.T) {
}
cpe := kanikoExecuteCommonPipelineEnvironment{}
runner := &mock.ExecMockRunner{}
runner.ShouldFailOnCommand = map[string]error{"/kaniko/executor": fmt.Errorf("execution failed")}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
execRunner.ShouldFailOnCommand = map[string]error{"/kaniko/executor": fmt.Errorf("execution failed")}
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("Dockerfile", []byte("some content"))
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, runner, nil, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, shellRunner, nil, fileUtils)
assert.Error(t, err)
assert.Contains(t, fmt.Sprint(err), "failed to build image")
@@ -563,8 +699,8 @@ func TestRunKanikoExecute(t *testing.T) {
config := &kanikoExecuteOptions{
ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json",
}
runner := &mock.ExecMockRunner{
shellRunner := &mock.ShellMockRunner{}
execRunner := &mock.ExecMockRunner{
ShouldFailOnCommand: map[string]error{"rm": fmt.Errorf("rm failed")},
}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
@@ -572,15 +708,15 @@ func TestRunKanikoExecute(t *testing.T) {
certClient := &kanikoMockClient{}
fileUtils := &mock.FilesMock{}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.EqualError(t, err, "failed to initialize Kaniko container: rm failed")
})
t.Run("error case - Kaniko execution failed", func(t *testing.T) {
config := &kanikoExecuteOptions{}
runner := &mock.ExecMockRunner{
shellRunner := &mock.ShellMockRunner{}
execRunner := &mock.ExecMockRunner{
ShouldFailOnCommand: map[string]error{"/kaniko/executor": fmt.Errorf("kaniko run failed")},
}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
@@ -588,7 +724,7 @@ func TestRunKanikoExecute(t *testing.T) {
certClient := &kanikoMockClient{}
fileUtils := &mock.FilesMock{}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.EqualError(t, err, "execution of '/kaniko/executor' failed: kaniko run failed")
})
@@ -604,15 +740,15 @@ func TestRunKanikoExecute(t *testing.T) {
DockerfilePath: "Dockerfile",
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
execRunner := &mock.ExecMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{}
fileUtils := &mock.FilesMock{}
fileUtils.FileReadErrors = map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.EqualError(t, err, "failed to update certificates: failed to load file '/kaniko/ssl/certs/ca-certificates.crt': read error")
})
@@ -621,15 +757,15 @@ func TestRunKanikoExecute(t *testing.T) {
config := &kanikoExecuteOptions{
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
execRunner := &mock.ExecMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{}
fileUtils := &mock.FilesMock{}
fileUtils.FileReadErrors = map[string]error{"path/to/docker/config.json": fmt.Errorf("read error")}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.EqualError(t, err, "failed to read existing docker config json at 'path/to/docker/config.json': read error")
})
@@ -638,8 +774,8 @@ func TestRunKanikoExecute(t *testing.T) {
config := &kanikoExecuteOptions{
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
execRunner := &mock.ExecMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{}
@@ -647,9 +783,48 @@ func TestRunKanikoExecute(t *testing.T) {
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.FileWriteErrors = map[string]error{"/kaniko/.docker/config.json": fmt.Errorf("write error")}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.EqualError(t, err, "failed to write file '/kaniko/.docker/config.json': write error")
})
t.Run("error cases - createBOM", func(t *testing.T) {
config := &kanikoExecuteOptions{
ContainerImage: "myImage:tag",
DockerfilePath: "Dockerfile",
DockerConfigJSON: "path/to/docker/config.json",
ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json",
CreateBOM: true,
}
execRunner := &mock.ExecMockRunner{}
shellRunner := &mock.ShellMockRunner{}
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
certClient := &kanikoMockClient{
responseBody: "testCert",
}
fileUtils := &mock.FilesMock{}
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
fileUtils.AddFile("install.sh", []byte(`echo syft`))
// Case 1 - Download of syft installation file failed
certClient.errorMessage = "Download failed"
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient, fileUtils)
assert.Error(t, err)
assert.Contains(t, fmt.Sprint(err), certClient.errorMessage)
// Case 2 - Installation of syft failed, using new kanikoMockClient here
certClient1 := &kanikoMockClient{}
shellRunner.ShouldFailOnCommand = map[string]error{"cat ./install.sh | sh -s -- -b .": fmt.Errorf("install failed")}
err = runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner, certClient1, fileUtils)
assert.Error(t, err)
assert.Contains(t, fmt.Sprint(err), "failed to install syft")
// Case 3 syft run failed, using new ShellMockRunner here
shellRunner1 := &mock.ShellMockRunner{}
shellRunner1.ShouldFailOnCommand = map[string]error{"./syft index.docker.io/myImage:tag -o cyclonedx-xml=bom-docker-0.xml": fmt.Errorf("run failed")}
err = runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, shellRunner1, certClient1, fileUtils)
assert.Error(t, err)
assert.Contains(t, fmt.Sprint(err), "failed to generate SBOM")
})
}

View File

@@ -257,6 +257,14 @@ spec:
- STEPS
- STAGES
- PARAMETERS
- name: createBOM
type: bool
description: Creates the bill of materials (BOM) using Syft and stored in a file of CycloneDX 1.4 format.
scope:
- GENERAL
- STEPS
- STAGES
- PARAMETERS
outputs:
resources:
- name: commonPipelineEnvironment