From c42d7b5c7503debdd4f8ba9f520e5fc53c28dcc8 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Tue, 7 Nov 2023 18:35:50 -0600 Subject: [PATCH] fix: pathing issues loading images with Zarf on Windows (#2106) ## Description This fixes path issues when Zarf tries to load images on Windows. ## Related Issue Fixes #2102 ## Type of change - [X] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Other (security config, docs update, etc) ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [X] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow) followed --- .github/actions/packages/action.yaml | 16 +++- .github/workflows/test-windows.yml | 25 +++--- examples/component-actions/zarf.yaml | 6 +- src/pkg/layout/package.go | 9 +- src/pkg/layout/package_test.go | 78 +++++++++-------- src/pkg/oci/manifest.go | 5 +- src/pkg/oci/pull.go | 9 +- src/pkg/packager/common.go | 2 - src/pkg/packager/compose.go | 8 +- src/pkg/packager/create.go | 1 + src/pkg/packager/sources/oci.go | 21 ++++- src/pkg/packager/sources/tarball.go | 16 +++- src/pkg/packager/sources/validate.go | 9 +- src/pkg/utils/network.go | 4 +- src/pkg/utils/network_test.go | 1 + src/test/common.go | 25 ++++++ src/test/e2e/00_use_cli_test.go | 1 - src/test/e2e/09_component_compose_test.go | 25 +++--- src/test/e2e/10_component_flavor_test.go | 2 - src/test/e2e/11_oci_pull_inspect_test.go | 84 +++++++++++++++++++ ..._test.go => 50_oci_publish_deploy_test.go} | 64 +++----------- src/test/nightly/ecr_publish_test.go | 8 +- 22 files changed, 274 insertions(+), 145 deletions(-) create mode 100644 src/test/e2e/11_oci_pull_inspect_test.go rename src/test/e2e/{50_oci_package_test.go => 50_oci_publish_deploy_test.go} (65%) diff --git a/.github/actions/packages/action.yaml b/.github/actions/packages/action.yaml index 5405cf9110..ea436c5998 100644 --- a/.github/actions/packages/action.yaml +++ b/.github/actions/packages/action.yaml @@ -10,18 +10,26 @@ inputs: description: 'Build the example packages' required: false default: 'true' + os: + description: 'Which OS to build for' + required: false + default: 'linux' + shell: + description: 'Which shell to build in' + required: false + default: 'bash' runs: using: composite steps: - run: | - make build-cli-linux-amd ARCH=amd64 - shell: bash + make build-cli-${{ inputs.os }}-amd ARCH=amd64 + shell: ${{ inputs.shell }} - run: | make init-package ARCH=amd64 - shell: bash + shell: ${{ inputs.shell }} if: ${{ inputs.init-package == 'true' }} - run: | make build-examples ARCH=amd64 - shell: bash + shell: ${{ inputs.shell }} if: ${{ inputs.build-examples == 'true' }} diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 8af56ec7fc..d75332b219 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -33,21 +33,24 @@ jobs: - name: Setup golang uses: ./.github/actions/golang - - name: Build windows binary - run: make build-cli-windows-amd + - name: Run Windows unit tests + run: make test-unit shell: pwsh - # Builds an init package manually off of the v0.23.6 release since - # Windows in GitHub cannot natively build linux containers and - # the tests this workflow runs do not use the agent at all! + - name: Build Windows binary and zarf packages + uses: ./.github/actions/packages + with: + init-package: "false" + os: windows + shell: pwsh + + # TODO: (@WSTARR) Builds an init package manually off of the v0.30.1 + # release since Windows in GitHub cannot natively build linux containers + # and the tests this workflow run do not use the agent at all! - name: Build init-package run: | - make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.25.2 - - - name: Build zarf packages - run: make build-examples ARCH=amd64 - shell: pwsh + make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.30.1 - - name: Run windows tests + - name: Run windows E2E tests run: make test-e2e ARCH=amd64 -e SKIP_K8S=true shell: pwsh diff --git a/examples/component-actions/zarf.yaml b/examples/component-actions/zarf.yaml index 717a146b45..bdf2b1e7ae 100644 --- a/examples/component-actions/zarf.yaml +++ b/examples/component-actions/zarf.yaml @@ -117,7 +117,7 @@ components: - name: SNAKE_SOUND # marks this variable as sensitive to prevent it from being output in the Zarf log sensitive: true - # autoIndent tells Zarf to maintain spacing for any newlines when templating into a yaml file + # autoIndent tells Zarf to maintain spacing for any newlines when templating into a yaml file autoIndent: true # onSuccess will only run if steps in this component are successful onSuccess: @@ -185,13 +185,13 @@ components: actions: onCreate: after: - - description: Cloudflare 1.1.1.1 site to be available + - description: Github.com to be available maxTotalSeconds: 15 wait: # wait for a network address to return a 200 OK response network: protocol: https - address: 1.1.1.1 + address: github.com code: 200 - name: on-deploy-with-wait-action diff --git a/src/pkg/layout/package.go b/src/pkg/layout/package.go index 8cdb50058c..a9dfb36b97 100644 --- a/src/pkg/layout/package.go +++ b/src/pkg/layout/package.go @@ -177,7 +177,8 @@ func (pp *PackagePaths) SetFromLayers(layers []ocispec.Descriptor) { // SetFromPaths maps paths to package paths. func (pp *PackagePaths) SetFromPaths(paths []string) { for _, rel := range paths { - switch path := rel; { + // Convert from the standard '/' to the OS path separator for Windows support + switch path := filepath.FromSlash(rel); { case path == ZarfYAML: pp.ZarfYAML = filepath.Join(pp.Base, path) case path == Signature: @@ -213,16 +214,20 @@ func (pp *PackagePaths) SetFromPaths(paths []string) { // Files returns a map of all the files in the package. func (pp *PackagePaths) Files() map[string]string { pathMap := make(map[string]string) + stripBase := func(path string) string { rel, _ := filepath.Rel(pp.Base, path) - return rel + // Convert from the OS path separator to the standard '/' for Windows support + return filepath.ToSlash(rel) } + add := func(path string) { if path == "" { return } pathMap[stripBase(path)] = path } + add(pp.ZarfYAML) add(pp.Signature) add(pp.Checksums) diff --git a/src/pkg/layout/package_test.go b/src/pkg/layout/package_test.go index dc98286775..55c3aeb695 100644 --- a/src/pkg/layout/package_test.go +++ b/src/pkg/layout/package_test.go @@ -5,6 +5,7 @@ package layout import ( + "runtime" "strings" "testing" @@ -17,10 +18,10 @@ func TestPackageFiles(t *testing.T) { raw := &PackagePaths{ Base: "test", - ZarfYAML: "test/zarf.yaml", - Checksums: "test/checksums.txt", + ZarfYAML: normalizePath("test/zarf.yaml"), + Checksums: normalizePath("test/checksums.txt"), Components: Components{ - Base: "test/components", + Base: normalizePath("test/components"), }, } @@ -29,8 +30,8 @@ func TestPackageFiles(t *testing.T) { files := pp.Files() expected := map[string]string{ - "zarf.yaml": "test/zarf.yaml", - "checksums.txt": "test/checksums.txt", + "zarf.yaml": normalizePath("test/zarf.yaml"), + "checksums.txt": normalizePath("test/checksums.txt"), } require.Equal(t, expected, files) @@ -47,23 +48,23 @@ func TestPackageFiles(t *testing.T) { files = pp.Files() expected = map[string]string{ - "zarf.yaml": "test/zarf.yaml", - "checksums.txt": "test/checksums.txt", - "zarf.yaml.sig": "test/zarf.yaml.sig", + "zarf.yaml": normalizePath("test/zarf.yaml"), + "checksums.txt": normalizePath("test/checksums.txt"), + "zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"), } require.Equal(t, expected, files) - pp = pp.AddImages() files = pp.Files() + // Note that the map key will always be the forward "Slash" (/) version of the file path (never \) expected = map[string]string{ - "zarf.yaml": "test/zarf.yaml", - "checksums.txt": "test/checksums.txt", - "zarf.yaml.sig": "test/zarf.yaml.sig", - "images/index.json": "test/images/index.json", - "images/oci-layout": "test/images/oci-layout", + "zarf.yaml": normalizePath("test/zarf.yaml"), + "checksums.txt": normalizePath("test/checksums.txt"), + "zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"), + "images/index.json": normalizePath("test/images/index.json"), + "images/oci-layout": normalizePath("test/images/oci-layout"), } require.Equal(t, expected, files) @@ -79,10 +80,10 @@ func TestPackageFiles(t *testing.T) { "zarf.yaml", "checksums.txt", "sboms.tar", - "components/c1.tar", - "images/index.json", - "images/oci-layout", - "images/blobs/sha256/" + strings.Repeat("1", 64), + normalizePath("components/c1.tar"), + normalizePath("images/index.json"), + normalizePath("images/oci-layout"), + normalizePath("images/blobs/sha256/" + strings.Repeat("1", 64)), } pp = New("test") @@ -92,13 +93,13 @@ func TestPackageFiles(t *testing.T) { files = pp.Files() expected = map[string]string{ - "zarf.yaml": "test/zarf.yaml", - "checksums.txt": "test/checksums.txt", - "sboms.tar": "test/sboms.tar", - "components/c1.tar": "test/components/c1.tar", - "images/index.json": "test/images/index.json", - "images/oci-layout": "test/images/oci-layout", - "images/blobs/sha256/" + strings.Repeat("1", 64): "test/images/blobs/sha256/" + strings.Repeat("1", 64), + "zarf.yaml": normalizePath("test/zarf.yaml"), + "checksums.txt": normalizePath("test/checksums.txt"), + "sboms.tar": normalizePath("test/sboms.tar"), + "components/c1.tar": normalizePath("test/components/c1.tar"), + "images/index.json": normalizePath("test/images/index.json"), + "images/oci-layout": normalizePath("test/images/oci-layout"), + "images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)), } require.Len(t, pp.Images.Blobs, 1) @@ -123,16 +124,25 @@ func TestPackageFiles(t *testing.T) { files = pp.Files() expected = map[string]string{ - "zarf.yaml": "test/zarf.yaml", - "checksums.txt": "test/checksums.txt", - "sboms.tar": "test/sboms.tar", - "components/c1.tar": "test/components/c1.tar", - "components/c2.tar": "test/components/c2.tar", - "images/index.json": "test/images/index.json", - "images/oci-layout": "test/images/oci-layout", - "images/blobs/sha256/" + strings.Repeat("1", 64): "test/images/blobs/sha256/" + strings.Repeat("1", 64), - "images/blobs/sha256/" + strings.Repeat("2", 64): "test/images/blobs/sha256/" + strings.Repeat("2", 64), + "zarf.yaml": normalizePath("test/zarf.yaml"), + "checksums.txt": normalizePath("test/checksums.txt"), + "sboms.tar": normalizePath("test/sboms.tar"), + "components/c1.tar": normalizePath("test/components/c1.tar"), + "components/c2.tar": normalizePath("test/components/c2.tar"), + "images/index.json": normalizePath("test/images/index.json"), + "images/oci-layout": normalizePath("test/images/oci-layout"), + "images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)), + "images/blobs/sha256/" + strings.Repeat("2", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("2", 64)), } require.Equal(t, expected, files) } + +// normalizePath ensures that the filepaths being generated are normalized to the host OS. +func normalizePath(path string) string { + if runtime.GOOS != "windows" { + return path + } + + return strings.ReplaceAll(path, "/", "\\") +} diff --git a/src/pkg/oci/manifest.go b/src/pkg/oci/manifest.go index 7c370e6864..dc23f8aee8 100644 --- a/src/pkg/oci/manifest.go +++ b/src/pkg/oci/manifest.go @@ -35,9 +35,10 @@ func NewZarfOCIManifest(manifest *ocispec.Manifest) *ZarfOCIManifest { } // Locate returns the descriptor for the first layer with the given path or digest. -func (m *ZarfOCIManifest) Locate(uri string) ocispec.Descriptor { +func (m *ZarfOCIManifest) Locate(pathOrDigest string) ocispec.Descriptor { return helpers.Find(m.Layers, func(layer ocispec.Descriptor) bool { - return layer.Annotations[ocispec.AnnotationTitle] == uri || layer.Digest.Encoded() == uri + // Convert from the OS path separator to the standard '/' for Windows support + return layer.Annotations[ocispec.AnnotationTitle] == filepath.ToSlash(pathOrDigest) || layer.Digest.Encoded() == pathOrDigest }) } diff --git a/src/pkg/oci/pull.go b/src/pkg/oci/pull.go index c7f33d97f8..6152d57dda 100644 --- a/src/pkg/oci/pull.go +++ b/src/pkg/oci/pull.go @@ -32,7 +32,9 @@ var ( // FileDescriptorExists returns true if the given file exists in the given directory with the expected SHA. func (o *OrasRemote) FileDescriptorExists(desc ocispec.Descriptor, destinationDir string) bool { - destinationPath := filepath.Join(destinationDir, desc.Annotations[ocispec.AnnotationTitle]) + rel := desc.Annotations[ocispec.AnnotationTitle] + destinationPath := filepath.Join(destinationDir, rel) + info, err := os.Stat(destinationPath) if err != nil { return false @@ -252,7 +254,10 @@ func (o *OrasRemote) PullLayer(desc ocispec.Descriptor, destinationDir string) e if err != nil { return err } - return utils.WriteFile(filepath.Join(destinationDir, desc.Annotations[ocispec.AnnotationTitle]), b) + + rel := desc.Annotations[ocispec.AnnotationTitle] + + return utils.WriteFile(filepath.Join(destinationDir, rel), b) } // PullPackagePaths pulls multiple files from the remote repository and saves them to `destinationDir`. diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index bc16e78385..202c327c98 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -261,8 +261,6 @@ func (p *Packager) attemptClusterChecks() (err error) { if existingInitPackage, _ := p.cluster.GetDeployedPackage("init"); existingInitPackage != nil { // Use the build version instead of the metadata since this will support older Zarf versions deprecated.PrintBreakingChanges(existingInitPackage.Data.Build.Version) - } else { - message.Warnf("Unable to retrieve the initialized Zarf version. There is potential for breaking changes.") } spinner.Success() diff --git a/src/pkg/packager/compose.go b/src/pkg/packager/compose.go index 1f1c94e471..6961e8b876 100644 --- a/src/pkg/packager/compose.go +++ b/src/pkg/packager/compose.go @@ -22,12 +22,12 @@ func (p *Packager) composeComponents() error { // filter by architecture if !composer.CompatibleComponent(component, arch, p.cfg.CreateOpts.Flavor) { continue - } else { - // if a match was found, strip flavor and architecture to reduce bloat in the package definition - component.Only.Cluster.Architecture = "" - component.Only.Flavor = "" } + // if a match was found, strip flavor and architecture to reduce bloat in the package definition + component.Only.Cluster.Architecture = "" + component.Only.Flavor = "" + // build the import chain chain, err := composer.NewImportChain(component, arch, p.cfg.CreateOpts.Flavor) if err != nil { diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index 2e09168c02..8d91214e54 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -608,6 +608,7 @@ func (p *Packager) generatePackageChecksums() (string, error) { if rel == layout.ZarfYAML || rel == layout.Checksums { continue } + sum, err := utils.GetSHA256OfFile(abs) if err != nil { return "", err diff --git a/src/pkg/packager/sources/oci.go b/src/pkg/packager/sources/oci.go index 5eccc7720c..62e22a5f47 100644 --- a/src/pkg/packager/sources/oci.go +++ b/src/pkg/packager/sources/oci.go @@ -74,10 +74,15 @@ func (s *OCISource) LoadPackage(dst *layout.PackagePaths, unarchiveAll bool) (er } if !dst.IsLegacyLayout() { + spinner := message.NewProgressSpinner("Validating pulled layer checksums") + defer spinner.Stop() + if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, isPartial); err != nil { return err } + spinner.Success() + if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil { return err } @@ -131,8 +136,15 @@ func (s *OCISource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, } if !dst.IsLegacyLayout() { - if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil { - return err + if wantSBOM { + spinner := message.NewProgressSpinner("Validating SBOM checksums") + defer spinner.Stop() + + if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil { + return err + } + + spinner.Success() } if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil { @@ -176,10 +188,15 @@ func (s *OCISource) Collect(dir string) (string, error) { return "", err } + spinner := message.NewProgressSpinner("Validating full package checksums") + defer spinner.Stop() + if err := ValidatePackageIntegrity(loaded, pkg.Metadata.AggregateChecksum, false); err != nil { return "", err } + spinner.Success() + isSkeleton := strings.HasSuffix(s.Repo().Reference.Reference, oci.SkeletonSuffix) name := NameFromMetadata(&pkg, isSkeleton) diff --git a/src/pkg/packager/sources/tarball.go b/src/pkg/packager/sources/tarball.go index fd6f5f002d..e5b13a2749 100644 --- a/src/pkg/packager/sources/tarball.go +++ b/src/pkg/packager/sources/tarball.go @@ -93,10 +93,15 @@ func (s *TarballSource) LoadPackage(dst *layout.PackagePaths, unarchiveAll bool) } if !dst.IsLegacyLayout() { + spinner := message.NewProgressSpinner("Validating full package checksums") + defer spinner.Stop() + if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, false); err != nil { return err } + spinner.Success() + if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil { return err } @@ -165,8 +170,15 @@ func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM b } if !dst.IsLegacyLayout() { - if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil { - return err + if wantSBOM { + spinner := message.NewProgressSpinner("Validating SBOM checksums") + defer spinner.Stop() + + if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil { + return err + } + + spinner.Success() } if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil { diff --git a/src/pkg/packager/sources/validate.go b/src/pkg/packager/sources/validate.go index bf4b259268..d8c2e8fee4 100644 --- a/src/pkg/packager/sources/validate.go +++ b/src/pkg/packager/sources/validate.go @@ -60,9 +60,6 @@ func ValidatePackageSignature(paths *layout.PackagePaths, publicKeyPath string) // ValidatePackageIntegrity validates the integrity of a package by comparing checksums func ValidatePackageIntegrity(loaded *layout.PackagePaths, aggregateChecksum string, isPartial bool) error { - spinner := message.NewProgressSpinner("Validating package checksums") - defer spinner.Stop() - // ensure checksums.txt and zarf.yaml were loaded if utils.InvalidPath(loaded.Checksums) { return fmt.Errorf("unable to validate checksums, %s was not loaded", layout.Checksums) @@ -89,14 +86,12 @@ func ValidatePackageIntegrity(loaded *layout.PackagePaths, aggregateChecksum str split := strings.Split(line, " ") sha := split[0] rel := split[1] + if sha == "" || rel == "" { return fmt.Errorf("invalid checksum line: %s", line) } path := filepath.Join(loaded.Base, rel) - status := fmt.Sprintf("Validating checksum of %s", utils.First30last30(rel)) - spinner.Updatef(status) - if utils.InvalidPath(path) { if !isPartial && !checkedMap[path] { return fmt.Errorf("unable to validate checksums - missing file: %s", rel) @@ -141,8 +136,6 @@ func ValidatePackageIntegrity(loaded *layout.PackagePaths, aggregateChecksum str } } - spinner.Successf("Checksums validated!") - return nil } diff --git a/src/pkg/utils/network.go b/src/pkg/utils/network.go index e1b91f7a77..0898bda028 100644 --- a/src/pkg/utils/network.go +++ b/src/pkg/utils/network.go @@ -58,6 +58,7 @@ func DownloadToFile(src string, dst string, cosignKeyPath string) (err error) { if err != nil { return fmt.Errorf(lang.ErrWritingFile, dst, err.Error()) } + defer file.Close() parsed, err := url.Parse(src) if err != nil { @@ -89,7 +90,8 @@ func DownloadToFile(src string, dst string, cosignKeyPath string) (err error) { return fmt.Errorf("shasum mismatch for file %s: expected %s, got %s ", dst, checksum, received) } } - return file.Close() + + return nil } func httpGetFile(url string, destinationFile *os.File) error { diff --git a/src/pkg/utils/network_test.go b/src/pkg/utils/network_test.go index 9704e3f838..9aa2d4892d 100644 --- a/src/pkg/utils/network_test.go +++ b/src/pkg/utils/network_test.go @@ -86,6 +86,7 @@ func (suite *TestNetworkSuite) Test_1_DownloadToFile() { url = adr + "?foo=bar@" + sum path = filepath.Join(tmp, ".adr-dir.good") suite.NoError(DownloadToFile(url, path, "")) + suite.FileExists(path) } func TestNetwork(t *testing.T) { diff --git a/src/test/common.go b/src/test/common.go index e979e072c4..34d877c659 100644 --- a/src/test/common.go +++ b/src/test/common.go @@ -5,6 +5,7 @@ package test import ( + "bufio" "context" "fmt" "os" @@ -135,3 +136,27 @@ func (e2e *ZarfE2ETest) StripANSICodes(input string) string { ansiRegex := regexp.MustCompile(`\x1b\[(.*?)m`) return ansiRegex.ReplaceAllString(input, "") } + +// NormalizeYAMLFilenames normalizes YAML filenames / paths across Operating Systems (i.e Windows vs Linux) +func (e2e *ZarfE2ETest) NormalizeYAMLFilenames(input string) string { + if runtime.GOOS != "windows" { + return input + } + + // Match YAML lines that have files in them https://regex101.com/r/C78kRD/1 + fileMatcher := regexp.MustCompile(`^(?P.* )(?P[^:\n]+\/.*)$`) + scanner := bufio.NewScanner(strings.NewReader(input)) + + output := "" + for scanner.Scan() { + line := scanner.Text() + get, err := helpers.MatchRegex(fileMatcher, line) + if err != nil { + output += line + "\n" + continue + } + output += fmt.Sprintf("%s\"%s\"\n", get("start"), strings.ReplaceAll(get("file"), "/", "\\\\")) + } + + return output +} diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index d265035b52..a6448dcd49 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -210,6 +210,5 @@ func TestUseCLI(t *testing.T) { require.FileExists(t, tlsCert) require.FileExists(t, tlsKey) - }) } diff --git a/src/test/e2e/09_component_compose_test.go b/src/test/e2e/09_component_compose_test.go index a244b5d9c1..b96cc6b3f6 100644 --- a/src/test/e2e/09_component_compose_test.go +++ b/src/test/e2e/09_component_compose_test.go @@ -24,6 +24,7 @@ var ( composeExamplePath string composeTest = filepath.Join("src", "test", "packages", "09-composable-packages") composeTestPath string + relCacheDir string ) func (suite *CompositionSuite) SetupSuite() { @@ -33,6 +34,9 @@ func (suite *CompositionSuite) SetupSuite() { composeExamplePath = filepath.Join("build", fmt.Sprintf("zarf-package-composable-packages-%s.tar.zst", e2e.Arch)) composeTestPath = filepath.Join("build", fmt.Sprintf("zarf-package-test-compose-package-%s.tar.zst", e2e.Arch)) + // We make the cache dir relative to the working directory to make it work on the Windows Runners + // - they use two drives which filepath.Rel cannot cope with. + relCacheDir, _ = filepath.Abs(".cache-location") } func (suite *CompositionSuite) TearDownSuite() { @@ -40,16 +44,18 @@ func (suite *CompositionSuite) TearDownSuite() { suite.NoError(err) err = os.RemoveAll(composeTestPath) suite.NoError(err) + err = os.RemoveAll(relCacheDir) + suite.NoError(err) } func (suite *CompositionSuite) Test_0_ComposabilityExample() { suite.T().Log("E2E: Package Compose Example") - _, stdErr, err := e2e.Zarf("package", "create", composeExample, "-o", "build", "--no-color", "--confirm") + _, stdErr, err := e2e.Zarf("package", "create", composeExample, "-o", "build", "--zarf-cache", relCacheDir, "--no-color", "--confirm") suite.NoError(err) // Ensure that common names merge - suite.Contains(stdErr, ` + manifests := e2e.NormalizeYAMLFilenames(` manifests: - name: multi-games namespace: dos-games @@ -57,6 +63,7 @@ func (suite *CompositionSuite) Test_0_ComposabilityExample() { - ../dos-games/manifests/deployment.yaml - ../dos-games/manifests/service.yaml - quake-service.yaml`) + suite.Contains(stdErr, manifests) // Ensure that the action was appended suite.Contains(stdErr, ` @@ -83,16 +90,16 @@ func (suite *CompositionSuite) Test_1_FullComposability() { `) // Check files - suite.Contains(stdErr, ` + suite.Contains(stdErr, e2e.NormalizeYAMLFilenames(` files: - source: files/coffee-ipsum.txt target: coffee-ipsum.txt - source: files/coffee-ipsum.txt target: coffee-ipsum.txt -`) +`)) // Check charts - suite.Contains(stdErr, ` + suite.Contains(stdErr, e2e.NormalizeYAMLFilenames(` charts: - name: podinfo-compose releaseName: podinfo-override @@ -109,10 +116,10 @@ func (suite *CompositionSuite) Test_1_FullComposability() { namespace: podinfo-compose-two valuesFiles: - files/test-values.yaml -`) +`)) // Check manifests - suite.Contains(stdErr, ` + suite.Contains(stdErr, e2e.NormalizeYAMLFilenames(` manifests: - name: connect-service namespace: podinfo-override @@ -128,7 +135,7 @@ func (suite *CompositionSuite) Test_1_FullComposability() { - files/service.yaml kustomizations: - files -`) +`)) // Check images + repos suite.Contains(stdErr, ` @@ -178,7 +185,5 @@ func (suite *CompositionSuite) Test_1_FullComposability() { } func TestCompositionSuite(t *testing.T) { - e2e.SetupWithCluster(t) - suite.Run(t, new(CompositionSuite)) } diff --git a/src/test/e2e/10_component_flavor_test.go b/src/test/e2e/10_component_flavor_test.go index 5387491312..e94a8f1efb 100644 --- a/src/test/e2e/10_component_flavor_test.go +++ b/src/test/e2e/10_component_flavor_test.go @@ -120,7 +120,5 @@ func (suite *FlavorSuite) Test_1_FlavorArchFiltering() { } func TestFlavorSuite(t *testing.T) { - e2e.SetupWithCluster(t) - suite.Run(t, new(FlavorSuite)) } diff --git a/src/test/e2e/11_oci_pull_inspect_test.go b/src/test/e2e/11_oci_pull_inspect_test.go new file mode 100644 index 0000000000..47d31232fb --- /dev/null +++ b/src/test/e2e/11_oci_pull_inspect_test.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package test provides e2e tests for Zarf. +package test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "oras.land/oras-go/v2/registry" +) + +type PullInspectTestSuite struct { + suite.Suite + *require.Assertions + Reference registry.Reference + PackagesDir string +} + +var badPullInspectRef = registry.Reference{ + Registry: "localhost:5000", + Repository: "zarf-test", + Reference: "bad-tag", +} + +func (suite *PullInspectTestSuite) SetupSuite() { + suite.Assertions = require.New(suite.T()) + suite.PackagesDir = "build" +} + +func (suite *PullInspectTestSuite) TearDownSuite() { + local := fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch) + e2e.CleanFiles(local) +} + +func (suite *PullInspectTestSuite) Test_0_Pull() { + suite.T().Log("E2E: Package Pull oci://") + + out := fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch) + + // Build the fully qualified reference. + ref := fmt.Sprintf("oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0-%s", e2e.Arch) + + // Pull the package via OCI. + stdOut, stdErr, err := e2e.Zarf("package", "pull", ref) + suite.NoError(err, stdOut, stdErr) + suite.Contains(stdErr, fmt.Sprintf("Pulling %q", ref)) + suite.Contains(stdErr, "Validating full package checksums") + suite.NotContains(stdErr, "Package signature validated!") + + sbomTmp := suite.T().TempDir() + + // Verify the package was pulled correctly. + suite.FileExists(out) + stdOut, stdErr, err = e2e.Zarf("package", "inspect", out, "--key", "https://zarf.dev/cosign.pub", "--sbom-out", sbomTmp) + suite.NoError(err, stdOut, stdErr) + suite.Contains(stdErr, "Validating SBOM checksums") + suite.Contains(stdErr, "Package signature validated!") + + // Test pull w/ bad ref. + stdOut, stdErr, err = e2e.Zarf("package", "pull", "oci://"+badPullInspectRef.String(), "--insecure") + suite.Error(err, stdOut, stdErr) +} + +func (suite *PullInspectTestSuite) Test_1_Remote_Inspect() { + suite.T().Log("E2E: Package Inspect oci://") + + // Test inspect w/ bad ref. + _, stdErr, err := e2e.Zarf("package", "inspect", "oci://"+badPullInspectRef.String(), "--insecure") + suite.Error(err, stdErr) + + // Test inspect on a public package. + // NOTE: This also makes sure that Zarf does not attempt auth when inspecting a public package. + ref := fmt.Sprintf("oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0-%s", e2e.Arch) + _, stdErr, err = e2e.Zarf("package", "inspect", ref) + suite.NoError(err, stdErr) +} + +func TestPullInspectSuite(t *testing.T) { + suite.Run(t, new(PullInspectTestSuite)) +} diff --git a/src/test/e2e/50_oci_package_test.go b/src/test/e2e/50_oci_publish_deploy_test.go similarity index 65% rename from src/test/e2e/50_oci_package_test.go rename to src/test/e2e/50_oci_publish_deploy_test.go index b698ba6207..563d700750 100644 --- a/src/test/e2e/50_oci_package_test.go +++ b/src/test/e2e/50_oci_publish_deploy_test.go @@ -19,20 +19,20 @@ import ( "oras.land/oras-go/v2/registry/remote" ) -type RegistryClientTestSuite struct { +type PublishDeploySuiteTestSuite struct { suite.Suite *require.Assertions Reference registry.Reference PackagesDir string } -var badRef = registry.Reference{ +var badDeployRef = registry.Reference{ Registry: "localhost:5000", Repository: "zarf-test", Reference: "bad-tag", } -func (suite *RegistryClientTestSuite) SetupSuite() { +func (suite *PublishDeploySuiteTestSuite) SetupSuite() { suite.Assertions = require.New(suite.T()) suite.PackagesDir = "build" @@ -40,14 +40,14 @@ func (suite *RegistryClientTestSuite) SetupSuite() { suite.Reference.Registry = "localhost:555" } -func (suite *RegistryClientTestSuite) TearDownSuite() { +func (suite *PublishDeploySuiteTestSuite) TearDownSuite() { local := fmt.Sprintf("zarf-package-helm-charts-%s-0.0.1.tar.zst", e2e.Arch) e2e.CleanFiles(local) e2e.TeardownRegistry(suite.T(), 555) } -func (suite *RegistryClientTestSuite) Test_0_Publish() { +func (suite *PublishDeploySuiteTestSuite) Test_0_Publish() { suite.T().Log("E2E: Package Publish oci://") // Publish package. @@ -70,32 +70,13 @@ func (suite *RegistryClientTestSuite) Test_0_Publish() { dir := filepath.Join("examples", "helm-charts") stdOut, stdErr, err = e2e.Zarf("package", "create", dir, "-o", "oci://"+ref, "--insecure", "--oci-concurrency=5", "--confirm") suite.NoError(err, stdOut, stdErr) -} - -func (suite *RegistryClientTestSuite) Test_1_Pull() { - suite.T().Log("E2E: Package Pull oci://") - - out := fmt.Sprintf("zarf-package-helm-charts-%s-0.0.1.tar.zst", e2e.Arch) - // Build the fully qualified reference. - suite.Reference.Repository = "helm-charts" - suite.Reference.Reference = fmt.Sprintf("0.0.1-%s", e2e.Arch) - ref := suite.Reference.String() - - // Pull the package via OCI. - stdOut, stdErr, err := e2e.Zarf("package", "pull", "oci://"+ref, "--insecure") + // Inspect the published package. + stdOut, stdErr, err = e2e.Zarf("package", "inspect", "oci://"+ref+"/helm-charts:0.0.1-"+e2e.Arch, "--insecure") suite.NoError(err, stdOut, stdErr) - suite.Contains(stdErr, fmt.Sprintf("Pulling %q", "oci://"+ref)) - - // Verify the package was pulled. - suite.FileExists(out) - - // Test pull w/ bad ref. - stdOut, stdErr, err = e2e.Zarf("package", "pull", "oci://"+badRef.String(), "--insecure") - suite.Error(err, stdOut, stdErr) } -func (suite *RegistryClientTestSuite) Test_2_Deploy() { +func (suite *PublishDeploySuiteTestSuite) Test_1_Deploy() { suite.T().Log("E2E: Package Deploy oci://") // Build the fully qualified reference. @@ -112,30 +93,11 @@ func (suite *RegistryClientTestSuite) Test_2_Deploy() { suite.NoError(err, stdOut, stdErr) // Test deploy w/ bad ref. - _, stdErr, err = e2e.Zarf("package", "deploy", "oci://"+badRef.String(), "--insecure", "--confirm") + _, stdErr, err = e2e.Zarf("package", "deploy", "oci://"+badDeployRef.String(), "--insecure", "--confirm") suite.Error(err, stdErr) } -func (suite *RegistryClientTestSuite) Test_3_Inspect() { - suite.T().Log("E2E: Package Inspect oci://") - - suite.Reference.Repository = "helm-charts" - suite.Reference.Reference = fmt.Sprintf("0.0.1-%s", e2e.Arch) - ref := suite.Reference.String() - stdOut, stdErr, err := e2e.Zarf("package", "inspect", "oci://"+ref, "--insecure") - suite.NoError(err, stdOut, stdErr) - - // Test inspect w/ bad ref. - _, stdErr, err = e2e.Zarf("package", "inspect", "oci://"+badRef.String(), "--insecure") - suite.Error(err, stdErr) - - // Test inspect on a public package. - // NOTE: This also makes sure that Zarf does not attempt auth when inspecting a public package. - _, stdErr, err = e2e.Zarf("package", "inspect", "oci://ghcr.io/defenseunicorns/packages/dubbd-k3d:0.3.0-amd64") - suite.NoError(err, stdErr) -} - -func (suite *RegistryClientTestSuite) Test_4_Pull_And_Deploy() { +func (suite *PublishDeploySuiteTestSuite) Test_2_Pull_And_Deploy() { suite.T().Log("E2E: Package Pull oci:// && Package Deploy tarball") local := fmt.Sprintf("zarf-package-helm-charts-%s-0.0.1.tar.zst", e2e.Arch) @@ -148,7 +110,7 @@ func (suite *RegistryClientTestSuite) Test_4_Pull_And_Deploy() { suite.NoError(err, stdOut, stdErr) } -func (suite *RegistryClientTestSuite) Test_5_Copy() { +func (suite *PublishDeploySuiteTestSuite) Test_3_Copy() { t := suite.T() ref := suite.Reference.String() dstRegistryPort := 556 @@ -196,8 +158,8 @@ func (suite *RegistryClientTestSuite) Test_5_Copy() { } } -func TestRegistryClientSuite(t *testing.T) { +func TestPublishDeploySuite(t *testing.T) { e2e.SetupWithCluster(t) - suite.Run(t, new(RegistryClientTestSuite)) + suite.Run(t, new(PublishDeploySuiteTestSuite)) } diff --git a/src/test/nightly/ecr_publish_test.go b/src/test/nightly/ecr_publish_test.go index aff3bf2aef..b201f80793 100644 --- a/src/test/nightly/ecr_publish_test.go +++ b/src/test/nightly/ecr_publish_test.go @@ -60,9 +60,9 @@ func TestECRPublishing(t *testing.T) { require.NoError(t, err, stdOut, stdErr) // Ensure we get a warning when trying to inspect the online published package - stdOut, stdErr, err = e2e.Zarf("package", "inspect", upstreamPackageURL, keyFlag) + stdOut, stdErr, err = e2e.Zarf("package", "inspect", upstreamPackageURL, keyFlag, "--sbom-out", tmpDir) require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Checksums validated!") + require.Contains(t, stdErr, "Validating SBOM checksums") require.Contains(t, stdErr, "Package signature validated!") // Validate that we can pull the package down from ECR @@ -73,12 +73,12 @@ func TestECRPublishing(t *testing.T) { // Ensure we get a warning when trying to inspect the package without providing the public key stdOut, stdErr, err = e2e.Zarf("package", "inspect", testPackageFileName) require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Checksums validated!") + require.NotContains(t, stdErr, "Validating SBOM checksums") require.Contains(t, stdErr, "The package was signed but no public key was provided, skipping signature validation") // Validate that we get no warnings when inspecting the package while providing the public key stdOut, stdErr, err = e2e.Zarf("package", "inspect", testPackageFileName, keyFlag) require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Checksums validated!") + require.NotContains(t, stdErr, "Validating SBOM checksums") require.Contains(t, stdErr, "Package signature validated!") }