diff --git a/release/cli/pkg/aws/ecrpublic/ecrpublic.go b/release/cli/pkg/aws/ecrpublic/ecrpublic.go index c314d383df0f..d27288973dab 100644 --- a/release/cli/pkg/aws/ecrpublic/ecrpublic.go +++ b/release/cli/pkg/aws/ecrpublic/ecrpublic.go @@ -83,6 +83,65 @@ func GetAuthConfig(ecrPublicClient *ecrpublic.ECRPublic) (*docker.AuthConfigurat return authConfig, nil } +func GetAllImagesCount(imageRepository string, ecrPublicClient *ecrpublic.ECRPublic) (int, error) { + allImages := []*ecrpublic.ImageDetail{} + + describeImagesOutput, err := ecrPublicClient.DescribeImages( + &ecrpublic.DescribeImagesInput{ + RepositoryName: aws.String(imageRepository), + MaxResults: aws.Int64(1000), + }, + ) + if err != nil { + return 0, errors.Cause(err) + } + allImages = append(allImages, describeImagesOutput.ImageDetails...) + nextToken := describeImagesOutput.NextToken + + for nextToken != nil { + describeImagesOutput, err = ecrPublicClient.DescribeImages( + &ecrpublic.DescribeImagesInput{ + RepositoryName: aws.String(imageRepository), + MaxResults: aws.Int64(1000), + NextToken: nextToken, + }, + ) + if err != nil { + return 0, errors.Cause(err) + } + allImages = append(allImages, describeImagesOutput.ImageDetails...) + nextToken = describeImagesOutput.NextToken + } + + return len(allImages), nil +} + +func GetTagsCountForImage(imageRepository, imageDigest string, ecrPublicClient *ecrpublic.ECRPublic) (int, error) { + describeImagesOutput, err := ecrPublicClient.DescribeImages( + &ecrpublic.DescribeImagesInput{ + RepositoryName: aws.String(imageRepository), + ImageIds: []*ecrpublic.ImageIdentifier{ + { + ImageDigest: aws.String(imageDigest), + }, + }, + }, + ) + if err != nil { + if strings.Contains(err.Error(), ecrpublic.ErrCodeImageNotFoundException) { + return 0, nil + } else { + return 0, errors.Cause(err) + } + } + + if len(describeImagesOutput.ImageDetails) == 0 { + return 0, nil + } + + return len(describeImagesOutput.ImageDetails[0].ImageTags), nil +} + func CheckImageExistence(imageUri, imageContainerRegistry string, ecrPublicClient *ecrpublic.ECRPublic) (bool, error) { repository, tag := artifactutils.SplitImageUri(imageUri, imageContainerRegistry) _, err := ecrPublicClient.DescribeImages( diff --git a/release/cli/pkg/constants/constants.go b/release/cli/pkg/constants/constants.go index 431e6329c531..194068793f4c 100644 --- a/release/cli/pkg/constants/constants.go +++ b/release/cli/pkg/constants/constants.go @@ -53,4 +53,7 @@ const ( // // (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). YYYYMMDD = "2006-01-02" + + MAX_IMAGES_PER_REPOSITORY = 10000 + MAX_TAGS_PER_IMAGE = 1000 ) diff --git a/release/cli/pkg/images/images.go b/release/cli/pkg/images/images.go index 3ac572b13de4..c73cc152a258 100644 --- a/release/cli/pkg/images/images.go +++ b/release/cli/pkg/images/images.go @@ -25,6 +25,7 @@ import ( "strings" "time" + ecrsdk "github.com/aws/aws-sdk-go/service/ecr" ecrpublicsdk "github.com/aws/aws-sdk-go/service/ecrpublic" docker "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" @@ -35,6 +36,7 @@ import ( "github.com/aws/eks-anywhere/release/cli/pkg/aws/ecr" "github.com/aws/eks-anywhere/release/cli/pkg/aws/ecrpublic" "github.com/aws/eks-anywhere/release/cli/pkg/aws/s3" + "github.com/aws/eks-anywhere/release/cli/pkg/constants" "github.com/aws/eks-anywhere/release/cli/pkg/filereader" "github.com/aws/eks-anywhere/release/cli/pkg/retrier" releasetypes "github.com/aws/eks-anywhere/release/cli/pkg/types" @@ -385,3 +387,32 @@ func ComputeImageDigestFromManifest(ecrPublicClient *ecrpublicsdk.ECRPublic, reg return fmt.Sprintf("%x", digest), nil } + +func CheckRepositoryImagesAndTagsCountLimit(sourceImageUri, releaseImageUri, sourceContainerRegistry, releaseContainerRegistry string, ecrClient *ecrsdk.ECR, ecrPublicClient *ecrpublicsdk.ECRPublic) error { + repository, _ := artifactutils.SplitImageUri(releaseImageUri, releaseContainerRegistry) + + fmt.Printf("Checking if image %s can be pushed to repository %s\n", releaseImageUri, repository) + + sourceImageDigest, err := ecr.GetImageDigest(sourceImageUri, sourceContainerRegistry, ecrClient) + if err != nil { + return errors.Cause(err) + } + + allImagesCount, err := ecrpublic.GetAllImagesCount(repository, ecrPublicClient) + if err != nil { + return errors.Cause(err) + } + if allImagesCount >= constants.MAX_IMAGES_PER_REPOSITORY { + return fmt.Errorf("cannot push image [%s] since the repository %s already has the maximum allowed number of images which is '%d'", releaseImageUri, repository, constants.MAX_IMAGES_PER_REPOSITORY) + } + + tagsForImageCount, err := ecrpublic.GetTagsCountForImage(repository, sourceImageDigest, ecrPublicClient) + if err != nil { + return errors.Cause(err) + } + if tagsForImageCount >= constants.MAX_TAGS_PER_IMAGE { + return fmt.Errorf("cannot push image [%s] since the image with digest [%s] in repository %s already has the maximum allowed number of tags per image which is '%d'", releaseImageUri, sourceImageDigest, repository, constants.MAX_TAGS_PER_IMAGE) + } + + return nil +} diff --git a/release/cli/pkg/operations/upload.go b/release/cli/pkg/operations/upload.go index 36bc7f23cdfb..caa48040488c 100644 --- a/release/cli/pkg/operations/upload.go +++ b/release/cli/pkg/operations/upload.go @@ -162,12 +162,24 @@ func handleImageUpload(_ context.Context, r *releasetypes.ReleaseConfig, package sourceImageUri := artifact.Image.SourceImageURI releaseImageUri := artifact.Image.ReleaseImageURI sourceEcrAuthConfig := defaultSourceEcrAuthConfig + sourceContainerRegistry := r.SourceContainerRegistry + sourceEcrClient := r.SourceClients.ECR.EcrClient if packagesutils.NeedsPackagesAccountArtifacts(r) && (strings.Contains(sourceImageUri, "eks-anywhere-packages") || strings.Contains(sourceImageUri, "ecr-token-refresher") || strings.Contains(sourceImageUri, "credential-provider-package")) { sourceEcrAuthConfig = packagesSourceEcrAuthConfig + sourceContainerRegistry = r.PackagesSourceContainerRegistry + sourceEcrClient = r.SourceClients.Packages.EcrClient } + releaseContainerRegistry := r.ReleaseContainerRegistry + releaseEcrPublicClient := r.ReleaseClients.ECRPublic.Client fmt.Printf("Source Image - %s\n", sourceImageUri) fmt.Printf("Destination Image - %s\n", releaseImageUri) - err := images.CopyToDestination(sourceEcrAuthConfig, releaseEcrAuthConfig, sourceImageUri, releaseImageUri) + + err := images.CheckRepositoryImagesAndTagsCountLimit(sourceImageUri, releaseImageUri, sourceContainerRegistry, releaseContainerRegistry, sourceEcrClient, releaseEcrPublicClient) + if err != nil { + return fmt.Errorf("checking pushability of image [%s] based on destination repository images or tags limits: %v", releaseImageUri, err) + } + + err = images.CopyToDestination(sourceEcrAuthConfig, releaseEcrAuthConfig, sourceImageUri, releaseImageUri) if err != nil { return fmt.Errorf("copying image from source [%s] to destination [%s]: %v", sourceImageUri, releaseImageUri, err) }