github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/composer/oci.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package composer contains functions for composing components within Jackal packages.
     5  package composer
     6  
     7  import (
     8  	"context"
     9  	"crypto/sha256"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/Racer159/jackal/src/config"
    15  	"github.com/Racer159/jackal/src/pkg/layout"
    16  	"github.com/Racer159/jackal/src/pkg/message"
    17  	"github.com/Racer159/jackal/src/pkg/utils"
    18  	"github.com/Racer159/jackal/src/pkg/zoci"
    19  	"github.com/defenseunicorns/pkg/helpers"
    20  	"github.com/defenseunicorns/pkg/oci"
    21  	"github.com/mholt/archiver/v3"
    22  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  	ocistore "oras.land/oras-go/v2/content/oci"
    24  )
    25  
    26  func (ic *ImportChain) getRemote(url string) (*zoci.Remote, error) {
    27  	if ic.remote != nil {
    28  		return ic.remote, nil
    29  	}
    30  	var err error
    31  	ic.remote, err = zoci.NewRemote(url, zoci.PlatformForSkeleton())
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	_, err = ic.remote.ResolveRoot(context.TODO())
    36  	if err != nil {
    37  		return nil, fmt.Errorf("published skeleton package for %q does not exist: %w", url, err)
    38  	}
    39  	return ic.remote, nil
    40  }
    41  
    42  // ContainsOCIImport returns true if the import chain contains a remote import
    43  func (ic *ImportChain) ContainsOCIImport() bool {
    44  	// only the 2nd to last node may have a remote import
    45  	return ic.tail.prev != nil && ic.tail.prev.Import.URL != ""
    46  }
    47  
    48  func (ic *ImportChain) fetchOCISkeleton() error {
    49  	if !ic.ContainsOCIImport() {
    50  		return nil
    51  	}
    52  	node := ic.tail.prev
    53  	remote, err := ic.getRemote(node.Import.URL)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	ctx := context.TODO()
    59  	manifest, err := remote.FetchRoot(ctx)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	name := node.ImportName()
    65  
    66  	componentDesc := manifest.Locate(filepath.Join(layout.ComponentsDir, fmt.Sprintf("%s.tar", name)))
    67  
    68  	cache := filepath.Join(config.GetAbsCachePath(), "oci")
    69  	if err := helpers.CreateDirectory(cache, helpers.ReadWriteExecuteUser); err != nil {
    70  		return err
    71  	}
    72  
    73  	var tb, dir string
    74  
    75  	// if there is not a tarball to fetch, create a directory named based upon
    76  	// the import url and the component name
    77  	if oci.IsEmptyDescriptor(componentDesc) {
    78  		h := sha256.New()
    79  		h.Write([]byte(node.Import.URL + name))
    80  		id := fmt.Sprintf("%x", h.Sum(nil))
    81  
    82  		dir = filepath.Join(cache, "dirs", id)
    83  
    84  		message.Debug("creating empty directory for remote component:", filepath.Join("<jackal-cache>", "oci", "dirs", id))
    85  	} else {
    86  		tb = filepath.Join(cache, "blobs", "sha256", componentDesc.Digest.Encoded())
    87  		dir = filepath.Join(cache, "dirs", componentDesc.Digest.Encoded())
    88  
    89  		store, err := ocistore.New(cache)
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		ctx := context.TODO()
    95  		// ensure the tarball is in the cache
    96  		exists, err := store.Exists(ctx, componentDesc)
    97  		if err != nil {
    98  			return err
    99  		} else if !exists {
   100  			doneSaving := make(chan error)
   101  			successText := fmt.Sprintf("Pulling %q", helpers.OCIURLPrefix+remote.Repo().Reference.String())
   102  			go utils.RenderProgressBarForLocalDirWrite(cache, componentDesc.Size, doneSaving, "Pulling", successText)
   103  			err = remote.CopyToTarget(ctx, []ocispec.Descriptor{componentDesc}, store, remote.GetDefaultCopyOpts())
   104  			doneSaving <- err
   105  			<-doneSaving
   106  			if err != nil {
   107  				return err
   108  			}
   109  
   110  		}
   111  	}
   112  
   113  	if err := helpers.CreateDirectory(dir, helpers.ReadWriteExecuteUser); err != nil {
   114  		return err
   115  	}
   116  
   117  	cwd, err := os.Getwd()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	rel, err := filepath.Rel(cwd, dir)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	// the tail node is the only node whose relativeToHead is based solely upon cwd<->cache
   126  	// contrary to the other nodes, which are based upon the previous node
   127  	ic.tail.relativeToHead = rel
   128  
   129  	if oci.IsEmptyDescriptor(componentDesc) {
   130  		// nothing was fetched, nothing to extract
   131  		return nil
   132  	}
   133  
   134  	tu := archiver.Tar{
   135  		OverwriteExisting: true,
   136  		// removes /<component-name>/ from the paths
   137  		StripComponents: 1,
   138  	}
   139  	return tu.Unarchive(tb, dir)
   140  }