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  }