1 // Package image provides utilities for defining container image structs and working 2 // with them. 3 package image 4 5 import ( 6 "fmt" 7 "strings" 8 9 "edge-infra.dev/pkg/lib/errors" 10 11 "github.com/google/go-containerregistry/pkg/crane" 12 ) 13 14 // Image defines information we care about for a given container image. 15 // `yaml` tags used so the struct can be correctly marshaled by Kpt 16 type Image struct { 17 // Digest is the sha256 digest for the image 18 Digest string `yaml:"digest"` 19 // Registry is the container registry string, e.g., us.gcr.io, 20 // ncr-emeraldedge-docker-dev.jfrog.io. 21 // https://docs.microsoft.com/en-us/azure/container-registry/container-registry-concepts#registry 22 Registry string `yaml:"registry,omitempty"` 23 // Repository is the container name, e.g., talaria. They can also contain slashes, 24 // used to implement namespacing in some registries, such as GCR. 25 // For GCR, the repository would be $project-id/talaria by default, but they 26 // can be more deeply nested. 27 // Multiple namespaces can also be used: 28 // https://docs.microsoft.com/en-us/azure/container-registry/container-registry-concepts#repository 29 Repository string `yaml:"repository"` 30 } 31 32 // Reference creates the fully qualified image reference string for an Image based 33 // on the digest, e.g. us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/talaria@sha256:... 34 func (img *Image) Reference() string { 35 return fmt.Sprintf("%s/%s@%s", img.Registry, img.Repository, img.Digest) 36 } 37 38 // Name returns the colloquial container image name, e.g., `talaria`. 39 // 40 // Useful for pulling out a consistent image identifier regardless of the 41 // repository. 42 // For example, say we need to identify container image references for the same 43 // container image across multiple registries, GCR and JFrog. Since GCR 44 // supports repository namespacing (and enforces you namespace by your $project-id), 45 // simply comparing the repository will not work ($project-id/talaria wont match talaria). 46 // 47 // Note that the name is a substring of the Image.Repository. If the 48 // registry implementation supports repository namespacing, then it will 49 // be the last token after splitting on `/` and removing the tag/digest. 50 // If the registry implementation does _not_ support repository namespacing, 51 // the repository is the container image name. 52 func (img *Image) Name() string { 53 if strings.Contains(img.Repository, "/") { 54 return NameFromRef(img.Repository) 55 } 56 return img.Repository 57 } 58 59 // NameFromRef takes in a full container image reference string, such as 60 // us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/talaria:dev or gcr.io/edge/talaria@sha256:... 61 // and returns the image name. A very generous function which will return "" if 62 // it cannot find a match. 63 func NameFromRef(ref string) string { 64 if strings.Contains(ref, "@") { 65 tokens := strings.Split(ref, "@") 66 tokens = strings.Split(tokens[0], "/") 67 return tokens[len(tokens)-1] 68 } 69 if strings.Contains(ref, ":") { 70 // no @ means we are dealing with vanilla tag, can split on ':' 71 tokens := strings.Split(ref, ":") 72 tokens = strings.Split(tokens[0], "/") 73 return tokens[len(tokens)-1] 74 } 75 76 // no @ or : means its not really a ref, we'll try anyway and return 77 // the string after the final '/' 78 tokens := strings.Split(ref, "/") 79 // return empty string if we can't even do that 80 if len(tokens) == 0 { 81 return "" 82 } 83 return tokens[len(tokens)-1] 84 } 85 86 // TagImage tags an individual image with all passed in tags 87 func TagImage(img Image, tags []string) error { 88 for _, tag := range tags { 89 ref := img.Reference() 90 if err := crane.Tag(ref, tag); err != nil { 91 return errors.New(fmt.Sprintf("failed to tag %s with %s", ref, tag), err) 92 } 93 } 94 return nil 95 } 96