github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/sources/split.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  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  
    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/types"
    20  	"github.com/defenseunicorns/pkg/helpers"
    21  )
    22  
    23  var (
    24  	// verify that SplitTarballSource implements PackageSource
    25  	_ PackageSource = (*SplitTarballSource)(nil)
    26  )
    27  
    28  // SplitTarballSource is a package source for split tarballs.
    29  type SplitTarballSource struct {
    30  	*types.JackalPackageOptions
    31  }
    32  
    33  // Collect turns a split tarball into a full tarball.
    34  func (s *SplitTarballSource) Collect(dir string) (string, error) {
    35  	pattern := strings.Replace(s.PackageSource, ".part000", ".part*", 1)
    36  	fileList, err := filepath.Glob(pattern)
    37  	if err != nil {
    38  		return "", fmt.Errorf("unable to find split tarball files: %s", err)
    39  	}
    40  
    41  	// Ensure the files are in order so they are appended in the correct order
    42  	sort.Strings(fileList)
    43  
    44  	reassembled := filepath.Join(dir, filepath.Base(strings.Replace(s.PackageSource, ".part000", "", 1)))
    45  	// Create the new package
    46  	pkgFile, err := os.Create(reassembled)
    47  	if err != nil {
    48  		return "", fmt.Errorf("unable to create new package file: %s", err)
    49  	}
    50  	defer pkgFile.Close()
    51  
    52  	var pkgData types.JackalSplitPackageData
    53  	for idx, file := range fileList {
    54  		// The first file contains metadata about the package
    55  		if idx == 0 {
    56  			var bytes []byte
    57  
    58  			if bytes, err = os.ReadFile(file); err != nil {
    59  				return "", fmt.Errorf("unable to read file %s: %w", file, err)
    60  			}
    61  
    62  			if err := json.Unmarshal(bytes, &pkgData); err != nil {
    63  				return "", fmt.Errorf("unable to unmarshal file %s: %w", file, err)
    64  			}
    65  
    66  			count := len(fileList) - 1
    67  			if count != pkgData.Count {
    68  				return "", fmt.Errorf("package is missing parts, expected %d, found %d", pkgData.Count, count)
    69  			}
    70  
    71  			if len(s.Shasum) > 0 && pkgData.Sha256Sum != s.Shasum {
    72  				return "", fmt.Errorf("mismatch in CLI options and package metadata, expected %s, found %s", s.Shasum, pkgData.Sha256Sum)
    73  			}
    74  
    75  			continue
    76  		}
    77  
    78  		// Open the file
    79  		f, err := os.Open(file)
    80  		if err != nil {
    81  			return "", fmt.Errorf("unable to open file %s: %w", file, err)
    82  		}
    83  		defer f.Close()
    84  
    85  		// Add the file contents to the package
    86  		if _, err = io.Copy(pkgFile, f); err != nil {
    87  			return "", fmt.Errorf("unable to copy file %s: %w", file, err)
    88  		}
    89  
    90  		// Close the file when done copying
    91  		if err := f.Close(); err != nil {
    92  			return "", fmt.Errorf("unable to close file %s: %w", file, err)
    93  		}
    94  	}
    95  
    96  	if err := helpers.SHAsMatch(reassembled, pkgData.Sha256Sum); err != nil {
    97  		return "", fmt.Errorf("package integrity check failed: %w", err)
    98  	}
    99  
   100  	// Remove the parts to reduce disk space before extracting
   101  	for _, file := range fileList {
   102  		_ = os.Remove(file)
   103  	}
   104  
   105  	// communicate to the user that the package was reassembled
   106  	message.Infof("Reassembled package to: %q", reassembled)
   107  
   108  	return reassembled, nil
   109  }
   110  
   111  // LoadPackage loads a package from a split tarball.
   112  func (s *SplitTarballSource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.JackalPackage, warnings []string, err error) {
   113  	tb, err := s.Collect(filepath.Dir(s.PackageSource))
   114  	if err != nil {
   115  		return pkg, nil, err
   116  	}
   117  
   118  	// Update the package source to the reassembled tarball
   119  	s.PackageSource = tb
   120  	// Clear the shasum so it is not used for validation
   121  	s.Shasum = ""
   122  
   123  	ts := &TarballSource{
   124  		s.JackalPackageOptions,
   125  	}
   126  	return ts.LoadPackage(dst, filter, unarchiveAll)
   127  }
   128  
   129  // LoadPackageMetadata loads a package's metadata from a split tarball.
   130  func (s *SplitTarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.JackalPackage, warnings []string, err error) {
   131  	tb, err := s.Collect(filepath.Dir(s.PackageSource))
   132  	if err != nil {
   133  		return pkg, nil, err
   134  	}
   135  
   136  	// Update the package source to the reassembled tarball
   137  	s.PackageSource = tb
   138  
   139  	ts := &TarballSource{
   140  		s.JackalPackageOptions,
   141  	}
   142  	return ts.LoadPackageMetadata(dst, wantSBOM, skipValidation)
   143  }