diff --git a/cmd/cnbBuild.go b/cmd/cnbBuild.go index 6b3dd3fa2..4ef75a4c0 100644 --- a/cmd/cnbBuild.go +++ b/cmd/cnbBuild.go @@ -7,7 +7,6 @@ import ( "os" "path" "path/filepath" - "regexp" "github.com/SAP/jenkins-library/pkg/buildsettings" "github.com/SAP/jenkins-library/pkg/certutils" @@ -212,28 +211,26 @@ func extractZip(source, target string) error { return nil } -func renameDockerConfig(config *cnbBuildOptions, utils cnbutils.BuildUtils) error { - if filepath.Base(config.DockerConfigJSON) != "config.json" { - log.Entry().Debugf("Renaming docker config file from '%s' to 'config.json'", filepath.Base(config.DockerConfigJSON)) +func ensureDockerConfig(config *cnbBuildOptions, utils cnbutils.BuildUtils) error { + newFile := "/tmp/config.json" + if config.DockerConfigJSON == "" { + config.DockerConfigJSON = newFile - newPath := filepath.Join(filepath.Dir(config.DockerConfigJSON), "config.json") - alreadyExists, err := utils.FileExists(newPath) - if err != nil { - return err - } - - if alreadyExists { - return nil - } - - err = utils.FileRename(config.DockerConfigJSON, newPath) - if err != nil { - return err - } - - config.DockerConfigJSON = newPath + return utils.FileWrite(config.DockerConfigJSON, []byte("{}"), os.ModePerm) } + log.Entry().Debugf("Copying docker config file from '%s' to '%s'", config.DockerConfigJSON, newFile) + _, err := utils.Copy(config.DockerConfigJSON, newFile) + if err != nil { + return err + } + + err = utils.Chmod(newFile, 0644) + if err != nil { + return err + } + + config.DockerConfigJSON = newFile return nil } @@ -382,38 +379,19 @@ func callCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, } commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo - if len(config.DockerConfigJSON) > 0 { - err = renameDockerConfig(config, utils) - if err != nil { - log.SetErrorCategory(log.ErrorConfiguration) - return errors.Wrapf(err, "failed to rename DockerConfigJSON file '%s'", config.DockerConfigJSON) - } + err = ensureDockerConfig(config, utils) + if err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return errors.Wrapf(err, "failed to create/rename DockerConfigJSON file") } - if config.ContainerRegistryUser != "" && config.ContainerRegistryPassword != "" { - log.Entry().Debug("enhancing docker config with the provided credentials") - if config.DockerConfigJSON == "" { - config.DockerConfigJSON = "/tmp/config.json" - } - log.Entry().Debugf("using docker config file %q", config.DockerConfigJSON) - - if matched, _ := regexp.MatchString("^(http|https)://.*", config.ContainerRegistryURL); !matched { - config.ContainerRegistryURL = fmt.Sprintf("https://%s", config.ContainerRegistryURL) - } - - containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL) + if config.DockerConfigJSONCPE != "" { + log.Entry().Debugf("merging docker config file '%s' into '%s'", config.DockerConfigJSONCPE, config.DockerConfigJSON) + err = docker.MergeDockerConfigJSON(config.DockerConfigJSONCPE, config.DockerConfigJSON, utils) if err != nil { log.SetErrorCategory(log.ErrorConfiguration) - return errors.Wrapf(err, "failed to read registry url %q", config.ContainerRegistryURL) + return errors.Wrapf(err, "failed to merge DockerConfigJSON files") } - - _, err = docker.CreateDockerConfigJSON(containerRegistry, config.ContainerRegistryUser, config.ContainerRegistryPassword, "", config.DockerConfigJSON, utils) - if err != nil { - log.SetErrorCategory(log.ErrorBuild) - return errors.Wrapf(err, "failed to update DockerConfigJSON file %q", config.DockerConfigJSON) - } - - log.Entry().Debugf("docker config %q has been updated", config.DockerConfigJSON) } mergedConfigs, err := processConfigs(*config, config.MultipleImages) diff --git a/cmd/cnbBuild_generated.go b/cmd/cnbBuild_generated.go index c714123ed..945daa660 100644 --- a/cmd/cnbBuild_generated.go +++ b/cmd/cnbBuild_generated.go @@ -26,13 +26,12 @@ type cnbBuildOptions struct { ContainerImageAlias string `json:"containerImageAlias,omitempty"` ContainerImageTag string `json:"containerImageTag,omitempty"` ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"` - ContainerRegistryUser string `json:"containerRegistryUser,omitempty"` - ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"` Buildpacks []string `json:"buildpacks,omitempty"` BuildEnvVars map[string]interface{} `json:"buildEnvVars,omitempty"` Path string `json:"path,omitempty"` ProjectDescriptor string `json:"projectDescriptor,omitempty"` DockerConfigJSON string `json:"dockerConfigJSON,omitempty"` + DockerConfigJSONCPE string `json:"dockerConfigJSONCPE,omitempty"` CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"` AdditionalTags []string `json:"additionalTags,omitempty"` Bindings map[string]interface{} `json:"bindings,omitempty"` @@ -158,6 +157,7 @@ func CnbBuildCommand() *cobra.Command { return err } log.RegisterSecret(stepConfig.DockerConfigJSON) + log.RegisterSecret(stepConfig.DockerConfigJSONCPE) if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) @@ -226,13 +226,12 @@ func addCnbBuildFlags(cmd *cobra.Command, stepConfig *cnbBuildOptions) { cmd.Flags().StringVar(&stepConfig.ContainerImageAlias, "containerImageAlias", os.Getenv("PIPER_containerImageAlias"), "Logical name used for this image.\n") cmd.Flags().StringVar(&stepConfig.ContainerImageTag, "containerImageTag", os.Getenv("PIPER_containerImageTag"), "Tag of the container which will be built") cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "Container registry where the image should be pushed to.\n\n**Note**: `containerRegistryUrl` should include only the domain. If you want to publish an image under `docker.io/example/my-image`, you must set `containerRegistryUrl: \"docker.io\"` and `containerImageName: \"example/my-image\"`.\n") - cmd.Flags().StringVar(&stepConfig.ContainerRegistryUser, "containerRegistryUser", os.Getenv("PIPER_containerRegistryUser"), "Username of the container registry where the image should be pushed to - which will updated in a docker config json file. If a docker config json file is provided via parameter `dockerConfigJSON`, then the existing file will be enhanced") - cmd.Flags().StringVar(&stepConfig.ContainerRegistryPassword, "containerRegistryPassword", os.Getenv("PIPER_containerRegistryPassword"), "Password of the container registry where the image should be pushed to - which will updated in a docker config json file. If a docker config json file is provided via parameter `dockerConfigJSON`, then the existing file will be enhanced") cmd.Flags().StringSliceVar(&stepConfig.Buildpacks, "buildpacks", []string{}, "List of custom buildpacks to use in the form of `$HOSTNAME/$REPO[:$TAG]`.") cmd.Flags().StringVar(&stepConfig.Path, "path", os.Getenv("PIPER_path"), "Glob that should either point to a directory with your sources or one artifact in zip format.\nThis property determines the input to the buildpack.\n") cmd.Flags().StringVar(&stepConfig.ProjectDescriptor, "projectDescriptor", `project.toml`, "Relative path to the project.toml file.\nSee [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.\nParameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file, except the `env` block.\nEnvironment variables declared in a project descriptor file, will be merged with the `buildEnvVars` property, with the `buildEnvVars` having a precedence.\n\n*Note*: The project descriptor path should be relative to what is set in the [path](#path) property. If the `path` property is pointing to a zip archive (e.g. jar file), project descriptor path will be relative to the root of the workspace.\n\n*Note*: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.\n") 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.DockerConfigJSONCPE, "dockerConfigJSONCPE", os.Getenv("PIPER_dockerConfigJSONCPE"), "This property is intended only for reading the `dockerConfigJSON` from the Common Pipeline Environment. If you want to provide your own credentials, please refer to the [dockerConfigJSON](#dockerConfigJSON) property. If both properties are set, the config files will be merged, with the [dockerConfigJSON](#dockerConfigJSON) having higher priority.") 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().StringSliceVar(&stepConfig.AdditionalTags, "additionalTags", []string{}, "List of tags which will be pushed to the registry (additionally to the provided `containerImageTag`), e.g. \"latest\".") @@ -312,34 +311,6 @@ func cnbBuildMetadata() config.StepData { Aliases: []config.Alias{{Name: "dockerRegistryUrl"}}, Default: os.Getenv("PIPER_containerRegistryUrl"), }, - { - Name: "containerRegistryUser", - ResourceRef: []config.ResourceReference{ - { - Name: "commonPipelineEnvironment", - Param: "container/repositoryUsername", - }, - }, - Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, - Type: "string", - Mandatory: false, - Aliases: []config.Alias{{Name: "dockerRegistryUser"}}, - Default: os.Getenv("PIPER_containerRegistryUser"), - }, - { - Name: "containerRegistryPassword", - ResourceRef: []config.ResourceReference{ - { - Name: "commonPipelineEnvironment", - Param: "container/repositoryPassword", - }, - }, - Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, - Type: "string", - Mandatory: false, - Aliases: []config.Alias{{Name: "dockerRegistryPassword"}}, - Default: os.Getenv("PIPER_containerRegistryPassword"), - }, { Name: "buildpacks", ResourceRef: []config.ResourceReference{ @@ -400,6 +371,20 @@ func cnbBuildMetadata() config.StepData { Aliases: []config.Alias{}, Default: os.Getenv("PIPER_dockerConfigJSON"), }, + { + Name: "dockerConfigJSONCPE", + ResourceRef: []config.ResourceReference{ + { + Name: "commonPipelineEnvironment", + Param: "custom/dockerConfigJSON", + }, + }, + Scope: []string{}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_dockerConfigJSONCPE"), + }, { Name: "customTlsCertificateLinks", ResourceRef: []config.ResourceReference{}, diff --git a/cmd/cnbBuild_test.go b/cmd/cnbBuild_test.go index 98f8b0f69..2f1fa8226 100644 --- a/cmd/cnbBuild_test.go +++ b/cmd/cnbBuild_test.go @@ -230,11 +230,8 @@ func TestRunCnbBuild(t *testing.T) { assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag)) assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:latest", config.ContainerRegistryURL, config.ContainerImageName)) - initialFileExists, _ := utils.FileExists("/path/to/test.json") - renamedFileExists, _ := utils.FileExists("/path/to/config.json") - - assert.False(t, initialFileExists) - assert.True(t, renamedFileExists) + copiedFileExists, _ := utils.FileExists("/tmp/config.json") + assert.True(t, copiedFileExists) }) t.Run("success case (customTlsCertificates)", func(t *testing.T) { @@ -420,7 +417,7 @@ func TestRunCnbBuild(t *testing.T) { addBuilderFiles(&utils) err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{}) - assert.EqualError(t, err, "failed to generate CNB_REGISTRY_AUTH: could not read 'not-there/config.json'") + assert.EqualError(t, err, "failed to create/rename DockerConfigJSON file: cannot copy 'not-there/config.json': file does not exist") }) t.Run("error case: DockerConfigJSON file not there (not config.json)", func(t *testing.T) { @@ -436,7 +433,7 @@ func TestRunCnbBuild(t *testing.T) { addBuilderFiles(&utils) err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{}) - assert.EqualError(t, err, "failed to rename DockerConfigJSON file 'not-there': renaming file 'not-there' is not supported, since it does not exist, or is not a leaf-entry") + assert.EqualError(t, err, "failed to create/rename DockerConfigJSON file: cannot copy 'not-there': file does not exist") }) t.Run("error case: dockerImage is not a valid builder", func(t *testing.T) { diff --git a/integration/integration_cnb_test.go b/integration/integration_cnb_test.go index 159cb0bfe..944651cf8 100644 --- a/integration/integration_cnb_test.go +++ b/integration/integration_cnb_test.go @@ -44,7 +44,7 @@ func TestCNBIntegrationNPMProject(t *testing.T) { container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ Image: baseBuilder, User: "cnb", - TestDir: []string{"testdata"}, + TestDir: []string{"testdata", "TestCnbIntegration"}, Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Environment: map[string]string{ "PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content", @@ -54,14 +54,14 @@ func TestCNBIntegrationNPMProject(t *testing.T) { container2 := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ Image: baseBuilder, User: "cnb", - TestDir: []string{"testdata"}, + TestDir: []string{"testdata", "TestCnbIntegration"}, Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Environment: map[string]string{ "PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content", }, }) - err := container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--path", "TestCnbIntegration/project", "--customConfig", "TestCnbIntegration/config.yml", "--containerImageName", "node", "--containerImageTag", "0.0.1", "--dockerConfigJSON", "TestCnbIntegration/config.json", "--containerRegistryUrl", fmt.Sprintf("http://%s", registryURL), "--containerRegistryUser", "foo", "--containerRegistryPassword", "bar", "--defaultProcess", "greeter") + err := container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--path", "project", "--customConfig", "config.yml", "--containerImageName", "node", "--containerImageTag", "0.0.1", "--dockerConfigJSON", "config.json", "--containerRegistryUrl", fmt.Sprintf("http://%s", registryURL), "--defaultProcess", "greeter") assert.NoError(t, err) container.assertHasOutput(t, "running command: /cnb/lifecycle/creator") container.assertHasOutput(t, "Selected Node Engine version (using BP_NODE_VERSION): 16") @@ -70,10 +70,10 @@ func TestCNBIntegrationNPMProject(t *testing.T) { container.assertHasOutput(t, "Setting default process type 'greeter'") container.assertHasOutput(t, "*** Images (sha256:") container.assertHasOutput(t, "SUCCESS") - container.assertFileContentEquals(t, "/project/TestCnbIntegration/config.json", "{\"auths\":{\"localhost:5000\":{\"auth\":\"Zm9vOmJhcg==\"},\"test.registry.io\":{}}}") + container.assertFileContentEquals(t, "/tmp/config.json", "{\n\t\"auths\": {\n\t\t\"test.registry.io\": {},\n\t\t\"test2.registry.io\": {}\n\t}\n}") container.terminate(t) - err = container2.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--path", "TestCnbIntegration/project", "--customConfig", "TestCnbIntegration/config.yml", "--containerImageName", "node", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL, "--containerRegistryUser", "foo", "--containerRegistryPassword", "bar", "--projectDescriptor", "project-with-id.toml") + err = container2.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--path", "project", "--customConfig", "config.yml", "--containerImageName", "node", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL, "--projectDescriptor", "project-with-id.toml") assert.NoError(t, err) container2.assertHasOutput(t, "running command: /cnb/lifecycle/creator") container2.assertHasOutput(t, "Selected Node Engine version (using BP_NODE_VERSION): 16") @@ -81,7 +81,7 @@ func TestCNBIntegrationNPMProject(t *testing.T) { container2.assertHasOutput(t, fmt.Sprintf("Saving %s/node:0.0.1", registryURL)) container2.assertHasOutput(t, "*** Images (sha256:") container2.assertHasOutput(t, "SUCCESS") - container2.assertFileContentEquals(t, "/tmp/config.json", "{\"auths\":{\"localhost:5000\":{\"auth\":\"Zm9vOmJhcg==\"}}}") + container2.assertFileContentEquals(t, "/tmp/config.json", "{\n\t\"auths\": {\n\t\t\"test2.registry.io\": {}\n\t}\n}") container2.terminate(t) } @@ -243,17 +243,16 @@ func TestCNBIntegrationBindings(t *testing.T) { container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ Image: baseBuilder, User: "cnb", - TestDir: []string{"testdata"}, + TestDir: []string{"testdata", "TestCnbIntegration"}, Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Environment: map[string]string{ "PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content", }, }) - err := container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--customConfig", "TestCnbIntegration/config.yml", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL, "--path", "TestMtaIntegration/maven") - assert.Error(t, err) + err := container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--customConfig", "config.yml", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL, "--path", "project") + assert.NoError(t, err) - container.assertHasOutput(t, "bindings/maven-settings/settings.xml: only whitespace content allowed before start tag") container.assertHasFiles(t, "/tmp/platform/bindings/dummy-binding/type", "/tmp/platform/bindings/dummy-binding/dummy.yml", diff --git a/integration/testdata/TestCnbIntegration/.pipeline/commonPipelineEnvironment/custom/dockerConfigJSON b/integration/testdata/TestCnbIntegration/.pipeline/commonPipelineEnvironment/custom/dockerConfigJSON new file mode 100644 index 000000000..737090703 --- /dev/null +++ b/integration/testdata/TestCnbIntegration/.pipeline/commonPipelineEnvironment/custom/dockerConfigJSON @@ -0,0 +1 @@ +.pipeline/config.json \ No newline at end of file diff --git a/integration/testdata/TestCnbIntegration/.pipeline/config.json b/integration/testdata/TestCnbIntegration/.pipeline/config.json new file mode 100644 index 000000000..3316381b5 --- /dev/null +++ b/integration/testdata/TestCnbIntegration/.pipeline/config.json @@ -0,0 +1,5 @@ +{ + "auths": { + "test2.registry.io": {} + } +} \ No newline at end of file diff --git a/integration/testdata/TestCnbIntegration/config.yml b/integration/testdata/TestCnbIntegration/config.yml index e2428c1c8..da73f0510 100644 --- a/integration/testdata/TestCnbIntegration/config.yml +++ b/integration/testdata/TestCnbIntegration/config.yml @@ -12,7 +12,7 @@ steps: type: dummy data: - key: dummy.yml - file: TestCnbIntegration/config.yml + file: config.yml dynatrace: type: Dynatrace data: diff --git a/pkg/docker/docker.go b/pkg/docker/docker.go index d2bb9d724..dc614119f 100644 --- a/pkg/docker/docker.go +++ b/pkg/docker/docker.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "encoding/base64" "encoding/json" "fmt" @@ -14,6 +15,9 @@ import ( "github.com/SAP/jenkins-library/pkg/piperutils" "github.com/pkg/errors" + "github.com/docker/cli/cli/config" + "github.com/docker/cli/cli/config/configfile" + cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/crane" @@ -27,6 +31,61 @@ type AuthEntry struct { Auth string `json:"auth,omitempty"` } +// MergeDockerConfigJSON merges two docker config.json files. +func MergeDockerConfigJSON(sourcePath, targetPath string, utils piperutils.FileUtils) error { + if exists, _ := utils.FileExists(sourcePath); !exists { + return fmt.Errorf("source dockerConfigJSON file %q does not exist", sourcePath) + } + + sourceReader, err := utils.Open(sourcePath) + if err != nil { + return errors.Wrapf(err, "failed to open file %q", sourcePath) + } + defer sourceReader.Close() + + sourceConfig, err := config.LoadFromReader(sourceReader) + if err != nil { + return errors.Wrapf(err, "failed to read file %q", sourcePath) + } + + var targetConfig *configfile.ConfigFile + if exists, _ := utils.FileExists(targetPath); !exists { + log.Entry().Warnf("target dockerConfigJSON file %q does not exist, creating a new one", sourcePath) + targetConfig = configfile.New(targetPath) + } else { + targetReader, err := utils.Open(targetPath) + if err != nil { + return errors.Wrapf(err, "failed to open file %q", targetReader) + } + defer targetReader.Close() + targetConfig, err = config.LoadFromReader(targetReader) + if err != nil { + return errors.Wrapf(err, "failed to read file %q", targetPath) + } + } + + for registry, auth := range sourceConfig.GetAuthConfigs() { + targetConfig.AuthConfigs[registry] = auth + } + + buf := bytes.NewBuffer(nil) + err = targetConfig.SaveToWriter(buf) + if err != nil { + return errors.Wrapf(err, "failed to save file %q", targetPath) + } + + err = utils.MkdirAll(filepath.Dir(targetPath), 0777) + if err != nil { + return fmt.Errorf("failed to create directory path for the file %q: %w", targetPath, err) + } + err = utils.FileWrite(targetPath, buf.Bytes(), 0666) + if err != nil { + return fmt.Errorf("failed to write %q: %w", targetPath, err) + } + + return nil +} + // CreateDockerConfigJSON creates / updates a Docker config.json with registry credentials func CreateDockerConfigJSON(registryURL, username, password, targetPath, configPath string, utils piperutils.FileUtils) (string, error) { diff --git a/pkg/docker/docker_test.go b/pkg/docker/docker_test.go index 399651fd3..d3d8d8992 100644 --- a/pkg/docker/docker_test.go +++ b/pkg/docker/docker_test.go @@ -148,3 +148,47 @@ func TestImageListWithFilePath(t *testing.T) { }) } } + +func TestMergeDockerConfigJSON(t *testing.T) { + t.Parallel() + + t.Run("success - both files present", func(t *testing.T) { + sourceFile := "/tmp/source.json" + targetFile := "/tmp/target.json" + expectedContent := "{\n\t\"auths\": {\n\t\t\"bar\": {},\n\t\t\"foo\": {\n\t\t\t\"auth\": \"Zm9vOmJhcg==\"\n\t\t}\n\t}\n}" + + utilsMock := mock.FilesMock{} + utilsMock.AddFile(targetFile, []byte("{\"auths\": {\"foo\": {\"auth\": \"dGVzdDp0ZXN0\"}}}")) + utilsMock.AddFile(sourceFile, []byte("{\"auths\": {\"bar\": {}, \"foo\": {\"auth\": \"Zm9vOmJhcg==\"}}}")) + + err := MergeDockerConfigJSON(sourceFile, targetFile, &utilsMock) + assert.NoError(t, err) + + content, err := utilsMock.FileRead(targetFile) + assert.NoError(t, err) + assert.Equal(t, expectedContent, string(content)) + }) + + t.Run("success - target file is missing", func(t *testing.T) { + sourceFile := "/tmp/source.json" + targetFile := "/tmp/target.json" + expectedContent := "{\n\t\"auths\": {\n\t\t\"bar\": {},\n\t\t\"foo\": {\n\t\t\t\"auth\": \"Zm9vOmJhcg==\"\n\t\t}\n\t}\n}" + + utilsMock := mock.FilesMock{} + utilsMock.AddFile(sourceFile, []byte("{\"auths\": {\"bar\": {}, \"foo\": {\"auth\": \"Zm9vOmJhcg==\"}}}")) + + err := MergeDockerConfigJSON(sourceFile, targetFile, &utilsMock) + assert.NoError(t, err) + + content, err := utilsMock.FileRead(targetFile) + assert.NoError(t, err) + assert.Equal(t, expectedContent, string(content)) + }) + + t.Run("error - source file is missing", func(t *testing.T) { + utilsMock := mock.FilesMock{} + err := MergeDockerConfigJSON("missing-file", "also-missing-file", &utilsMock) + assert.Error(t, err) + assert.Equal(t, "source dockerConfigJSON file \"missing-file\" does not exist", err.Error()) + }) +} diff --git a/resources/metadata/cnbBuild.yaml b/resources/metadata/cnbBuild.yaml index 809ca5dbd..9fe08524f 100644 --- a/resources/metadata/cnbBuild.yaml +++ b/resources/metadata/cnbBuild.yaml @@ -95,32 +95,6 @@ spec: resourceRef: - name: commonPipelineEnvironment param: container/registryUrl - - name: containerRegistryUser - aliases: - - name: dockerRegistryUser - type: string - description: Username of the container registry where the image should be pushed to - which will updated in a docker config json file. If a docker config json file is provided via parameter `dockerConfigJSON`, then the existing file will be enhanced - scope: - - GENERAL - - PARAMETERS - - STAGES - - STEPS - resourceRef: - - name: commonPipelineEnvironment - param: container/repositoryUsername - - name: containerRegistryPassword - aliases: - - name: dockerRegistryPassword - type: string - description: Password of the container registry where the image should be pushed to - which will updated in a docker config json file. If a docker config json file is provided via parameter `dockerConfigJSON`, then the existing file will be enhanced - scope: - - GENERAL - - PARAMETERS - - STAGES - - STEPS - resourceRef: - - name: commonPipelineEnvironment - param: container/repositoryPassword - name: buildpacks type: "[]string" description: List of custom buildpacks to use in the form of `$HOSTNAME/$REPO[:$TAG]`. @@ -181,6 +155,13 @@ spec: - type: vaultSecretFile name: dockerConfigFileVaultSecretName default: docker-config + - name: dockerConfigJSONCPE + type: string + description: This property is intended only for reading the `dockerConfigJSON` from the Common Pipeline Environment. If you want to provide your own credentials, please refer to the [dockerConfigJSON](#dockerConfigJSON) property. If both properties are set, the config files will be merged, with the [dockerConfigJSON](#dockerConfigJSON) having higher priority. + secret: true + resourceRef: + - name: commonPipelineEnvironment + param: custom/dockerConfigJSON - 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.