github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/utils/image.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package utils provides generic utility functions. 5 package utils 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "os" 11 "path/filepath" 12 13 "github.com/Racer159/jackal/src/pkg/transform" 14 "github.com/defenseunicorns/pkg/helpers" 15 v1 "github.com/google/go-containerregistry/pkg/v1" 16 "github.com/google/go-containerregistry/pkg/v1/layout" 17 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 18 ) 19 20 // LoadOCIImage returns a v1.Image with the image ref specified from a location provided, or an error if the image cannot be found. 21 func LoadOCIImage(imgPath string, refInfo transform.Image) (v1.Image, error) { 22 // Use the manifest within the index.json to load the specific image we want 23 layoutPath := layout.Path(imgPath) 24 imgIdx, err := layoutPath.ImageIndex() 25 if err != nil { 26 return nil, err 27 } 28 idxManifest, err := imgIdx.IndexManifest() 29 if err != nil { 30 return nil, err 31 } 32 33 // Search through all the manifests within this package until we find the annotation that matches our ref 34 for _, manifest := range idxManifest.Manifests { 35 if manifest.Annotations[ocispec.AnnotationBaseImageName] == refInfo.Reference || 36 // A backwards compatibility shim for older Jackal versions that would leave docker.io off of image annotations 37 (manifest.Annotations[ocispec.AnnotationBaseImageName] == refInfo.Path+refInfo.TagOrDigest && refInfo.Host == "docker.io") { 38 39 // This is the image we are looking for, load it and then return 40 return layoutPath.Image(manifest.Digest) 41 } 42 } 43 44 return nil, fmt.Errorf("unable to find image (%s) at the path (%s)", refInfo.Reference, imgPath) 45 } 46 47 // AddImageNameAnnotation adds an annotation to the index.json file so that the deploying code can figure out what the image reference <-> digest shasum will be. 48 func AddImageNameAnnotation(ociPath string, referenceToDigest map[string]string) error { 49 indexPath := filepath.Join(ociPath, "index.json") 50 51 // Read the file contents and turn it into a usable struct that we can manipulate 52 var index ocispec.Index 53 byteValue, err := os.ReadFile(indexPath) 54 if err != nil { 55 return fmt.Errorf("unable to read the contents of the file (%s) so we can add an annotation: %w", indexPath, err) 56 } 57 if err = json.Unmarshal(byteValue, &index); err != nil { 58 return fmt.Errorf("unable to process the contents of the file (%s): %w", indexPath, err) 59 } 60 61 // Loop through the manifests and add the appropriate OCI Base Image Name Annotation 62 for idx, manifest := range index.Manifests { 63 if manifest.Annotations == nil { 64 manifest.Annotations = make(map[string]string) 65 } 66 67 var baseImageName string 68 69 for reference, digest := range referenceToDigest { 70 if digest == manifest.Digest.String() { 71 baseImageName = reference 72 } 73 } 74 75 if baseImageName != "" { 76 manifest.Annotations[ocispec.AnnotationBaseImageName] = baseImageName 77 index.Manifests[idx] = manifest 78 delete(referenceToDigest, baseImageName) 79 } 80 } 81 82 // Write the file back to the package 83 indexJSONBytes, err := json.Marshal(index) 84 if err != nil { 85 return err 86 } 87 return os.WriteFile(indexPath, indexJSONBytes, helpers.ReadWriteUser) 88 } 89 90 // HasImageLayers checks if any layers in the v1.Image are known image layers. 91 func HasImageLayers(img v1.Image) (bool, error) { 92 layers, err := img.Layers() 93 if err != nil { 94 return false, err 95 } 96 for _, layer := range layers { 97 mediatype, err := layer.MediaType() 98 if err != nil { 99 return false, err 100 } 101 // Check if mediatype is a known image layer 102 if mediatype.IsLayer() { 103 return true, nil 104 } 105 } 106 return false, nil 107 }