mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
fix(cnbBuild): read dockerConfigJSON from CPE and merge it with user-provided (#4444)
Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
This commit is contained in:
parent
1befaa80a2
commit
13f1e94ade
@ -7,7 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/buildsettings"
|
"github.com/SAP/jenkins-library/pkg/buildsettings"
|
||||||
"github.com/SAP/jenkins-library/pkg/certutils"
|
"github.com/SAP/jenkins-library/pkg/certutils"
|
||||||
@ -212,28 +211,26 @@ func extractZip(source, target string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func renameDockerConfig(config *cnbBuildOptions, utils cnbutils.BuildUtils) error {
|
func ensureDockerConfig(config *cnbBuildOptions, utils cnbutils.BuildUtils) error {
|
||||||
if filepath.Base(config.DockerConfigJSON) != "config.json" {
|
newFile := "/tmp/config.json"
|
||||||
log.Entry().Debugf("Renaming docker config file from '%s' to 'config.json'", filepath.Base(config.DockerConfigJSON))
|
if config.DockerConfigJSON == "" {
|
||||||
|
config.DockerConfigJSON = newFile
|
||||||
|
|
||||||
newPath := filepath.Join(filepath.Dir(config.DockerConfigJSON), "config.json")
|
return utils.FileWrite(config.DockerConfigJSON, []byte("{}"), os.ModePerm)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,38 +379,19 @@ func callCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData,
|
|||||||
}
|
}
|
||||||
commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo
|
commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo
|
||||||
|
|
||||||
if len(config.DockerConfigJSON) > 0 {
|
err = ensureDockerConfig(config, utils)
|
||||||
err = renameDockerConfig(config, utils)
|
if err != nil {
|
||||||
if err != nil {
|
log.SetErrorCategory(log.ErrorConfiguration)
|
||||||
log.SetErrorCategory(log.ErrorConfiguration)
|
return errors.Wrapf(err, "failed to create/rename DockerConfigJSON file")
|
||||||
return errors.Wrapf(err, "failed to rename DockerConfigJSON file '%s'", config.DockerConfigJSON)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ContainerRegistryUser != "" && config.ContainerRegistryPassword != "" {
|
if config.DockerConfigJSONCPE != "" {
|
||||||
log.Entry().Debug("enhancing docker config with the provided credentials")
|
log.Entry().Debugf("merging docker config file '%s' into '%s'", config.DockerConfigJSONCPE, config.DockerConfigJSON)
|
||||||
if config.DockerConfigJSON == "" {
|
err = docker.MergeDockerConfigJSON(config.DockerConfigJSONCPE, config.DockerConfigJSON, utils)
|
||||||
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 err != nil {
|
if err != nil {
|
||||||
log.SetErrorCategory(log.ErrorConfiguration)
|
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)
|
mergedConfigs, err := processConfigs(*config, config.MultipleImages)
|
||||||
|
@ -26,13 +26,12 @@ type cnbBuildOptions struct {
|
|||||||
ContainerImageAlias string `json:"containerImageAlias,omitempty"`
|
ContainerImageAlias string `json:"containerImageAlias,omitempty"`
|
||||||
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
||||||
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
|
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
|
||||||
ContainerRegistryUser string `json:"containerRegistryUser,omitempty"`
|
|
||||||
ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"`
|
|
||||||
Buildpacks []string `json:"buildpacks,omitempty"`
|
Buildpacks []string `json:"buildpacks,omitempty"`
|
||||||
BuildEnvVars map[string]interface{} `json:"buildEnvVars,omitempty"`
|
BuildEnvVars map[string]interface{} `json:"buildEnvVars,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
ProjectDescriptor string `json:"projectDescriptor,omitempty"`
|
ProjectDescriptor string `json:"projectDescriptor,omitempty"`
|
||||||
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
|
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
|
||||||
|
DockerConfigJSONCPE string `json:"dockerConfigJSONCPE,omitempty"`
|
||||||
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
|
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
|
||||||
AdditionalTags []string `json:"additionalTags,omitempty"`
|
AdditionalTags []string `json:"additionalTags,omitempty"`
|
||||||
Bindings map[string]interface{} `json:"bindings,omitempty"`
|
Bindings map[string]interface{} `json:"bindings,omitempty"`
|
||||||
@ -158,6 +157,7 @@ func CnbBuildCommand() *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.RegisterSecret(stepConfig.DockerConfigJSON)
|
log.RegisterSecret(stepConfig.DockerConfigJSON)
|
||||||
|
log.RegisterSecret(stepConfig.DockerConfigJSONCPE)
|
||||||
|
|
||||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
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.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.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.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().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.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.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.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.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\".")
|
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"}},
|
Aliases: []config.Alias{{Name: "dockerRegistryUrl"}},
|
||||||
Default: os.Getenv("PIPER_containerRegistryUrl"),
|
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",
|
Name: "buildpacks",
|
||||||
ResourceRef: []config.ResourceReference{
|
ResourceRef: []config.ResourceReference{
|
||||||
@ -400,6 +371,20 @@ func cnbBuildMetadata() config.StepData {
|
|||||||
Aliases: []config.Alias{},
|
Aliases: []config.Alias{},
|
||||||
Default: os.Getenv("PIPER_dockerConfigJSON"),
|
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",
|
Name: "customTlsCertificateLinks",
|
||||||
ResourceRef: []config.ResourceReference{},
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
@ -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:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
|
||||||
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:latest", config.ContainerRegistryURL, config.ContainerImageName))
|
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:latest", config.ContainerRegistryURL, config.ContainerImageName))
|
||||||
|
|
||||||
initialFileExists, _ := utils.FileExists("/path/to/test.json")
|
copiedFileExists, _ := utils.FileExists("/tmp/config.json")
|
||||||
renamedFileExists, _ := utils.FileExists("/path/to/config.json")
|
assert.True(t, copiedFileExists)
|
||||||
|
|
||||||
assert.False(t, initialFileExists)
|
|
||||||
assert.True(t, renamedFileExists)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("success case (customTlsCertificates)", func(t *testing.T) {
|
t.Run("success case (customTlsCertificates)", func(t *testing.T) {
|
||||||
@ -420,7 +417,7 @@ func TestRunCnbBuild(t *testing.T) {
|
|||||||
addBuilderFiles(&utils)
|
addBuilderFiles(&utils)
|
||||||
|
|
||||||
err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
|
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) {
|
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)
|
addBuilderFiles(&utils)
|
||||||
|
|
||||||
err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
|
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) {
|
t.Run("error case: dockerImage is not a valid builder", func(t *testing.T) {
|
||||||
|
@ -44,7 +44,7 @@ func TestCNBIntegrationNPMProject(t *testing.T) {
|
|||||||
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
|
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
|
||||||
Image: baseBuilder,
|
Image: baseBuilder,
|
||||||
User: "cnb",
|
User: "cnb",
|
||||||
TestDir: []string{"testdata"},
|
TestDir: []string{"testdata", "TestCnbIntegration"},
|
||||||
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
|
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
|
||||||
Environment: map[string]string{
|
Environment: map[string]string{
|
||||||
"PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content",
|
"PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content",
|
||||||
@ -54,14 +54,14 @@ func TestCNBIntegrationNPMProject(t *testing.T) {
|
|||||||
container2 := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
|
container2 := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
|
||||||
Image: baseBuilder,
|
Image: baseBuilder,
|
||||||
User: "cnb",
|
User: "cnb",
|
||||||
TestDir: []string{"testdata"},
|
TestDir: []string{"testdata", "TestCnbIntegration"},
|
||||||
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
|
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
|
||||||
Environment: map[string]string{
|
Environment: map[string]string{
|
||||||
"PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content",
|
"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)
|
assert.NoError(t, err)
|
||||||
container.assertHasOutput(t, "running command: /cnb/lifecycle/creator")
|
container.assertHasOutput(t, "running command: /cnb/lifecycle/creator")
|
||||||
container.assertHasOutput(t, "Selected Node Engine version (using BP_NODE_VERSION): 16")
|
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, "Setting default process type 'greeter'")
|
||||||
container.assertHasOutput(t, "*** Images (sha256:")
|
container.assertHasOutput(t, "*** Images (sha256:")
|
||||||
container.assertHasOutput(t, "SUCCESS")
|
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)
|
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)
|
assert.NoError(t, err)
|
||||||
container2.assertHasOutput(t, "running command: /cnb/lifecycle/creator")
|
container2.assertHasOutput(t, "running command: /cnb/lifecycle/creator")
|
||||||
container2.assertHasOutput(t, "Selected Node Engine version (using BP_NODE_VERSION): 16")
|
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, fmt.Sprintf("Saving %s/node:0.0.1", registryURL))
|
||||||
container2.assertHasOutput(t, "*** Images (sha256:")
|
container2.assertHasOutput(t, "*** Images (sha256:")
|
||||||
container2.assertHasOutput(t, "SUCCESS")
|
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)
|
container2.terminate(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,17 +243,16 @@ func TestCNBIntegrationBindings(t *testing.T) {
|
|||||||
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
|
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
|
||||||
Image: baseBuilder,
|
Image: baseBuilder,
|
||||||
User: "cnb",
|
User: "cnb",
|
||||||
TestDir: []string{"testdata"},
|
TestDir: []string{"testdata", "TestCnbIntegration"},
|
||||||
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
|
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
|
||||||
Environment: map[string]string{
|
Environment: map[string]string{
|
||||||
"PIPER_VAULTCREDENTIAL_DYNATRACE_API_KEY": "api-key-content",
|
"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")
|
err := container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--customConfig", "config.yml", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL, "--path", "project")
|
||||||
assert.Error(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
container.assertHasOutput(t, "bindings/maven-settings/settings.xml: only whitespace content allowed before start tag")
|
|
||||||
container.assertHasFiles(t,
|
container.assertHasFiles(t,
|
||||||
"/tmp/platform/bindings/dummy-binding/type",
|
"/tmp/platform/bindings/dummy-binding/type",
|
||||||
"/tmp/platform/bindings/dummy-binding/dummy.yml",
|
"/tmp/platform/bindings/dummy-binding/dummy.yml",
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
.pipeline/config.json
|
5
integration/testdata/TestCnbIntegration/.pipeline/config.json
vendored
Normal file
5
integration/testdata/TestCnbIntegration/.pipeline/config.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"auths": {
|
||||||
|
"test2.registry.io": {}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ steps:
|
|||||||
type: dummy
|
type: dummy
|
||||||
data:
|
data:
|
||||||
- key: dummy.yml
|
- key: dummy.yml
|
||||||
file: TestCnbIntegration/config.yml
|
file: config.yml
|
||||||
dynatrace:
|
dynatrace:
|
||||||
type: Dynatrace
|
type: Dynatrace
|
||||||
data:
|
data:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,6 +15,9 @@ import (
|
|||||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||||
"github.com/pkg/errors"
|
"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"
|
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/crane"
|
"github.com/google/go-containerregistry/pkg/crane"
|
||||||
@ -27,6 +31,61 @@ type AuthEntry struct {
|
|||||||
Auth string `json:"auth,omitempty"`
|
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
|
// CreateDockerConfigJSON creates / updates a Docker config.json with registry credentials
|
||||||
func CreateDockerConfigJSON(registryURL, username, password, targetPath, configPath string, utils piperutils.FileUtils) (string, error) {
|
func CreateDockerConfigJSON(registryURL, username, password, targetPath, configPath string, utils piperutils.FileUtils) (string, error) {
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -95,32 +95,6 @@ spec:
|
|||||||
resourceRef:
|
resourceRef:
|
||||||
- name: commonPipelineEnvironment
|
- name: commonPipelineEnvironment
|
||||||
param: container/registryUrl
|
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
|
- name: buildpacks
|
||||||
type: "[]string"
|
type: "[]string"
|
||||||
description: List of custom buildpacks to use in the form of `$HOSTNAME/$REPO[:$TAG]`.
|
description: List of custom buildpacks to use in the form of `$HOSTNAME/$REPO[:$TAG]`.
|
||||||
@ -181,6 +155,13 @@ spec:
|
|||||||
- type: vaultSecretFile
|
- type: vaultSecretFile
|
||||||
name: dockerConfigFileVaultSecretName
|
name: dockerConfigFileVaultSecretName
|
||||||
default: docker-config
|
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
|
- name: customTlsCertificateLinks
|
||||||
type: "[]string"
|
type: "[]string"
|
||||||
description: List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.
|
description: List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.
|
||||||
|
Loading…
Reference in New Issue
Block a user