You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	kanikoExecute: improve user experience (#2141)
* kanikoExecute: improve user experience * ensure proper tags * update permissions in case a container runs with a different user we need to make sure that the orchestrator user can work on the file * update permissions * ensure availablility of directories on Jenkins * (fix) clean up tmp dir in test * add resilience for incorrect step yaml * incorporate PR feedback
This commit is contained in:
		| @@ -4,6 +4,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/config" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| @@ -64,6 +66,12 @@ func generateConfig() error { | ||||
| 		return errors.Wrap(err, "metadata: read failed") | ||||
| 	} | ||||
|  | ||||
| 	// prepare output resource directories: | ||||
| 	// this is needed in order to have proper directory permissions in case | ||||
| 	// resources written inside a container image with a different user | ||||
| 	// Remark: This is so far only relevant for Jenkins environments where getConfig is executed | ||||
| 	prepareOutputEnvironment(metadata.Spec.Outputs.Resources, GeneralConfig.EnvRootPath) | ||||
|  | ||||
| 	resourceParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment") | ||||
|  | ||||
| 	projectConfigFile := getProjectConfigFile(GeneralConfig.CustomConfig) | ||||
| @@ -153,3 +161,21 @@ func applyContextConditions(metadata config.StepData, stepConfig *config.StepCon | ||||
| 	//ToDo: remove all unnecessary sub maps? | ||||
| 	// e.g. extract delete() from applyContainerConditions - loop over all stepConfig.Config[param.Value] and remove ... | ||||
| } | ||||
|  | ||||
| func prepareOutputEnvironment(outputResources []config.StepResources, envRootPath string) { | ||||
| 	for _, oResource := range outputResources { | ||||
| 		for _, oParam := range oResource.Parameters { | ||||
| 			paramPath := path.Join(envRootPath, oResource.Name, fmt.Sprint(oParam["name"])) | ||||
| 			if oParam["fields"] != nil { | ||||
| 				paramFields, ok := oParam["fields"].([]map[string]string) | ||||
| 				if ok && len(paramFields) > 0 { | ||||
| 					paramPath = path.Join(paramPath, paramFields[0]["name"]) | ||||
| 				} | ||||
| 			} | ||||
| 			if _, err := os.Stat(filepath.Dir(paramPath)); os.IsNotExist(err) { | ||||
| 				log.Entry().Debugf("Creating directory: %v", filepath.Dir(paramPath)) | ||||
| 				os.MkdirAll(filepath.Dir(paramPath), 0777) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| @@ -11,6 +13,7 @@ import ( | ||||
| 	"github.com/spf13/cobra" | ||||
| 	flag "github.com/spf13/pflag" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func configOpenFileMock(name string) (io.ReadCloser, error) { | ||||
| @@ -257,3 +260,53 @@ func TestApplyContextConditions(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPrepareOutputEnvironment(t *testing.T) { | ||||
| 	outputResources := []config.StepResources{ | ||||
| 		{ | ||||
| 			Name: "commonPipelineEnvironment", | ||||
| 			Type: "piperEnvironment", | ||||
| 			Parameters: []map[string]interface{}{ | ||||
| 				{"name": "param0"}, | ||||
| 				{"name": "path1/param1"}, | ||||
| 				{"name": "path2/param2"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "influx", | ||||
| 			Type: "influx", | ||||
| 			Parameters: []map[string]interface{}{ | ||||
| 				{ | ||||
| 					"name": "measurement0", | ||||
| 					"fields": []map[string]string{ | ||||
| 						{"name": "influx0_0"}, | ||||
| 						{"name": "influx0_1"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					"name": "measurement1", | ||||
| 					"fields": []map[string]string{ | ||||
| 						{"name": "influx1_0"}, | ||||
| 						{"name": "influx1_1"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	dir, tempDirErr := ioutil.TempDir("", "") | ||||
| 	defer os.RemoveAll(dir) | ||||
| 	require.NoError(t, tempDirErr) | ||||
| 	require.DirExists(t, dir, "Failed to create temporary directory") | ||||
|  | ||||
| 	prepareOutputEnvironment(outputResources, dir) | ||||
| 	assert.DirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path1")) | ||||
| 	assert.DirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path2")) | ||||
| 	assert.DirExists(t, filepath.Join(dir, "influx", "measurement0")) | ||||
| 	assert.DirExists(t, filepath.Join(dir, "influx", "measurement1")) | ||||
| 	assert.NoDirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "param0")) | ||||
| 	assert.NoDirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path1", "param1")) | ||||
| 	assert.NoDirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path2", "param2")) | ||||
| 	assert.NoDirExists(t, filepath.Join(dir, "influx", "measurement0", "influx0_0")) | ||||
| 	assert.NoDirExists(t, filepath.Join(dir, "influx", "measurement1", "influx0_1")) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| @@ -10,12 +11,13 @@ import ( | ||||
| 	"github.com/pkg/errors" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/command" | ||||
| 	"github.com/SAP/jenkins-library/pkg/docker" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/piperutils" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| ) | ||||
|  | ||||
| func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomData) { | ||||
| func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) { | ||||
| 	// for command execution use Command | ||||
| 	c := command.Command{ | ||||
| 		ErrorCategoryMapping: map[string][]string{ | ||||
| @@ -33,13 +35,13 @@ func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomD | ||||
|  | ||||
| 	fileUtils := &piperutils.Files{} | ||||
|  | ||||
| 	err := runKanikoExecute(&config, telemetryData, &c, client, fileUtils) | ||||
| 	err := runKanikoExecute(&config, telemetryData, commonPipelineEnvironment, &c, client, fileUtils) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("Kaniko execution failed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, execRunner command.ExecRunner, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error { | ||||
| func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment, execRunner command.ExecRunner, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error { | ||||
| 	// backward compatibility for parameter ContainerBuildOptions | ||||
| 	if len(config.ContainerBuildOptions) > 0 { | ||||
| 		config.BuildOptions = strings.Split(config.ContainerBuildOptions, " ") | ||||
| @@ -63,7 +65,25 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus | ||||
| 	if !piperutils.ContainsString(config.BuildOptions, "--destination") { | ||||
| 		dest := []string{"--no-push"} | ||||
| 		if len(config.ContainerImage) > 0 { | ||||
| 			containerRegistry, err := docker.ContainerRegistryFromImage(config.ContainerImage) | ||||
| 			if err != nil { | ||||
| 				return errors.Wrapf(err, "invalid registry part in image %v", config.ContainerImage) | ||||
| 			} | ||||
| 			// errors are already caught with previous call to docker.ContainerRegistryFromImage | ||||
| 			containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(config.ContainerImage) | ||||
| 			dest = []string{"--destination", config.ContainerImage} | ||||
| 			commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry) | ||||
| 			commonPipelineEnvironment.container.imageNameTag = containerImageNameTag | ||||
| 		} | ||||
| 		if len(config.ContainerRegistryURL) > 0 && len(config.ContainerImageName) > 0 && len(config.ContainerImageTag) > 0 { | ||||
| 			containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL) | ||||
| 			if err != nil { | ||||
| 				return errors.Wrapf(err, "failed to read registry url %v", config.ContainerRegistryURL) | ||||
| 			} | ||||
| 			containerImageTag := fmt.Sprintf("%v:%v", config.ContainerImageName, strings.ReplaceAll(config.ContainerImageTag, "+", "-")) | ||||
| 			dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageTag)} | ||||
| 			commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL | ||||
| 			commonPipelineEnvironment.container.imageNameTag = containerImageTag | ||||
| 		} | ||||
| 		config.BuildOptions = append(config.BuildOptions, dest...) | ||||
| 	} | ||||
|   | ||||
| @@ -5,10 +5,12 @@ package cmd | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/config" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/piperenv" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| @@ -17,12 +19,45 @@ type kanikoExecuteOptions struct { | ||||
| 	BuildOptions                []string `json:"buildOptions,omitempty"` | ||||
| 	ContainerBuildOptions       string   `json:"containerBuildOptions,omitempty"` | ||||
| 	ContainerImage              string   `json:"containerImage,omitempty"` | ||||
| 	ContainerImageName          string   `json:"containerImageName,omitempty"` | ||||
| 	ContainerImageTag           string   `json:"containerImageTag,omitempty"` | ||||
| 	ContainerPreparationCommand string   `json:"containerPreparationCommand,omitempty"` | ||||
| 	ContainerRegistryURL        string   `json:"containerRegistryUrl,omitempty"` | ||||
| 	CustomTLSCertificateLinks   []string `json:"customTlsCertificateLinks,omitempty"` | ||||
| 	DockerConfigJSON            string   `json:"dockerConfigJSON,omitempty"` | ||||
| 	DockerfilePath              string   `json:"dockerfilePath,omitempty"` | ||||
| } | ||||
|  | ||||
| type kanikoExecuteCommonPipelineEnvironment struct { | ||||
| 	container struct { | ||||
| 		registryURL  string | ||||
| 		imageNameTag string | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *kanikoExecuteCommonPipelineEnvironment) persist(path, resourceName string) { | ||||
| 	content := []struct { | ||||
| 		category string | ||||
| 		name     string | ||||
| 		value    interface{} | ||||
| 	}{ | ||||
| 		{category: "container", name: "registryUrl", value: p.container.registryURL}, | ||||
| 		{category: "container", name: "imageNameTag", value: p.container.imageNameTag}, | ||||
| 	} | ||||
|  | ||||
| 	errCount := 0 | ||||
| 	for _, param := range content { | ||||
| 		err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value) | ||||
| 		if err != nil { | ||||
| 			log.Entry().WithError(err).Error("Error persisting piper environment.") | ||||
| 			errCount++ | ||||
| 		} | ||||
| 	} | ||||
| 	if errCount > 0 { | ||||
| 		log.Entry().Fatal("failed to persist Piper environment") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // KanikoExecuteCommand Executes a [Kaniko](https://github.com/GoogleContainerTools/kaniko) build for creating a Docker container. | ||||
| func KanikoExecuteCommand() *cobra.Command { | ||||
| 	const STEP_NAME = "kanikoExecute" | ||||
| @@ -30,6 +65,7 @@ func KanikoExecuteCommand() *cobra.Command { | ||||
| 	metadata := kanikoExecuteMetadata() | ||||
| 	var stepConfig kanikoExecuteOptions | ||||
| 	var startTime time.Time | ||||
| 	var commonPipelineEnvironment kanikoExecuteCommonPipelineEnvironment | ||||
|  | ||||
| 	var createKanikoExecuteCmd = &cobra.Command{ | ||||
| 		Use:   STEP_NAME, | ||||
| @@ -62,6 +98,7 @@ func KanikoExecuteCommand() *cobra.Command { | ||||
| 			telemetryData := telemetry.CustomData{} | ||||
| 			telemetryData.ErrorCode = "1" | ||||
| 			handler := func() { | ||||
| 				commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment") | ||||
| 				telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) | ||||
| 				telemetryData.ErrorCategory = log.GetErrorCategory().String() | ||||
| 				telemetry.Send(&telemetryData) | ||||
| @@ -69,7 +106,7 @@ func KanikoExecuteCommand() *cobra.Command { | ||||
| 			log.DeferExitHandler(handler) | ||||
| 			defer handler() | ||||
| 			telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) | ||||
| 			kanikoExecute(stepConfig, &telemetryData) | ||||
| 			kanikoExecute(stepConfig, &telemetryData, &commonPipelineEnvironment) | ||||
| 			telemetryData.ErrorCode = "0" | ||||
| 			log.Entry().Info("SUCCESS") | ||||
| 		}, | ||||
| @@ -83,7 +120,10 @@ func addKanikoExecuteFlags(cmd *cobra.Command, stepConfig *kanikoExecuteOptions) | ||||
| 	cmd.Flags().StringSliceVar(&stepConfig.BuildOptions, "buildOptions", []string{`--skip-tls-verify-pull`}, "Defines a list of build options for the [kaniko](https://github.com/GoogleContainerTools/kaniko) build.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ContainerBuildOptions, "containerBuildOptions", os.Getenv("PIPER_containerBuildOptions"), "Deprected, please use buildOptions. Defines the build options for the [kaniko](https://github.com/GoogleContainerTools/kaniko) build.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ContainerImage, "containerImage", os.Getenv("PIPER_containerImage"), "Defines the full name of the Docker image to be created including registry, image name and tag like `my.docker.registry/path/myImageName:myTag`. If left empty, image will not be pushed.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ContainerImageName, "containerImageName", os.Getenv("PIPER_containerImageName"), "Name of the container which will be built - will be used instead of parameter `containerImage`") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ContainerImageTag, "containerImageTag", os.Getenv("PIPER_containerImageTag"), "Tag of the container which will be built - will be used instead of parameter `containerImage`") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ContainerPreparationCommand, "containerPreparationCommand", `rm -f /kaniko/.docker/config.json`, "Defines the command to prepare the Kaniko container. By default the contained credentials are removed in order to allow anonymous access to container registries.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "http(s) url of the Container registry where the image should be pushed to - will be used instead of parameter `containerImage`") | ||||
| 	cmd.Flags().StringSliceVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", []string{}, "List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).") | ||||
| 	cmd.Flags().StringVar(&stepConfig.DockerfilePath, "dockerfilePath", `Dockerfile`, "Defines the location of the Dockerfile relative to the Jenkins workspace.") | ||||
| @@ -117,17 +157,33 @@ func kanikoExecuteMetadata() config.StepData { | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "containerImage", | ||||
| 						Name:        "containerImage", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{{Name: "containerImageNameAndTag"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "containerImageName", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{{Name: "dockerImageName"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "containerImageTag", | ||||
| 						ResourceRef: []config.ResourceReference{ | ||||
| 							{ | ||||
| 								Name:  "commonPipelineEnvironment", | ||||
| 								Param: "container/imageNameTag", | ||||
| 								Param: "artifactVersion", | ||||
| 							}, | ||||
| 						}, | ||||
| 						Scope:     []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Scope:     []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:      "string", | ||||
| 						Mandatory: false, | ||||
| 						Aliases:   []config.Alias{{Name: "containerImageNameAndTag"}}, | ||||
| 						Aliases:   []config.Alias{{Name: "artifactVersion"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "containerPreparationCommand", | ||||
| @@ -137,6 +193,19 @@ func kanikoExecuteMetadata() config.StepData { | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "containerRegistryUrl", | ||||
| 						ResourceRef: []config.ResourceReference{ | ||||
| 							{ | ||||
| 								Name:  "commonPipelineEnvironment", | ||||
| 								Param: "container/registryUrl", | ||||
| 							}, | ||||
| 						}, | ||||
| 						Scope:     []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:      "string", | ||||
| 						Mandatory: false, | ||||
| 						Aliases:   []config.Alias{{Name: "dockerRegistryUrl"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "customTlsCertificateLinks", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
|   | ||||
| @@ -84,6 +84,8 @@ func (f *kanikoFileMock) Glob(pattern string) (matches []string, err error) { | ||||
|  | ||||
| func TestRunKanikoExecute(t *testing.T) { | ||||
|  | ||||
| 	commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{} | ||||
|  | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		config := &kanikoExecuteOptions{ | ||||
| 			BuildOptions:                []string{"--skip-tls-verify-pull"}, | ||||
| @@ -104,7 +106,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileWriteContent: map[string]string{}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| @@ -120,6 +122,44 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - image params", func(t *testing.T) { | ||||
| 		config := &kanikoExecuteOptions{ | ||||
| 			BuildOptions:                []string{"--skip-tls-verify-pull"}, | ||||
| 			ContainerImageName:          "myImage", | ||||
| 			ContainerImageTag:           "1.2.3-a+x", | ||||
| 			ContainerRegistryURL:        "https://my.registry.com:50000", | ||||
| 			ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json", | ||||
| 			CustomTLSCertificateLinks:   []string{"https://test.url/cert.crt"}, | ||||
| 			DockerfilePath:              "Dockerfile", | ||||
| 			DockerConfigJSON:            "path/to/docker/config.json", | ||||
| 		} | ||||
|  | ||||
| 		runner := &mock.ExecMockRunner{} | ||||
|  | ||||
| 		certClient := &kanikoMockClient{ | ||||
| 			responseBody: "testCert", | ||||
| 		} | ||||
| 		fileUtils := &kanikoFileMock{ | ||||
| 			fileReadContent:  map[string]string{"path/to/docker/config.json": `{"auths":{"custom":"test"}}`}, | ||||
| 			fileWriteContent: map[string]string{}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, 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, config.CustomTLSCertificateLinks, certClient.urlsCalled) | ||||
| 		assert.Equal(t, `{"auths":{"custom":"test"}}`, fileUtils.fileWriteContent["/kaniko/.docker/config.json"]) | ||||
|  | ||||
| 		assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec) | ||||
| 		cwd, _ := os.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) | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - no push, no docker config.json", func(t *testing.T) { | ||||
| 		config := &kanikoExecuteOptions{ | ||||
| 			ContainerBuildOptions:       "--skip-tls-verify-pull", | ||||
| @@ -137,7 +177,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileWriteContent: map[string]string{}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| @@ -167,7 +207,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileWriteContent: map[string]string{}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		cwd, _ := os.Getwd() | ||||
| @@ -186,7 +226,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 		certClient := &kanikoMockClient{} | ||||
| 		fileUtils := &kanikoFileMock{} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to initialize Kaniko container: rm failed") | ||||
| 	}) | ||||
| @@ -203,7 +243,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileWriteContent: map[string]string{}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.EqualError(t, err, "execution of '/kaniko/executor' failed: kaniko run failed") | ||||
| 	}) | ||||
| @@ -219,7 +259,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileReadErr:      map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to update certificates: failed to load file '/kaniko/ssl/certs/ca-certificates.crt': read error") | ||||
| 	}) | ||||
| @@ -237,7 +277,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileReadErr:      map[string]error{"path/to/docker/config.json": fmt.Errorf("read error")}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to read file 'path/to/docker/config.json': read error") | ||||
| 	}) | ||||
| @@ -255,7 +295,7 @@ func TestRunKanikoExecute(t *testing.T) { | ||||
| 			fileWriteErr:     map[string]error{"/kaniko/.docker/config.json": fmt.Errorf("write error")}, | ||||
| 		} | ||||
|  | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils) | ||||
| 		err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) | ||||
|  | ||||
| 		assert.EqualError(t, err, "failed to write file '/kaniko/.docker/config.json': write error") | ||||
| 	}) | ||||
|   | ||||
							
								
								
									
										41
									
								
								pkg/docker/container.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								pkg/docker/container.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| package docker | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	containerName "github.com/google/go-containerregistry/pkg/name" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| // ContainerRegistryFromURL provides the registry part of a complete registry url including the port | ||||
| func ContainerRegistryFromURL(registryURL string) (string, error) { | ||||
| 	u, err := url.ParseRequestURI(registryURL) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrap(err, "invalid registry url") | ||||
| 	} | ||||
| 	if len(u.Host) == 0 { | ||||
| 		return "", fmt.Errorf("invalid registry url") | ||||
| 	} | ||||
| 	return u.Host, nil | ||||
| } | ||||
|  | ||||
| // ContainerRegistryFromImage provides the registry part of a full image name | ||||
| func ContainerRegistryFromImage(fullImage string) (string, error) { | ||||
| 	ref, err := containerName.ParseReference(strings.ToLower(fullImage)) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrap(err, "failed to parse image name") | ||||
| 	} | ||||
| 	return ref.Context().RegistryStr(), nil | ||||
| } | ||||
|  | ||||
| // ContainerImageNameTagFromImage provides the name & tag part of a full image name | ||||
| func ContainerImageNameTagFromImage(fullImage string) (string, error) { | ||||
| 	ref, err := containerName.ParseReference(strings.ToLower(fullImage)) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrap(err, "failed to parse image name") | ||||
| 	} | ||||
| 	registryOnly := fmt.Sprintf("%v/", ref.Context().RegistryStr()) | ||||
| 	return strings.ReplaceAll(fullImage, registryOnly, ""), nil | ||||
| } | ||||
							
								
								
									
										107
									
								
								pkg/docker/container_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								pkg/docker/container_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| package docker | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestContainerRegistryFromURL(t *testing.T) { | ||||
| 	tt := []struct { | ||||
| 		url           string | ||||
| 		expected      string | ||||
| 		expectedError string | ||||
| 	}{ | ||||
| 		{url: "", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "invalidUrl", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "no.protocol.com", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "no.protocol.com:50000", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "no.protocol.com:50000/my/path/to/image", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "no.protocol.com:50000/my/path/to/image:withTag", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "no.protocol.com:50000/my/path/to/image@withDigest", expected: "", expectedError: "invalid registry url"}, | ||||
| 		{url: "https://my.registry.com", expected: "my.registry.com"}, | ||||
| 		{url: "https://my.registry.com:50000", expected: "my.registry.com:50000"}, | ||||
| 		{url: "https://my.registry.com:50000/", expected: "my.registry.com:50000"}, | ||||
| 		{url: "https://my.registry.com:50000/my/path/to/image:withTag", expected: "my.registry.com:50000"}, | ||||
| 		{url: "https://my.registry.com:50000/my/path/to/image@withDigest", expected: "my.registry.com:50000"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range tt { | ||||
| 		t.Run(test.url, func(t *testing.T) { | ||||
| 			got, err := ContainerRegistryFromURL(test.url) | ||||
| 			if len(test.expectedError) > 0 { | ||||
| 				assert.Contains(t, fmt.Sprint(err), test.expectedError) | ||||
| 			} else { | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
| 			assert.Equal(t, test.expected, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestContainerRegistryFromImage(t *testing.T) { | ||||
| 	tt := []struct { | ||||
| 		image         string | ||||
| 		expected      string | ||||
| 		expectedError string | ||||
| 	}{ | ||||
| 		{image: "", expected: "", expectedError: "failed to parse image name"}, | ||||
| 		{image: "onlyImage", expected: "index.docker.io"}, | ||||
| 		{image: "onlyimage", expected: "index.docker.io"}, | ||||
| 		{image: "onlyimage:withTag", expected: "index.docker.io"}, | ||||
| 		{image: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "index.docker.io"}, | ||||
| 		{image: "path/to/image", expected: "index.docker.io"}, | ||||
| 		{image: "my.registry.com/onlyimage", expected: "my.registry.com"}, | ||||
| 		{image: "my.registry.com:50000/onlyimage", expected: "my.registry.com:50000"}, | ||||
| 		{image: "my.registry.com:50000/onlyimage:withTag", expected: "my.registry.com:50000"}, | ||||
| 		{image: "my.registry.com:50000/onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "my.registry.com:50000"}, | ||||
| 		{image: "my.registry.com:50000/path/to/image:withTag", expected: "my.registry.com:50000"}, | ||||
| 		{image: "my.registry.com:50000/path/to/image@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "my.registry.com:50000"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range tt { | ||||
| 		t.Run(test.image, func(t *testing.T) { | ||||
| 			got, err := ContainerRegistryFromImage(test.image) | ||||
| 			if len(test.expectedError) > 0 { | ||||
| 				assert.Contains(t, fmt.Sprint(err), test.expectedError) | ||||
| 			} else { | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
| 			assert.Equal(t, test.expected, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestContainerImageNameTagFromImage(t *testing.T) { | ||||
| 	tt := []struct { | ||||
| 		image         string | ||||
| 		expected      string | ||||
| 		expectedError string | ||||
| 	}{ | ||||
| 		{image: "", expected: "", expectedError: "failed to parse image name"}, | ||||
| 		{image: "onlyImage", expected: "onlyImage"}, | ||||
| 		{image: "onlyimage", expected: "onlyimage"}, | ||||
| 		{image: "onlyimage:withTag", expected: "onlyimage:withTag"}, | ||||
| 		{image: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205"}, | ||||
| 		{image: "path/to/image", expected: "path/to/image"}, | ||||
| 		{image: "my.registry.com/onlyimage", expected: "onlyimage"}, | ||||
| 		{image: "my.registry.com:50000/onlyimage", expected: "onlyimage"}, | ||||
| 		{image: "my.registry.com:50000/onlyimage:withTag", expected: "onlyimage:withTag"}, | ||||
| 		{image: "my.registry.com:50000/onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205"}, | ||||
| 		{image: "my.registry.com:50000/path/to/image:withTag", expected: "path/to/image:withTag"}, | ||||
| 		{image: "my.registry.com:50000/path/to/image@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "path/to/image@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range tt { | ||||
| 		t.Run(test.image, func(t *testing.T) { | ||||
| 			got, err := ContainerImageNameTagFromImage(test.image) | ||||
| 			if len(test.expectedError) > 0 { | ||||
| 				assert.Contains(t, fmt.Sprint(err), test.expectedError) | ||||
| 			} else { | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
| 			assert.Equal(t, test.expected, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -55,13 +55,13 @@ func writeToDisk(filename string, data []byte) error { | ||||
|  | ||||
| 	if _, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) { | ||||
| 		log.Entry().Debugf("Creating directory: %v", filepath.Dir(filename)) | ||||
| 		os.MkdirAll(filepath.Dir(filename), 0755) | ||||
| 		os.MkdirAll(filepath.Dir(filename), 0777) | ||||
| 	} | ||||
|  | ||||
| 	//ToDo: make sure to not overwrite file but rather add another file? Create error if already existing? | ||||
| 	if len(data) > 0 { | ||||
| 		log.Entry().Debugf("Writing file to disk: %v", filename) | ||||
| 		return ioutil.WriteFile(filename, data, 0755) | ||||
| 		return ioutil.WriteFile(filename, data, 0766) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -35,9 +35,29 @@ spec: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: containerImageName | ||||
|         aliases: | ||||
|           - name: dockerImageName | ||||
|         type: string | ||||
|         description: Name of the container which will be built - will be used instead of parameter `containerImage` | ||||
|         scope: | ||||
|           - GENERAL | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: containerImageTag | ||||
|         aliases: | ||||
|           - name: artifactVersion | ||||
|         type: string | ||||
|         description: Tag of the container which will be built - will be used instead of parameter `containerImage` | ||||
|         scope: | ||||
|           - GENERAL | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         resourceRef: | ||||
|           - name: commonPipelineEnvironment | ||||
|             param: container/imageNameTag | ||||
|             param: artifactVersion | ||||
|       - name: containerPreparationCommand | ||||
|         type: string | ||||
|         description: Defines the command to prepare the Kaniko container. By default the contained credentials are removed in order to allow anonymous access to container registries. | ||||
| @@ -46,6 +66,19 @@ spec: | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         default: rm -f /kaniko/.docker/config.json | ||||
|       - name: containerRegistryUrl | ||||
|         aliases: | ||||
|           - name: dockerRegistryUrl | ||||
|         type: string | ||||
|         description: http(s) url of the Container registry where the image should be pushed to - will be used instead of parameter `containerImage` | ||||
|         scope: | ||||
|           - GENERAL | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         resourceRef: | ||||
|           - name: commonPipelineEnvironment | ||||
|             param: container/registryUrl | ||||
|       - name: customTlsCertificateLinks | ||||
|         type: "[]string" | ||||
|         description: List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates. | ||||
| @@ -75,6 +108,13 @@ spec: | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         default: Dockerfile | ||||
|   outputs: | ||||
|     resources: | ||||
|       - name: commonPipelineEnvironment | ||||
|         type: piperEnvironment | ||||
|         params: | ||||
|           - name: container/registryUrl | ||||
|           - name: container/imageNameTag | ||||
|   containers: | ||||
|     - image: gcr.io/kaniko-project/executor:debug | ||||
|       command: | ||||
|   | ||||
| @@ -141,8 +141,6 @@ spec: | ||||
|         type: piperEnvironment | ||||
|         params: | ||||
|           - name: mtarFilePath | ||||
|             fields: | ||||
|               - name: mtarFilePath | ||||
|   containers: | ||||
|     - image: devxci/mbtci:1.0.14.1 | ||||
|       imagePullPolicy: Always | ||||
|   | ||||
| @@ -155,8 +155,6 @@ spec: | ||||
|         type: piperEnvironment | ||||
|         params: | ||||
|           - name: operationId | ||||
|             fields: | ||||
|               - name: operationId | ||||
|   containers: | ||||
|     - name: xs | ||||
|       image: ppiper/xs-cli | ||||
|   | ||||
		Reference in New Issue
	
	Block a user