mirror of
https://github.com/drone-plugins/drone-docker.git
synced 2026-06-04 18:24:24 +08:00
Merge branch 'master' into Remove-EoL-Components
Resolved conflicts in cmd/drone-ecr/main.go and go.mod: - Kept slog logging from our branch instead of logrus - Integrated AWS SDK v2 dependencies from master - Updated tagExists call to include ctx parameter from master Made-with: Cursor
This commit is contained in:
+91
-91
@@ -1,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
@@ -11,25 +12,20 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecr"
|
||||
ecrtypes "github.com/aws/aws-sdk-go-v2/service/ecr/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
|
||||
docker "github.com/drone-plugins/drone-docker"
|
||||
)
|
||||
|
||||
type ecrAPI interface {
|
||||
DescribeImages(*ecr.DescribeImagesInput) (*ecr.DescribeImagesOutput, error)
|
||||
}
|
||||
|
||||
const defaultRegion = "us-east-1"
|
||||
|
||||
func main() {
|
||||
// Load env-file if it exists first
|
||||
if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" {
|
||||
godotenv.Load(env)
|
||||
}
|
||||
@@ -50,7 +46,6 @@ func main() {
|
||||
skipPushIfTagExists = parseBoolOrDefault(false, getenv("PLUGIN_SKIP_PUSH_IF_TAG_EXISTS"))
|
||||
)
|
||||
|
||||
// set the region
|
||||
if region == "" {
|
||||
region = defaultRegion
|
||||
}
|
||||
@@ -62,13 +57,15 @@ func main() {
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", secret)
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(&aws.Config{Region: ®ion})
|
||||
ctx := context.Background()
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("error creating aws session: %v", err))
|
||||
log.Fatal(fmt.Sprintf("error creating aws config: %v", err))
|
||||
}
|
||||
|
||||
svc := getECRClient(sess, assumeRole, externalId, idToken)
|
||||
username, password, defaultRegistry, err := getAuthInfo(svc)
|
||||
svc := getECRClient(cfg, assumeRole, externalId, idToken)
|
||||
username, password, defaultRegistry, err := getAuthInfo(ctx, svc)
|
||||
|
||||
if registry == "" {
|
||||
registry = defaultRegistry
|
||||
@@ -83,32 +80,32 @@ func main() {
|
||||
}
|
||||
|
||||
if create {
|
||||
err = ensureRepoExists(svc, trimHostname(repo, registry), scanOnPush)
|
||||
err = ensureRepoExists(ctx, svc, trimHostname(repo, registry), scanOnPush)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("error creating ECR repo: %v", err))
|
||||
}
|
||||
err = updateImageScannningConfig(svc, trimHostname(repo, registry), scanOnPush)
|
||||
err = updateImageScanningConfig(ctx, svc, trimHostname(repo, registry), scanOnPush)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("error updating scan on push for ECR repo: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if lifecyclePolicy != "" {
|
||||
p, err := ioutil.ReadFile(lifecyclePolicy)
|
||||
p, err := os.ReadFile(lifecyclePolicy)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := uploadLifeCyclePolicy(svc, string(p), trimHostname(repo, registry)); err != nil {
|
||||
if err := uploadLifeCyclePolicy(ctx, svc, string(p), trimHostname(repo, registry)); err != nil {
|
||||
log.Fatal(fmt.Sprintf("error uploading ECR lifecycle policy: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if repositoryPolicy != "" {
|
||||
p, err := ioutil.ReadFile(repositoryPolicy)
|
||||
p, err := os.ReadFile(repositoryPolicy)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := uploadRepositoryPolicy(svc, string(p), trimHostname(repo, registry)); err != nil {
|
||||
if err := uploadRepositoryPolicy(ctx, svc, string(p), trimHostname(repo, registry)); err != nil {
|
||||
log.Fatal(fmt.Sprintf("error uploading ECR repository policy. %v", err))
|
||||
}
|
||||
}
|
||||
@@ -119,7 +116,6 @@ func main() {
|
||||
os.Setenv("DOCKER_PASSWORD", password)
|
||||
os.Setenv("PLUGIN_REGISTRY_TYPE", "ECR")
|
||||
|
||||
// Skip if tag already exits for both mutable and immutable repos
|
||||
if skipPushIfTagExists {
|
||||
tagInput := getenv("PLUGIN_TAG", "PLUGIN_TAGS")
|
||||
var tags []string
|
||||
@@ -134,9 +130,9 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
repositoryName := trimHostname(repo, registry)
|
||||
for _, t := range tags {
|
||||
exists, err := tagExists(svc, repositoryName, t)
|
||||
repositoryName := trimHostname(repo, registry)
|
||||
for _, t := range tags {
|
||||
exists, err := tagExists(ctx, svc, repositoryName, t)
|
||||
if err != nil {
|
||||
slog.Error("error checking if image exists for tag", "tag", t, "error", err)
|
||||
os.Exit(1)
|
||||
@@ -145,10 +141,9 @@ func main() {
|
||||
slog.Info("image tag exists, skipping push", "repo", repo, "tag", t)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// invoke the base docker plugin binary
|
||||
cmd := exec.Command(docker.GetDroneDockerExecCmd())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -164,57 +159,63 @@ func trimHostname(repo, registry string) string {
|
||||
return repo
|
||||
}
|
||||
|
||||
func ensureRepoExists(svc *ecr.ECR, name string, scanOnPush bool) (err error) {
|
||||
input := &ecr.CreateRepositoryInput{}
|
||||
input.SetRepositoryName(name)
|
||||
input.SetImageScanningConfiguration(&ecr.ImageScanningConfiguration{ScanOnPush: &scanOnPush})
|
||||
_, err = svc.CreateRepository(input)
|
||||
func ensureRepoExists(ctx context.Context, svc *ecr.Client, name string, scanOnPush bool) error {
|
||||
_, err := svc.CreateRepository(ctx, &ecr.CreateRepositoryInput{
|
||||
RepositoryName: aws.String(name),
|
||||
ImageScanningConfiguration: &ecrtypes.ImageScanningConfiguration{
|
||||
ScanOnPush: scanOnPush,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryAlreadyExistsException {
|
||||
// eat it, we skip checking for existing to save two requests
|
||||
err = nil
|
||||
var rae *ecrtypes.RepositoryAlreadyExistsException
|
||||
if errors.As(err, &rae) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateImageScannningConfig(svc *ecr.ECR, name string, scanOnPush bool) (err error) {
|
||||
input := &ecr.PutImageScanningConfigurationInput{}
|
||||
input.SetRepositoryName(name)
|
||||
input.SetImageScanningConfiguration(&ecr.ImageScanningConfiguration{ScanOnPush: &scanOnPush})
|
||||
_, err = svc.PutImageScanningConfiguration(input)
|
||||
|
||||
func updateImageScanningConfig(ctx context.Context, svc *ecr.Client, name string, scanOnPush bool) error {
|
||||
_, err := svc.PutImageScanningConfiguration(ctx, &ecr.PutImageScanningConfigurationInput{
|
||||
RepositoryName: aws.String(name),
|
||||
ImageScanningConfiguration: &ecrtypes.ImageScanningConfiguration{
|
||||
ScanOnPush: scanOnPush,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func uploadLifeCyclePolicy(svc *ecr.ECR, lifecyclePolicy string, name string) (err error) {
|
||||
input := &ecr.PutLifecyclePolicyInput{}
|
||||
input.SetLifecyclePolicyText(lifecyclePolicy)
|
||||
input.SetRepositoryName(name)
|
||||
_, err = svc.PutLifecyclePolicy(input)
|
||||
|
||||
func uploadLifeCyclePolicy(ctx context.Context, svc *ecr.Client, lifecyclePolicy string, name string) error {
|
||||
_, err := svc.PutLifecyclePolicy(ctx, &ecr.PutLifecyclePolicyInput{
|
||||
LifecyclePolicyText: aws.String(lifecyclePolicy),
|
||||
RepositoryName: aws.String(name),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func uploadRepositoryPolicy(svc *ecr.ECR, repositoryPolicy string, name string) (err error) {
|
||||
input := &ecr.SetRepositoryPolicyInput{}
|
||||
input.SetPolicyText(repositoryPolicy)
|
||||
input.SetRepositoryName(name)
|
||||
_, err = svc.SetRepositoryPolicy(input)
|
||||
|
||||
func uploadRepositoryPolicy(ctx context.Context, svc *ecr.Client, repositoryPolicy string, name string) error {
|
||||
_, err := svc.SetRepositoryPolicy(ctx, &ecr.SetRepositoryPolicyInput{
|
||||
PolicyText: aws.String(repositoryPolicy),
|
||||
RepositoryName: aws.String(name),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func getAuthInfo(svc *ecr.ECR) (username, password, registry string, err error) {
|
||||
func getAuthInfo(ctx context.Context, svc *ecr.Client) (username, password, registry string, err error) {
|
||||
var result *ecr.GetAuthorizationTokenOutput
|
||||
var decoded []byte
|
||||
|
||||
result, err = svc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
|
||||
result, err = svc.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenInput{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(result.AuthorizationData) == 0 {
|
||||
err = fmt.Errorf("no authorization data returned from ECR")
|
||||
return
|
||||
}
|
||||
|
||||
auth := result.AuthorizationData[0]
|
||||
token := *auth.AuthorizationToken
|
||||
decoded, err = base64.StdEncoding.DecodeString(token)
|
||||
@@ -223,7 +224,11 @@ func getAuthInfo(svc *ecr.ECR) (username, password, registry string, err error)
|
||||
}
|
||||
|
||||
registry = strings.TrimPrefix(*auth.ProxyEndpoint, "https://")
|
||||
creds := strings.Split(string(decoded), ":")
|
||||
creds := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(creds) < 2 {
|
||||
err = fmt.Errorf("invalid ECR authorization token format")
|
||||
return
|
||||
}
|
||||
username = creds[0]
|
||||
password = creds[1]
|
||||
return
|
||||
@@ -235,7 +240,6 @@ func parseBoolOrDefault(defaultValue bool, s string) (result bool) {
|
||||
if err != nil {
|
||||
result = defaultValue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -249,55 +253,51 @@ func getenv(key ...string) (s string) {
|
||||
return
|
||||
}
|
||||
|
||||
func getECRClient(sess *session.Session, role string, externalId string, idToken string) *ecr.ECR {
|
||||
func getECRClient(cfg aws.Config, role string, externalId string, idToken string) *ecr.Client {
|
||||
if role == "" {
|
||||
return ecr.New(sess)
|
||||
return ecr.NewFromConfig(cfg)
|
||||
}
|
||||
|
||||
stsSvc := sts.NewFromConfig(cfg)
|
||||
|
||||
if idToken != "" {
|
||||
tempFile, err := os.CreateTemp("/tmp", "idToken-*.jwt")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create temporary file: %v", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
provider := stscreds.NewWebIdentityRoleProvider(stsSvc, role, identityToken(idToken))
|
||||
cfg.Credentials = aws.NewCredentialsCache(provider)
|
||||
return ecr.NewFromConfig(cfg)
|
||||
}
|
||||
|
||||
if err := os.Chmod(tempFile.Name(), 0600); err != nil {
|
||||
log.Fatalf("Failed to set file permissions: %v", err)
|
||||
}
|
||||
|
||||
if _, err := tempFile.WriteString(idToken); err != nil {
|
||||
log.Fatalf("Failed to write ID token to temporary file: %v", err)
|
||||
}
|
||||
|
||||
// Create credentials using the path to the ID token file
|
||||
creds := stscreds.NewWebIdentityCredentials(sess, role, "", tempFile.Name())
|
||||
return ecr.New(sess, &aws.Config{Credentials: creds})
|
||||
} else if externalId != "" {
|
||||
return ecr.New(sess, &aws.Config{
|
||||
Credentials: stscreds.NewCredentials(sess, role, func(p *stscreds.AssumeRoleProvider) {
|
||||
p.ExternalID = &externalId
|
||||
}),
|
||||
var provider *stscreds.AssumeRoleProvider
|
||||
if externalId != "" {
|
||||
provider = stscreds.NewAssumeRoleProvider(stsSvc, role, func(o *stscreds.AssumeRoleOptions) {
|
||||
o.ExternalID = &externalId
|
||||
})
|
||||
} else {
|
||||
return ecr.New(sess, &aws.Config{
|
||||
Credentials: stscreds.NewCredentials(sess, role),
|
||||
})
|
||||
provider = stscreds.NewAssumeRoleProvider(stsSvc, role)
|
||||
}
|
||||
cfg.Credentials = aws.NewCredentialsCache(provider)
|
||||
return ecr.NewFromConfig(cfg)
|
||||
}
|
||||
|
||||
func tagExists(svc ecrAPI, repository, tag string) (bool, error) {
|
||||
func tagExists(ctx context.Context, svc *ecr.Client, repository, tag string) (bool, error) {
|
||||
input := &ecr.DescribeImagesInput{
|
||||
RepositoryName: aws.String(repository),
|
||||
ImageIds: []*ecr.ImageIdentifier{
|
||||
ImageIds: []ecrtypes.ImageIdentifier{
|
||||
{ImageTag: aws.String(tag)},
|
||||
},
|
||||
}
|
||||
output, err := svc.DescribeImages(input)
|
||||
output, err := svc.DescribeImages(ctx, input)
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "ImageNotFoundException" {
|
||||
var inf *ecrtypes.ImageNotFoundException
|
||||
if errors.As(err, &inf) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return len(output.ImageDetails) > 0, nil
|
||||
}
|
||||
|
||||
type identityToken string
|
||||
|
||||
func (t identityToken) GetIdentityToken() ([]byte, error) {
|
||||
return []byte(t), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user