//go:build acceptance package nfpm_test import ( "archive/zip" "bytes" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/goreleaser/nfpm/v2" _ "github.com/goreleaser/nfpm/v2/apk" _ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/deb" _ "github.com/goreleaser/nfpm/v2/ipk" _ "github.com/goreleaser/nfpm/v2/msix" _ "github.com/goreleaser/nfpm/v2/rpm" "github.com/stretchr/testify/require" ) // nolint: gochecknoglobals var formatArchs = map[string][]string{ "apk": {"amd64", "arm64", "386", "ppc64le", "armv6", "armv7", "s390x"}, "deb": {"amd64", "arm64", "ppc64le", "armv7", "s390x"}, "ipk": {"x86_64", "aarch64_generic"}, "rpm": {"amd64", "arm64", "ppc64le"}, "archlinux": {"amd64"}, } func TestCore(t *testing.T) { t.Parallel() testNames := []string{ "min", "simple", "no-glob", "complex", "env-var-version", "overrides", "meta", "withchangelog", "symlink", "signed", } for _, name := range testNames { for format, architecture := range formatArchs { for _, arch := range architecture { func(t *testing.T, testName, testFormat, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", testFormat, testArch, testName), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_%s", testName, testArch), Conf: fmt.Sprintf("core.%s.yaml", testName), Format: testFormat, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", testFormat), Target: testName, Arch: testArch, }, }) }) }(t, name, format, arch) } } } } func TestUpgrade(t *testing.T) { t.Parallel() testNames := []string{ "upgrade", } for _, name := range testNames { for format, architecture := range formatArchs { for _, arch := range architecture { func(t *testing.T, testName, testFormat, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", testFormat, testArch, testName), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } arch := strings.ReplaceAll(testArch, "armv", "arm/") oldpkg := fmt.Sprintf("tmp/%s_%s.v1.%s", testName, testArch, testFormat) target := fmt.Sprintf("./testdata/acceptance/%s", oldpkg) require.NoError(t, os.MkdirAll("./testdata/acceptance/tmp", 0o700)) config, err := nfpm.ParseFileWithEnvMapping( fmt.Sprintf("./testdata/acceptance/%s.v1.yaml", testName), func(s string) string { switch s { case "BUILD_ARCH": return strings.ReplaceAll(arch, "/", "") case "SEMVER": return "v1.0.0-0.1.b1+git.abcdefgh" default: return os.Getenv(s) } }, ) require.NoError(t, err) info, err := config.Get(testFormat) require.NoError(t, err) require.NoError(t, nfpm.Validate(info)) pkg, err := nfpm.Get(testFormat) require.NoError(t, err) f, err := os.Create(target) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, f.Close()) }) info.Target = target require.NoError(t, pkg.Package(nfpm.WithDefaults(info), f)) accept(t, acceptParms{ Name: fmt.Sprintf("%s_%s.v2", testName, testArch), Conf: fmt.Sprintf("%s.v2.yaml", testName), Format: testFormat, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", testFormat), Target: testName, Arch: testArch, BuildArgs: []string{fmt.Sprintf("oldpackage=%s", oldpkg)}, }, }) }) }(t, name, format, arch) } } } } func TestRPMCompression(t *testing.T) { t.Parallel() format := "rpm" compressFormats := []string{"gzip", "xz", "lzma", "zstd"} for _, arch := range formatArchs[format] { for _, compFormat := range compressFormats { func(t *testing.T, testCompFormat, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", format, testArch, testCompFormat), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_compression_%s", testCompFormat, testArch), Conf: fmt.Sprintf("rpm.%s.compression.yaml", testCompFormat), Format: format, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", format), Target: "compression", Arch: testArch, BuildArgs: []string{fmt.Sprintf("compression=%s", testCompFormat)}, }, }) }) }(t, compFormat, arch) } } } func TestDebCompression(t *testing.T) { t.Parallel() format := "deb" compressFormats := []string{"gzip", "xz", "zstd", "none"} for _, arch := range formatArchs[format] { for _, compFormat := range compressFormats { func(t *testing.T, testCompFormat, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", format, testArch, testCompFormat), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_compression_%s", testCompFormat, testArch), Conf: fmt.Sprintf("deb.%s.compression.yaml", testCompFormat), Format: format, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", format), Target: "compression", Arch: testArch, }, }) }) }(t, compFormat, arch) } } } func TestRPMSpecific(t *testing.T) { t.Parallel() format := "rpm" testNames := []string{ "release", "directories", "verify", "postrequires", } for _, name := range testNames { for _, arch := range formatArchs[format] { func(t *testing.T, testName, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", format, testArch, testName), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_%s", testName, testArch), Conf: fmt.Sprintf("%s.%s.yaml", format, testName), Format: format, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", format), Target: testName, Arch: testArch, }, }) }) }(t, name, arch) } } } func TestDebSpecific(t *testing.T) { t.Parallel() format := "deb" testNames := []string{ "rules", "triggers", "breaks", "predepends", } for _, name := range testNames { for _, arch := range formatArchs[format] { func(t *testing.T, testName, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", format, testArch, testName), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_%s", testName, testArch), Conf: fmt.Sprintf("%s.%s.yaml", format, testName), Format: format, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", format), Target: testName, Arch: testArch, }, }) }) }(t, name, arch) } } } func TestIPKSpecific(t *testing.T) { t.Parallel() format := "ipk" testNames := []string{ "alternatives", "conflicts", "predepends", } for _, name := range testNames { for _, arch := range formatArchs[format] { func(t *testing.T, testName, testArch string) { t.Run(fmt.Sprintf("%s/%s/%s", format, testArch, testName), func(t *testing.T) { t.Parallel() if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_%s", testName, testArch), Conf: fmt.Sprintf("%s.%s.yaml", format, testName), Format: format, Docker: dockerParams{ File: fmt.Sprintf("%s.dockerfile", format), Target: testName, Arch: testArch, }, }) }) }(t, name, arch) } } } func TestRPMSign(t *testing.T) { for _, os := range []string{ "centos9", "centos10", "fedora40", "fedora41", } { os := os t.Run(fmt.Sprintf("rpm/amd64/sign/%s", os), func(t *testing.T) { accept(t, acceptParms{ Name: fmt.Sprintf("sign_%s_amd64", os), Conf: "core.signed.yaml", Format: "rpm", Docker: dockerParams{ File: fmt.Sprintf("rpm_%s.dockerfile", os), Target: "signed", Arch: "amd64", }, }) }) } } func TestDebSign(t *testing.T) { t.Parallel() for _, arch := range formatArchs["deb"] { for _, sigtype := range []string{"dpkg-sig", "debsign"} { func(t *testing.T, testSigtype, testArch string) { t.Run(fmt.Sprintf("deb/%s/%s", testArch, testSigtype), func(t *testing.T) { t.Parallel() target := "signed" if testSigtype == "dpkg-sig" { target = "dpkg-signed" } if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" { t.Skip("ppc64le arch not supported in pipeline") } accept(t, acceptParms{ Name: fmt.Sprintf("%s_sign_%s", testSigtype, testArch), Conf: fmt.Sprintf("deb.%s.sign.yaml", testSigtype), Format: "deb", Docker: dockerParams{ File: "deb.dockerfile", Target: target, Arch: testArch, }, }) }) }(t, sigtype, arch) } } } type acceptParms struct { Name string Conf string Format string Docker dockerParams } type dockerParams struct { File string Target string Arch string BuildArgs []string } func accept(t *testing.T, params acceptParms) { t.Helper() arch := strings.ReplaceAll(params.Docker.Arch, "armv", "arm/") configFile := filepath.Join("./testdata/acceptance/", params.Conf) tmp, err := filepath.Abs("./testdata/acceptance/tmp") require.NoError(t, err) packageName := params.Name + "." + params.Format target := filepath.Join(tmp, packageName) require.NoError(t, os.MkdirAll(tmp, 0o700)) envFunc := func(s string) string { switch s { case "BUILD_ARCH": return strings.ReplaceAll(arch, "/", "") case "SEMVER": return "v1.0.0-0.1.b1+git.abcdefgh" default: return os.Getenv(s) } } config, err := nfpm.ParseFileWithEnvMapping(configFile, envFunc) require.NoError(t, err) info, err := config.Get(params.Format) require.NoError(t, err) require.NoError(t, nfpm.Validate(info)) pkg, err := nfpm.Get(params.Format) require.NoError(t, err) cmdArgs := []string{ "build", "--rm", "--force-rm", "--platform", fmt.Sprintf("linux/%s", arch), "-f", params.Docker.File, "--target", params.Docker.Target, "--build-arg", "package=" + filepath.Join("tmp", packageName), } for _, arg := range params.Docker.BuildArgs { cmdArgs = append(cmdArgs, "--build-arg", arg) } cmdArgs = append(cmdArgs, ".") f, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o764) require.NoError(t, err) info.Target = target require.NoError(t, pkg.Package(nfpm.WithDefaults(info), f)) //nolint:gosec cmd := exec.Command("docker", cmdArgs...) cmd.Dir = "./testdata/acceptance" bts, err := cmd.CombinedOutput() require.NoError( t, err, "failed: %v; env BUILD_ARCH: %s; package: %s; output: %s", cmd.Args, envFunc("BUILD_ARCH"), target, string(bts), ) } func TestMSIXStructure(t *testing.T) { t.Parallel() for _, arch := range []string{"amd64", "arm64"} { arch := arch t.Run(arch, func(t *testing.T) { t.Parallel() configFile := "./testdata/acceptance/msix.basic.yaml" envFunc := func(s string) string { switch s { case "BUILD_ARCH": return arch case "SEMVER": return "v1.0.0-0.1.b1+git.abcdefgh" default: return os.Getenv(s) } } config, err := nfpm.ParseFileWithEnvMapping(configFile, envFunc) require.NoError(t, err) info, err := config.Get("msix") require.NoError(t, err) require.NoError(t, nfpm.Validate(info)) pkg, err := nfpm.Get("msix") require.NoError(t, err) var buf bytes.Buffer require.NoError(t, pkg.Package(nfpm.WithDefaults(info), &buf)) // Open the MSIX as a ZIP archive and verify structure reader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) require.NoError(t, err) fileNames := make(map[string]bool) for _, f := range reader.File { fileNames[f.Name] = true } // Verify required MSIX structure files exist require.True(t, fileNames["AppxManifest.xml"], "AppxManifest.xml must exist") require.True(t, fileNames["AppxBlockMap.xml"], "AppxBlockMap.xml must exist") require.True(t, fileNames["[Content_Types].xml"], "[Content_Types].xml must exist") // Verify payload file exists require.True(t, fileNames["app/fake.exe"], "payload file app/fake.exe must exist") }) } }