github.com/goreleaser/nfpm/v2@v2.44.0/deb/deb_test.go (about)

     1  package deb
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"crypto/md5" // nolint: gosec
     8  	"crypto/sha1"
     9  	"encoding/hex"
    10  	"errors"
    11  	"flag"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"path"
    16  	"path/filepath"
    17  	"slices"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/blakesmith/ar"
    24  	"github.com/goreleaser/chglog"
    25  	"github.com/goreleaser/nfpm/v2"
    26  	"github.com/goreleaser/nfpm/v2/files"
    27  	"github.com/goreleaser/nfpm/v2/internal/sign"
    28  	"github.com/klauspost/compress/zstd"
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/ulikunitz/xz"
    31  )
    32  
    33  // nolint: gochecknoglobals
    34  var update = flag.Bool("update", false, "update .golden files")
    35  
    36  var mtime = time.Date(2023, 11, 5, 23, 15, 17, 0, time.UTC)
    37  
    38  func exampleInfo() *nfpm.Info {
    39  	return nfpm.WithDefaults(&nfpm.Info{
    40  		Name:        "foo",
    41  		Arch:        "amd64",
    42  		Description: "Foo does things",
    43  		Priority:    "extra",
    44  		Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
    45  		Version:     "v1.0.0",
    46  		Section:     "default",
    47  		Homepage:    "http://carlosbecker.com",
    48  		Vendor:      "nope",
    49  		License:     "MIT",
    50  		Overridables: nfpm.Overridables{
    51  			Depends: []string{
    52  				"bash",
    53  			},
    54  			Recommends: []string{
    55  				"git",
    56  			},
    57  			Suggests: []string{
    58  				"bash",
    59  			},
    60  			Replaces: []string{
    61  				"svn",
    62  			},
    63  			Provides: []string{
    64  				"bzr",
    65  			},
    66  			Conflicts: []string{
    67  				"zsh",
    68  			},
    69  			Contents: []*files.Content{
    70  				{
    71  					Source:      "../testdata/fake",
    72  					Destination: "/usr/bin/fake",
    73  				},
    74  				{
    75  					Source:      "../testdata/whatever.conf",
    76  					Destination: "/usr/share/doc/fake/fake.txt",
    77  				},
    78  				{
    79  					Source:      "../testdata/whatever.conf",
    80  					Destination: "/etc/fake/fake.conf",
    81  					Type:        files.TypeConfig,
    82  				},
    83  				{
    84  					Source:      "../testdata/whatever.conf",
    85  					Destination: "/etc/fake/fake2.conf",
    86  					Type:        files.TypeConfigNoReplace,
    87  				},
    88  				{
    89  					Source:      "../testdata/whatever.conf",
    90  					Destination: "/etc/fake/fake3.conf",
    91  					Type:        files.TypeConfigMissingOK,
    92  				},
    93  				{
    94  					Destination: "/var/log/whatever",
    95  					Type:        files.TypeDir,
    96  				},
    97  				{
    98  					Destination: "/usr/share/whatever",
    99  					Type:        files.TypeDir,
   100  				},
   101  			},
   102  			Deb: nfpm.Deb{
   103  				Predepends: []string{"less"},
   104  			},
   105  		},
   106  	})
   107  }
   108  
   109  func TestConventionalExtension(t *testing.T) {
   110  	require.Equal(t, ".deb", Default.ConventionalExtension())
   111  }
   112  
   113  func TestDeb(t *testing.T) {
   114  	for _, arch := range []string{"386", "amd64"} {
   115  		arch := arch
   116  		t.Run(arch, func(t *testing.T) {
   117  			info := exampleInfo()
   118  			info.Arch = arch
   119  			err := Default.Package(info, io.Discard)
   120  			require.NoError(t, err)
   121  		})
   122  	}
   123  }
   124  
   125  func TestDebPlatform(t *testing.T) {
   126  	f, err := os.CreateTemp(t.TempDir(), "test*.deb")
   127  	require.NoError(t, err)
   128  	t.Cleanup(func() { require.NoError(t, f.Close()) })
   129  	info := exampleInfo()
   130  	info.Platform = "darwin"
   131  	err = Default.Package(info, f)
   132  	require.NoError(t, err)
   133  }
   134  
   135  func extractDebArchitecture(deb *bytes.Buffer) string {
   136  	for _, s := range strings.Split(deb.String(), "\n") {
   137  		if strings.Contains(s, "Architecture: ") {
   138  			return strings.TrimPrefix(s, "Architecture: ")
   139  		}
   140  	}
   141  	return ""
   142  }
   143  
   144  func splitDebArchitecture(deb *bytes.Buffer) (string, string) {
   145  	a := extractDebArchitecture(deb)
   146  	if strings.Contains(a, "-") {
   147  		f := strings.Split(a, "-")
   148  		return f[0], f[1]
   149  	}
   150  	return "linux", a
   151  }
   152  
   153  func TestDebOS(t *testing.T) {
   154  	info := exampleInfo()
   155  	var buf bytes.Buffer
   156  	err := writeControl(&buf, controlData{info, 0})
   157  	require.NoError(t, err)
   158  	o, _ := splitDebArchitecture(&buf)
   159  	require.Equal(t, "linux", o)
   160  }
   161  
   162  func TestDebArch(t *testing.T) {
   163  	info := exampleInfo()
   164  	var buf bytes.Buffer
   165  	err := writeControl(&buf, controlData{info, 0})
   166  	require.NoError(t, err)
   167  	_, a := splitDebArchitecture(&buf)
   168  	require.Equal(t, "amd64", a)
   169  }
   170  
   171  func extractDebVersion(deb *bytes.Buffer) string {
   172  	for _, s := range strings.Split(deb.String(), "\n") {
   173  		if strings.Contains(s, "Version: ") {
   174  			return strings.TrimPrefix(s, "Version: ")
   175  		}
   176  	}
   177  	return ""
   178  }
   179  
   180  func TestDebVersionWithDash(t *testing.T) {
   181  	info := exampleInfo()
   182  	info.Version = "1.0.0-beta"
   183  	err := Default.Package(info, io.Discard)
   184  	require.NoError(t, err)
   185  }
   186  
   187  func TestDebVersion(t *testing.T) {
   188  	info := exampleInfo()
   189  	info.Version = "1.0.0" //nolint:golint,goconst
   190  	var buf bytes.Buffer
   191  	err := writeControl(&buf, controlData{info, 0})
   192  	require.NoError(t, err)
   193  	v := extractDebVersion(&buf)
   194  	require.Equal(t, "1.0.0", v)
   195  }
   196  
   197  func TestDebVersionWithRelease(t *testing.T) {
   198  	info := exampleInfo()
   199  	info.Version = "1.0.0" //nolint:golint,goconst
   200  	info.Release = "1"
   201  	var buf bytes.Buffer
   202  	err := writeControl(&buf, controlData{info, 0})
   203  	require.NoError(t, err)
   204  	v := extractDebVersion(&buf)
   205  	require.Equal(t, "1.0.0-1", v)
   206  }
   207  
   208  func TestDebVersionWithPrerelease(t *testing.T) {
   209  	var buf bytes.Buffer
   210  
   211  	info := exampleInfo()
   212  	info.Version = "1.0.0" //nolint:golint,goconst
   213  	info.Prerelease = "1"
   214  	err := writeControl(&buf, controlData{info, 0})
   215  	require.NoError(t, err)
   216  	v := extractDebVersion(&buf)
   217  	require.Equal(t, "1.0.0~1", v)
   218  }
   219  
   220  func TestDebVersionWithReleaseAndPrerelease(t *testing.T) {
   221  	var buf bytes.Buffer
   222  
   223  	info := exampleInfo()
   224  	info.Version = "1.0.0" //nolint:golint,goconst
   225  	info.Release = "2"
   226  	info.Prerelease = "rc1" //nolint:golint,goconst
   227  	err := writeControl(&buf, controlData{info, 0})
   228  	require.NoError(t, err)
   229  	v := extractDebVersion(&buf)
   230  	require.Equal(t, "1.0.0~rc1-2", v)
   231  }
   232  
   233  func TestDebVersionWithVersionMetadata(t *testing.T) {
   234  	var buf bytes.Buffer
   235  
   236  	info := exampleInfo()
   237  	info.Version = "1.0.0+meta" //nolint:golint,goconst
   238  	info.VersionMetadata = ""
   239  	err := writeControl(&buf, controlData{info, 0})
   240  	require.NoError(t, err)
   241  	v := extractDebVersion(&buf)
   242  	require.Equal(t, "1.0.0+meta", v)
   243  
   244  	buf.Reset()
   245  
   246  	info.Version = "1.0.0" //nolint:golint,goconst
   247  	info.VersionMetadata = "meta"
   248  	err = writeControl(&buf, controlData{info, 0})
   249  	require.NoError(t, err)
   250  	v = extractDebVersion(&buf)
   251  	require.Equal(t, "1.0.0+meta", v)
   252  
   253  	buf.Reset()
   254  
   255  	info.Version = "1.0.0+foo" //nolint:golint,goconst
   256  	info.Prerelease = "alpha"
   257  	info.VersionMetadata = "meta"
   258  	err = writeControl(&buf, controlData{nfpm.WithDefaults(info), 0})
   259  	require.NoError(t, err)
   260  	v = extractDebVersion(&buf)
   261  	require.Equal(t, "1.0.0~alpha+meta", v)
   262  }
   263  
   264  func TestControl(t *testing.T) {
   265  	var w bytes.Buffer
   266  	require.NoError(t, writeControl(&w, controlData{
   267  		Info:          exampleInfo(),
   268  		InstalledSize: 10,
   269  	}))
   270  	golden := "testdata/control.golden"
   271  	if *update {
   272  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   273  	}
   274  	bts, err := os.ReadFile(golden) //nolint:gosec
   275  	require.NoError(t, err)
   276  	require.Equal(t, string(bts), w.String())
   277  }
   278  
   279  func TestSpecialFiles(t *testing.T) {
   280  	var w bytes.Buffer
   281  	out := tar.NewWriter(&w)
   282  	filePath := "testdata/templates.golden"
   283  	require.Error(t, newFilePathInsideTar(out, "doesnotexit", "templates", 0o644, mtime))
   284  	require.NoError(t, newFilePathInsideTar(out, filePath, "templates", 0o644, mtime))
   285  	in := tar.NewReader(&w)
   286  	header, err := in.Next()
   287  	require.NoError(t, err)
   288  	require.Equal(t, "templates", header.FileInfo().Name())
   289  	mode, err := strconv.ParseInt("0644", 8, 64)
   290  	require.NoError(t, err)
   291  	require.Equal(t, int64(header.FileInfo().Mode()), mode)
   292  	data, err := io.ReadAll(in)
   293  	require.NoError(t, err)
   294  	org, err := os.ReadFile(filePath)
   295  	require.NoError(t, err)
   296  	require.Equal(t, data, org)
   297  }
   298  
   299  func TestNoJoinsControl(t *testing.T) {
   300  	var w bytes.Buffer
   301  	require.NoError(t, writeControl(&w, controlData{
   302  		Info: nfpm.WithDefaults(&nfpm.Info{
   303  			Name:        "foo",
   304  			Arch:        "amd64",
   305  			Description: "Foo does things",
   306  			Priority:    "extra",
   307  			Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
   308  			Version:     "v1.0.0",
   309  			Section:     "default",
   310  			Homepage:    "http://carlosbecker.com",
   311  			Vendor:      "nope",
   312  			Overridables: nfpm.Overridables{
   313  				Depends:    []string{},
   314  				Recommends: []string{},
   315  				Suggests:   []string{},
   316  				Replaces:   []string{},
   317  				Provides:   []string{},
   318  				Conflicts:  []string{},
   319  				Contents:   []*files.Content{},
   320  			},
   321  		}),
   322  		InstalledSize: 10,
   323  	}))
   324  	golden := "testdata/control2.golden"
   325  	if *update {
   326  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   327  	}
   328  	bts, err := os.ReadFile(golden) //nolint:gosec
   329  	require.NoError(t, err)
   330  	require.Equal(t, string(bts), w.String())
   331  }
   332  
   333  func TestVersionControl(t *testing.T) {
   334  	var w bytes.Buffer
   335  	require.NoError(t, writeControl(&w, controlData{
   336  		Info: nfpm.WithDefaults(&nfpm.Info{
   337  			Name:        "foo",
   338  			Arch:        "amd64",
   339  			Description: "Foo does things",
   340  			Priority:    "extra",
   341  			Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
   342  			Version:     "v1.0.0-beta+meta",
   343  			Release:     "2",
   344  			Section:     "default",
   345  			Homepage:    "http://carlosbecker.com",
   346  			Vendor:      "nope",
   347  			Overridables: nfpm.Overridables{
   348  				Depends:    []string{},
   349  				Recommends: []string{},
   350  				Suggests:   []string{},
   351  				Replaces:   []string{},
   352  				Provides:   []string{},
   353  				Conflicts:  []string{},
   354  				Contents:   []*files.Content{},
   355  			},
   356  		}),
   357  		InstalledSize: 10,
   358  	}))
   359  	golden := "testdata/control4.golden"
   360  	if *update {
   361  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   362  	}
   363  	bts, err := os.ReadFile(golden) //nolint:gosec
   364  	require.NoError(t, err)
   365  	require.Equal(t, string(bts), w.String())
   366  }
   367  
   368  func TestDebFileDoesNotExist(t *testing.T) {
   369  	abs, err := filepath.Abs("../testdata/whatever.confzzz")
   370  	require.NoError(t, err)
   371  	err = Default.Package(
   372  		nfpm.WithDefaults(&nfpm.Info{
   373  			Name:        "foo",
   374  			Arch:        "amd64",
   375  			Description: "Foo does things",
   376  			Priority:    "extra",
   377  			Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
   378  			Version:     "1.0.0",
   379  			Section:     "default",
   380  			Homepage:    "http://carlosbecker.com",
   381  			Vendor:      "nope",
   382  			Overridables: nfpm.Overridables{
   383  				Depends: []string{
   384  					"bash",
   385  				},
   386  				Contents: []*files.Content{
   387  					{
   388  						Source:      "../testdata/fake",
   389  						Destination: "/usr/bin/fake",
   390  					},
   391  					{
   392  						Source:      "../testdata/whatever.confzzz",
   393  						Destination: "/etc/fake/fake.conf",
   394  						Type:        files.TypeConfig,
   395  					},
   396  				},
   397  			},
   398  		}),
   399  		io.Discard,
   400  	)
   401  	require.EqualError(t, err, fmt.Sprintf("matching \"%s\": file does not exist", filepath.ToSlash(abs)))
   402  }
   403  
   404  func TestDebNoFiles(t *testing.T) {
   405  	err := Default.Package(
   406  		nfpm.WithDefaults(&nfpm.Info{
   407  			Name:        "foo",
   408  			Arch:        "amd64",
   409  			Description: "Foo does things",
   410  			Priority:    "extra",
   411  			Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
   412  			Version:     "1.0.0",
   413  			Section:     "default",
   414  			Homepage:    "http://carlosbecker.com",
   415  			Vendor:      "nope",
   416  			Overridables: nfpm.Overridables{
   417  				Depends: []string{
   418  					"bash",
   419  				},
   420  			},
   421  		}),
   422  		io.Discard,
   423  	)
   424  	require.NoError(t, err)
   425  }
   426  
   427  func TestDebNoInfo(t *testing.T) {
   428  	err := Default.Package(nfpm.WithDefaults(&nfpm.Info{}), io.Discard)
   429  	require.Error(t, err)
   430  }
   431  
   432  func TestConffiles(t *testing.T) {
   433  	info := nfpm.WithDefaults(&nfpm.Info{
   434  		Name:        "minimal",
   435  		Arch:        "arm64",
   436  		Description: "Minimal does nothing",
   437  		Priority:    "extra",
   438  		Version:     "1.0.0",
   439  		Section:     "default",
   440  		Maintainer:  "maintainer",
   441  		Overridables: nfpm.Overridables{
   442  			Contents: []*files.Content{
   443  				{
   444  					Source:      "../testdata/fake",
   445  					Destination: "/etc/fake",
   446  					Type:        files.TypeConfig,
   447  				},
   448  			},
   449  		},
   450  	})
   451  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   452  	require.NoError(t, err)
   453  	out, ok := conffiles(info)
   454  	require.True(t, ok)
   455  	require.Equal(t, "/etc/fake\n", string(out), "should have a trailing empty line")
   456  }
   457  
   458  func TestNoConffiles(t *testing.T) {
   459  	info := nfpm.WithDefaults(&nfpm.Info{
   460  		Name:        "minimal",
   461  		Arch:        "arm64",
   462  		Description: "Minimal does nothing",
   463  		Priority:    "extra",
   464  		Version:     "1.0.0",
   465  		Section:     "default",
   466  		Maintainer:  "maintainer",
   467  		Overridables: nfpm.Overridables{
   468  			Contents: []*files.Content{
   469  				{
   470  					Source:      "../testdata/fake",
   471  					Destination: "/etc/fake",
   472  				},
   473  			},
   474  		},
   475  	})
   476  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   477  	require.NoError(t, err)
   478  	out, ok := conffiles(info)
   479  	require.False(t, ok)
   480  	require.Nil(t, out)
   481  }
   482  
   483  func TestMinimalFields(t *testing.T) {
   484  	var w bytes.Buffer
   485  	require.NoError(t, writeControl(&w, controlData{
   486  		Info: nfpm.WithDefaults(&nfpm.Info{
   487  			Name:        "minimal",
   488  			Arch:        "arm64",
   489  			Description: "Minimal does nothing",
   490  			Priority:    "extra",
   491  			Version:     "1.0.0",
   492  			Maintainer:  "maintainer",
   493  			Section:     "default",
   494  		}),
   495  	}))
   496  	golden := "testdata/minimal.golden"
   497  	if *update {
   498  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   499  	}
   500  	bts, err := os.ReadFile(golden) //nolint:gosec
   501  	require.NoError(t, err)
   502  	require.Equal(t, string(bts), w.String())
   503  }
   504  
   505  func TestDebEpoch(t *testing.T) {
   506  	var w bytes.Buffer
   507  	require.NoError(t, writeControl(&w, controlData{
   508  		Info: nfpm.WithDefaults(&nfpm.Info{
   509  			Name:        "withepoch",
   510  			Arch:        "arm64",
   511  			Description: "Has an epoch added to it's version",
   512  			Priority:    "extra",
   513  			Epoch:       "2",
   514  			Version:     "1.0.0",
   515  			Section:     "default",
   516  		}),
   517  	}))
   518  	golden := "testdata/withepoch.golden"
   519  	if *update {
   520  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   521  	}
   522  	bts, err := os.ReadFile(golden) //nolint:gosec
   523  	require.NoError(t, err)
   524  	require.Equal(t, string(bts), w.String())
   525  }
   526  
   527  func TestDebRules(t *testing.T) {
   528  	var w bytes.Buffer
   529  	require.NoError(t, writeControl(&w, controlData{
   530  		Info: nfpm.WithDefaults(&nfpm.Info{
   531  			Name:        "lala",
   532  			Arch:        "arm64",
   533  			Description: "Has rules script",
   534  			Priority:    "extra",
   535  			Epoch:       "2",
   536  			Version:     "1.2.0",
   537  			Section:     "default",
   538  			Maintainer:  "maintainer",
   539  			Overridables: nfpm.Overridables{
   540  				Deb: nfpm.Deb{
   541  					Scripts: nfpm.DebScripts{
   542  						Rules: "foo.sh",
   543  					},
   544  				},
   545  			},
   546  		}),
   547  	}))
   548  	golden := "testdata/rules.golden"
   549  	if *update {
   550  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   551  	}
   552  	bts, err := os.ReadFile(golden) //nolint:gosec
   553  	require.NoError(t, err)
   554  	require.Equal(t, string(bts), w.String())
   555  }
   556  
   557  func TestMultilineFields(t *testing.T) {
   558  	var w bytes.Buffer
   559  	require.NoError(t, writeControl(&w, controlData{
   560  		Info: nfpm.WithDefaults(&nfpm.Info{
   561  			Name:        "multiline",
   562  			Arch:        "riscv64",
   563  			Description: "This field is a\nmultiline field\n\nthat should work.",
   564  			Priority:    "extra",
   565  			Version:     "1.0.0",
   566  			Section:     "default",
   567  		}),
   568  	}))
   569  	golden := "testdata/multiline.golden"
   570  	if *update {
   571  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
   572  	}
   573  	bts, err := os.ReadFile(golden) //nolint:gosec
   574  	require.NoError(t, err)
   575  	require.Equal(t, string(bts), w.String())
   576  }
   577  
   578  func TestDEBConventionalFileName(t *testing.T) {
   579  	info := &nfpm.Info{
   580  		Name:       "testpkg",
   581  		Arch:       "all",
   582  		Maintainer: "maintainer",
   583  	}
   584  
   585  	testCases := []struct {
   586  		Version    string
   587  		Release    string
   588  		Prerelease string
   589  		Expected   string
   590  		Metadata   string
   591  	}{
   592  		{
   593  			Version: "1.2.3", Release: "", Prerelease: "", Metadata: "",
   594  			Expected: fmt.Sprintf("%s_1.2.3_%s.deb", info.Name, info.Arch),
   595  		},
   596  		{
   597  			Version: "1.2.3", Release: "4", Prerelease: "", Metadata: "",
   598  			Expected: fmt.Sprintf("%s_1.2.3-4_%s.deb", info.Name, info.Arch),
   599  		},
   600  		{
   601  			Version: "1.2.3", Release: "4", Prerelease: "5", Metadata: "",
   602  			Expected: fmt.Sprintf("%s_1.2.3~5-4_%s.deb", info.Name, info.Arch),
   603  		},
   604  		{
   605  			Version: "1.2.3", Release: "", Prerelease: "5", Metadata: "",
   606  			Expected: fmt.Sprintf("%s_1.2.3~5_%s.deb", info.Name, info.Arch),
   607  		},
   608  		{
   609  			Version: "1.2.3", Release: "1", Prerelease: "5", Metadata: "git",
   610  			Expected: fmt.Sprintf("%s_1.2.3~5+git-1_%s.deb", info.Name, info.Arch),
   611  		},
   612  	}
   613  
   614  	for _, testCase := range testCases {
   615  		info.Version = testCase.Version
   616  		info.Release = testCase.Release
   617  		info.Prerelease = testCase.Prerelease
   618  		info.VersionMetadata = testCase.Metadata
   619  
   620  		require.Equal(t, testCase.Expected, Default.ConventionalFileName(info))
   621  	}
   622  }
   623  
   624  func TestDebChangelogData(t *testing.T) {
   625  	info := &nfpm.Info{
   626  		Name:        "changelog-test",
   627  		Arch:        "amd64",
   628  		Description: "This package has changelogs.",
   629  		Version:     "1.0.0",
   630  		Changelog:   "../testdata/changelog.yaml",
   631  		Maintainer:  "maintainer",
   632  	}
   633  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   634  	require.NoError(t, err)
   635  
   636  	dataTarball, _, _, dataTarballName, err := createDataTarball(info)
   637  	require.NoError(t, err)
   638  
   639  	changelogName := fmt.Sprintf("/usr/share/doc/%s/changelog.Debian.gz", info.Name)
   640  	dataChangelogGz := extractFileFromTar(t,
   641  		inflate(t, dataTarballName, dataTarball), changelogName)
   642  
   643  	dataChangelog := inflate(t, "gz", dataChangelogGz)
   644  	goldenChangelog := readAndFormatAsDebChangelog(t, info.Changelog, info.Name)
   645  
   646  	require.Equal(t, goldenChangelog, string(dataChangelog))
   647  }
   648  
   649  func TestDebNoChangelogDataWithoutChangelogConfigured(t *testing.T) {
   650  	info := &nfpm.Info{
   651  		Name:        "no-changelog-test",
   652  		Arch:        "amd64",
   653  		Description: "This package has explicitly no changelog.",
   654  		Version:     "1.0.0",
   655  		Maintainer:  "maintainer",
   656  	}
   657  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   658  	require.NoError(t, err)
   659  
   660  	dataTarball, _, _, dataTarballName, err := createDataTarball(info)
   661  	require.NoError(t, err)
   662  
   663  	changelogName := fmt.Sprintf("/usr/share/doc/%s/changelog.gz", info.Name)
   664  
   665  	require.False(t, tarContains(t, inflate(t, dataTarballName, dataTarball), changelogName))
   666  }
   667  
   668  func TestDebTriggers(t *testing.T) {
   669  	info := &nfpm.Info{
   670  		Name:        "no-triggers-test",
   671  		Arch:        "amd64",
   672  		Description: "This package has multiple triggers.",
   673  		Version:     "1.0.0",
   674  		Maintainer:  "maintainer",
   675  		Overridables: nfpm.Overridables{
   676  			Deb: nfpm.Deb{
   677  				Triggers: nfpm.DebTriggers{
   678  					Interest:      []string{"trigger1", "trigger2"},
   679  					InterestAwait: []string{"trigger3"},
   680  					// InterestNoAwait omitted
   681  					// Activate omitted
   682  					ActivateAwait:   []string{"trigger4"},
   683  					ActivateNoAwait: []string{"trigger5", "trigger6"},
   684  				},
   685  			},
   686  		},
   687  	}
   688  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   689  	require.NoError(t, err)
   690  
   691  	controlTarGz, err := createControl(0, []byte{}, info)
   692  	require.NoError(t, err)
   693  
   694  	controlTriggers := extractFileFromTar(t, inflate(t, "gz", controlTarGz), "triggers")
   695  
   696  	goldenTriggers := createTriggers(info)
   697  
   698  	require.Equal(t, string(goldenTriggers), string(controlTriggers))
   699  
   700  	// check if specified triggers are included and also that
   701  	// no remnants of triggers that were not specified are included
   702  	require.True(t, bytes.Contains(controlTriggers,
   703  		[]byte("interest trigger1\n")))
   704  	require.True(t, bytes.Contains(controlTriggers,
   705  		[]byte("interest trigger2\n")))
   706  	require.True(t, bytes.Contains(controlTriggers,
   707  		[]byte("interest-await trigger3\n")))
   708  	require.False(t, bytes.Contains(controlTriggers,
   709  		[]byte("interest-noawait ")))
   710  	require.False(t, bytes.Contains(controlTriggers,
   711  		[]byte("activate ")))
   712  	require.True(t, bytes.Contains(controlTriggers,
   713  		[]byte("activate-await trigger4\n")))
   714  	require.True(t, bytes.Contains(controlTriggers,
   715  		[]byte("activate-noawait trigger5\n")))
   716  	require.True(t, bytes.Contains(controlTriggers,
   717  		[]byte("activate-noawait trigger6\n")))
   718  }
   719  
   720  func TestDebNoTriggersInControlIfNoneProvided(t *testing.T) {
   721  	info := &nfpm.Info{
   722  		Name:        "no-triggers-test",
   723  		Arch:        "amd64",
   724  		Description: "This package has explicitly no triggers.",
   725  		Version:     "1.0.0",
   726  		Maintainer:  "maintainer",
   727  	}
   728  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   729  	require.NoError(t, err)
   730  
   731  	controlTarGz, err := createControl(0, []byte{}, info)
   732  	require.NoError(t, err)
   733  
   734  	require.False(t, tarContains(t, inflate(t, "gz", controlTarGz), "triggers"))
   735  }
   736  
   737  func TestSymlink(t *testing.T) {
   738  	var (
   739  		configFilePath = "/usr/share/doc/fake/fake.txt"
   740  		symlink        = "/path/to/symlink"
   741  		symlinkTarget  = configFilePath
   742  	)
   743  
   744  	info := &nfpm.Info{
   745  		Name:        "symlink-in-files",
   746  		Arch:        "amd64",
   747  		Description: "This package's config references a file via symlink.",
   748  		Version:     "1.0.0",
   749  		Maintainer:  "maintainer",
   750  		Overridables: nfpm.Overridables{
   751  			Contents: []*files.Content{
   752  				{
   753  					Source:      "../testdata/whatever.conf",
   754  					Destination: configFilePath,
   755  				},
   756  				{
   757  					Source:      symlinkTarget,
   758  					Destination: symlink,
   759  					Type:        files.TypeSymlink,
   760  				},
   761  			},
   762  		},
   763  	}
   764  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   765  	require.NoError(t, err)
   766  
   767  	dataTarball, _, _, dataTarballName, err := createDataTarball(info)
   768  	require.NoError(t, err)
   769  
   770  	packagedSymlinkHeader := extractFileHeaderFromTar(t,
   771  		inflate(t, dataTarballName, dataTarball), symlink)
   772  
   773  	require.Equal(t, symlink, path.Join("/", packagedSymlinkHeader.Name)) // nolint:gosec
   774  	require.Equal(t, uint8(tar.TypeSymlink), packagedSymlinkHeader.Typeflag)
   775  	require.Equal(t, symlinkTarget, packagedSymlinkHeader.Linkname)
   776  }
   777  
   778  func TestEnsureRelativePrefixInTarballs(t *testing.T) {
   779  	info := exampleInfo()
   780  	info.Contents = []*files.Content{
   781  		{
   782  			Source:      "/symlink/to/fake.txt",
   783  			Destination: "/usr/share/doc/fake/fake.txt",
   784  			Type:        files.TypeSymlink,
   785  		},
   786  	}
   787  	info.Changelog = "../testdata/changelog.yaml"
   788  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   789  	require.NoError(t, err)
   790  
   791  	dataTarball, md5sums, instSize, tarballName, err := createDataTarball(info)
   792  	require.NoError(t, err)
   793  	testRelativePathPrefixInTar(t, inflate(t, tarballName, dataTarball))
   794  
   795  	controlTarGz, err := createControl(instSize, md5sums, info)
   796  	require.NoError(t, err)
   797  	testRelativePathPrefixInTar(t, inflate(t, "gz", controlTarGz))
   798  }
   799  
   800  func TestMD5Sums(t *testing.T) {
   801  	info := exampleInfo()
   802  	info.Changelog = "../testdata/changelog.yaml"
   803  
   804  	nFiles := 1
   805  	for _, f := range info.Contents {
   806  		if f.Type != files.TypeDir {
   807  			nFiles++
   808  		}
   809  	}
   810  
   811  	err := nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName)
   812  	require.NoError(t, err)
   813  
   814  	dataTarball, md5sums, instSize, tarballName, err := createDataTarball(info)
   815  	require.NoError(t, err)
   816  
   817  	controlTarGz, err := createControl(instSize, md5sums, info)
   818  	require.NoError(t, err)
   819  
   820  	md5sumsFile := extractFileFromTar(t, inflate(t, "gz", controlTarGz), "./md5sums")
   821  
   822  	lines := strings.Split(strings.TrimRight(string(md5sumsFile), "\n"), "\n")
   823  	require.Len(t, lines, nFiles, string(md5sumsFile))
   824  
   825  	dataTar := inflate(t, tarballName, dataTarball)
   826  
   827  	for _, line := range lines {
   828  		parts := strings.Fields(line)
   829  		require.Len(t, parts, 2)
   830  
   831  		md5sum, fileName := parts[0], parts[1]
   832  
   833  		digest := md5.New() // nolint:gosec
   834  		_, err = digest.Write(extractFileFromTar(t, dataTar, fileName))
   835  		require.NoError(t, err)
   836  		require.Equal(t, md5sum, hex.EncodeToString(digest.Sum(nil)))
   837  	}
   838  }
   839  
   840  func TestDirectories(t *testing.T) {
   841  	info := exampleInfo()
   842  	info.Contents = []*files.Content{
   843  		{
   844  			Source:      "../testdata/whatever.conf",
   845  			Destination: "/etc/foo/file",
   846  		},
   847  		{
   848  			Source:      "../testdata/whatever.conf",
   849  			Destination: "/etc/bar/file",
   850  		},
   851  		{
   852  			Destination: "/etc/bar",
   853  			Type:        files.TypeDir,
   854  			FileInfo: &files.ContentFileInfo{
   855  				Owner: "test",
   856  				Mode:  0o700,
   857  			},
   858  		},
   859  		{
   860  			Destination: "/etc/baz",
   861  			Type:        files.TypeDir,
   862  		},
   863  		{
   864  			Destination: "/usr/lib/something/somethingelse",
   865  			Type:        files.TypeDir,
   866  		},
   867  	}
   868  
   869  	require.NoError(t, nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName))
   870  
   871  	deflatedDataTarball, _, _, dataTarballName, err := createDataTarball(info)
   872  	require.NoError(t, err)
   873  	dataTarball := inflate(t, dataTarballName, deflatedDataTarball)
   874  
   875  	require.Equal(t, []string{
   876  		"./etc/",
   877  		"./etc/bar/",
   878  		"./etc/bar/file",
   879  		"./etc/baz/",
   880  		"./etc/foo/",
   881  		"./etc/foo/file",
   882  		"./usr/",
   883  		"./usr/lib/",
   884  		"./usr/lib/something/",
   885  		"./usr/lib/something/somethingelse/",
   886  	}, getTree(t, dataTarball))
   887  
   888  	// for debs all implicit or explicit directories are created in the tarball
   889  	h := extractFileHeaderFromTar(t, dataTarball, "/etc")
   890  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   891  	h = extractFileHeaderFromTar(t, dataTarball, "/etc/foo")
   892  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   893  	h = extractFileHeaderFromTar(t, dataTarball, "/etc/bar")
   894  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   895  	require.Equal(t, int64(0o700), h.Mode)
   896  	require.Equal(t, "test", h.Uname)
   897  	h = extractFileHeaderFromTar(t, dataTarball, "/etc/baz")
   898  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   899  
   900  	h = extractFileHeaderFromTar(t, dataTarball, "/usr")
   901  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   902  	h = extractFileHeaderFromTar(t, dataTarball, "/usr/lib")
   903  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   904  	h = extractFileHeaderFromTar(t, dataTarball, "/usr/lib/something")
   905  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   906  	h = extractFileHeaderFromTar(t, dataTarball, "/usr/lib/something/somethingelse")
   907  	require.Equal(t, h.Typeflag, byte(tar.TypeDir))
   908  }
   909  
   910  func TestNoDuplicateContents(t *testing.T) {
   911  	info := exampleInfo()
   912  	info.Contents = []*files.Content{
   913  		{
   914  			Source:      "../testdata/whatever.conf",
   915  			Destination: "/etc/foo/file",
   916  		},
   917  		{
   918  			Source:      "../testdata/whatever.conf",
   919  			Destination: "/etc/foo/file2",
   920  		},
   921  		{
   922  			Destination: "/etc/foo",
   923  			Type:        files.TypeDir,
   924  		},
   925  		{
   926  			Destination: "/etc/baz",
   927  			Type:        files.TypeDir,
   928  		},
   929  	}
   930  
   931  	require.NoError(t, nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName))
   932  
   933  	deflatedDataTarball, _, _, dataTarballName, err := createDataTarball(info)
   934  	require.NoError(t, err)
   935  	dataTarball := inflate(t, dataTarballName, deflatedDataTarball)
   936  
   937  	exists := map[string]bool{}
   938  
   939  	tr := tar.NewReader(bytes.NewReader(dataTarball))
   940  	for {
   941  		hdr, err := tr.Next()
   942  		if errors.Is(err, io.EOF) {
   943  			break // End of archive
   944  		}
   945  		require.NoError(t, err)
   946  
   947  		_, ok := exists[hdr.Name]
   948  		if ok {
   949  			t.Fatalf("%s exists more than once in tarball", hdr.Name)
   950  		}
   951  
   952  		exists[hdr.Name] = true
   953  	}
   954  }
   955  
   956  func testRelativePathPrefixInTar(tb testing.TB, tarFile []byte) {
   957  	tb.Helper()
   958  
   959  	tr := tar.NewReader(bytes.NewReader(tarFile))
   960  	for {
   961  		hdr, err := tr.Next()
   962  		if errors.Is(err, io.EOF) {
   963  			break // End of archive
   964  		}
   965  		require.NoError(tb, err)
   966  		require.True(tb, strings.HasPrefix(hdr.Name, "./"), "%s does not start with './'", hdr.Name)
   967  	}
   968  }
   969  
   970  func TestDebsigsSignature(t *testing.T) {
   971  	info := exampleInfo()
   972  	info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
   973  	info.Deb.Signature.KeyPassphrase = "hunter2"
   974  
   975  	var deb bytes.Buffer
   976  	err := Default.Package(info, &deb)
   977  	require.NoError(t, err)
   978  
   979  	debBinary := extractFileFromAr(t, deb.Bytes(), "debian-binary")
   980  	controlTarGz := extractFileFromAr(t, deb.Bytes(), "control.tar.gz")
   981  	dataTarball := extractFileFromAr(t, deb.Bytes(), findDataTarball(t, deb.Bytes()))
   982  	signature := extractFileFromAr(t, deb.Bytes(), "_gpgorigin")
   983  
   984  	message := io.MultiReader(bytes.NewReader(debBinary),
   985  		bytes.NewReader(controlTarGz), bytes.NewReader(dataTarball))
   986  
   987  	err = sign.PGPVerify(message, signature, "../internal/sign/testdata/pubkey.asc")
   988  	require.NoError(t, err)
   989  }
   990  
   991  func TestDebsigsSignatureError(t *testing.T) {
   992  	info := exampleInfo()
   993  	info.Deb.Signature.KeyFile = "/does/not/exist"
   994  
   995  	var deb bytes.Buffer
   996  	err := Default.Package(info, &deb)
   997  	require.Error(t, err)
   998  
   999  	var expectedError *nfpm.ErrSigningFailure
  1000  	require.ErrorAs(t, err, &expectedError)
  1001  }
  1002  
  1003  func TestDebsigsSignatureCallback(t *testing.T) {
  1004  	info := exampleInfo()
  1005  	info.Deb.Signature.SignFn = func(r io.Reader) ([]byte, error) {
  1006  		return sign.PGPArmoredDetachSignWithKeyID(r, "../internal/sign/testdata/privkey.asc", "hunter2", nil)
  1007  	}
  1008  
  1009  	var deb bytes.Buffer
  1010  	err := Default.Package(info, &deb)
  1011  	require.NoError(t, err)
  1012  
  1013  	debBinary := extractFileFromAr(t, deb.Bytes(), "debian-binary")
  1014  	controlTarGz := extractFileFromAr(t, deb.Bytes(), "control.tar.gz")
  1015  	dataTarball := extractFileFromAr(t, deb.Bytes(), findDataTarball(t, deb.Bytes()))
  1016  	signature := extractFileFromAr(t, deb.Bytes(), "_gpgorigin")
  1017  
  1018  	message := io.MultiReader(bytes.NewReader(debBinary),
  1019  		bytes.NewReader(controlTarGz), bytes.NewReader(dataTarball))
  1020  
  1021  	err = sign.PGPVerify(message, signature, "../internal/sign/testdata/pubkey.asc")
  1022  	require.NoError(t, err)
  1023  }
  1024  
  1025  func TestDpkgSigSignature(t *testing.T) {
  1026  	info := exampleInfo()
  1027  	info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
  1028  	info.Deb.Signature.KeyPassphrase = "hunter2"
  1029  	info.Deb.Signature.Method = "dpkg-sig"
  1030  	info.Deb.Signature.Signer = "bob McRobert"
  1031  
  1032  	var deb bytes.Buffer
  1033  	err := Default.Package(info, &deb)
  1034  	require.NoError(t, err)
  1035  
  1036  	signature := extractFileFromAr(t, deb.Bytes(), "_gpgbuilder")
  1037  
  1038  	msg, err := sign.PGPReadMessage(signature, "../internal/sign/testdata/pubkey.asc")
  1039  	require.NoError(t, err)
  1040  
  1041  	require.NoError(t, verifyDpkgSigFileHashes(extractAllFilesFromAr(t, deb.Bytes()), string(msg)))
  1042  }
  1043  
  1044  func TestDpkgSigSignatureError(t *testing.T) {
  1045  	info := exampleInfo()
  1046  	info.Deb.Signature.KeyFile = "/does/not/exist"
  1047  	info.Deb.Signature.Method = "dpkg-sig"
  1048  
  1049  	var deb bytes.Buffer
  1050  	err := Default.Package(info, &deb)
  1051  	require.Error(t, err)
  1052  
  1053  	var expectedError *nfpm.ErrSigningFailure
  1054  	require.ErrorAs(t, err, &expectedError)
  1055  }
  1056  
  1057  func TestDpkgSigSignatureCallback(t *testing.T) {
  1058  	info := exampleInfo()
  1059  	info.Deb.Signature.SignFn = func(r io.Reader) ([]byte, error) {
  1060  		return sign.PGPClearSignWithKeyID(r, "../internal/sign/testdata/privkey.asc", "hunter2", nil)
  1061  	}
  1062  	info.Deb.Signature.Method = "dpkg-sig"
  1063  	info.Deb.Signature.Signer = "bob McRobert"
  1064  
  1065  	var deb bytes.Buffer
  1066  	err := Default.Package(info, &deb)
  1067  	require.NoError(t, err)
  1068  
  1069  	signature := extractFileFromAr(t, deb.Bytes(), "_gpgbuilder")
  1070  
  1071  	msg, err := sign.PGPReadMessage(signature, "../internal/sign/testdata/pubkey.asc")
  1072  	require.NoError(t, err)
  1073  
  1074  	require.NoError(t, verifyDpkgSigFileHashes(extractAllFilesFromAr(t, deb.Bytes()), string(msg)))
  1075  }
  1076  
  1077  func TestDisableGlobbing(t *testing.T) {
  1078  	info := exampleInfo()
  1079  	info.DisableGlobbing = true
  1080  	info.Contents = []*files.Content{
  1081  		{
  1082  			Source:      "../testdata/{file}[",
  1083  			Destination: "/test/{file}[",
  1084  		},
  1085  	}
  1086  	require.NoError(t, nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName))
  1087  
  1088  	dataTarball, _, _, tarballName, err := createDataTarball(info)
  1089  	require.NoError(t, err)
  1090  
  1091  	expectedContent, err := os.ReadFile("../testdata/{file}[")
  1092  	require.NoError(t, err)
  1093  
  1094  	actualContent := extractFileFromTar(t, inflate(t, tarballName, dataTarball), "/test/{file}[")
  1095  
  1096  	require.Equal(t, expectedContent, actualContent)
  1097  }
  1098  
  1099  func TestNoDuplicateAutocreatedDirectories(t *testing.T) {
  1100  	info := exampleInfo()
  1101  	info.DisableGlobbing = true
  1102  	info.Contents = []*files.Content{
  1103  		{
  1104  			Source:      "../testdata/fake",
  1105  			Destination: "/etc/foo/bar",
  1106  		},
  1107  		{
  1108  			Type:        files.TypeDir,
  1109  			Destination: "/etc/foo",
  1110  		},
  1111  	}
  1112  	require.NoError(t, nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName))
  1113  
  1114  	expected := map[string]bool{
  1115  		"./etc/":        true,
  1116  		"./etc/foo/":    true,
  1117  		"./etc/foo/bar": true,
  1118  	}
  1119  
  1120  	dataTarball, _, _, tarballName, err := createDataTarball(info)
  1121  	require.NoError(t, err)
  1122  
  1123  	contents := tarContents(t, inflate(t, tarballName, dataTarball))
  1124  
  1125  	if len(expected) != len(contents) {
  1126  		t.Fatalf("contents has %d entries instead of %d: %#v", len(contents), len(expected), contents)
  1127  	}
  1128  
  1129  	for _, entry := range contents {
  1130  		if !expected[entry] {
  1131  			t.Fatalf("unexpected content: %q", entry)
  1132  		}
  1133  	}
  1134  }
  1135  
  1136  func TestNoDuplicateDirectories(t *testing.T) {
  1137  	info := exampleInfo()
  1138  	info.DisableGlobbing = true
  1139  	info.Contents = []*files.Content{
  1140  		{
  1141  			Type:        files.TypeDir,
  1142  			Destination: "/etc/foo",
  1143  		},
  1144  		{
  1145  			Type:        files.TypeDir,
  1146  			Destination: "/etc/foo/",
  1147  		},
  1148  	}
  1149  	require.Error(t, nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName))
  1150  }
  1151  
  1152  func TestCompressionAlgorithms(t *testing.T) {
  1153  	testCases := []struct {
  1154  		algorithm       string
  1155  		dataTarballName string
  1156  		expectedError   string
  1157  	}{
  1158  		{"gzip", "data.tar.gz", ""},
  1159  		{"gzip:-1", "data.tar.gz", ""},
  1160  		{"gzip:0", "data.tar.gz", ""},
  1161  		{"gzip:1", "data.tar.gz", ""},
  1162  		{"gzip:9", "data.tar.gz", ""},
  1163  		{"gzip:foo", "data.tar.gz", "parse gzip compressor level"},
  1164  		{"", "data.tar.gz", ""}, // test current default
  1165  		{"xz", "data.tar.xz", ""},
  1166  		{"xz:9", "data.tar.xz", "no compressor level supported"},
  1167  		{"none", "data.tar", ""},
  1168  		{"zstd", "data.tar.zst", ""},
  1169  		{"zstd:-1", "data.tar.zst", ""},
  1170  		{"zstd:0", "data.tar.zst", ""},
  1171  		{"zstd:1", "data.tar.zst", ""},
  1172  		{"zstd:9", "data.tar.zst", ""},
  1173  		{"zstd:19", "data.tar.zst", ""},
  1174  		{"zstd:best", "data.tar.zst", ""},
  1175  		{"zstd:bar", "data.tar.zst", "invalid zstd compressor level"},
  1176  		{"foo", "data.tar", "unknown compression algorithm"},
  1177  		{"foo:bar:baz", "data.tar", "malformed compressor setting"},
  1178  	}
  1179  
  1180  	for _, testCase := range testCases {
  1181  		testCase := testCase
  1182  
  1183  		t.Run(testCase.algorithm, func(t *testing.T) {
  1184  			info := exampleInfo()
  1185  			info.Deb.Compression = testCase.algorithm
  1186  
  1187  			var deb bytes.Buffer
  1188  
  1189  			err := Default.Package(info, &deb)
  1190  			if testCase.expectedError == "" {
  1191  				require.NoError(t, err)
  1192  			} else {
  1193  				require.ErrorContains(t, err, testCase.expectedError)
  1194  				return
  1195  			}
  1196  
  1197  			dataTarballName := findDataTarball(t, deb.Bytes())
  1198  			require.Equal(t, dataTarballName, testCase.dataTarballName)
  1199  
  1200  			dataTarball := extractFileFromAr(t, deb.Bytes(), dataTarballName)
  1201  			dataTar := inflate(t, dataTarballName, dataTarball)
  1202  
  1203  			for _, file := range info.Contents {
  1204  				tarContains(t, dataTar, file.Destination)
  1205  			}
  1206  		})
  1207  	}
  1208  }
  1209  
  1210  func TestIgnoreUnrelatedFiles(t *testing.T) {
  1211  	info := exampleInfo()
  1212  	info.Contents = files.Contents{
  1213  		{
  1214  			Source:      "../testdata/fake",
  1215  			Destination: "/usr/bin/fake",
  1216  			Packager:    "rpm",
  1217  		},
  1218  		{
  1219  			Source:      "../testdata/whatever.conf",
  1220  			Destination: "/usr/share/doc/fake/fake.txt",
  1221  			Type:        files.TypeRPMLicence,
  1222  		},
  1223  		{
  1224  			Source:      "../testdata/whatever.conf",
  1225  			Destination: "/etc/fake/fake.conf",
  1226  			Type:        files.TypeRPMLicense,
  1227  		},
  1228  		{
  1229  			Source:      "../testdata/whatever.conf",
  1230  			Destination: "/etc/fake/fake2.conf",
  1231  			Type:        files.TypeRPMReadme,
  1232  		},
  1233  		{
  1234  			Destination: "/var/log/whatever",
  1235  			Type:        files.TypeRPMDoc,
  1236  		},
  1237  	}
  1238  
  1239  	require.NoError(t, nfpm.PrepareForPackager(withChangelogIfRequested(info), packagerName))
  1240  
  1241  	dataTarball, _, _, tarballName, err := createDataTarball(info)
  1242  	require.NoError(t, err)
  1243  
  1244  	contents := tarContents(t, inflate(t, tarballName, dataTarball))
  1245  	require.Empty(t, contents)
  1246  }
  1247  
  1248  func extractFileFromTar(tb testing.TB, tarFile []byte, filename string) []byte {
  1249  	tb.Helper()
  1250  
  1251  	tr := tar.NewReader(bytes.NewReader(tarFile))
  1252  	for {
  1253  		hdr, err := tr.Next()
  1254  		if errors.Is(err, io.EOF) {
  1255  			break // End of archive
  1256  		}
  1257  		require.NoError(tb, err)
  1258  
  1259  		if path.Join("/", hdr.Name) != path.Join("/", filename) {
  1260  			continue
  1261  		}
  1262  
  1263  		fileContents, err := io.ReadAll(tr)
  1264  		require.NoError(tb, err)
  1265  
  1266  		return fileContents
  1267  	}
  1268  
  1269  	tb.Fatalf("file %q does not exist in tar", filename)
  1270  
  1271  	return nil
  1272  }
  1273  
  1274  func tarContains(tb testing.TB, tarFile []byte, filename string) bool {
  1275  	tb.Helper()
  1276  
  1277  	tr := tar.NewReader(bytes.NewReader(tarFile))
  1278  	for {
  1279  		hdr, err := tr.Next()
  1280  		if errors.Is(err, io.EOF) {
  1281  			break // End of archive
  1282  		}
  1283  		require.NoError(tb, err)
  1284  
  1285  		if path.Join("/", hdr.Name) == path.Join("/", filename) { // nolint:gosec
  1286  			return true
  1287  		}
  1288  	}
  1289  
  1290  	return false
  1291  }
  1292  
  1293  func tarContents(tb testing.TB, tarFile []byte) []string {
  1294  	tb.Helper()
  1295  
  1296  	contents := []string{}
  1297  
  1298  	tr := tar.NewReader(bytes.NewReader(tarFile))
  1299  	for {
  1300  		hdr, err := tr.Next()
  1301  		if errors.Is(err, io.EOF) {
  1302  			break // End of archive
  1303  		}
  1304  		require.NoError(tb, err)
  1305  
  1306  		contents = append(contents, hdr.Name)
  1307  	}
  1308  
  1309  	return contents
  1310  }
  1311  
  1312  func getTree(tb testing.TB, tarFile []byte) []string {
  1313  	tb.Helper()
  1314  
  1315  	var result []string
  1316  	tr := tar.NewReader(bytes.NewReader(tarFile))
  1317  	for {
  1318  		hdr, err := tr.Next()
  1319  		if errors.Is(err, io.EOF) {
  1320  			break // End of archive
  1321  		}
  1322  		require.NoError(tb, err)
  1323  
  1324  		result = append(result, hdr.Name)
  1325  	}
  1326  
  1327  	return result
  1328  }
  1329  
  1330  func extractFileHeaderFromTar(tb testing.TB, tarFile []byte, filename string) *tar.Header {
  1331  	tb.Helper()
  1332  
  1333  	tr := tar.NewReader(bytes.NewReader(tarFile))
  1334  	for {
  1335  		hdr, err := tr.Next()
  1336  		if errors.Is(err, io.EOF) {
  1337  			break // End of archive
  1338  		}
  1339  		require.NoError(tb, err)
  1340  
  1341  		if path.Join("/", hdr.Name) != path.Join("/", filename) { // nolint:gosec
  1342  			continue
  1343  		}
  1344  
  1345  		return hdr
  1346  	}
  1347  
  1348  	tb.Fatalf("file %q does not exist in tar", filename)
  1349  
  1350  	return nil
  1351  }
  1352  
  1353  func inflate(tb testing.TB, nameOrType string, data []byte) []byte {
  1354  	tb.Helper()
  1355  
  1356  	ext := filepath.Ext(nameOrType)
  1357  	if ext == "" {
  1358  		ext = nameOrType
  1359  	} else {
  1360  		ext = strings.TrimPrefix(ext, ".")
  1361  	}
  1362  
  1363  	dataReader := bytes.NewReader(data)
  1364  
  1365  	var (
  1366  		inflateReadCloser io.ReadCloser
  1367  		err               error
  1368  	)
  1369  
  1370  	switch ext {
  1371  	case "gz", "gzip":
  1372  		inflateReadCloser, err = gzip.NewReader(dataReader)
  1373  		require.NoError(tb, err)
  1374  	case "xz":
  1375  		r, err := xz.NewReader(dataReader)
  1376  		require.NoError(tb, err)
  1377  		inflateReadCloser = io.NopCloser(r)
  1378  	case "zst":
  1379  		r, err := zstd.NewReader(dataReader)
  1380  		require.NoError(tb, err)
  1381  		inflateReadCloser = &zstdReadCloser{r}
  1382  	case "tar", "": // no compression
  1383  		inflateReadCloser = io.NopCloser(dataReader)
  1384  	default:
  1385  		tb.Fatalf("invalid inflation type: %s", ext)
  1386  	}
  1387  
  1388  	inflatedData, err := io.ReadAll(inflateReadCloser)
  1389  	require.NoError(tb, err)
  1390  
  1391  	err = inflateReadCloser.Close()
  1392  	require.NoError(tb, err)
  1393  
  1394  	return inflatedData
  1395  }
  1396  
  1397  func readAndFormatAsDebChangelog(tb testing.TB, changelogFileName, packageName string) string {
  1398  	tb.Helper()
  1399  
  1400  	changelogEntries, err := chglog.Parse(changelogFileName)
  1401  	require.NoError(tb, err)
  1402  
  1403  	tpl, err := chglog.DebTemplate()
  1404  	require.NoError(tb, err)
  1405  
  1406  	debChangelog, err := chglog.FormatChangelog(&chglog.PackageChangeLog{
  1407  		Name:    packageName,
  1408  		Entries: changelogEntries,
  1409  	}, tpl)
  1410  	require.NoError(tb, err)
  1411  
  1412  	return strings.TrimSpace(debChangelog) + "\n"
  1413  }
  1414  
  1415  func findDataTarball(tb testing.TB, arFile []byte) string {
  1416  	tb.Helper()
  1417  
  1418  	tr := ar.NewReader(bytes.NewReader(arFile))
  1419  	for {
  1420  		hdr, err := tr.Next()
  1421  		if errors.Is(err, io.EOF) {
  1422  			break // End of archive
  1423  		}
  1424  		require.NoError(tb, err)
  1425  
  1426  		if strings.HasPrefix(path.Join("/", hdr.Name), "/data.tar") {
  1427  			return hdr.Name
  1428  		}
  1429  	}
  1430  
  1431  	tb.Fatalf("data taball does not exist in ar")
  1432  
  1433  	return ""
  1434  }
  1435  
  1436  func extractFileFromAr(tb testing.TB, arFile []byte, filename string) []byte {
  1437  	tb.Helper()
  1438  
  1439  	tr := ar.NewReader(bytes.NewReader(arFile))
  1440  	for {
  1441  		hdr, err := tr.Next()
  1442  		if errors.Is(err, io.EOF) {
  1443  			break // End of archive
  1444  		}
  1445  		require.NoError(tb, err)
  1446  
  1447  		if path.Join("/", hdr.Name) != path.Join("/", filename) {
  1448  			continue
  1449  		}
  1450  
  1451  		fileContents, err := io.ReadAll(tr)
  1452  		require.NoError(tb, err)
  1453  
  1454  		return fileContents
  1455  	}
  1456  
  1457  	tb.Fatalf("file %q does not exist in ar", filename)
  1458  
  1459  	return nil
  1460  }
  1461  
  1462  func extractAllFilesFromAr(tb testing.TB, arFile []byte) map[string][]byte {
  1463  	tb.Helper()
  1464  
  1465  	tr := ar.NewReader(bytes.NewReader(arFile))
  1466  	files := make(map[string][]byte)
  1467  	for {
  1468  		hdr, err := tr.Next()
  1469  		if errors.Is(err, io.EOF) {
  1470  			break // End of archive
  1471  		}
  1472  		require.NoError(tb, err)
  1473  
  1474  		fileContents, err := io.ReadAll(tr)
  1475  		require.NoError(tb, err)
  1476  
  1477  		files[hdr.Name] = fileContents
  1478  	}
  1479  	return files
  1480  }
  1481  
  1482  func TestEmptyButRequiredDebFields(t *testing.T) {
  1483  	item := nfpm.WithDefaults(&nfpm.Info{
  1484  		Name:    "foo",
  1485  		Version: "v1.0.0",
  1486  	})
  1487  	Default.SetPackagerDefaults(item)
  1488  
  1489  	require.Equal(t, "optional", item.Priority)
  1490  	require.Equal(t, "Unset Maintainer <unset@localhost>", item.Maintainer)
  1491  
  1492  	var deb bytes.Buffer
  1493  	err := Default.Package(item, &deb)
  1494  	require.NoError(t, err)
  1495  }
  1496  
  1497  func TestArches(t *testing.T) {
  1498  	for k := range archToDebian {
  1499  		t.Run(k, func(t *testing.T) {
  1500  			info := exampleInfo()
  1501  			info.Arch = k
  1502  			info = ensureValidArch(info)
  1503  			require.Equal(t, archToDebian[k], info.Arch)
  1504  		})
  1505  	}
  1506  
  1507  	t.Run("override", func(t *testing.T) {
  1508  		info := exampleInfo()
  1509  		info.Deb.Arch = "foo64"
  1510  		info = ensureValidArch(info)
  1511  		require.Equal(t, "foo64", info.Arch)
  1512  	})
  1513  }
  1514  
  1515  func TestFields(t *testing.T) {
  1516  	var w bytes.Buffer
  1517  	require.NoError(t, writeControl(&w, controlData{
  1518  		Info: nfpm.WithDefaults(&nfpm.Info{
  1519  			Name:        "foo",
  1520  			Description: "Foo does things",
  1521  			Priority:    "extra",
  1522  			Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
  1523  			Version:     "v1.0.0",
  1524  			Section:     "default",
  1525  			Homepage:    "http://carlosbecker.com",
  1526  			Overridables: nfpm.Overridables{
  1527  				Deb: nfpm.Deb{
  1528  					Fields: map[string]string{
  1529  						"Bugs":  "https://github.com/goreleaser/nfpm/issues",
  1530  						"Empty": "",
  1531  					},
  1532  				},
  1533  			},
  1534  		}),
  1535  		InstalledSize: 10,
  1536  	}))
  1537  	golden := "testdata/control3.golden"
  1538  	if *update {
  1539  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
  1540  	}
  1541  	bts, err := os.ReadFile(golden) //nolint:gosec
  1542  	require.NoError(t, err)
  1543  	require.Equal(t, string(bts), w.String())
  1544  }
  1545  
  1546  func TestGlob(t *testing.T) {
  1547  	require.NoError(t, Default.Package(nfpm.WithDefaults(&nfpm.Info{
  1548  		Name:       "nfpm-repro",
  1549  		Version:    "1.0.0",
  1550  		Maintainer: "asdfasdf",
  1551  
  1552  		Overridables: nfpm.Overridables{
  1553  			Contents: files.Contents{
  1554  				{
  1555  					Destination: "/usr/share/nfpm-repro",
  1556  					Source:      "../files/*",
  1557  				},
  1558  			},
  1559  		},
  1560  	}), io.Discard))
  1561  }
  1562  
  1563  func TestBadProvides(t *testing.T) {
  1564  	var w bytes.Buffer
  1565  	info := exampleInfo()
  1566  	info.Provides = []string{"  "}
  1567  	require.NoError(t, writeControl(&w, controlData{
  1568  		Info: nfpm.WithDefaults(info),
  1569  	}))
  1570  	golden := "testdata/bad_provides.golden"
  1571  	if *update {
  1572  		require.NoError(t, os.WriteFile(golden, w.Bytes(), 0o600))
  1573  	}
  1574  	bts, err := os.ReadFile(golden) //nolint:gosec
  1575  	require.NoError(t, err)
  1576  	require.Equal(t, string(bts), w.String())
  1577  }
  1578  
  1579  type zstdReadCloser struct {
  1580  	*zstd.Decoder
  1581  }
  1582  
  1583  func (zrc *zstdReadCloser) Close() error {
  1584  	zrc.Decoder.Close()
  1585  
  1586  	return nil
  1587  }
  1588  
  1589  func verifyDpkgSigFileHashes(arFiles map[string][]byte, msg string) error {
  1590  	_, hashes, ok := strings.Cut(msg, "Files:")
  1591  	if !ok {
  1592  		return errors.New("expected Files section in dpkg-sig message")
  1593  	}
  1594  	lines := strings.Split(hashes, "\n")
  1595  	for i := range lines {
  1596  		lines[i] = strings.TrimSpace(lines[i])
  1597  		if lines[i] == "" {
  1598  			continue
  1599  		}
  1600  		var md5Hex, sha1Hex, size, name string
  1601  		if n, err := fmt.Sscanln(lines[i], &md5Hex, &sha1Hex, &size, &name); err != nil {
  1602  			return err
  1603  		} else if n != 4 {
  1604  			return fmt.Errorf("expected 4 elements in line %q, but got %d", lines[i], n)
  1605  		}
  1606  
  1607  		md5Sum, err := hex.DecodeString(md5Hex)
  1608  		if err != nil {
  1609  			return err
  1610  		}
  1611  		sha1Sum, err := hex.DecodeString(sha1Hex)
  1612  		if err != nil {
  1613  			return err
  1614  		}
  1615  
  1616  		content, ok := arFiles[name]
  1617  		if !ok {
  1618  			return fmt.Errorf("dpkg-sig message contains hash of file %q, but the package does not contain the file", name)
  1619  		}
  1620  		actualMD5Sum, actualSHA1Sum := md5.Sum(content), sha1.Sum(content)
  1621  		if !slices.Equal(actualMD5Sum[:], md5Sum) {
  1622  			return fmt.Errorf("file %q has invalid MD5 sum", name)
  1623  		}
  1624  		if !slices.Equal(actualSHA1Sum[:], sha1Sum) {
  1625  			return fmt.Errorf("file %q has invalid SHA1 sum", name)
  1626  		}
  1627  	}
  1628  	return nil
  1629  }