You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	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:
		| @@ -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 { | ||||
|   | ||||
| @@ -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{}, | ||||
|   | ||||
| @@ -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() | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user