Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add imagePushToRegistry step #4609

Merged
merged 78 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
6d035a1
imagePushToRegistry new step
anilkeshav27 Sep 8, 2023
f53566c
Merge branch 'master' into anil/ImagePushToRegistry
anilkeshav27 Oct 2, 2023
dc1aa1f
adding copy and push functionality
anilkeshav27 Oct 2, 2023
9d8f44e
including only copy correctly
anilkeshav27 Oct 4, 2023
6ac5480
groovy step for imagePushToRegistry
Jk1484 Oct 24, 2023
1020019
create .docker folder
Jk1484 Oct 24, 2023
6eaa638
imagePushToRegistry new step
anilkeshav27 Sep 8, 2023
b63d891
adding copy and push functionality
anilkeshav27 Oct 2, 2023
46cb37c
including only copy correctly
anilkeshav27 Oct 4, 2023
4c1fbd2
groovy step for imagePushToRegistry
Jk1484 Oct 24, 2023
58b1f90
create .docker folder
Jk1484 Oct 24, 2023
d577ea8
fix CopyImage
Nov 9, 2023
b1a3b06
test
Nov 15, 2023
bfac80c
test
Nov 15, 2023
9241449
Correct docker config path
vstarostin Nov 15, 2023
eb36ad0
Merge branch 'master' into vstarostin/push-to-registry
vstarostin Nov 15, 2023
0816ffe
Update
vstarostin Nov 16, 2023
a84b669
Update
vstarostin Nov 16, 2023
e4b5994
Update
vstarostin Nov 16, 2023
b3c1ae9
Update
vstarostin Nov 16, 2023
12477cd
Update
vstarostin Nov 16, 2023
4f21daa
Use creds from Vault
vstarostin Nov 16, 2023
cdf1f22
Use creds from Vault
vstarostin Nov 16, 2023
8cf105b
Use creds from Vault
vstarostin Nov 16, 2023
b7c96cf
Use creds from Vault
vstarostin Nov 16, 2023
a250f4c
Test
vstarostin Nov 20, 2023
308efec
Comment some logic
vstarostin Nov 20, 2023
8750f75
Test: move regexp logic
vstarostin Nov 20, 2023
726dfad
Test
vstarostin Nov 20, 2023
ec03774
Update
vstarostin Nov 21, 2023
60846d6
Update
vstarostin Nov 21, 2023
0fbdb60
Clean up
vstarostin Nov 21, 2023
94b2e21
Merge branch 'master' into vstarostin/push-to-registry
vstarostin Nov 21, 2023
7f77742
Merge branch 'master' into anil/ImagePushToRegistry
vstarostin Nov 21, 2023
49db80e
Update
vstarostin Nov 21, 2023
0ef6f2a
Merge branch 'anil/ImagePushToRegistry' into vstarostin/push-to-registry
vstarostin Nov 21, 2023
1ceb89f
Update
vstarostin Nov 21, 2023
983e0a6
Update interface
vstarostin Nov 22, 2023
b5c9137
Rename function
vstarostin Nov 22, 2023
97a0ee1
imagePushToRegistry: small refactoring (#4688)
vstarostin Nov 22, 2023
119998c
Update step yaml file
vstarostin Nov 22, 2023
02852f3
Update interface
vstarostin Nov 22, 2023
a500ff7
Rename func
vstarostin Nov 22, 2023
f0a9f46
Update tests
vstarostin Nov 22, 2023
9cca53f
Update interface, create mock methods, update tests
vstarostin Nov 22, 2023
43ef741
Merge branch 'vstarostin/push-to-registry-1' into anil/ImagePushToReg…
vstarostin Nov 22, 2023
493f565
Update mock
vstarostin Nov 22, 2023
d8a7e08
Add md file
vstarostin Nov 22, 2023
3991f91
Fix groovy doc, unit test, go unit test
vstarostin Nov 23, 2023
3fce0e2
Update
vstarostin Nov 23, 2023
88d872a
Add unit tests
vstarostin Nov 23, 2023
1fd506c
Support tagLatest param
vstarostin Nov 24, 2023
3a7826d
Fetch source creds from Vault
vstarostin Nov 24, 2023
6fbec1d
Update yaml file
vstarostin Nov 27, 2023
a5353ad
Support multiple images
vstarostin Nov 27, 2023
893f4c4
Update test
vstarostin Nov 27, 2023
667d48d
Support copy images in parallel
vstarostin Nov 28, 2023
d8a5f1a
Update yaml
vstarostin Nov 28, 2023
36e4c4d
Clean up
vstarostin Nov 28, 2023
93ca620
Return err if no creds provided
vstarostin Nov 28, 2023
94c31eb
Fix tests
vstarostin Nov 28, 2023
c61d571
Add err msg
vstarostin Nov 28, 2023
b9aba13
Add debug log
vstarostin Nov 28, 2023
39eeacd
Do not use CPE for targetImages
vstarostin Nov 28, 2023
117f96b
Support platform
vstarostin Nov 28, 2023
70365df
Delete Jenkins specific creds
vstarostin Nov 28, 2023
cd39439
Update groovy: do not handle Jenkins creds
vstarostin Nov 28, 2023
de9c5fc
Delete unused code
vstarostin Nov 29, 2023
82e037d
Fix: Support platform
vstarostin Nov 29, 2023
674e6a2
Fix: Support platform
vstarostin Nov 29, 2023
d00273d
Apply suggestion from code review
vstarostin Nov 29, 2023
c0ba579
Apply suggestion from code review
vstarostin Nov 29, 2023
d7ad042
Add tests for parseDockerImageName
vstarostin Nov 29, 2023
0b90d40
Add comment that tagArtifactVersion is not supported yet
vstarostin Nov 29, 2023
5998075
Set limit of running goroutines
vstarostin Nov 29, 2023
69422de
Fix: Set limit of running goroutines
vstarostin Nov 29, 2023
2b8a9cd
The tagArtifactVersion is not supported yet
vstarostin Nov 30, 2023
63b357b
Merge branch 'master' into anil/ImagePushToRegistry
vstarostin Nov 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions cmd/imagePushToRegistry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package cmd

import (
"context"
"fmt"
"regexp"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"

"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"
)

const (
targetDockerConfigPath = "/root/.docker/config.json"
)

type dockerImageUtils interface {
LoadImage(ctx context.Context, src string) (v1.Image, error)
PushImage(ctx context.Context, im v1.Image, dest, platform string) error
CopyImage(ctx context.Context, src, dest, platform string) error
}

type imagePushToRegistryUtils interface {
command.ExecRunner
piperutils.FileUtils
dockerImageUtils

// Add more methods here, or embed additional interfaces, or remove/replace as required.
// The imagePushToRegistryUtils interface should be descriptive of your runtime dependencies,
// i.e. include everything you need to be able to mock in tests.
// Unit tests shall be executable in parallel (not depend on global state), and don't (re-)test dependencies.
}

type imagePushToRegistryUtilsBundle struct {
*command.Command
*piperutils.Files
dockerImageUtils

// Embed more structs as necessary to implement methods or interfaces you add to imagePushToRegistryUtils.
// Structs embedded in this way must each have a unique set of methods attached.
// If there is no struct which implements the method you need, attach the method to
// imagePushToRegistryUtilsBundle and forward to the implementation of the dependency.
}

func newImagePushToRegistryUtils() imagePushToRegistryUtils {
utils := imagePushToRegistryUtilsBundle{
Command: &command.Command{
StepName: "imagePushToRegistry",
},
Files: &piperutils.Files{},
dockerImageUtils: &docker.CraneUtilsBundle{},
}
// Reroute command output to logging framework
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}

func imagePushToRegistry(config imagePushToRegistryOptions, telemetryData *telemetry.CustomData) {
// Utils can be used wherever the command.ExecRunner interface is expected.
// It can also be used for example as a mavenExecRunner.
utils := newImagePushToRegistryUtils()

// For HTTP calls import piperhttp "github.com/SAP/jenkins-library/pkg/http"
// and use a &piperhttp.Client{} in a custom system
// Example: step checkmarxExecuteScan.go

// Error situations should be bubbled up until they reach the line below which will then stop execution
// through the log.Entry().Fatal() call leading to an os.Exit(1) in the end.
err := runImagePushToRegistry(&config, telemetryData, utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}

func runImagePushToRegistry(config *imagePushToRegistryOptions, telemetryData *telemetry.CustomData, utils imagePushToRegistryUtils) error {
if len(config.TargetImages) == 0 {
config.TargetImages = config.SourceImages
}

if len(config.TargetImages) != len(config.SourceImages) {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.New("configuration error: please configure targetImage and sourceImage properly")
}

re := regexp.MustCompile(`^https?://`)
config.SourceRegistryURL = re.ReplaceAllString(config.SourceRegistryURL, "")
config.TargetRegistryURL = re.ReplaceAllString(config.TargetRegistryURL, "")

log.Entry().Debug("Handling destination registry credentials")
if err := handleCredentialsForPrivateRegistry(config.DockerConfigJSON, config.TargetRegistryURL, config.TargetRegistryUser, config.TargetRegistryPassword, utils); err != nil {
return errors.Wrap(err, "failed to handle credentials for target registry")
}

if len(config.LocalDockerImagePath) > 0 {
if err := pushLocalImageToTargetRegistry(config, utils); err != nil {
return errors.Wrapf(err, "failed to push local image to %q", config.TargetRegistryURL)
}
return nil
}

log.Entry().Debug("Handling source registry credentials")
if err := handleCredentialsForPrivateRegistry(config.DockerConfigJSON, config.SourceRegistryURL, config.SourceRegistryUser, config.SourceRegistryPassword, utils); err != nil {
return errors.Wrap(err, "failed to handle credentials for source registry")
}

if err := copyImages(config, utils); err != nil {
return errors.Wrap(err, "failed to copy images")
}

return nil
}

func handleCredentialsForPrivateRegistry(dockerConfigJsonPath, registry, username, password string, utils imagePushToRegistryUtils) error {
if len(dockerConfigJsonPath) == 0 {
if len(registry) == 0 || len(username) == 0 || len(password) == 0 {
return errors.New("docker credentials not provided")
}

if _, err := docker.CreateDockerConfigJSON(registry, username, password, "", targetDockerConfigPath, utils); err != nil {
return errors.Wrap(err, "failed to create new docker config")
}
return nil
}

if _, err := docker.CreateDockerConfigJSON(registry, username, password, targetDockerConfigPath, dockerConfigJsonPath, utils); err != nil {
return errors.Wrapf(err, "failed to update docker config %q", dockerConfigJsonPath)
}

if err := docker.MergeDockerConfigJSON(targetDockerConfigPath, dockerConfigJsonPath, utils); err != nil {
return errors.Wrapf(err, "failed to merge docker config files")
}

return nil
}

func copyImages(config *imagePushToRegistryOptions, utils imagePushToRegistryUtils) error {
g, ctx := errgroup.WithContext(context.Background())
m1ron0xFF marked this conversation as resolved.
Show resolved Hide resolved
g.SetLimit(10)
platform := config.TargetArchitecture

for i := 0; i < len(config.SourceImages); i++ {
src := fmt.Sprintf("%s/%s", config.SourceRegistryURL, config.SourceImages[i])
dst := fmt.Sprintf("%s/%s", config.TargetRegistryURL, config.TargetImages[i])

g.Go(func() error {
log.Entry().Infof("Copying %s to %s...", src, dst)
if err := utils.CopyImage(ctx, src, dst, platform); err != nil {
return err
}
log.Entry().Infof("Copying %s to %s... Done", src, dst)
return nil
})

if config.TagLatest {
g.Go(func() error {
// imageName is repository + image, e.g test.registry/testImage
imageName := parseDockerImageName(dst)
log.Entry().Infof("Copying %s to %s...", src, imageName)
if err := utils.CopyImage(ctx, src, imageName, platform); err != nil {
return err
}
log.Entry().Infof("Copying %s to %s... Done", src, imageName)
return nil
})
}
}

if err := g.Wait(); err != nil {
return err
}

return nil
}

func pushLocalImageToTargetRegistry(config *imagePushToRegistryOptions, utils imagePushToRegistryUtils) error {
g, ctx := errgroup.WithContext(context.Background())
m1ron0xFF marked this conversation as resolved.
Show resolved Hide resolved
g.SetLimit(10)
platform := config.TargetArchitecture

log.Entry().Infof("Loading local image...")
img, err := utils.LoadImage(ctx, config.LocalDockerImagePath)
if err != nil {
return err
}
log.Entry().Infof("Loading local image... Done")

for i := 0; i < len(config.TargetImages); i++ {
dst := fmt.Sprintf("%s/%s", config.TargetRegistryURL, config.TargetImages[i])

g.Go(func() error {
log.Entry().Infof("Pushing %s...", dst)
if err := utils.PushImage(ctx, img, dst, platform); err != nil {
return err
}
log.Entry().Infof("Pushing %s... Done", dst)
return nil
})

if config.TagLatest {
g.Go(func() error {
// imageName is repository + image, e.g test.registry/testImage
imageName := parseDockerImageName(dst)
log.Entry().Infof("Pushing %s...", imageName)
if err := utils.PushImage(ctx, img, imageName, platform); err != nil {
return err
}
log.Entry().Infof("Pushing %s... Done", imageName)
return nil
})
}
}

if err := g.Wait(); err != nil {
return err
}

return nil
}

func parseDockerImageName(image string) string {
re := regexp.MustCompile(`^(.*?)(?::([^:/]+))?$`)
matches := re.FindStringSubmatch(image)
if len(matches) > 1 {
return matches[1]
}

return image
}
Loading
Loading