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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package sources contains core implementations of the PackageSource interface.
     5  package sources
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/Racer159/jackal/src/config"
    16  	"github.com/Racer159/jackal/src/pkg/layout"
    17  	"github.com/Racer159/jackal/src/pkg/message"
    18  	"github.com/Racer159/jackal/src/pkg/packager/filters"
    19  	"github.com/Racer159/jackal/src/pkg/utils"
    20  	"github.com/Racer159/jackal/src/pkg/zoci"
    21  	"github.com/Racer159/jackal/src/types"
    22  	"github.com/mholt/archiver/v3"
    23  )
    24  
    25  var (
    26  	// verify that OCISource implements PackageSource
    27  	_ PackageSource = (*OCISource)(nil)
    28  )
    29  
    30  // OCISource is a package source for OCI registries.
    31  type OCISource struct {
    32  	*types.JackalPackageOptions
    33  	*zoci.Remote
    34  }
    35  
    36  // LoadPackage loads a package from an OCI registry.
    37  func (s *OCISource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.JackalPackage, warnings []string, err error) {
    38  	ctx := context.TODO()
    39  
    40  	message.Debugf("Loading package from %q", s.PackageSource)
    41  
    42  	pkg, err = s.FetchJackalYAML(ctx)
    43  	if err != nil {
    44  		return pkg, nil, err
    45  	}
    46  	pkg.Components, err = filter.Apply(pkg)
    47  	if err != nil {
    48  		return pkg, nil, err
    49  	}
    50  
    51  	layersToPull, err := s.LayersFromRequestedComponents(ctx, pkg.Components)
    52  	if err != nil {
    53  		return pkg, nil, fmt.Errorf("unable to get published component image layers: %s", err.Error())
    54  	}
    55  
    56  	isPartial := true
    57  	root, err := s.FetchRoot(ctx)
    58  	if err != nil {
    59  		return pkg, nil, err
    60  	}
    61  	if len(root.Layers) == len(layersToPull) {
    62  		isPartial = false
    63  	}
    64  
    65  	layersFetched, err := s.PullPackage(ctx, dst.Base, config.CommonOptions.OCIConcurrency, layersToPull...)
    66  	if err != nil {
    67  		return pkg, nil, fmt.Errorf("unable to pull the package: %w", err)
    68  	}
    69  	dst.SetFromLayers(layersFetched)
    70  
    71  	if err := dst.MigrateLegacy(); err != nil {
    72  		return pkg, nil, err
    73  	}
    74  
    75  	if !dst.IsLegacyLayout() {
    76  		spinner := message.NewProgressSpinner("Validating pulled layer checksums")
    77  		defer spinner.Stop()
    78  
    79  		if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, isPartial); err != nil {
    80  			return pkg, nil, err
    81  		}
    82  
    83  		spinner.Success()
    84  
    85  		if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
    86  			return pkg, nil, err
    87  		}
    88  	}
    89  
    90  	if unarchiveAll {
    91  		for _, component := range pkg.Components {
    92  			if err := dst.Components.Unarchive(component); err != nil {
    93  				if layout.IsNotLoaded(err) {
    94  					_, err := dst.Components.Create(component)
    95  					if err != nil {
    96  						return pkg, nil, err
    97  					}
    98  				} else {
    99  					return pkg, nil, err
   100  				}
   101  			}
   102  		}
   103  
   104  		if dst.SBOMs.Path != "" {
   105  			if err := dst.SBOMs.Unarchive(); err != nil {
   106  				return pkg, nil, err
   107  			}
   108  		}
   109  	}
   110  
   111  	return pkg, warnings, nil
   112  }
   113  
   114  // LoadPackageMetadata loads a package's metadata from an OCI registry.
   115  func (s *OCISource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.JackalPackage, warnings []string, err error) {
   116  	toPull := zoci.PackageAlwaysPull
   117  	if wantSBOM {
   118  		toPull = append(toPull, layout.SBOMTar)
   119  	}
   120  	ctx := context.TODO()
   121  	layersFetched, err := s.PullPaths(ctx, dst.Base, toPull)
   122  	if err != nil {
   123  		return pkg, nil, err
   124  	}
   125  	dst.SetFromLayers(layersFetched)
   126  
   127  	pkg, warnings, err = dst.ReadJackalYAML()
   128  	if err != nil {
   129  		return pkg, nil, err
   130  	}
   131  
   132  	if err := dst.MigrateLegacy(); err != nil {
   133  		return pkg, nil, err
   134  	}
   135  
   136  	if !dst.IsLegacyLayout() {
   137  		if wantSBOM {
   138  			spinner := message.NewProgressSpinner("Validating SBOM checksums")
   139  			defer spinner.Stop()
   140  
   141  			if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil {
   142  				return pkg, nil, err
   143  			}
   144  
   145  			spinner.Success()
   146  		}
   147  
   148  		if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
   149  			if errors.Is(err, ErrPkgSigButNoKey) && skipValidation {
   150  				message.Warn("The package was signed but no public key was provided, skipping signature validation")
   151  			} else {
   152  				return pkg, nil, err
   153  			}
   154  		}
   155  	}
   156  
   157  	// unpack sboms.tar
   158  	if wantSBOM {
   159  		if err := dst.SBOMs.Unarchive(); err != nil {
   160  			return pkg, nil, err
   161  		}
   162  	}
   163  
   164  	return pkg, warnings, nil
   165  }
   166  
   167  // Collect pulls a package from an OCI registry and writes it to a tarball.
   168  func (s *OCISource) Collect(dir string) (string, error) {
   169  	tmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
   170  	if err != nil {
   171  		return "", err
   172  	}
   173  	defer os.RemoveAll(tmp)
   174  	ctx := context.TODO()
   175  	fetched, err := s.PullPackage(ctx, tmp, config.CommonOptions.OCIConcurrency)
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  
   180  	loaded := layout.New(tmp)
   181  	loaded.SetFromLayers(fetched)
   182  
   183  	var pkg types.JackalPackage
   184  
   185  	if err := utils.ReadYaml(loaded.JackalYAML, &pkg); err != nil {
   186  		return "", err
   187  	}
   188  
   189  	spinner := message.NewProgressSpinner("Validating full package checksums")
   190  	defer spinner.Stop()
   191  
   192  	if err := ValidatePackageIntegrity(loaded, pkg.Metadata.AggregateChecksum, false); err != nil {
   193  		return "", err
   194  	}
   195  
   196  	spinner.Success()
   197  
   198  	// TODO (@Noxsios) remove the suffix check at v1.0.0
   199  	isSkeleton := pkg.Build.Architecture == zoci.SkeletonArch || strings.HasSuffix(s.Repo().Reference.Reference, zoci.SkeletonArch)
   200  	name := fmt.Sprintf("%s%s", NameFromMetadata(&pkg, isSkeleton), PkgSuffix(pkg.Metadata.Uncompressed))
   201  
   202  	dstTarball := filepath.Join(dir, name)
   203  
   204  	allTheLayers, err := filepath.Glob(filepath.Join(tmp, "*"))
   205  	if err != nil {
   206  		return "", err
   207  	}
   208  
   209  	_ = os.Remove(dstTarball)
   210  
   211  	return dstTarball, archiver.Archive(allTheLayers, dstTarball)
   212  }