diff --git a/cmd/drone-ecr/main.go b/cmd/drone-ecr/main.go index ae268299..431b0903 100644 --- a/cmd/drone-ecr/main.go +++ b/cmd/drone-ecr/main.go @@ -22,6 +22,10 @@ import ( docker "github.com/drone-plugins/drone-docker" ) +type ecrAPI interface { + DescribeImages(*ecr.DescribeImagesInput) (*ecr.DescribeImagesOutput, error) +} + const defaultRegion = "us-east-1" func main() { @@ -31,18 +35,19 @@ func main() { } var ( - repo = getenv("PLUGIN_REPO") - registry = getenv("PLUGIN_REGISTRY") - region = getenv("PLUGIN_REGION", "ECR_REGION", "AWS_REGION") - key = getenv("PLUGIN_ACCESS_KEY", "ECR_ACCESS_KEY", "AWS_ACCESS_KEY_ID") - secret = getenv("PLUGIN_SECRET_KEY", "ECR_SECRET_KEY", "AWS_SECRET_ACCESS_KEY") - create = parseBoolOrDefault(false, getenv("PLUGIN_CREATE_REPOSITORY", "ECR_CREATE_REPOSITORY")) - lifecyclePolicy = getenv("PLUGIN_LIFECYCLE_POLICY") - repositoryPolicy = getenv("PLUGIN_REPOSITORY_POLICY") - assumeRole = getenv("PLUGIN_ASSUME_ROLE") - externalId = getenv("PLUGIN_EXTERNAL_ID") - scanOnPush = parseBoolOrDefault(false, getenv("PLUGIN_SCAN_ON_PUSH")) - idToken = os.Getenv("PLUGIN_OIDC_TOKEN_ID") + repo = getenv("PLUGIN_REPO") + registry = getenv("PLUGIN_REGISTRY") + region = getenv("PLUGIN_REGION", "ECR_REGION", "AWS_REGION") + key = getenv("PLUGIN_ACCESS_KEY", "ECR_ACCESS_KEY", "AWS_ACCESS_KEY_ID") + secret = getenv("PLUGIN_SECRET_KEY", "ECR_SECRET_KEY", "AWS_SECRET_ACCESS_KEY") + create = parseBoolOrDefault(false, getenv("PLUGIN_CREATE_REPOSITORY", "ECR_CREATE_REPOSITORY")) + lifecyclePolicy = getenv("PLUGIN_LIFECYCLE_POLICY") + repositoryPolicy = getenv("PLUGIN_REPOSITORY_POLICY") + assumeRole = getenv("PLUGIN_ASSUME_ROLE") + externalId = getenv("PLUGIN_EXTERNAL_ID") + scanOnPush = parseBoolOrDefault(false, getenv("PLUGIN_SCAN_ON_PUSH")) + idToken = os.Getenv("PLUGIN_OIDC_TOKEN_ID") + skipPushIfTagExists = parseBoolOrDefault(false, getenv("PLUGIN_SKIP_PUSH_IF_TAG_EXISTS")) ) // set the region @@ -114,6 +119,34 @@ 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 + if tagInput == "" { + tags = []string{"latest"} + } else { + for _, t := range strings.Split(tagInput, ",") { + trimmed := strings.TrimSpace(t) + if trimmed != "" { + tags = append(tags, trimmed) + } + } + } + + repositoryName := trimHostname(repo, registry) + for _, t := range tags { + exists, err := tagExists(svc, repositoryName, t) + if err != nil { + logrus.Fatalf("Error checking if image exists for tag %s: %v", t, err) + } + if exists { + logrus.Infof("%s:%s: Image tag exists. Skipping push.", repo, t) + os.Exit(0) + } + } + } + // invoke the base docker plugin binary cmd := exec.Command(docker.GetDroneDockerExecCmd()) cmd.Stdout = os.Stdout @@ -249,3 +282,20 @@ func getECRClient(sess *session.Session, role string, externalId string, idToken }) } } + +func tagExists(svc ecrAPI, repository, tag string) (bool, error) { + input := &ecr.DescribeImagesInput{ + RepositoryName: aws.String(repository), + ImageIds: []*ecr.ImageIdentifier{ + {ImageTag: aws.String(tag)}, + }, + } + output, err := svc.DescribeImages(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "ImageNotFoundException" { + return false, nil + } + return false, err + } + return len(output.ImageDetails) > 0, nil +}