// Package image provides utilities for defining container image structs and working // with them. package image import ( "fmt" "strings" "edge-infra.dev/pkg/lib/errors" "github.com/google/go-containerregistry/pkg/crane" ) // Image defines information we care about for a given container image. // `yaml` tags used so the struct can be correctly marshaled by Kpt type Image struct { // Digest is the sha256 digest for the image Digest string `yaml:"digest"` // Registry is the container registry string, e.g., us.gcr.io, // ncr-emeraldedge-docker-dev.jfrog.io. // https://docs.microsoft.com/en-us/azure/container-registry/container-registry-concepts#registry Registry string `yaml:"registry,omitempty"` // Repository is the container name, e.g., talaria. They can also contain slashes, // used to implement namespacing in some registries, such as GCR. // For GCR, the repository would be $project-id/talaria by default, but they // can be more deeply nested. // Multiple namespaces can also be used: // https://docs.microsoft.com/en-us/azure/container-registry/container-registry-concepts#repository Repository string `yaml:"repository"` } // Reference creates the fully qualified image reference string for an Image based // on the digest, e.g. us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/talaria@sha256:... func (img *Image) Reference() string { return fmt.Sprintf("%s/%s@%s", img.Registry, img.Repository, img.Digest) } // Name returns the colloquial container image name, e.g., `talaria`. // // Useful for pulling out a consistent image identifier regardless of the // repository. // For example, say we need to identify container image references for the same // container image across multiple registries, GCR and JFrog. Since GCR // supports repository namespacing (and enforces you namespace by your $project-id), // simply comparing the repository will not work ($project-id/talaria wont match talaria). // // Note that the name is a substring of the Image.Repository. If the // registry implementation supports repository namespacing, then it will // be the last token after splitting on `/` and removing the tag/digest. // If the registry implementation does _not_ support repository namespacing, // the repository is the container image name. func (img *Image) Name() string { if strings.Contains(img.Repository, "/") { return NameFromRef(img.Repository) } return img.Repository } // NameFromRef takes in a full container image reference string, such as // us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/talaria:dev or gcr.io/edge/talaria@sha256:... // and returns the image name. A very generous function which will return "" if // it cannot find a match. func NameFromRef(ref string) string { if strings.Contains(ref, "@") { tokens := strings.Split(ref, "@") tokens = strings.Split(tokens[0], "/") return tokens[len(tokens)-1] } if strings.Contains(ref, ":") { // no @ means we are dealing with vanilla tag, can split on ':' tokens := strings.Split(ref, ":") tokens = strings.Split(tokens[0], "/") return tokens[len(tokens)-1] } // no @ or : means its not really a ref, we'll try anyway and return // the string after the final '/' tokens := strings.Split(ref, "/") // return empty string if we can't even do that if len(tokens) == 0 { return "" } return tokens[len(tokens)-1] } // TagImage tags an individual image with all passed in tags func TagImage(img Image, tags []string) error { for _, tag := range tags { ref := img.Reference() if err := crane.Tag(ref, tag); err != nil { return errors.New(fmt.Sprintf("failed to tag %s with %s", ref, tag), err) } } return nil }