diff --git a/src/cmd/package.go b/src/cmd/package.go index 3ae1b0e2ff..bbd61c3ff7 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -153,14 +153,15 @@ var packageMirrorCmd = &cobra.Command{ SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, Filter: filter, } - pkgPaths, err := packager2.LoadPackage(cmd.Context(), loadOpt) + pkgLayout, err := packager2.LoadPackage(cmd.Context(), loadOpt) if err != nil { return err } - defer os.RemoveAll(pkgPaths.Base) + //nolint: errcheck // ignore + defer pkgLayout.Cleanup() mirrorOpt := packager2.MirrorOptions{ Cluster: c, - PackagePaths: *pkgPaths, + PkgLayout: pkgLayout, Filter: filter, RegistryInfo: pkgConfig.InitOpts.RegistryInfo, GitInfo: pkgConfig.InitOpts.GitServer, diff --git a/src/internal/packager2/inspect.go b/src/internal/packager2/inspect.go index bfad933b3a..0e4c1e0321 100644 --- a/src/internal/packager2/inspect.go +++ b/src/internal/packager2/inspect.go @@ -7,12 +7,15 @@ package packager2 import ( "context" "fmt" + "os" "github.com/defenseunicorns/pkg/helpers/v2" "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/config" "github.com/zarf-dev/zarf/src/internal/packager/sbom" "github.com/zarf-dev/zarf/src/pkg/cluster" "github.com/zarf-dev/zarf/src/pkg/packager/filters" + "github.com/zarf-dev/zarf/src/pkg/utils" ) // ZarfInspectOptions tracks the user-defined preferences during a package inspection. @@ -35,7 +38,7 @@ func Inspect(ctx context.Context, opt ZarfInspectOptions) (v1alpha1.ZarfPackage, } if getSBOM(opt.ViewSBOM, opt.SBOMOutputDir) { - err = handleSBOMOptions(ctx, pkg, opt) + err = handleSBOMOptions(ctx, opt) if err != nil { return pkg, err } @@ -75,7 +78,7 @@ func getPackageMetadata(ctx context.Context, opt ZarfInspectOptions) (v1alpha1.Z return pkg, nil } -func handleSBOMOptions(ctx context.Context, pkg v1alpha1.ZarfPackage, opt ZarfInspectOptions) error { +func handleSBOMOptions(ctx context.Context, opt ZarfInspectOptions) error { loadOpt := LoadOptions{ Source: opt.Source, SkipSignatureValidation: opt.SkipSignatureValidation, @@ -86,23 +89,25 @@ func handleSBOMOptions(ctx context.Context, pkg v1alpha1.ZarfPackage, opt ZarfIn if err != nil { return err } - if opt.SBOMOutputDir != "" { - out, err := layout.SBOMs.OutputSBOMFiles(opt.SBOMOutputDir, pkg.Metadata.Name) + + sbomDirPath := opt.SBOMOutputDir + if sbomDirPath == "" { + tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { return err } - if opt.ViewSBOM { - err := sbom.ViewSBOMFiles(out) - if err != nil { - return err - } - } - } else if opt.ViewSBOM { - err := sbom.ViewSBOMFiles(layout.SBOMs.Path) + defer os.RemoveAll(tmpDir) + sbomDirPath = tmpDir + } + sbomPath, err := layout.GetSBOM(sbomDirPath) + if err != nil { + return err + } + if opt.ViewSBOM { + err := sbom.ViewSBOMFiles(sbomPath) if err != nil { return err } - return err } return nil } diff --git a/src/internal/packager2/layout/layout.go b/src/internal/packager2/layout/layout.go new file mode 100644 index 0000000000..7dcd09971c --- /dev/null +++ b/src/internal/packager2/layout/layout.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package layout contains functions for inteacting the Zarf packages. +package layout + +// Constants used in the default package layout. +const ( + ZarfYAML = "zarf.yaml" + Signature = "zarf.yaml.sig" + Checksums = "checksums.txt" + + ImagesDir = "images" + ComponentsDir = "components" + + SBOMDir = "zarf-sbom" + SBOMTar = "sboms.tar" + + IndexJSON = "index.json" + OCILayout = "oci-layout" +) + +// ComponentDir is the type for the different directories in a component. +type ComponentDir string + +// Different component directory types. +const ( + RepoComponentDir ComponentDir = "repos" + FilesComponentDir ComponentDir = "files" + ChartsComponentDir ComponentDir = "charts" + ManifestsComponentDir ComponentDir = "manifests" + DataComponentDir ComponentDir = "data" + ValuesComponentDir ComponentDir = "values" +) diff --git a/src/internal/packager2/layout/package.go b/src/internal/packager2/layout/package.go new file mode 100644 index 0000000000..cf8dbf179b --- /dev/null +++ b/src/internal/packager2/layout/package.go @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package layout + +import ( + "archive/tar" + "context" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/defenseunicorns/pkg/helpers/v2" + goyaml "github.com/goccy/go-yaml" + registryv1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/mholt/archiver/v3" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" + + "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/pkg/packager/deprecated" + "github.com/zarf-dev/zarf/src/pkg/transform" + "github.com/zarf-dev/zarf/src/pkg/utils" +) + +// PackageLayout manages the layout for a package. +type PackageLayout struct { + dirPath string + Pkg v1alpha1.ZarfPackage +} + +// PackageLayoutOptions are the options used when loading a package. +type PackageLayoutOptions struct { + PublicKeyPath string + SkipSignatureValidation bool + IsPartial bool +} + +// LoadFromTar unpacks the give compressed package and loads it. +func LoadFromTar(ctx context.Context, tarPath string, opt PackageLayoutOptions) (*PackageLayout, error) { + dirPath, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return nil, err + } + err = archiver.Walk(tarPath, func(f archiver.File) error { + if f.IsDir() { + return nil + } + header, ok := f.Header.(*tar.Header) + if !ok { + return fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) + } + // If path has nested directories we want to create them. + dir := filepath.Dir(header.Name) + if dir != "." { + err := os.MkdirAll(filepath.Join(dirPath, dir), helpers.ReadExecuteAllWriteUser) + if err != nil { + return err + } + } + dst, err := os.Create(filepath.Join(dirPath, header.Name)) + if err != nil { + return err + } + defer dst.Close() + _, err = io.Copy(dst, f) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + p, err := LoadFromDir(ctx, dirPath, opt) + if err != nil { + return nil, err + } + return p, nil +} + +// LoadFromDir loads and validates a package from the given directory path. +func LoadFromDir(ctx context.Context, dirPath string, opt PackageLayoutOptions) (*PackageLayout, error) { + b, err := os.ReadFile(filepath.Join(dirPath, ZarfYAML)) + if err != nil { + return nil, err + } + var pkg v1alpha1.ZarfPackage + err = goyaml.Unmarshal(b, &pkg) + if err != nil { + return nil, err + } + if len(pkg.Build.Migrations) > 0 { + for idx, component := range pkg.Components { + pkg.Components[idx], _ = deprecated.MigrateComponent(pkg.Build, component) + } + } + + pkgLayout := &PackageLayout{ + dirPath: dirPath, + Pkg: pkg, + } + err = validatePackageIntegrity(pkgLayout, opt.IsPartial) + if err != nil { + return nil, err + } + err = validatePackageSignature(ctx, pkgLayout, opt.PublicKeyPath, opt.SkipSignatureValidation) + if err != nil { + return nil, err + } + return pkgLayout, nil +} + +// Cleanup removes any temporary directories created. +func (p *PackageLayout) Cleanup() error { + err := os.RemoveAll(p.dirPath) + if err != nil { + return err + } + return nil +} + +// GetSBOM outputs the SBOM data from the package to the give destination path. +func (p *PackageLayout) GetSBOM(destPath string) (string, error) { + path := filepath.Join(destPath, p.Pkg.Metadata.Name) + err := archiver.Extract(filepath.Join(p.dirPath, SBOMTar), "", path) + if err != nil { + return "", err + } + return path, nil +} + +// GetComponentDir returns a path to the directory in the given component. +func (p *PackageLayout) GetComponentDir(destPath, componentName string, ct ComponentDir) (string, error) { + sourcePath := filepath.Join(p.dirPath, ComponentsDir, fmt.Sprintf("%s.tar", componentName)) + _, err := os.Stat(sourcePath) + if errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("component %s does not exist in package: %w", componentName, err) + } + if err != nil { + return "", err + } + tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return "", err + } + defer os.RemoveAll(tmpDir) + // TODO (phillebaba): We are not using archiver.Extract here because there is a bug in Windows where the files will not be extracted properly from nested directories. + // https://github.com/zarf-dev/zarf/issues/3051 + err = archiver.Unarchive(sourcePath, tmpDir) + if err != nil { + return "", err + } + compPath := filepath.Join(tmpDir, componentName, string(ct)) + _, err = os.Stat(compPath) + if errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("component %s could not access a %s directory: %w", componentName, ct, err) + } + if err != nil { + return "", err + } + outPath := filepath.Join(destPath, string(ct)) + err = os.Rename(compPath, outPath) + if err != nil { + return "", err + } + return outPath, nil +} + +// GetImage returns the image with the given reference in the package layout. +func (p *PackageLayout) GetImage(ref transform.Image) (registryv1.Image, error) { + // Use the manifest within the index.json to load the specific image we want + layoutPath := layout.Path(filepath.Join(p.dirPath, ImagesDir)) + imgIdx, err := layoutPath.ImageIndex() + if err != nil { + return nil, err + } + idxManifest, err := imgIdx.IndexManifest() + if err != nil { + return nil, err + } + // Search through all the manifests within this package until we find the annotation that matches our ref + for _, manifest := range idxManifest.Manifests { + if manifest.Annotations[ocispec.AnnotationBaseImageName] == ref.Reference || + // A backwards compatibility shim for older Zarf versions that would leave docker.io off of image annotations + (manifest.Annotations[ocispec.AnnotationBaseImageName] == ref.Path+ref.TagOrDigest && ref.Host == "docker.io") { + // This is the image we are looking for, load it and then return + return layoutPath.Image(manifest.Digest) + } + } + return nil, fmt.Errorf("unable to find the image %s", ref.Reference) +} + +func validatePackageIntegrity(pkgLayout *PackageLayout, isPartial bool) error { + _, err := os.Stat(filepath.Join(pkgLayout.dirPath, ZarfYAML)) + if err != nil { + return err + } + _, err = os.Stat(filepath.Join(pkgLayout.dirPath, Checksums)) + if err != nil { + return err + } + err = helpers.SHAsMatch(filepath.Join(pkgLayout.dirPath, Checksums), pkgLayout.Pkg.Metadata.AggregateChecksum) + if err != nil { + return err + } + + packageFiles := map[string]interface{}{} + err = filepath.Walk(pkgLayout.dirPath, func(path string, info fs.FileInfo, err error) error { + if info.IsDir() { + return nil + } + packageFiles[path] = nil + return err + }) + if err != nil { + return err + } + // Remove files which are not in the checksums. + delete(packageFiles, filepath.Join(pkgLayout.dirPath, ZarfYAML)) + delete(packageFiles, filepath.Join(pkgLayout.dirPath, Checksums)) + delete(packageFiles, filepath.Join(pkgLayout.dirPath, Signature)) + + b, err := os.ReadFile(filepath.Join(pkgLayout.dirPath, Checksums)) + if err != nil { + return err + } + lines := strings.Split(string(b), "\n") + for _, line := range lines { + // If the line is empty (i.e. there is no checksum) simply skip it, this can result from a package with no images/components. + if line == "" { + continue + } + + split := strings.Split(line, " ") + if len(split) != 2 { + return fmt.Errorf("invalid checksum line: %s", line) + } + sha := split[0] + rel := split[1] + if sha == "" || rel == "" { + return fmt.Errorf("invalid checksum line: %s", line) + } + + path := filepath.Join(pkgLayout.dirPath, rel) + _, ok := packageFiles[path] + if !ok && isPartial { + delete(packageFiles, path) + continue + } + if !ok { + return fmt.Errorf("file %s from checksum missing in layout", rel) + } + err = helpers.SHAsMatch(path, sha) + if err != nil { + return err + } + delete(packageFiles, path) + } + + if len(packageFiles) > 0 { + // TODO (phillebaba): Replace with maps.Keys after upgrading to Go 1.23. + filePaths := []string{} + for k := range packageFiles { + filePaths = append(filePaths, k) + } + return fmt.Errorf("package contains additional files not present in the checksum %s", strings.Join(filePaths, ", ")) + } + + return nil +} + +func validatePackageSignature(ctx context.Context, pkgLayout *PackageLayout, publicKeyPath string, skipSignatureValidation bool) error { + if skipSignatureValidation { + return nil + } + + signaturePath := filepath.Join(pkgLayout.dirPath, Signature) + sigExist := true + _, err := os.Stat(signaturePath) + if err != nil { + sigExist = false + } + if !sigExist && publicKeyPath == "" { + // Nobody was expecting a signature, so we can just return + return nil + } else if sigExist && publicKeyPath == "" { + return errors.New("package is signed but no key was provided") + } else if !sigExist && publicKeyPath != "" { + return errors.New("a key was provided but the package is not signed") + } + + keyOptions := options.KeyOpts{KeyRef: publicKeyPath} + cmd := &verify.VerifyBlobCmd{ + KeyOpts: keyOptions, + SigRef: signaturePath, + IgnoreSCT: true, + Offline: true, + IgnoreTlog: true, + } + err = cmd.Exec(ctx, filepath.Join(pkgLayout.dirPath, ZarfYAML)) + if err != nil { + return fmt.Errorf("package signature did not match the provided key: %w", err) + } + return nil +} diff --git a/src/internal/packager2/layout/package_test.go b/src/internal/packager2/layout/package_test.go new file mode 100644 index 0000000000..79fd057429 --- /dev/null +++ b/src/internal/packager2/layout/package_test.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package layout + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/zarf-dev/zarf/src/pkg/transform" + "github.com/zarf-dev/zarf/src/test/testutil" +) + +func TestPackageLayout(t *testing.T) { + t.Parallel() + + ctx := testutil.TestContext(t) + + pkgLayout, err := LoadFromTar(ctx, "../testdata/zarf-package-test-amd64-0.0.1.tar.zst", PackageLayoutOptions{}) + require.NoError(t, err) + + require.Equal(t, "test", pkgLayout.Pkg.Metadata.Name) + require.Equal(t, "0.0.1", pkgLayout.Pkg.Metadata.Version) + + tmpDir := t.TempDir() + manifestDir, err := pkgLayout.GetComponentDir(tmpDir, "test", ManifestsComponentDir) + require.NoError(t, err) + expected, err := os.ReadFile("../testdata/deployment.yaml") + require.NoError(t, err) + b, err := os.ReadFile(filepath.Join(manifestDir, "deployment-0.yaml")) + require.NoError(t, err) + require.Equal(t, expected, b) + + _, err = pkgLayout.GetComponentDir(t.TempDir(), "does-not-exist", ManifestsComponentDir) + require.ErrorContains(t, err, "component does-not-exist does not exist in package") + + _, err = pkgLayout.GetComponentDir(t.TempDir(), "test", FilesComponentDir) + require.ErrorContains(t, err, "component test could not access a files directory") + + tmpDir = t.TempDir() + sbomPath, err := pkgLayout.GetSBOM(tmpDir) + require.NoError(t, err) + require.FileExists(t, filepath.Join(sbomPath, "compare.html")) + + ref, err := transform.ParseImageRef("docker.io/library/alpine:3.20") + require.NoError(t, err) + img, err := pkgLayout.GetImage(ref) + require.NoError(t, err) + dgst, err := img.Digest() + require.NoError(t, err) + require.Equal(t, "sha256:33735bd63cf84d7e388d9f6d297d348c523c044410f553bd878c6d7829612735", dgst.String()) +} diff --git a/src/internal/packager2/load.go b/src/internal/packager2/load.go index c17fcf24b5..b757315173 100644 --- a/src/internal/packager2/load.go +++ b/src/internal/packager2/load.go @@ -4,10 +4,8 @@ package packager2 import ( - "archive/tar" "context" "encoding/json" - "errors" "fmt" "io" "net/url" @@ -17,14 +15,12 @@ import ( "strings" "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/mholt/archiver/v3" "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/internal/packager2/layout" "github.com/zarf-dev/zarf/src/pkg/cluster" - "github.com/zarf-dev/zarf/src/pkg/layout" "github.com/zarf-dev/zarf/src/pkg/packager/filters" - "github.com/zarf-dev/zarf/src/pkg/packager/sources" "github.com/zarf-dev/zarf/src/pkg/utils" "github.com/zarf-dev/zarf/src/types" ) @@ -39,7 +35,7 @@ type LoadOptions struct { } // LoadPackage optionally fetches and loads the package from the given source. -func LoadPackage(ctx context.Context, opt LoadOptions) (*layout.PackagePaths, error) { +func LoadPackage(ctx context.Context, opt LoadOptions) (*layout.PackageLayout, error) { srcType, err := identifySource(opt.Source) if err != nil { return nil, err @@ -81,87 +77,16 @@ func LoadPackage(ctx context.Context, opt LoadOptions) (*layout.PackagePaths, er } } - // Extract the package - packageDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) - if err != nil { - return nil, err + layoutOpt := layout.PackageLayoutOptions{ + PublicKeyPath: opt.PublicKeyPath, + SkipSignatureValidation: opt.SkipSignatureValidation, + IsPartial: isPartial, } - pathsExtracted := []string{} - err = archiver.Walk(tarPath, func(f archiver.File) error { - if f.IsDir() { - return nil - } - header, ok := f.Header.(*tar.Header) - if !ok { - return fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) - } - // If path has nested directories we want to create them. - dir := filepath.Dir(header.Name) - if dir != "." { - err := os.MkdirAll(filepath.Join(packageDir, dir), helpers.ReadExecuteAllWriteUser) - if err != nil { - return err - } - } - dst, err := os.Create(filepath.Join(packageDir, header.Name)) - if err != nil { - return err - } - defer dst.Close() - _, err = io.Copy(dst, f) - if err != nil { - return err - } - pathsExtracted = append(pathsExtracted, header.Name) - return nil - }) + pkgLayout, err := layout.LoadFromTar(ctx, tarPath, layoutOpt) if err != nil { return nil, err } - - // Load the package paths - pkgPaths := layout.New(packageDir) - pkgPaths.SetFromPaths(pathsExtracted) - pkg, _, err := pkgPaths.ReadZarfYAML() - if err != nil { - return nil, err - } - // TODO: Filter is not persistently applied. - pkg.Components, err = opt.Filter.Apply(pkg) - if err != nil { - return nil, err - } - if err := pkgPaths.MigrateLegacy(); err != nil { - return nil, err - } - if !pkgPaths.IsLegacyLayout() { - if err := sources.ValidatePackageIntegrity(pkgPaths, pkg.Metadata.AggregateChecksum, isPartial); err != nil { - return nil, err - } - if !opt.SkipSignatureValidation { - if err := sources.ValidatePackageSignature(ctx, pkgPaths, opt.PublicKeyPath); err != nil { - return nil, err - } - } - } - for _, component := range pkg.Components { - if err := pkgPaths.Components.Unarchive(component); err != nil { - if errors.Is(err, layout.ErrNotLoaded) { - _, err := pkgPaths.Components.Create(component) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - } - if pkgPaths.SBOMs.Path != "" { - if err := pkgPaths.SBOMs.Unarchive(); err != nil { - return nil, err - } - } - return pkgPaths, nil + return pkgLayout, nil } // identifySource returns the source type for the given source. @@ -246,14 +171,11 @@ func packageFromSourceOrCluster(ctx context.Context, cluster *cluster.Cluster, s Filter: filters.Empty(), PublicKeyPath: publicKeyPath, } - pkgPaths, err := LoadPackage(ctx, loadOpt) - if err != nil { - return v1alpha1.ZarfPackage{}, err - } - defer os.RemoveAll(pkgPaths.Base) - pkg, _, err := pkgPaths.ReadZarfYAML() + p, err := LoadPackage(ctx, loadOpt) if err != nil { return v1alpha1.ZarfPackage{}, err } - return pkg, nil + //nolint: errcheck // ignore + defer p.Cleanup() + return p.Pkg, nil } diff --git a/src/internal/packager2/load_test.go b/src/internal/packager2/load_test.go index 4c09a12260..e48140c966 100644 --- a/src/internal/packager2/load_test.go +++ b/src/internal/packager2/load_test.go @@ -28,12 +28,12 @@ func TestLoadPackage(t *testing.T) { { name: "tarball", source: "./testdata/zarf-package-test-amd64-0.0.1.tar.zst", - shasum: "307294e3a066cebea6f04772c2ba31210b2753b40b0d5da86a1983c29c5545dd", + shasum: "bef73d652f004d214d5cf9e00195293f7ae8390b8ff6ed45e39c2c9eb622b873", }, { name: "split", source: "./testdata/zarf-package-test-amd64-0.0.1.tar.zst.part000", - shasum: "6c0de217e3eeff224679ec0a26751655759a30f4aae7fbe793ca1617ddfc4228", + shasum: "9c021ef9f62f58cca6dc01641521f372f387cc54c0d959a4f3861c6c636d98f1", }, } for _, tt := range tests { @@ -48,14 +48,12 @@ func TestLoadPackage(t *testing.T) { SkipSignatureValidation: false, Filter: filters.Empty(), } - pkgPaths, err := LoadPackage(ctx, opt) + pkgLayout, err := LoadPackage(ctx, opt) require.NoError(t, err) - pkg, _, err := pkgPaths.ReadZarfYAML() - require.NoError(t, err) - require.Equal(t, "test", pkg.Metadata.Name) - require.Equal(t, "0.0.1", pkg.Metadata.Version) - require.Len(t, pkg.Components, 1) + require.Equal(t, "test", pkgLayout.Pkg.Metadata.Name) + require.Equal(t, "0.0.1", pkgLayout.Pkg.Metadata.Version) + require.Len(t, pkgLayout.Pkg.Components, 1) } opt := LoadOptions{ diff --git a/src/internal/packager2/mirror.go b/src/internal/packager2/mirror.go index 7649b62757..8532a8c4e6 100644 --- a/src/internal/packager2/mirror.go +++ b/src/internal/packager2/mirror.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "os" "time" "github.com/avast/retry-go/v4" @@ -21,8 +22,8 @@ import ( "github.com/zarf-dev/zarf/src/internal/dns" "github.com/zarf-dev/zarf/src/internal/git" "github.com/zarf-dev/zarf/src/internal/gitea" + "github.com/zarf-dev/zarf/src/internal/packager2/layout" "github.com/zarf-dev/zarf/src/pkg/cluster" - "github.com/zarf-dev/zarf/src/pkg/layout" "github.com/zarf-dev/zarf/src/pkg/message" "github.com/zarf-dev/zarf/src/pkg/packager/filters" "github.com/zarf-dev/zarf/src/pkg/transform" @@ -33,7 +34,7 @@ import ( // MirrorOptions are the options for Mirror. type MirrorOptions struct { Cluster *cluster.Cluster - PackagePaths layout.PackagePaths + PkgLayout *layout.PackageLayout Filter filters.ComponentFilterStrategy RegistryInfo types.RegistryInfo GitInfo types.GitServerInfo @@ -43,33 +44,28 @@ type MirrorOptions struct { // Mirror mirrors the package contents to the given registry and git server. func Mirror(ctx context.Context, opt MirrorOptions) error { - err := pushImagesToRegistry(ctx, opt.Cluster, opt.PackagePaths, opt.Filter, opt.RegistryInfo, opt.NoImageChecksum, opt.Retries) + err := pushImagesToRegistry(ctx, opt.Cluster, opt.PkgLayout, opt.Filter, opt.RegistryInfo, opt.NoImageChecksum, opt.Retries) if err != nil { return err } - err = pushReposToRepository(ctx, opt.Cluster, opt.PackagePaths, opt.Filter, opt.GitInfo, opt.Retries) + err = pushReposToRepository(ctx, opt.Cluster, opt.PkgLayout, opt.Filter, opt.GitInfo, opt.Retries) if err != nil { return err } return nil } -func pushImagesToRegistry(ctx context.Context, c *cluster.Cluster, pkgPaths layout.PackagePaths, filter filters.ComponentFilterStrategy, regInfo types.RegistryInfo, noImgChecksum bool, retries int) error { +func pushImagesToRegistry(ctx context.Context, c *cluster.Cluster, pkgLayout *layout.PackageLayout, filter filters.ComponentFilterStrategy, regInfo types.RegistryInfo, noImgChecksum bool, retries int) error { logs.Warn.SetOutput(&message.DebugWriter{}) logs.Progress.SetOutput(&message.DebugWriter{}) - pkg, _, err := pkgPaths.ReadZarfYAML() + components, err := filter.Apply(pkgLayout.Pkg) if err != nil { return err } - components, err := filter.Apply(pkg) - if err != nil { - return err - } - pkg.Components = components images := map[transform.Image]v1.Image{} - for _, component := range pkg.Components { + for _, component := range components { for _, img := range component.Images { ref, err := transform.ParseImageRef(img) if err != nil { @@ -78,11 +74,11 @@ func pushImagesToRegistry(ctx context.Context, c *cluster.Cluster, pkgPaths layo if _, ok := images[ref]; ok { continue } - ociImage, err := utils.LoadOCIImage(pkgPaths.Images.Base, ref) + img, err := pkgLayout.GetImage(ref) if err != nil { return err } - images[ref] = ociImage + images[ref] = img } } if len(images) == 0 { @@ -96,7 +92,7 @@ func pushImagesToRegistry(ctx context.Context, c *cluster.Cluster, pkgPaths layo transportWithProgressBar := helpers.NewTransport(transport, nil) pushOptions := []crane.Option{ - crane.WithPlatform(&v1.Platform{OS: "linux", Architecture: pkg.Build.Architecture}), + crane.WithPlatform(&v1.Platform{OS: "linux", Architecture: pkgLayout.Pkg.Build.Architecture}), crane.WithTransport(transportWithProgressBar), crane.WithAuth(authn.FromConfig(authn.AuthConfig{ Username: regInfo.PushUsername, @@ -171,20 +167,23 @@ func pushImagesToRegistry(ctx context.Context, c *cluster.Cluster, pkgPaths layo return nil } -func pushReposToRepository(ctx context.Context, c *cluster.Cluster, pkgPaths layout.PackagePaths, filter filters.ComponentFilterStrategy, gitInfo types.GitServerInfo, retries int) error { - pkg, _, err := pkgPaths.ReadZarfYAML() +func pushReposToRepository(ctx context.Context, c *cluster.Cluster, pkgLayout *layout.PackageLayout, filter filters.ComponentFilterStrategy, gitInfo types.GitServerInfo, retries int) error { + components, err := filter.Apply(pkgLayout.Pkg) if err != nil { return err } - components, err := filter.Apply(pkg) - if err != nil { - return err - } - pkg.Components = components - - for _, component := range pkg.Components { + for _, component := range components { for _, repoURL := range component.Repos { - repository, err := git.Open(pkgPaths.Components.Dirs[component.Name].Repos, repoURL) + tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + reposPath, err := pkgLayout.GetComponentDir(tmpDir, component.Name, layout.RepoComponentDir) + if err != nil { + return err + } + repository, err := git.Open(reposPath, repoURL) if err != nil { return err } diff --git a/src/internal/packager2/pull_test.go b/src/internal/packager2/pull_test.go index 72c85ac4d5..3d8d46b2ce 100644 --- a/src/internal/packager2/pull_test.go +++ b/src/internal/packager2/pull_test.go @@ -38,7 +38,7 @@ func TestPull(t *testing.T) { }) dir := t.TempDir() - shasum := "307294e3a066cebea6f04772c2ba31210b2753b40b0d5da86a1983c29c5545dd" + shasum := "bef73d652f004d214d5cf9e00195293f7ae8390b8ff6ed45e39c2c9eb622b873" err := Pull(ctx, srv.URL, dir, shasum, filters.Empty()) require.NoError(t, err) diff --git a/src/internal/packager2/testdata/deployment.yaml b/src/internal/packager2/testdata/deployment.yaml new file mode 100644 index 0000000000..a03ca172d7 --- /dev/null +++ b/src/internal/packager2/testdata/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: docker.io/library/nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst index 19b43aa279..6262cc3f2b 100644 Binary files a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst and b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst differ diff --git a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part000 b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part000 index 2bb849cd7e..d9c5f11fce 100644 --- a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part000 +++ b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part000 @@ -1 +1 @@ -{"sha256Sum":"6c0de217e3eeff224679ec0a26751655759a30f4aae7fbe793ca1617ddfc4228","bytes":3683508,"count":4} \ No newline at end of file +{"Sha256Sum":"9c021ef9f62f58cca6dc01641521f372f387cc54c0d959a4f3861c6c636d98f1","Bytes":3683281,"Count":4} \ No newline at end of file diff --git a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part001 b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part001 index 5ed2b42c1b..da156d41d4 100644 Binary files a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part001 and b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part001 differ diff --git a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part002 b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part002 index 55b6a5bb67..216593b3fe 100644 Binary files a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part002 and b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part002 differ diff --git a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part003 b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part003 index 2dac39314d..3bb8756237 100644 Binary files a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part003 and b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part003 differ diff --git a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part004 b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part004 index d04f2ef445..3a595f2fba 100644 Binary files a/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part004 and b/src/internal/packager2/testdata/zarf-package-test-amd64-0.0.1.tar.zst.part004 differ diff --git a/src/internal/packager2/testdata/zarf.yaml b/src/internal/packager2/testdata/zarf.yaml index c388a95be4..648cf6e778 100644 --- a/src/internal/packager2/testdata/zarf.yaml +++ b/src/internal/packager2/testdata/zarf.yaml @@ -5,5 +5,10 @@ metadata: components: - name: test required: true + manifests: + - name: deployment + namespace: nginx + files: + - deployment.yaml images: - docker.io/library/alpine:3.20