1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

feat(imagePushToRegistry): Support imageNameTags (#4853)

* add imageNameTags related parameters to step

* fix registry+imageNameTags

* add debug logging

* remove debug logging

* update parameter docs

---------

Co-authored-by: jliempt <>
This commit is contained in:
Jordi van Liempt
2024-03-18 13:46:35 +01:00
committed by GitHub
parent df2e976eaa
commit 8bf6298250
4 changed files with 138 additions and 1 deletions

View File

@@ -81,7 +81,7 @@ func imagePushToRegistry(config imagePushToRegistryOptions, telemetryData *telem
}
func runImagePushToRegistry(config *imagePushToRegistryOptions, telemetryData *telemetry.CustomData, utils imagePushToRegistryUtils) error {
if !config.PushLocalDockerImage {
if !config.PushLocalDockerImage && !config.UseImageNameTags {
if len(config.TargetImages) == 0 {
config.TargetImages = mapSourceTargetImages(config.SourceImages)
}
@@ -91,6 +91,13 @@ func runImagePushToRegistry(config *imagePushToRegistryOptions, telemetryData *t
}
}
if config.UseImageNameTags {
if len(config.TargetImageNameTags) > 0 && len(config.TargetImageNameTags) != len(config.SourceImageNameTags) {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.New("configuration error: please configure targetImageNameTags and sourceImageNameTags properly")
}
}
// Docker image tags don't allow plus signs in tags, thus replacing with dash
config.SourceImageTag = strings.ReplaceAll(config.SourceImageTag, "+", "-")
config.TargetImageTag = strings.ReplaceAll(config.TargetImageTag, "+", "-")
@@ -115,6 +122,13 @@ func runImagePushToRegistry(config *imagePushToRegistryOptions, telemetryData *t
return errors.Wrap(err, "failed to handle credentials for source registry")
}
if config.UseImageNameTags {
if err := pushImageNameTagsToTargetRegistry(config, utils); err != nil {
return errors.Wrapf(err, "failed to push imageNameTags to target registry")
}
return nil
}
if err := copyImages(config, utils); err != nil {
return errors.Wrap(err, "failed to copy images")
}
@@ -242,6 +256,37 @@ func pushLocalImageToTargetRegistry(config *imagePushToRegistryOptions, utils im
return nil
}
func pushImageNameTagsToTargetRegistry(config *imagePushToRegistryOptions, utils imagePushToRegistryUtils) error {
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(10)
for i, sourceImageNameTag := range config.SourceImageNameTags {
src := fmt.Sprintf("%s/%s", config.SourceRegistryURL, sourceImageNameTag)
dst := ""
if len(config.TargetImageNameTags) == 0 {
dst = fmt.Sprintf("%s/%s", config.TargetRegistryURL, sourceImageNameTag)
} else {
dst = fmt.Sprintf("%s/%s", config.TargetRegistryURL, config.TargetImageNameTags[i])
}
g.Go(func() error {
log.Entry().Infof("Copying %s to %s...", src, dst)
if err := utils.CopyImage(ctx, src, dst, ""); err != nil {
return err
}
log.Entry().Infof("Copying %s to %s... Done", src, dst)
return nil
})
}
if err := g.Wait(); err != nil {
return err
}
return nil
}
func mapSourceTargetImages(sourceImages []string) map[string]any {
targetImages := make(map[string]any, len(sourceImages))
for _, sourceImage := range sourceImages {

View File

@@ -26,6 +26,9 @@ type imagePushToRegistryOptions struct {
TargetRegistryUser string `json:"targetRegistryUser,omitempty"`
TargetRegistryPassword string `json:"targetRegistryPassword,omitempty"`
TargetImageTag string `json:"targetImageTag,omitempty" validate:"required_if=TagLatest false"`
UseImageNameTags bool `json:"useImageNameTags,omitempty"`
SourceImageNameTags []string `json:"sourceImageNameTags,omitempty"`
TargetImageNameTags []string `json:"targetImageNameTags,omitempty"`
TagLatest bool `json:"tagLatest,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
PushLocalDockerImage bool `json:"pushLocalDockerImage,omitempty"`
@@ -151,6 +154,9 @@ func addImagePushToRegistryFlags(cmd *cobra.Command, stepConfig *imagePushToRegi
cmd.Flags().StringVar(&stepConfig.TargetRegistryUser, "targetRegistryUser", os.Getenv("PIPER_targetRegistryUser"), "Username of the target registry where the image should be pushed to.")
cmd.Flags().StringVar(&stepConfig.TargetRegistryPassword, "targetRegistryPassword", os.Getenv("PIPER_targetRegistryPassword"), "Password of the target registry where the image should be pushed to.")
cmd.Flags().StringVar(&stepConfig.TargetImageTag, "targetImageTag", os.Getenv("PIPER_targetImageTag"), "Tag of the targetImages")
cmd.Flags().BoolVar(&stepConfig.UseImageNameTags, "useImageNameTags", false, "Will use the sourceImageNameTags and targetImageNameTags parameters, instead of sourceImages and targetImages.\nsourceImageNameTags can be set by a build step, e.g. kanikoExecute, and is then available in the pipeline environment.\n")
cmd.Flags().StringSliceVar(&stepConfig.SourceImageNameTags, "sourceImageNameTags", []string{}, "List of full names (registry and tag) of the images to be copied. Works in combination with useImageNameTags.")
cmd.Flags().StringSliceVar(&stepConfig.TargetImageNameTags, "targetImageNameTags", []string{}, "List of full names (registry and tag) of the images to be deployed. Works in combination with useImageNameTags.\nIf not set, the value will be the sourceImageNameTags with the targetRegistryUrl incorporated.\n")
cmd.Flags().BoolVar(&stepConfig.TagLatest, "tagLatest", false, "Defines if the image should be tagged as `latest`. The parameter is true if targetImageTag is not specified.")
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().BoolVar(&stepConfig.PushLocalDockerImage, "pushLocalDockerImage", false, "Defines if the local image should be pushed to registry")
@@ -319,6 +325,38 @@ func imagePushToRegistryMetadata() config.StepData {
Aliases: []config.Alias{{Name: "artifactVersion"}, {Name: "containerImageTag"}},
Default: os.Getenv("PIPER_targetImageTag"),
},
{
Name: "useImageNameTags",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
{
Name: "sourceImageNameTags",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "container/imageNameTags",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "targetImageNameTags",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "tagLatest",
ResourceRef: []config.ResourceReference{},

View File

@@ -71,6 +71,32 @@ func TestRunImagePushToRegistry(t *testing.T) {
assert.Equal(t, "1.0.0-123-456", config.TargetImageTag)
})
t.Run("multiple imageNameTags", func(t *testing.T) {
t.Parallel()
config := imagePushToRegistryOptions{
SourceRegistryURL: "https://source.registry",
SourceImages: []string{"source-image"},
SourceImageNameTags: []string{"com.sap.docker/ppiper:240104-20240227184612",
"com.sap.docker/ppiper:240104-20240227184612-amd64",
"com.sap.docker/ppiper:240104-20240227184612-aarch64",
},
SourceRegistryUser: "sourceuser",
SourceRegistryPassword: "sourcepassword",
TargetRegistryURL: "https://target.registry",
TargetImageTag: "1.0.0-123+456",
TargetRegistryUser: "targetuser",
TargetRegistryPassword: "targetpassword",
UseImageNameTags: true,
}
craneMockUtils := &dockermock.CraneMockUtils{}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := runImagePushToRegistry(&config, nil, utils)
assert.NoError(t, err)
createdConfig, err := utils.FileRead(targetDockerConfigPath)
assert.Equal(t, customDockerConfig, string(createdConfig))
})
t.Run("failed to copy image", func(t *testing.T) {
t.Parallel()

View File

@@ -171,6 +171,34 @@ spec:
resourceRef:
- name: commonPipelineEnvironment
param: artifactVersion
- name: useImageNameTags
description: |
Will use the sourceImageNameTags and targetImageNameTags parameters, instead of sourceImages and targetImages.
sourceImageNameTags can be set by a build step, e.g. kanikoExecute, and is then available in the pipeline environment.
type: bool
scope:
- PARAMETERS
- STAGES
- STEPS
- name: sourceImageNameTags
type: "[]string"
description: "List of full names (registry and tag) of the images to be copied. Works in combination with useImageNameTags."
resourceRef:
- name: commonPipelineEnvironment
param: container/imageNameTags
scope:
- PARAMETERS
- STAGES
- STEPS
- name: targetImageNameTags
type: "[]string"
description: |
List of full names (registry and tag) of the images to be deployed. Works in combination with useImageNameTags.
If not set, the value will be the sourceImageNameTags with the targetRegistryUrl incorporated.
scope:
- PARAMETERS
- STAGES
- STEPS
- name: tagLatest
description: "Defines if the image should be tagged as `latest`. The parameter is true if targetImageTag is not specified."
type: bool