diff --git a/.gitignore b/.gitignore index 493e7381c2..ab85b85bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ zarf-sbom/ test-*.txt __debug_bin .netlify +!**/testdata/** diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index b1cdea51bf..d028c55a77 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -5,49 +5,168 @@ package sources import ( + "encoding/json" "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path" "testing" - "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" + + "github.com/defenseunicorns/zarf/src/pkg/layout" + "github.com/defenseunicorns/zarf/src/pkg/packager/filters" + "github.com/defenseunicorns/zarf/src/types" ) -var ociS *OCISource -var urlS *URLSource -var tarballS *TarballSource -var splitS *SplitTarballSource -var packageS *PackageSource +func TestNewPackageSource(t *testing.T) { + t.Parallel() -type source struct { - pkgSrc string - srcType string - source PackageSource -} + tests := []struct { + name string + src string + expectedIdentify string + expectedType PackageSource + }{ + { + name: "oci", + src: "oci://ghcr.io/defenseunicorns/packages/init:1.0.0", + expectedIdentify: "oci", + expectedType: &OCISource{}, + }, + { + name: "sget with sub path", + src: "sget://github.com/defenseunicorns/zarf-hello-world:x86", + expectedIdentify: "sget", + expectedType: &URLSource{}, + }, + { + name: "sget without host", + src: "sget://defenseunicorns/zarf-hello-world:x86_64", + expectedIdentify: "sget", + expectedType: &URLSource{}, + }, + { + name: "https", + src: "https://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", + expectedIdentify: "https", + expectedType: &URLSource{}, + }, + { + name: "http", + src: "http://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", + expectedIdentify: "http", + expectedType: &URLSource{}, + }, + { + name: "local tar init zst", + src: "zarf-init-amd64-v1.0.0.tar.zst", + expectedIdentify: "tarball", + expectedType: &TarballSource{}, + }, + { + name: "local tar", + src: "zarf-package-manifests-amd64-v1.0.0.tar", + expectedIdentify: "tarball", + expectedType: &TarballSource{}, + }, + { + name: "local tar manifest zst", + src: "zarf-package-manifests-amd64-v1.0.0.tar.zst", + expectedIdentify: "tarball", + expectedType: &TarballSource{}, + }, + { + name: "local tar split", + src: "testdata/.part000", + expectedIdentify: "split", + expectedType: &SplitTarballSource{}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() -var sources = []source{ - {pkgSrc: "oci://ghcr.io/defenseunicorns/packages/init:1.0.0", srcType: "oci", source: ociS}, - {pkgSrc: "sget://github.com/defenseunicorns/zarf-hello-world:x86", srcType: "sget", source: urlS}, - {pkgSrc: "sget://defenseunicorns/zarf-hello-world:x86_64", srcType: "sget", source: urlS}, - {pkgSrc: "https://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", srcType: "https", source: urlS}, - {pkgSrc: "http://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", srcType: "http", source: urlS}, - {pkgSrc: "zarf-init-amd64-v1.0.0.tar.zst", srcType: "tarball", source: tarballS}, - {pkgSrc: "zarf-package-manifests-amd64-v1.0.0.tar", srcType: "tarball", source: tarballS}, - {pkgSrc: "zarf-package-manifests-amd64-v1.0.0.tar.zst", srcType: "tarball", source: tarballS}, - {pkgSrc: "some-dir/.part000", srcType: "split", source: splitS}, + require.Equal(t, tt.expectedIdentify, Identify(tt.src)) + ps, err := New(&types.ZarfPackageOptions{PackageSource: tt.src}) + require.NoError(t, err) + require.IsType(t, tt.expectedType, ps) + }) + } } -func Test_identifySourceType(t *testing.T) { - for _, source := range sources { - actual := Identify(source.pkgSrc) - require.Equalf(t, source.srcType, actual, fmt.Sprintf("source: %s", source)) +func TestPackageSource(t *testing.T) { + t.Parallel() + + b, err := os.ReadFile("./testdata/expected-pkg.json") + require.NoError(t, err) + expectedPkg := types.ZarfPackage{} + err = json.Unmarshal(b, &expectedPkg) + require.NoError(t, err) + + ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, fp := path.Split(req.URL.Path) + f, err := os.Open(path.Join("testdata", fp)) + if err != nil { + rw.WriteHeader(http.StatusNotFound) + return + } + defer f.Close() + io.Copy(rw, f) + })) + + tests := []struct { + name string + src string + shasum string + }{ + { + name: "local", + src: "./testdata/zarf-package-wordpress-amd64-16.0.4.tar.zst", + }, + { + name: "http", + src: fmt.Sprintf("%s/zarf-package-wordpress-amd64-16.0.4.tar.zst", ts.URL), + shasum: "7161a86e0f33e0273131ce60a205d7656472266edd6392f509de2879fc21c671", + }, } -} + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + opts := &types.ZarfPackageOptions{ + PackageSource: tt.src, + Shasum: tt.shasum, + } + + ps, err := New(opts) + require.NoError(t, err) + packageDir := t.TempDir() + pkgLayout := layout.New(packageDir) + pkg, warnings, err := ps.LoadPackage(pkgLayout, filters.Empty(), false) + require.NoError(t, err) + require.Empty(t, warnings) + require.Equal(t, expectedPkg, pkg) + + ps, err = New(opts) + require.NoError(t, err) + metadataDir := t.TempDir() + metadataLayout := layout.New(metadataDir) + metadata, warnings, err := ps.LoadPackageMetadata(metadataLayout, true, false) + require.NoError(t, err) + require.Empty(t, warnings) + require.Equal(t, expectedPkg, metadata) -func TestNew(t *testing.T) { - for _, source := range sources { - actual, err := New(&types.ZarfPackageOptions{PackageSource: source.pkgSrc}) - require.NoError(t, err) - require.IsType(t, source.source, actual) - require.Implements(t, packageS, actual) + ps, err = New(opts) + require.NoError(t, err) + collectDir := t.TempDir() + fp, err := ps.Collect(collectDir) + require.NoError(t, err) + require.Equal(t, path.Join(collectDir, path.Base(tt.src)), fp) + }) } } diff --git a/src/pkg/packager/sources/tarball.go b/src/pkg/packager/sources/tarball.go index 55ba0c6b2e..2172d7ef13 100644 --- a/src/pkg/packager/sources/tarball.go +++ b/src/pkg/packager/sources/tarball.go @@ -204,5 +204,34 @@ func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM b // Collect for the TarballSource is essentially an `mv` func (s *TarballSource) Collect(dir string) (string, error) { dst := filepath.Join(dir, filepath.Base(s.PackageSource)) - return dst, os.Rename(s.PackageSource, dst) + err := os.Rename(s.PackageSource, dst) + linkErr := &os.LinkError{} + isLinkErr := errors.As(err, &linkErr) + if err != nil && !isLinkErr { + return "", err + } + if err == nil { + return dst, nil + } + + // Copy file if rename is not possible due to existing on different partitions. + srcFile, err := os.Open(linkErr.Old) + if err != nil { + return "", err + } + defer srcFile.Close() + dstFile, err := os.Create(linkErr.New) + if err != nil { + return "", err + } + defer dstFile.Close() + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return "", err + } + err = os.Remove(linkErr.Old) + if err != nil { + return "", err + } + return dst, nil } diff --git a/src/pkg/packager/sources/testdata/expected-pkg.json b/src/pkg/packager/sources/testdata/expected-pkg.json new file mode 100644 index 0000000000..4e2bb5dddc --- /dev/null +++ b/src/pkg/packager/sources/testdata/expected-pkg.json @@ -0,0 +1,23 @@ +{ + "kind": "ZarfPackageConfig", + "metadata": { + "name": "wordpress", + "description": "\"A Zarf Package that deploys the WordPress blogging and content management platform\"\n", + "version": "16.0.4", + "architecture": "amd64", + "aggregateChecksum": "cd6cc0c238c45982d80b55d3dd3d27497d4f3b264e0f037a37c464be34a3640e" + }, + "build": { + "terminal": "control-center", + "user": "philip", + "architecture": "amd64", + "timestamp": "Tue, 21 May 2024 12:40:56 +0200", + "version": "v0.33.1", + "migrations": [ + "scripts-to-actions", + "pluralize-set-variable" + ], + "lastNonBreakingVersion": "v0.27.0" + }, + "components": [] +} \ No newline at end of file diff --git a/src/pkg/packager/sources/testdata/zarf.yaml b/src/pkg/packager/sources/testdata/zarf.yaml new file mode 100644 index 0000000000..0e81803fa9 --- /dev/null +++ b/src/pkg/packager/sources/testdata/zarf.yaml @@ -0,0 +1,6 @@ +kind: ZarfPackageConfig +metadata: + name: wordpress + version: 16.0.4 + description: | + "A Zarf Package that deploys the WordPress blogging and content management platform"