From 58bd727c07f75e63670d7a0f1900da216c5b6c96 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:31:18 +0530 Subject: [PATCH] feat: [CI-16588]: Add support to PLUGIN_TAR_PATH, PLUGIN_SOURCE_TAR_PATH and PLUGIN_PUSH_ONLY to kaniko-ecr (#141) * Add support for tar-path, source-tar-path and push-only operations * Updated cmd/kaniko-ecr/main.go * Updated cmd/kaniko-ecr/main.go * Update cmd/kaniko-ecr/main.go --- cmd/kaniko-ecr/main.go | 127 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/cmd/kaniko-ecr/main.go b/cmd/kaniko-ecr/main.go index 73c5d07..909d307 100644 --- a/cmd/kaniko-ecr/main.go +++ b/cmd/kaniko-ecr/main.go @@ -14,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr" "github.com/aws/aws-sdk-go-v2/service/ecrpublic" awsv1 "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/session" ecrv1 "github.com/aws/aws-sdk-go/service/ecr" @@ -29,6 +30,8 @@ import ( kaniko "github.com/drone/drone-kaniko" "github.com/drone/drone-kaniko/pkg/artifact" "github.com/drone/drone-kaniko/pkg/docker" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/crane" ) const ( @@ -403,6 +406,21 @@ func main() { Usage: "OIDC token for assuming role via web identity", EnvVar: "PLUGIN_OIDC_TOKEN_ID", }, + cli.StringFlag{ + Name: "tar-path", + Usage: "Set this flag to save the image as a tarball at path", + EnvVar: "PLUGIN_TAR_PATH, PLUGIN_DESTINATION_TAR_PATH", + }, + cli.StringFlag{ + Name: "source-tar-path", + Usage: "Set this flag for the source tarball during push operations.", + EnvVar: "PLUGIN_SOURCE_TAR_PATH", + }, + cli.BoolFlag{ + Name: "push-only", + Usage: "Specify if the operation is push-only", + EnvVar: "PLUGIN_PUSH_ONLY", + }, } if err := app.Run(os.Args); err != nil { @@ -415,10 +433,21 @@ func run(c *cli.Context) error { registry := c.String("registry") region := c.String("region") noPush := c.Bool("no-push") + pushOnly := c.Bool("push-only") assumeRole := c.String("assume-role") externalId := c.String("external-id") oidcToken := c.String("oidc-token-id") + // Validate flags + if noPush && pushOnly { + return fmt.Errorf("no-push and push-only flags cannot be used together") + } + + // Handle push-only operation + if pushOnly { + return handlePushOnly(c) + } + // setup docker config for azure registry and base image docker registry err := setDockerAuth( c.String("docker-registry"), @@ -521,6 +550,9 @@ func run(c *cli.Context) error { IgnorePaths: c.StringSlice("ignore-paths"), ImageFSExtractRetry: c.Int("image-fs-extract-retry"), ImageDownloadRetry: c.Int("image-download-retry"), + TarPath: c.String("tar-path"), + SourceTarPath: c.String("source-tar-path"), + PushOnly: c.Bool("push-only"), }, Artifact: kaniko.Artifact{ Tags: c.StringSlice("tags"), @@ -846,3 +878,98 @@ func getOidcCreds(oidcToken, assumeRole string) (string, string, string, error) // Return the credentials return *result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken, nil } + +func createECRSession(region, accessKey, secretKey, sessionToken string) *ecrv1.ECR { + sess := session.Must(session.NewSession(&awsv1.Config{ + Region: awsv1.String(region), + Credentials: credentials.NewStaticCredentials( + accessKey, + secretKey, + sessionToken, + ), + })) + return ecrv1.New(sess) +} + +func handlePushOnly(c *cli.Context) error { + sourceTarPath := c.String("source-tar-path") + if sourceTarPath == "" { + return fmt.Errorf("source_tar_path is required when push_only is set") + } + + if _, err := os.Stat(sourceTarPath); os.IsNotExist(err) { + return fmt.Errorf("image tarball does not exist at path: %s", sourceTarPath) + } + + repo := c.String("repo") + registry := c.String("registry") + if repo == "" || registry == "" { + return fmt.Errorf("repository and registry must be specified for push-only operation") + } + + // Load the image from the tarball + img, err := crane.Load(sourceTarPath) + if err != nil { + return fmt.Errorf("failed to load image from tarball: %v", err) + } + + // Get ECR credentials using existing auth methods + var username, password string + var svc *ecrv1.ECR + if oidcToken := c.String("oidc-token-id"); oidcToken != "" && c.String("assume-role") != "" { + accessKey, secretKey, sessionToken, err := getOidcCreds(oidcToken, c.String("assume-role")) + if err != nil { + return fmt.Errorf("failed to get OIDC credentials: %v", err) + } + + svc = createECRSession(c.String("region"), accessKey, secretKey, sessionToken) + } else if assumeRole := c.String("assume-role"); assumeRole != "" { + accessKey, secretKey, sessionToken, err := getAssumeRoleCreds(c.String("region"), assumeRole, c.String("external-id"), "") + if err != nil { + return fmt.Errorf("failed to get assume role credentials: %v", err) + } + + svc = createECRSession(c.String("region"), accessKey, secretKey, sessionToken) + } else { + // Use direct credentials or IAM role + sess := session.Must(session.NewSession(&awsv1.Config{ + Region: awsv1.String(c.String("region")), + Credentials: credentials.NewStaticCredentials( + c.String("access-key"), + c.String("secret-key"), + "", + ), + })) + svc = ecrv1.New(sess) + } + + // Get ECR auth token using the configured session + username, password, _, err = getAuthInfo(svc) + if err != nil { + return fmt.Errorf("failed to get ECR credentials: %v", err) + } + + // Setup crane auth + opts := []crane.Option{ + crane.WithAuth(&authn.Basic{ + Username: username, + Password: password, + }), + } + + // Push for each tag + tags := c.StringSlice("tags") + if len(tags) == 0 { + tags = []string{"latest"} + } + + for _, tag := range tags { + dest := fmt.Sprintf("%s/%s:%s", registry, repo, tag) + if err := crane.Push(img, dest, opts...); err != nil { + return fmt.Errorf("failed to push image to %s: %v", dest, err) + } + fmt.Printf("Successfully pushed image to %s\n", dest) + } + + return nil +}