diff --git a/cmd/getConfig.go b/cmd/getConfig.go index 19d464c77..428c59eb1 100644 --- a/cmd/getConfig.go +++ b/cmd/getConfig.go @@ -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) + } + } + } +} diff --git a/cmd/getConfig_test.go b/cmd/getConfig_test.go index 7802d8f91..a280c2bc6 100644 --- a/cmd/getConfig_test.go +++ b/cmd/getConfig_test.go @@ -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")) +} diff --git a/cmd/kanikoExecute.go b/cmd/kanikoExecute.go index da0967118..898f77a0d 100644 --- a/cmd/kanikoExecute.go +++ b/cmd/kanikoExecute.go @@ -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...) } diff --git a/cmd/kanikoExecute_generated.go b/cmd/kanikoExecute_generated.go index 694b4822b..a5542c3cc 100644 --- a/cmd/kanikoExecute_generated.go +++ b/cmd/kanikoExecute_generated.go @@ -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{}, diff --git a/cmd/kanikoExecute_test.go b/cmd/kanikoExecute_test.go index 297334a5e..9a1d5b580 100644 --- a/cmd/kanikoExecute_test.go +++ b/cmd/kanikoExecute_test.go @@ -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") }) diff --git a/pkg/docker/container.go b/pkg/docker/container.go new file mode 100644 index 000000000..3a44e9278 --- /dev/null +++ b/pkg/docker/container.go @@ -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 +} diff --git a/pkg/docker/container_test.go b/pkg/docker/container_test.go new file mode 100644 index 000000000..cc2e106ed --- /dev/null +++ b/pkg/docker/container_test.go @@ -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) + }) + } +} diff --git a/pkg/piperenv/environment.go b/pkg/piperenv/environment.go index ee84e9b38..2f1ce0f6c 100644 --- a/pkg/piperenv/environment.go +++ b/pkg/piperenv/environment.go @@ -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 } diff --git a/resources/metadata/kaniko.yaml b/resources/metadata/kaniko.yaml index 603d24c26..2f6705181 100644 --- a/resources/metadata/kaniko.yaml +++ b/resources/metadata/kaniko.yaml @@ -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: diff --git a/resources/metadata/mtaBuild.yaml b/resources/metadata/mtaBuild.yaml index 924c0a128..c2d6c73fd 100644 --- a/resources/metadata/mtaBuild.yaml +++ b/resources/metadata/mtaBuild.yaml @@ -141,8 +141,6 @@ spec: type: piperEnvironment params: - name: mtarFilePath - fields: - - name: mtarFilePath containers: - image: devxci/mbtci:1.0.14.1 imagePullPolicy: Always diff --git a/resources/metadata/xsDeploy.yaml b/resources/metadata/xsDeploy.yaml index a9c9ac1e8..27fbf3ab3 100644 --- a/resources/metadata/xsDeploy.yaml +++ b/resources/metadata/xsDeploy.yaml @@ -155,8 +155,6 @@ spec: type: piperEnvironment params: - name: operationId - fields: - - name: operationId containers: - name: xs image: ppiper/xs-cli