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 }