generatebundlefile/ecr.go (173 lines of code) (raw):

package main import ( "context" "encoding/json" "fmt" "os" "regexp" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ecr" ecrtypes "github.com/aws/aws-sdk-go-v2/service/ecr/types" "github.com/pkg/errors" api "github.com/aws/eks-anywhere-packages/api/v1alpha1" ) type ecrClient struct { registryClient AuthConfig string } type registryClient interface { DescribeImages(ctx context.Context, params *ecr.DescribeImagesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) } // NewECRClient Creates a new ECR Client client func NewECRClient(client registryClient, needsCreds bool) (*ecrClient, error) { ecrClient := &ecrClient{ registryClient: client, } if needsCreds { authorizationToken, err := ecrClient.GetAuthToken() if err != nil { return nil, err } ecrClient.AuthConfig = authorizationToken return ecrClient, nil } return ecrClient, nil } // Describe returns a list of ECR describe results, with Pagination from DescribeImages SDK request func (c *ecrClient) Describe(describeInput *ecr.DescribeImagesInput) ([]ecrtypes.ImageDetail, error) { var images []ecrtypes.ImageDetail resp, err := c.DescribeImages(context.TODO(), describeInput) if err != nil { return nil, fmt.Errorf("unable to complete DescribeImagesRequest to ECR: %s", err) } images = append(images, resp.ImageDetails...) if resp.NextToken != nil { next := describeInput next.NextToken = resp.NextToken nextdetails, _ := c.Describe(next) images = append(images, nextdetails...) } return images, nil } // GetShaForInputs returns a list of an images version/sha for given inputs to lookup func (c *ecrClient) GetShaForInputs(project Project) ([]api.SourceVersion, error) { sourceVersion := []api.SourceVersion{} BundleLog.Info("Looking up ECR for image SHA", "Repository", project.Repository) for _, tag := range project.Versions { if !strings.HasSuffix(tag.Name, "latest") { var imagelookup []ecrtypes.ImageIdentifier imagelookup = append(imagelookup, ecrtypes.ImageIdentifier{ImageTag: &tag.Name}) ImageDetails, err := c.Describe(&ecr.DescribeImagesInput{ RepositoryName: aws.String(project.Repository), ImageIds: imagelookup, }) if err != nil { return nil, fmt.Errorf("unable to complete DescribeImagesRequest to ECR: %s", err) } for _, images := range ImageDetails { if *images.ImageManifestMediaType != "application/vnd.oci.image.manifest.v1+json" || len(images.ImageTags) == 0 { continue } if len(images.ImageTags) > 0 { v := &api.SourceVersion{Name: tag.Name, Digest: *images.ImageDigest} sourceVersion = append(sourceVersion, *v) continue } } } if tag.Name == "latest" { ImageDetails, err := c.Describe(&ecr.DescribeImagesInput{ RepositoryName: aws.String(project.Repository), }) if err != nil { return nil, fmt.Errorf("unable to complete DescribeImagesRequest to ECR: %s", err) } filteredImageDetails := ImageTagFilter(ImageDetails, "") sha, err := getLatestImageSha(filteredImageDetails) if err != nil { return nil, err } sourceVersion = append(sourceVersion, *sha) continue } if strings.HasSuffix(tag.Name, "-latest") { regex := regexp.MustCompile(`-latest`) splitVersion := regex.Split(tag.Name, -1) // extract out the version without the latest ImageDetails, err := c.Describe(&ecr.DescribeImagesInput{ RepositoryName: aws.String(project.Repository), }) if err != nil { return nil, fmt.Errorf("unable to complete DescribeImagesRequest to ECR: %s", err) } filteredImageDetails := ImageTagFilter(ImageDetails, splitVersion[0]) sha, err := getLatestImageSha(filteredImageDetails) if err != nil { return nil, err } sourceVersion = append(sourceVersion, *sha) continue } } sourceVersion = removeDuplicates(sourceVersion) return sourceVersion, nil } // tagFromSha Looks up the Tag of an ECR artifact from a sha func (c *ecrClient) tagFromSha(repository, sha, substringTag string) (string, error) { if repository == "" || sha == "" { return "", fmt.Errorf("empty repository, or sha passed to the function") } var imagelookup []ecrtypes.ImageIdentifier imagelookup = append(imagelookup, ecrtypes.ImageIdentifier{ImageDigest: &sha}) BundleLog.Info("Looking up ECR for image SHA", "Repository", repository) ImageDetails, err := c.Describe(&ecr.DescribeImagesInput{ RepositoryName: aws.String(repository), ImageIds: imagelookup, }) if err != nil { if strings.Contains(err.Error(), "does not exist within the repository") { return "", nil } else { return "", fmt.Errorf("looking up image details %v", err) } } for _, detail := range ImageDetails { // We can return the first tag for an image, if it has multiple tags if len(detail.ImageTags) > 0 { detail.ImageTags = removeStringSlice(detail.ImageTags, "latest") for j, tag := range detail.ImageTags { if strings.HasSuffix(tag, substringTag) { return detail.ImageTags[j], nil } } return detail.ImageTags[0], nil } } return "", nil } // GetAuthToken gets an authorization token from ECR func (c *ecrClient) GetAuthToken() (string, error) { authTokenOutput, err := c.GetAuthorizationToken(context.TODO(), &ecr.GetAuthorizationTokenInput{}) if err != nil { return "", errors.Cause(err) } authToken := *authTokenOutput.AuthorizationData[0].AuthorizationToken return authToken, nil } // NewAuthFile writes a new Docker Authfile from the DockerAuth struct which a user to be used by Skopeo or Helm. func NewAuthFile(dockerstruct *DockerAuth) (DockerAuthFile, error) { jsonbytes, err := json.Marshal(*dockerstruct) auth := DockerAuthFile{} if err != nil { return auth, fmt.Errorf("marshalling docker auth file to json %w", err) } f, err := os.CreateTemp("", "dockerAuth") if err != nil { return auth, fmt.Errorf("creating tempfile %w", err) } defer f.Close() fmt.Fprint(f, string(jsonbytes)) auth.Authfile = f.Name() return auth, nil } func (d DockerAuthFile) Remove() error { if d.Authfile == "" { return fmt.Errorf("no Authfile in DockerAuthFile given") } defer os.Remove(d.Authfile) return nil }