github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/sources/tarball.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  	"archive/tar"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  
    15  	"github.com/Racer159/jackal/src/pkg/layout"
    16  	"github.com/Racer159/jackal/src/pkg/message"
    17  	"github.com/Racer159/jackal/src/pkg/packager/filters"
    18  	"github.com/Racer159/jackal/src/pkg/zoci"
    19  	"github.com/Racer159/jackal/src/types"
    20  	"github.com/defenseunicorns/pkg/helpers"
    21  	"github.com/mholt/archiver/v3"
    22  )
    23  
    24  var (
    25  	// verify that TarballSource implements PackageSource
    26  	_ PackageSource = (*TarballSource)(nil)
    27  )
    28  
    29  // TarballSource is a package source for tarballs.
    30  type TarballSource struct {
    31  	*types.JackalPackageOptions
    32  }
    33  
    34  // LoadPackage loads a package from a tarball.
    35  func (s *TarballSource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.JackalPackage, warnings []string, err error) {
    36  	spinner := message.NewProgressSpinner("Loading package from %q", s.PackageSource)
    37  	defer spinner.Stop()
    38  
    39  	if s.Shasum != "" {
    40  		if err := helpers.SHAsMatch(s.PackageSource, s.Shasum); err != nil {
    41  			return pkg, nil, err
    42  		}
    43  	}
    44  
    45  	pathsExtracted := []string{}
    46  
    47  	err = archiver.Walk(s.PackageSource, func(f archiver.File) error {
    48  		if f.IsDir() {
    49  			return nil
    50  		}
    51  		header, ok := f.Header.(*tar.Header)
    52  		if !ok {
    53  			return fmt.Errorf("expected header to be *tar.Header but was %T", f.Header)
    54  		}
    55  		path := header.Name
    56  
    57  		dir := filepath.Dir(path)
    58  		if dir != "." {
    59  			if err := os.MkdirAll(filepath.Join(dst.Base, dir), helpers.ReadExecuteAllWriteUser); err != nil {
    60  				return err
    61  			}
    62  		}
    63  
    64  		dstPath := filepath.Join(dst.Base, path)
    65  		pathsExtracted = append(pathsExtracted, path)
    66  		dst, err := os.Create(dstPath)
    67  		if err != nil {
    68  			return err
    69  		}
    70  		defer dst.Close()
    71  
    72  		_, err = io.Copy(dst, f)
    73  		if err != nil {
    74  			return err
    75  		}
    76  
    77  		return nil
    78  	})
    79  	if err != nil {
    80  		return pkg, nil, err
    81  	}
    82  
    83  	dst.SetFromPaths(pathsExtracted)
    84  
    85  	pkg, warnings, err = dst.ReadJackalYAML()
    86  	if err != nil {
    87  		return pkg, nil, err
    88  	}
    89  	pkg.Components, err = filter.Apply(pkg)
    90  	if err != nil {
    91  		return pkg, nil, err
    92  	}
    93  
    94  	if err := dst.MigrateLegacy(); err != nil {
    95  		return pkg, nil, err
    96  	}
    97  
    98  	if !dst.IsLegacyLayout() {
    99  		spinner := message.NewProgressSpinner("Validating full package checksums")
   100  		defer spinner.Stop()
   101  
   102  		if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, false); err != nil {
   103  			return pkg, nil, err
   104  		}
   105  
   106  		spinner.Success()
   107  
   108  		if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
   109  			return pkg, nil, err
   110  		}
   111  	}
   112  
   113  	if unarchiveAll {
   114  		for _, component := range pkg.Components {
   115  			if err := dst.Components.Unarchive(component); err != nil {
   116  				if layout.IsNotLoaded(err) {
   117  					_, err := dst.Components.Create(component)
   118  					if err != nil {
   119  						return pkg, nil, err
   120  					}
   121  				} else {
   122  					return pkg, nil, err
   123  				}
   124  			}
   125  		}
   126  
   127  		if dst.SBOMs.Path != "" {
   128  			if err := dst.SBOMs.Unarchive(); err != nil {
   129  				return pkg, nil, err
   130  			}
   131  		}
   132  	}
   133  
   134  	spinner.Success()
   135  
   136  	return pkg, warnings, nil
   137  }
   138  
   139  // LoadPackageMetadata loads a package's metadata from a tarball.
   140  func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.JackalPackage, warnings []string, err error) {
   141  	if s.Shasum != "" {
   142  		if err := helpers.SHAsMatch(s.PackageSource, s.Shasum); err != nil {
   143  			return pkg, nil, err
   144  		}
   145  	}
   146  
   147  	toExtract := zoci.PackageAlwaysPull
   148  	if wantSBOM {
   149  		toExtract = append(toExtract, layout.SBOMTar)
   150  	}
   151  	pathsExtracted := []string{}
   152  
   153  	for _, rel := range toExtract {
   154  		if err := archiver.Extract(s.PackageSource, rel, dst.Base); err != nil {
   155  			return pkg, nil, err
   156  		}
   157  		// archiver.Extract will not return an error if the file does not exist, so we must manually check
   158  		if !helpers.InvalidPath(filepath.Join(dst.Base, rel)) {
   159  			pathsExtracted = append(pathsExtracted, rel)
   160  		}
   161  	}
   162  
   163  	dst.SetFromPaths(pathsExtracted)
   164  
   165  	pkg, warnings, err = dst.ReadJackalYAML()
   166  	if err != nil {
   167  		return pkg, nil, err
   168  	}
   169  
   170  	if err := dst.MigrateLegacy(); err != nil {
   171  		return pkg, nil, err
   172  	}
   173  
   174  	if !dst.IsLegacyLayout() {
   175  		if wantSBOM {
   176  			spinner := message.NewProgressSpinner("Validating SBOM checksums")
   177  			defer spinner.Stop()
   178  
   179  			if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil {
   180  				return pkg, nil, err
   181  			}
   182  
   183  			spinner.Success()
   184  		}
   185  
   186  		if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
   187  			if errors.Is(err, ErrPkgSigButNoKey) && skipValidation {
   188  				message.Warn("The package was signed but no public key was provided, skipping signature validation")
   189  			} else {
   190  				return pkg, nil, err
   191  			}
   192  		}
   193  	}
   194  
   195  	if wantSBOM {
   196  		if err := dst.SBOMs.Unarchive(); err != nil {
   197  			return pkg, nil, err
   198  		}
   199  	}
   200  
   201  	return pkg, warnings, nil
   202  }
   203  
   204  // Collect for the TarballSource is essentially an `mv`
   205  func (s *TarballSource) Collect(dir string) (string, error) {
   206  	dst := filepath.Join(dir, filepath.Base(s.PackageSource))
   207  	return dst, os.Rename(s.PackageSource, dst)
   208  }