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 }