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 }