github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/client/oci/oci.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  // Package oci provides transparent caching of oci-like refs
     7  package oci
     8  
     9  import (
    10  	"context"
    11  	"crypto/sha256"
    12  	"fmt"
    13  	"io"
    14  	"strings"
    15  
    16  	"github.com/containers/image/copy"
    17  	"github.com/containers/image/oci/layout"
    18  	"github.com/containers/image/signature"
    19  	"github.com/containers/image/transports"
    20  	"github.com/containers/image/types"
    21  	"github.com/sylabs/singularity/internal/pkg/client/cache"
    22  	"github.com/sylabs/singularity/internal/pkg/sylog"
    23  )
    24  
    25  // ImageReference wraps containers/image ImageReference type
    26  type ImageReference struct {
    27  	source types.ImageReference
    28  	types.ImageReference
    29  }
    30  
    31  // ConvertReference converts a source reference into a cache.ImageReference to cache its blobs
    32  func ConvertReference(src types.ImageReference, sys *types.SystemContext) (types.ImageReference, error) {
    33  	// Our cache dir is an OCI directory. We are using this as a 'blob pool'
    34  	// storing all incoming containers under unique tags, which are a hash of
    35  	// their source URI.
    36  	cacheTag, err := calculateRefHash(src, sys)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	c, err := layout.ParseReference(cache.OciBlob() + ":" + cacheTag)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	return &ImageReference{
    47  		source:         src,
    48  		ImageReference: c,
    49  	}, nil
    50  
    51  }
    52  
    53  // NewImageSource wraps the cache's oci-layout ref to first download the real source image to the cache
    54  func (t *ImageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
    55  	return t.newImageSource(ctx, sys, sylog.Writer())
    56  }
    57  
    58  func (t *ImageReference) newImageSource(ctx context.Context, sys *types.SystemContext, w io.Writer) (types.ImageSource, error) {
    59  	policy := &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
    60  	policyCtx, err := signature.NewPolicyContext(policy)
    61  
    62  	// First we are fetching into the cache
    63  	err = copy.Image(context.Background(), policyCtx, t.ImageReference, t.source, &copy.Options{
    64  		ReportWriter: w,
    65  		SourceCtx:    sys,
    66  	})
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return t.ImageReference.NewImageSource(ctx, sys)
    71  }
    72  
    73  // ParseImageName parses a uri (e.g. docker://ubuntu) into it's transport:reference
    74  // combination and then returns the proper reference
    75  func ParseImageName(uri string, sys *types.SystemContext) (types.ImageReference, error) {
    76  	ref, err := parseURI(uri)
    77  	if err != nil {
    78  		return nil, fmt.Errorf("Unable to parse image name %v: %v", uri, err)
    79  	}
    80  
    81  	return ConvertReference(ref, sys)
    82  }
    83  
    84  func parseURI(uri string) (types.ImageReference, error) {
    85  	sylog.Debugf("Parsing %s into reference", uri)
    86  
    87  	split := strings.SplitN(uri, ":", 2)
    88  	if len(split) != 2 {
    89  		return nil, fmt.Errorf("%s not in transport:reference pair", uri)
    90  	}
    91  
    92  	transport := transports.Get(split[0])
    93  	if transport == nil {
    94  		return nil, fmt.Errorf("%s not a registered transport", split[0])
    95  	}
    96  
    97  	return transport.ParseReference(split[1])
    98  }
    99  
   100  // TempImageExists returns whether or not the uri exists splatted out in the cache.OciTemp() directory
   101  func TempImageExists(uri string) (bool, string, error) {
   102  	sum, err := ImageSHA(uri, nil)
   103  	if err != nil {
   104  		return false, "", err
   105  	}
   106  
   107  	split := strings.Split(uri, ":")
   108  	if len(split) < 2 {
   109  		return false, "", fmt.Errorf("poorly formatted URI %v", uri)
   110  	}
   111  
   112  	exists, err := cache.OciTempExists(sum, split[1])
   113  	return exists, cache.OciTempImage(sum, split[1]), err
   114  }
   115  
   116  // ImageSHA calculates the SHA of a uri's manifest
   117  func ImageSHA(uri string, sys *types.SystemContext) (string, error) {
   118  	ref, err := parseURI(uri)
   119  	if err != nil {
   120  		return "", fmt.Errorf("Unable to parse image name %v: %v", uri, err)
   121  	}
   122  
   123  	return calculateRefHash(ref, sys)
   124  }
   125  
   126  func calculateRefHash(ref types.ImageReference, sys *types.SystemContext) (string, error) {
   127  	source, err := ref.NewImageSource(context.TODO(), sys)
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  
   132  	man, _, err := source.GetManifest(context.TODO(), nil)
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  
   137  	hash := fmt.Sprintf("%x", sha256.Sum256(man))
   138  	return hash, nil
   139  }