github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/ociinstaller/ocidownloader.go (about)

     1  package ociinstaller
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/containerd/containerd/remotes"
    10  	"github.com/containerd/containerd/remotes/docker"
    11  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    12  	"github.com/sirupsen/logrus"
    13  	"oras.land/oras-go/v2"
    14  	"oras.land/oras-go/v2/content"
    15  	"oras.land/oras-go/v2/content/file"
    16  	"oras.land/oras-go/v2/content/memory"
    17  	"oras.land/oras-go/v2/registry/remote"
    18  	"oras.land/oras-go/v2/registry/remote/auth"
    19  	"oras.land/oras-go/v2/registry/remote/credentials"
    20  	"oras.land/oras-go/v2/registry/remote/retry"
    21  )
    22  
    23  type ociDownloader struct {
    24  	resolver remotes.Resolver
    25  	Images   []*SteampipeImage
    26  }
    27  
    28  // NewOciDownloader creates and returns a ociDownloader instance
    29  func NewOciDownloader() *ociDownloader {
    30  	// oras uses containerd, which uses logrus and is set up to log
    31  	// warning and above.  Set to ErrrLevel to get rid of unwanted error message
    32  	logrus.SetLevel(logrus.ErrorLevel)
    33  	return &ociDownloader{
    34  		resolver: docker.NewResolver(docker.ResolverOptions{}),
    35  	}
    36  }
    37  
    38  /*
    39  Pull downloads the image from the given `ref` to the supplied `destDir`
    40  
    41  Returns
    42  
    43  	imageDescription, configDescription, config, imageLayers, error
    44  */
    45  func (o *ociDownloader) Pull(ctx context.Context, ref string, mediaTypes []string, destDir string) (*ocispec.Descriptor, *ocispec.Descriptor, []byte, []ocispec.Descriptor, error) {
    46  	split := strings.Split(ref, ":")
    47  	tag := split[len(split)-1]
    48  	log.Println("[TRACE] ociDownloader.Pull:", "preparing to pull ref", ref, "tag", tag, "destDir", destDir)
    49  
    50  	// Create the target file store
    51  	memoryStore := memory.New()
    52  	fileStore, err := file.NewWithFallbackStorage(destDir, memoryStore)
    53  	if err != nil {
    54  		return nil, nil, nil, nil, err
    55  	}
    56  	defer fileStore.Close()
    57  
    58  	// Connect to the remote repository
    59  	repo, err := remote.NewRepository(ref)
    60  	if err != nil {
    61  		return nil, nil, nil, nil, err
    62  	}
    63  
    64  	// Get credentials from the docker credentials store
    65  	storeOpts := credentials.StoreOptions{}
    66  	credStore, err := credentials.NewStoreFromDocker(storeOpts)
    67  	if err != nil {
    68  		return nil, nil, nil, nil, err
    69  	}
    70  
    71  	// Prepare the auth client for the registry and credential store
    72  	repo.Client = &auth.Client{
    73  		Client:     retry.DefaultClient,
    74  		Cache:      auth.DefaultCache,
    75  		Credential: credentials.Credential(credStore), // Use the credential store
    76  	}
    77  
    78  	// Copy from the remote repository to the file store
    79  	log.Println("[TRACE] ociDownloader.Pull:", "pulling...")
    80  
    81  	copyOpt := oras.DefaultCopyOptions
    82  	manifestDescriptor, err := oras.Copy(ctx, repo, tag, fileStore, tag, copyOpt)
    83  	if err != nil {
    84  		log.Println("[TRACE] ociDownloader.Pull:", "failed to pull", ref, err)
    85  		return nil, nil, nil, nil, err
    86  	}
    87  	log.Println("[TRACE] ociDownloader.Pull:", "manifest", manifestDescriptor.Digest, manifestDescriptor.MediaType)
    88  
    89  	// FIXME: this seems redundant as oras.Copy() already downloads all artifacts, but that's the only I found
    90  	// to access the manifest config. Also, it shouldn't be an issue as files are not re-downloaded.
    91  	manifestJson, err := content.FetchAll(ctx, fileStore, manifestDescriptor)
    92  	if err != nil {
    93  		log.Println("[TRACE] ociDownloader.Pull:", "failed to fetch manifest", manifestDescriptor)
    94  		return nil, nil, nil, nil, err
    95  	}
    96  	log.Println("[TRACE] ociDownloader.Pull:", "manifest content", string(manifestJson))
    97  
    98  	// Parse the fetched manifest
    99  	var manifest ocispec.Manifest
   100  	err = json.Unmarshal(manifestJson, &manifest)
   101  	if err != nil {
   102  		log.Println("[TRACE] ociDownloader.Pull:", "failed to unmarshall manifest", manifestJson)
   103  		return nil, nil, nil, nil, err
   104  	}
   105  
   106  	// Fetch the config from the file store
   107  	configData, err := content.FetchAll(ctx, fileStore, manifest.Config)
   108  	if err != nil {
   109  		log.Println("[TRACE] ociDownloader.Pull:", "failed to fetch config", manifest.Config.MediaType, err)
   110  		return nil, nil, nil, nil, err
   111  	}
   112  	log.Println("[TRACE] ociDownloader.Pull:", "config", string(configData))
   113  
   114  	return &manifestDescriptor, &manifest.Config, configData, manifest.Layers, err
   115  }