github.com/goreleaser/nfpm/v2@v2.44.0/rpm/rpm_test.go (about) 1 package rpm 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/ProtonMail/go-crypto/openpgp" 15 "github.com/goreleaser/chglog" 16 "github.com/goreleaser/nfpm/v2" 17 "github.com/goreleaser/nfpm/v2/files" 18 "github.com/goreleaser/nfpm/v2/internal/sign" 19 "github.com/sassoftware/go-rpmutils" 20 "github.com/sassoftware/go-rpmutils/cpio" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func exampleInfo() *nfpm.Info { 25 return setDefaults(nfpm.WithDefaults(&nfpm.Info{ 26 Name: "foo", 27 Arch: "amd64", 28 Description: "Foo does things", 29 Priority: "extra", 30 Maintainer: "Carlos A Becker <pkg@carlosbecker.com>", 31 Version: "1.0.0", 32 Release: "1", 33 Epoch: "0", 34 Section: "default", 35 Homepage: "http://carlosbecker.com", 36 Vendor: "nope", 37 License: "MIT", 38 Overridables: nfpm.Overridables{ 39 Depends: []string{ 40 "bash", 41 }, 42 Recommends: []string{ 43 "git", 44 }, 45 Suggests: []string{ 46 "bash", 47 }, 48 Replaces: []string{ 49 "svn", 50 }, 51 Provides: []string{ 52 "bzr", 53 }, 54 Conflicts: []string{ 55 "zsh", 56 }, 57 Contents: []*files.Content{ 58 { 59 Source: "../testdata/fake", 60 Destination: "/usr/bin/fake", 61 }, 62 { 63 Source: "../testdata/whatever.conf", 64 Destination: "/etc/fake/fake.conf", 65 Type: files.TypeConfig, 66 }, 67 { 68 Destination: "/var/log/whatever", 69 Type: files.TypeDir, 70 }, 71 { 72 Destination: "/usr/share/whatever", 73 Type: files.TypeDir, 74 }, 75 }, 76 Scripts: nfpm.Scripts{ 77 PreInstall: "../testdata/scripts/preinstall.sh", 78 PostInstall: "../testdata/scripts/postinstall.sh", 79 PreRemove: "../testdata/scripts/preremove.sh", 80 PostRemove: "../testdata/scripts/postremove.sh", 81 }, 82 RPM: nfpm.RPM{ 83 Group: "foo", 84 BuildHost: "barhost", 85 Prefixes: []string{"/opt"}, 86 Scripts: nfpm.RPMScripts{ 87 PreTrans: "../testdata/scripts/pretrans.sh", 88 PostTrans: "../testdata/scripts/posttrans.sh", 89 Verify: "../testdata/scripts/verify.sh", 90 }, 91 }, 92 }, 93 })) 94 } 95 96 func TestConventionalExtension(t *testing.T) { 97 require.Equal(t, ".rpm", DefaultRPM.ConventionalExtension()) 98 } 99 100 func TestRPM(t *testing.T) { 101 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 102 require.NoError(t, err) 103 require.NoError(t, DefaultRPM.Package(exampleInfo(), f)) 104 105 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 106 require.NoError(t, err) 107 defer func() { 108 f.Close() 109 file.Close() 110 err = os.Remove(file.Name()) 111 require.NoError(t, err) 112 }() 113 rpm, err := rpmutils.ReadRpm(file) 114 require.NoError(t, err) 115 116 os, err := rpm.Header.GetString(rpmutils.OS) 117 require.NoError(t, err) 118 require.Equal(t, "linux", os) 119 120 arch, err := rpm.Header.GetString(rpmutils.ARCH) 121 require.NoError(t, err) 122 require.Equal(t, archToRPM["amd64"], arch) 123 124 version, err := rpm.Header.GetString(rpmutils.VERSION) 125 require.NoError(t, err) 126 require.Equal(t, "1.0.0", version) 127 128 release, err := rpm.Header.GetString(rpmutils.RELEASE) 129 require.NoError(t, err) 130 require.Equal(t, "1", release) 131 132 epoch, err := rpm.Header.Get(rpmutils.EPOCH) 133 require.NoError(t, err) 134 epochUint32, ok := epoch.([]uint32) 135 require.True(t, ok) 136 require.Len(t, epochUint32, 1) 137 require.Equal(t, uint32(0), epochUint32[0]) 138 139 group, err := rpm.Header.GetString(rpmutils.GROUP) 140 require.NoError(t, err) 141 require.Equal(t, "foo", group) 142 143 buildhost, err := rpm.Header.GetString(rpmutils.BUILDHOST) 144 require.NoError(t, err) 145 require.Equal(t, "barhost", buildhost) 146 147 summary, err := rpm.Header.GetString(rpmutils.SUMMARY) 148 require.NoError(t, err) 149 require.Equal(t, "Foo does things", summary) 150 151 description, err := rpm.Header.GetString(rpmutils.DESCRIPTION) 152 require.NoError(t, err) 153 require.Equal(t, "Foo does things", description) 154 } 155 156 func TestIssue952(t *testing.T) { 157 info := exampleInfo() 158 info.MTime = time.Time{} 159 160 info.Contents = files.Contents{ 161 &files.Content{ 162 Source: "/file-that-does-not-exist", 163 Destination: "/etc/link", 164 Type: files.TypeSymlink, 165 }, 166 } 167 168 var buf bytes.Buffer 169 err := DefaultRPM.Package(info, &buf) 170 require.NoError(t, err) 171 172 rpm, err := rpmutils.ReadRpm(&buf) 173 require.NoError(t, err) 174 175 files, err := rpm.Header.GetFiles() 176 require.NoError(t, err) 177 require.Len(t, files, 1) 178 f := files[0] 179 require.Equal(t, cpio.S_ISLNK, f.Mode()) 180 require.Equal(t, "/etc/link", f.Name()) 181 require.Equal(t, "/file-that-does-not-exist", f.Linkname()) 182 require.Positive(t, f.Mtime()) 183 } 184 185 func TestSRPM(t *testing.T) { 186 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 187 require.NoError(t, err) 188 require.NoError(t, DefaultSRPM.Package(exampleInfo(), f)) 189 190 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 191 require.NoError(t, err) 192 defer func() { 193 f.Close() 194 file.Close() 195 err = os.Remove(file.Name()) 196 require.NoError(t, err) 197 }() 198 rpm, err := rpmutils.ReadRpm(file) 199 require.NoError(t, err) 200 201 os, err := rpm.Header.GetString(rpmutils.OS) 202 require.NoError(t, err) 203 require.Equal(t, "linux", os) 204 205 arch, err := rpm.Header.GetString(rpmutils.ARCH) 206 require.NoError(t, err) 207 require.Equal(t, archToRPM["amd64"], arch) 208 209 version, err := rpm.Header.GetString(rpmutils.VERSION) 210 require.NoError(t, err) 211 require.Equal(t, "1.0.0", version) 212 213 release, err := rpm.Header.GetString(rpmutils.RELEASE) 214 require.NoError(t, err) 215 require.Equal(t, "1", release) 216 217 epoch, err := rpm.Header.Get(rpmutils.EPOCH) 218 require.NoError(t, err) 219 epochUint32, ok := epoch.([]uint32) 220 require.True(t, ok) 221 require.Len(t, epochUint32, 1) 222 require.Equal(t, uint32(0), epochUint32[0]) 223 224 group, err := rpm.Header.GetString(rpmutils.GROUP) 225 require.NoError(t, err) 226 require.Equal(t, "foo", group) 227 228 buildhost, err := rpm.Header.GetString(rpmutils.BUILDHOST) 229 require.NoError(t, err) 230 require.Equal(t, "barhost", buildhost) 231 232 summary, err := rpm.Header.GetString(rpmutils.SUMMARY) 233 require.NoError(t, err) 234 require.Equal(t, "Foo does things", summary) 235 236 description, err := rpm.Header.GetString(rpmutils.DESCRIPTION) 237 require.NoError(t, err) 238 require.Equal(t, "Foo does things", description) 239 240 bts, err := rpm.Header.GetUint32s(tagSourcePackage) 241 require.NoError(t, err) 242 require.Equal(t, []uint32{1}, bts) 243 } 244 245 func TestRPMMandatoryFieldsOnly(t *testing.T) { 246 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 247 require.NoError(t, err) 248 require.NoError(t, DefaultRPM.Package(&nfpm.Info{ 249 Name: "foo", 250 Arch: "amd64", 251 Version: "1.2", 252 Release: "1", 253 Description: "summary\nfoo bar\nlong description", 254 License: "MIT", 255 }, f)) 256 257 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 258 require.NoError(t, err) 259 defer func() { 260 f.Close() 261 file.Close() 262 err = os.Remove(file.Name()) 263 require.NoError(t, err) 264 }() 265 rpm, err := rpmutils.ReadRpm(file) 266 require.NoError(t, err) 267 268 os, err := rpm.Header.GetString(rpmutils.OS) 269 require.NoError(t, err) 270 require.Equal(t, "linux", os) 271 272 arch, err := rpm.Header.GetString(rpmutils.ARCH) 273 require.NoError(t, err) 274 require.Equal(t, archToRPM["amd64"], arch) 275 276 version, err := rpm.Header.GetString(rpmutils.VERSION) 277 require.NoError(t, err) 278 require.Equal(t, "1.2", version) 279 280 release, err := rpm.Header.GetString(rpmutils.RELEASE) 281 require.NoError(t, err) 282 require.Equal(t, "1", release) 283 284 _, err = rpm.Header.Get(rpmutils.EPOCH) 285 require.Error(t, err, "epoch should not be set") 286 287 _, err = rpm.Header.GetString(rpmutils.GROUP) 288 require.Error(t, err, "group should not be set") 289 290 summary, err := rpm.Header.GetString(rpmutils.SUMMARY) 291 require.NoError(t, err) 292 require.Equal(t, "summary", summary) 293 294 description, err := rpm.Header.GetString(rpmutils.DESCRIPTION) 295 require.NoError(t, err) 296 require.Equal(t, "summary\nfoo bar\nlong description", description) 297 } 298 299 func TestRPMPlatform(t *testing.T) { 300 f, err := os.CreateTemp(t.TempDir(), "test*.rpm") 301 require.NoError(t, err) 302 t.Cleanup(func() { require.NoError(t, f.Close()) }) 303 info := exampleInfo() 304 info.Platform = "darwin" 305 require.NoError(t, DefaultRPM.Package(info, f)) 306 } 307 308 func TestRPMGroup(t *testing.T) { 309 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 310 require.NoError(t, err) 311 info := exampleInfo() 312 info.RPM.Group = "Unspecified" 313 require.NoError(t, DefaultRPM.Package(info, f)) 314 315 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 316 require.NoError(t, err) 317 defer func() { 318 f.Close() 319 file.Close() 320 err = os.Remove(file.Name()) 321 require.NoError(t, err) 322 }() 323 324 rpm, err := rpmutils.ReadRpm(file) 325 require.NoError(t, err) 326 327 group, err := rpm.Header.GetString(rpmutils.GROUP) 328 require.NoError(t, err) 329 require.Equal(t, "Unspecified", group) 330 } 331 332 func TestRPMCompression(t *testing.T) { 333 for _, compressor := range []string{"gzip", "lzma", "xz", "zstd"} { 334 for _, level := range []int{-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9} { 335 compLevel := fmt.Sprintf("%v:%v", compressor, level) 336 if strings.HasPrefix(compLevel, "xz:") { 337 compLevel = "xz" 338 } 339 if strings.HasPrefix(compLevel, "lzma:") { 340 compLevel = "lzma" 341 } 342 t.Run(compLevel, func(t *testing.T) { 343 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 344 require.NoError(t, err) 345 346 info := exampleInfo() 347 info.RPM.Compression = compLevel 348 349 require.NoError(t, DefaultRPM.Package(info, f)) 350 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 351 require.NoError(t, err) 352 defer func() { 353 f.Close() 354 file.Close() 355 err = os.Remove(file.Name()) 356 require.NoError(t, err) 357 }() 358 rpm, err := rpmutils.ReadRpm(file) 359 require.NoError(t, err) 360 361 rpmCompressor, err := rpm.Header.GetString(rpmutils.PAYLOADCOMPRESSOR) 362 require.NoError(t, err) 363 require.Equal(t, compressor, rpmCompressor) 364 }) 365 if compLevel == "xz" || compLevel == "lzma" { 366 break 367 } 368 } 369 } 370 } 371 372 func TestRPMSummary(t *testing.T) { 373 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 374 require.NoError(t, err) 375 376 customSummary := "This is my custom summary" 377 info := exampleInfo() 378 info.RPM.Group = "Unspecified" 379 info.RPM.Summary = customSummary 380 381 require.NoError(t, DefaultRPM.Package(info, f)) 382 383 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 384 require.NoError(t, err) 385 defer func() { 386 f.Close() 387 file.Close() 388 err = os.Remove(file.Name()) 389 require.NoError(t, err) 390 }() 391 rpm, err := rpmutils.ReadRpm(file) 392 require.NoError(t, err) 393 394 summary, err := rpm.Header.GetString(rpmutils.SUMMARY) 395 require.NoError(t, err) 396 require.Equal(t, customSummary, summary) 397 } 398 399 func TestRPMPackager(t *testing.T) { 400 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 401 require.NoError(t, err) 402 403 customPackager := "GoReleaser <staff@goreleaser.com>" 404 info := exampleInfo() 405 info.RPM.Group = "Unspecified" 406 info.RPM.Packager = customPackager 407 408 require.NoError(t, DefaultRPM.Package(info, f)) 409 410 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 411 require.NoError(t, err) 412 defer func() { 413 f.Close() 414 file.Close() 415 err = os.Remove(file.Name()) 416 require.NoError(t, err) 417 }() 418 rpm, err := rpmutils.ReadRpm(file) 419 require.NoError(t, err) 420 421 packager, err := rpm.Header.GetString(rpmutils.PACKAGER) 422 require.NoError(t, err) 423 require.Equal(t, customPackager, packager) 424 } 425 426 func TestWithRPMTags(t *testing.T) { 427 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 428 require.NoError(t, err) 429 430 info := exampleInfo() 431 info.Release = "3" 432 info.Epoch = "42" 433 info.RPM = nfpm.RPM{ 434 Group: "default", 435 } 436 info.Description = "first line\nsecond line\nthird line" 437 require.NoError(t, DefaultRPM.Package(info, f)) 438 439 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 440 require.NoError(t, err) 441 defer func() { 442 f.Close() 443 file.Close() 444 err = os.Remove(file.Name()) 445 require.NoError(t, err) 446 }() 447 448 rpm, err := rpmutils.ReadRpm(file) 449 require.NoError(t, err) 450 451 version, err := rpm.Header.GetString(rpmutils.VERSION) 452 require.NoError(t, err) 453 require.Equal(t, "1.0.0", version) 454 455 release, err := rpm.Header.GetString(rpmutils.RELEASE) 456 require.NoError(t, err) 457 require.Equal(t, "3", release) 458 459 epoch, err := rpm.Header.Get(rpmutils.EPOCH) 460 require.NoError(t, err) 461 epochUint32, ok := epoch.([]uint32) 462 require.Len(t, epochUint32, 1) 463 require.True(t, ok) 464 require.Equal(t, uint32(42), epochUint32[0]) 465 466 group, err := rpm.Header.GetString(rpmutils.GROUP) 467 require.NoError(t, err) 468 require.Equal(t, "default", group) 469 470 summary, err := rpm.Header.GetString(rpmutils.SUMMARY) 471 require.NoError(t, err) 472 require.Equal(t, "first line", summary) 473 474 description, err := rpm.Header.GetString(rpmutils.DESCRIPTION) 475 require.NoError(t, err) 476 require.Equal(t, info.Description, description) 477 } 478 479 func TestRPMVersion(t *testing.T) { 480 info := exampleInfo() 481 info.Version = "1.0.0" //nolint:golint,goconst 482 meta, err := buildRPMMeta(info) 483 require.NoError(t, err) 484 require.Equal(t, "1.0.0", meta.Version) 485 require.Equal(t, "1", meta.Release) 486 } 487 488 func TestRPMVersionWithRelease(t *testing.T) { 489 info := exampleInfo() 490 info.Version = "1.0.0" //nolint:golint,goconst 491 info.Release = "2" 492 meta, err := buildRPMMeta(info) 493 require.NoError(t, err) 494 require.Equal(t, "1.0.0", meta.Version) 495 require.Equal(t, "2", meta.Release) 496 } 497 498 func TestRPMVersionWithPrerelease(t *testing.T) { 499 // https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples 500 info := exampleInfo() 501 502 info.Version = "1.0.0" 503 info.Prerelease = "rc1" // nolint:goconst 504 meta, err := buildRPMMeta(info) 505 require.NoError(t, err) 506 require.Equal(t, "1.0.0~rc1", meta.Version) 507 require.Equal(t, "1", meta.Release) 508 509 info.Version = "1.0.0~rc1" 510 info.Prerelease = "" 511 meta, err = buildRPMMeta(info) 512 require.NoError(t, err) 513 require.Equal(t, "1.0.0~rc1", meta.Version) 514 require.Equal(t, "1", meta.Release) 515 } 516 517 func TestRPMVersionWithPrereleaseWithDashes(t *testing.T) { 518 info := exampleInfo() 519 info.Version = "1.0.0" 520 info.Prerelease = "rc1-alpha-omega" // nolint:goconst 521 meta, err := buildRPMMeta(info) 522 require.NoError(t, err) 523 require.Equal(t, "1.0.0~rc1_alpha_omega", meta.Version) 524 require.Equal(t, "1", meta.Release) 525 } 526 527 func TestRPMVersionWithReleaseAndPrerelease(t *testing.T) { 528 // https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples 529 info := exampleInfo() 530 531 info.Version = "1.0.0" 532 info.Release = "2" 533 info.Prerelease = "rc1" 534 meta, err := buildRPMMeta(info) 535 require.NoError(t, err) 536 require.Equal(t, "1.0.0~rc1", meta.Version) 537 require.Equal(t, "2", meta.Release) 538 539 info.Version = "1.0.0~rc1" 540 info.Release = "3" 541 info.Prerelease = "" 542 meta, err = buildRPMMeta(info) 543 require.NoError(t, err) 544 require.Equal(t, "1.0.0~rc1", meta.Version) 545 require.Equal(t, "3", meta.Release) 546 } 547 548 func TestRPMVersionWithVersionMetadata(t *testing.T) { 549 // https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples 550 info := exampleInfo() 551 552 info.Version = "1.0.0+meta" 553 info.VersionMetadata = "" 554 meta, err := buildRPMMeta(info) 555 require.NoError(t, err) 556 require.Equal(t, "1.0.0+meta", meta.Version) 557 require.Equal(t, "1", meta.Release) 558 559 info.Version = "1.0.0" 560 info.VersionMetadata = "meta" 561 info.Release = "10" 562 meta, err = buildRPMMeta(nfpm.WithDefaults(info)) 563 require.NoError(t, err) 564 require.Equal(t, "1.0.0+meta", meta.Version) 565 require.Equal(t, "10", meta.Release) 566 } 567 568 func TestWithInvalidEpoch(t *testing.T) { 569 f, err := os.CreateTemp(t.TempDir(), "test.rpm") 570 defer func() { 571 _ = f.Close() 572 err = os.Remove(f.Name()) 573 require.NoError(t, err) 574 }() 575 576 info := exampleInfo() 577 info.Release = "3" 578 info.Epoch = "-1" 579 info.RPM = nfpm.RPM{ 580 Group: "default", 581 } 582 info.Description = "first line\nsecond line\nthird line" 583 require.Error(t, DefaultRPM.Package(info, f)) 584 } 585 586 func TestRPMScripts(t *testing.T) { 587 info := exampleInfo() 588 f, err := os.CreateTemp(t.TempDir(), fmt.Sprintf("%s-%s-*.rpm", info.Name, info.Version)) 589 require.NoError(t, err) 590 err = DefaultRPM.Package(info, f) 591 require.NoError(t, err) 592 file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0o600) //nolint:gosec 593 require.NoError(t, err) 594 defer func() { 595 f.Close() 596 file.Close() 597 err = os.Remove(file.Name()) 598 require.NoError(t, err) 599 }() 600 rpm, err := rpmutils.ReadRpm(file) 601 require.NoError(t, err) 602 603 data, err := rpm.Header.GetString(rpmutils.PREIN) 604 require.NoError(t, err) 605 require.Equal(t, `#!/bin/bash 606 607 echo "Preinstall" > /dev/null 608 `, data, "Preinstall script does not match") 609 610 data, err = rpm.Header.GetString(rpmutils.PREUN) 611 require.NoError(t, err) 612 require.Equal(t, `#!/bin/bash 613 614 echo "Preremove" > /dev/null 615 `, data, "Preremove script does not match") 616 617 data, err = rpm.Header.GetString(rpmutils.POSTIN) 618 require.NoError(t, err) 619 require.Equal(t, `#!/bin/bash 620 621 echo "Postinstall" > /dev/null 622 `, data, "Postinstall script does not match") 623 624 data, err = rpm.Header.GetString(rpmutils.POSTUN) 625 require.NoError(t, err) 626 require.Equal(t, `#!/bin/bash 627 628 echo "Postremove" > /dev/null 629 `, data, "Postremove script does not match") 630 631 rpmPreTransTag := 1151 632 data, err = rpm.Header.GetString(rpmPreTransTag) 633 require.NoError(t, err) 634 require.Equal(t, `#!/bin/bash 635 636 echo "Pretrans" > /dev/null 637 `, data, "Pretrans script does not match") 638 639 rpmPostTransTag := 1152 640 data, err = rpm.Header.GetString(rpmPostTransTag) 641 require.NoError(t, err) 642 require.Equal(t, `#!/bin/bash 643 644 echo "Posttrans" > /dev/null 645 `, data, "Posttrans script does not match") 646 647 data, err = rpm.Header.GetString(rpmutils.VERIFYSCRIPT) 648 require.NoError(t, err) 649 require.Equal(t, `#!/bin/bash 650 651 echo "Verify" > /dev/null 652 `, data, "Verify script does not match") 653 } 654 655 func TestRPMFileDoesNotExist(t *testing.T) { 656 info := exampleInfo() 657 info.Contents = []*files.Content{ 658 { 659 Source: "../testdata/fake", 660 Destination: "/usr/bin/fake", 661 }, 662 { 663 Source: "../testdata/whatever.confzzz", 664 Destination: "/etc/fake/fake.conf", 665 Type: files.TypeConfig, 666 }, 667 } 668 abs, err := filepath.Abs("../testdata/whatever.confzzz") 669 require.NoError(t, err) 670 err = DefaultRPM.Package(info, io.Discard) 671 require.EqualError(t, err, fmt.Sprintf("matching \"%s\": file does not exist", filepath.ToSlash(abs))) 672 } 673 674 func TestArches(t *testing.T) { 675 for k := range archToRPM { 676 t.Run(k, func(t *testing.T) { 677 info := exampleInfo() 678 info.Arch = k 679 info = setDefaults(info) 680 require.Equal(t, archToRPM[k], info.Arch) 681 }) 682 } 683 684 t.Run("override", func(t *testing.T) { 685 info := exampleInfo() 686 info.RPM.Arch = "foo64" 687 info = setDefaults(info) 688 require.Equal(t, "foo64", info.Arch) 689 }) 690 } 691 692 func TestConfigMissingOK(t *testing.T) { 693 var ( 694 buildConfigFile = "../testdata/whatever.conf" 695 packageConfigFile = "/etc/fake/fake.conf" 696 ) 697 698 info := &nfpm.Info{ 699 Name: "symlink-in-files", 700 Arch: "amd64", 701 Description: "This package's config references a file via symlink.", 702 Version: "1.0.0", 703 Maintainer: "maintainer", 704 Overridables: nfpm.Overridables{ 705 Contents: []*files.Content{ 706 { 707 Source: buildConfigFile, 708 Destination: packageConfigFile, 709 Type: files.TypeConfigMissingOK, 710 }, 711 }, 712 }, 713 } 714 715 var rpmFileBuffer bytes.Buffer 716 err := DefaultRPM.Package(info, &rpmFileBuffer) 717 require.NoError(t, err) 718 719 expectedConfigContent, err := os.ReadFile(buildConfigFile) 720 require.NoError(t, err) 721 722 packageConfigContent, err := extractFileFromRpm(rpmFileBuffer.Bytes(), packageConfigFile) 723 require.NoError(t, err) 724 725 require.Equal(t, expectedConfigContent, packageConfigContent) 726 } 727 728 func TestConfigNoReplace(t *testing.T) { 729 var ( 730 buildConfigFile = "../testdata/whatever.conf" 731 packageConfigFile = "/etc/fake/fake.conf" 732 ) 733 734 info := &nfpm.Info{ 735 Name: "symlink-in-files", 736 Arch: "amd64", 737 Description: "This package's config references a file via symlink.", 738 Version: "1.0.0", 739 Maintainer: "maintainer", 740 Overridables: nfpm.Overridables{ 741 Contents: []*files.Content{ 742 { 743 Source: buildConfigFile, 744 Destination: packageConfigFile, 745 Type: files.TypeConfigNoReplace, 746 }, 747 }, 748 }, 749 } 750 751 var rpmFileBuffer bytes.Buffer 752 err := DefaultRPM.Package(info, &rpmFileBuffer) 753 require.NoError(t, err) 754 755 expectedConfigContent, err := os.ReadFile(buildConfigFile) 756 require.NoError(t, err) 757 758 packageConfigContent, err := extractFileFromRpm(rpmFileBuffer.Bytes(), packageConfigFile) 759 require.NoError(t, err) 760 761 require.Equal(t, expectedConfigContent, packageConfigContent) 762 } 763 764 func TestRPMConventionalFileName(t *testing.T) { 765 info := &nfpm.Info{ 766 Name: "testpkg", 767 Arch: "noarch", 768 Maintainer: "maintainer", 769 } 770 771 testCases := []struct { 772 Version string 773 Release string 774 Prerelease string 775 Expected string 776 Metadata string 777 }{ 778 { 779 Version: "1.2.3", Release: "", Prerelease: "", Metadata: "", 780 Expected: fmt.Sprintf("%s-1.2.3-1.%s.rpm", info.Name, info.Arch), 781 }, 782 { 783 Version: "1.2.3", Release: "4", Prerelease: "", Metadata: "", 784 Expected: fmt.Sprintf("%s-1.2.3-4.%s.rpm", info.Name, info.Arch), 785 }, 786 { 787 Version: "1.2.3", Release: "4", Prerelease: "5", Metadata: "", 788 Expected: fmt.Sprintf("%s-1.2.3~5-4.%s.rpm", info.Name, info.Arch), 789 }, 790 { 791 Version: "1.2.3", Release: "", Prerelease: "5", Metadata: "", 792 Expected: fmt.Sprintf("%s-1.2.3~5-1.%s.rpm", info.Name, info.Arch), 793 }, 794 { 795 Version: "1.2.3", Release: "1", Prerelease: "5", Metadata: "git", 796 Expected: fmt.Sprintf("%s-1.2.3~5+git-1.%s.rpm", info.Name, info.Arch), 797 }, 798 } 799 800 for _, testCase := range testCases { 801 info.Version = testCase.Version 802 info.Release = testCase.Release 803 info.Prerelease = testCase.Prerelease 804 info.VersionMetadata = testCase.Metadata 805 806 require.Equal(t, testCase.Expected, DefaultRPM.ConventionalFileName(info)) 807 } 808 } 809 810 func TestRPMChangelog(t *testing.T) { 811 info := exampleInfo() 812 info.Changelog = "../testdata/changelog.yaml" 813 814 var rpmFileBuffer bytes.Buffer 815 err := DefaultRPM.Package(info, &rpmFileBuffer) 816 require.NoError(t, err) 817 818 rpm, err := rpmutils.ReadRpm(bytes.NewReader(rpmFileBuffer.Bytes())) 819 require.NoError(t, err) 820 821 changelog, err := chglog.Parse(info.Changelog) 822 require.NoError(t, err) 823 824 _times, err := rpm.Header.Get(tagChangelogTime) 825 require.NoError(t, err) 826 times, ok := _times.([]uint32) 827 require.True(t, ok) 828 require.Len(t, changelog, len(times)) 829 830 _titles, err := rpm.Header.Get(tagChangelogName) 831 require.NoError(t, err) 832 titles, ok := _titles.([]string) 833 require.True(t, ok) 834 require.Len(t, changelog, len(titles)) 835 836 _notes, err := rpm.Header.Get(tagChangelogText) 837 require.NoError(t, err) 838 allNotes, ok := _notes.([]string) 839 require.True(t, ok) 840 require.Len(t, changelog, len(allNotes)) 841 842 for i, entry := range changelog { 843 timestamp := time.Unix(int64(times[i]), 0).UTC() 844 title := titles[i] 845 notes := strings.Split(allNotes[i], "\n") 846 847 require.Equal(t, entry.Date, timestamp) 848 require.Contains(t, title, entry.Packager) 849 require.Contains(t, title, entry.Semver) 850 require.Len(t, entry.Changes, len(notes)) 851 852 for j, change := range entry.Changes { 853 require.Contains(t, notes[j], change.Note) 854 } 855 } 856 } 857 858 func TestRPMNoChangelogTagsWithoutChangelogConfigured(t *testing.T) { 859 info := exampleInfo() 860 861 var rpmFileBuffer bytes.Buffer 862 err := DefaultRPM.Package(info, &rpmFileBuffer) 863 require.NoError(t, err) 864 865 rpm, err := rpmutils.ReadRpm(bytes.NewReader(rpmFileBuffer.Bytes())) 866 require.NoError(t, err) 867 868 _, err = rpm.Header.Get(tagChangelogTime) 869 require.Error(t, err) 870 871 _, err = rpm.Header.Get(tagChangelogName) 872 require.Error(t, err) 873 874 _, err = rpm.Header.Get(tagChangelogText) 875 require.Error(t, err) 876 } 877 878 func TestSymlink(t *testing.T) { 879 var ( 880 configFilePath = "/usr/share/doc/fake/fake.txt" 881 symlink = "/path/to/symlink" 882 symlinkTarget = configFilePath 883 ) 884 885 info := &nfpm.Info{ 886 Name: "symlink-in-files", 887 Arch: "amd64", 888 Description: "This package's config references a file via symlink.", 889 Version: "1.0.0", 890 Maintainer: "maintainer", 891 Overridables: nfpm.Overridables{ 892 Contents: []*files.Content{ 893 { 894 Source: "../testdata/whatever.conf", 895 Destination: configFilePath, 896 }, 897 { 898 Source: symlinkTarget, 899 Destination: symlink, 900 Type: files.TypeSymlink, 901 }, 902 }, 903 }, 904 } 905 906 var rpmFileBuffer bytes.Buffer 907 err := DefaultRPM.Package(info, &rpmFileBuffer) 908 require.NoError(t, err) 909 910 packagedSymlinkHeader, err := extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), symlink) 911 require.NoError(t, err) 912 913 packagedSymlink, err := extractFileFromRpm(rpmFileBuffer.Bytes(), symlink) 914 require.NoError(t, err) 915 916 require.Equal(t, symlink, packagedSymlinkHeader.Filename()) 917 require.Equal(t, cpio.S_ISLNK, packagedSymlinkHeader.Mode()) 918 require.Equal(t, symlinkTarget, string(packagedSymlink)) 919 } 920 921 func TestRPMSignature(t *testing.T) { 922 info := exampleInfo() 923 info.RPM.Signature.KeyFile = "../internal/sign/testdata/privkey.asc" 924 info.RPM.Signature.KeyPassphrase = "hunter2" 925 926 pubkeyFileContent, err := os.ReadFile("../internal/sign/testdata/pubkey.gpg") 927 require.NoError(t, err) 928 929 keyring, err := openpgp.ReadKeyRing(bytes.NewReader(pubkeyFileContent)) 930 require.NoError(t, err) 931 require.NotNil(t, keyring, "cannot verify sigs with an empty keyring") 932 933 var rpmBuffer bytes.Buffer 934 err = DefaultRPM.Package(info, &rpmBuffer) 935 require.NoError(t, err) 936 937 _, sigs, err := rpmutils.Verify(bytes.NewReader(rpmBuffer.Bytes()), keyring) 938 require.NoError(t, err) 939 require.Len(t, sigs, 2) 940 } 941 942 func TestRPMSignatureError(t *testing.T) { 943 info := exampleInfo() 944 info.RPM.Signature.KeyFile = "../internal/sign/testdata/privkey.asc" 945 info.RPM.Signature.KeyPassphrase = "wrongpass" 946 947 var rpmBuffer bytes.Buffer 948 err := DefaultRPM.Package(info, &rpmBuffer) 949 require.Error(t, err) 950 951 var expectedError *nfpm.ErrSigningFailure 952 require.ErrorAs(t, err, &expectedError) 953 } 954 955 func TestRPMSignatureCallback(t *testing.T) { 956 info := exampleInfo() 957 info.RPM.Signature.SignFn = func(r io.Reader) ([]byte, error) { 958 data, err := io.ReadAll(r) 959 if err != nil { 960 return nil, err 961 } 962 return sign.PGPSignerWithKeyID("../internal/sign/testdata/privkey.asc", "hunter2", nil)(data) 963 } 964 965 pubkeyFileContent, err := os.ReadFile("../internal/sign/testdata/pubkey.gpg") 966 require.NoError(t, err) 967 968 keyring, err := openpgp.ReadKeyRing(bytes.NewReader(pubkeyFileContent)) 969 require.NoError(t, err) 970 require.NotNil(t, keyring, "cannot verify sigs with an empty keyring") 971 972 var rpmBuffer bytes.Buffer 973 err = DefaultRPM.Package(info, &rpmBuffer) 974 require.NoError(t, err) 975 976 _, sigs, err := rpmutils.Verify(bytes.NewReader(rpmBuffer.Bytes()), keyring) 977 require.NoError(t, err) 978 require.Len(t, sigs, 2) 979 } 980 981 func TestRPMGhostFiles(t *testing.T) { 982 filename := "/usr/lib/casper.a" 983 984 info := &nfpm.Info{ 985 Name: "rpm-ghost", 986 Arch: "amd64", 987 Description: "This RPM contains ghost files.", 988 Version: "1.0.0", 989 Maintainer: "maintainer", 990 Overridables: nfpm.Overridables{ 991 Contents: []*files.Content{ 992 { 993 Destination: filename, 994 Type: files.TypeRPMGhost, 995 }, 996 }, 997 }, 998 } 999 1000 var rpmFileBuffer bytes.Buffer 1001 err := DefaultRPM.Package(info, &rpmFileBuffer) 1002 require.NoError(t, err) 1003 1004 headerFiles, err := extraFileInfoSliceFromRpm(rpmFileBuffer.Bytes()) 1005 require.NoError(t, err) 1006 1007 type headerFileInfo struct { 1008 Name string 1009 Size int64 1010 Mode int 1011 } 1012 expected := []headerFileInfo{ 1013 {filename, 0, cpio.S_ISREG | 0o644}, 1014 } 1015 actual := make([]headerFileInfo, 0) 1016 for _, fileInfo := range headerFiles { 1017 actual = append(actual, headerFileInfo{fileInfo.Name(), fileInfo.Size(), fileInfo.Mode()}) 1018 } 1019 require.Equal(t, expected, actual) 1020 1021 _, err = extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), filename) 1022 require.Error(t, err) 1023 1024 _, err = extractFileFromRpm(rpmFileBuffer.Bytes(), filename) 1025 require.Error(t, err) 1026 } 1027 1028 func TestDisableGlobbing(t *testing.T) { 1029 info := exampleInfo() 1030 info.DisableGlobbing = true 1031 info.Contents = []*files.Content{ 1032 { 1033 Source: "../testdata/{file}[", 1034 Destination: "/test/{file}[", 1035 }, 1036 } 1037 1038 var rpmFileBuffer bytes.Buffer 1039 err := DefaultRPM.Package(info, &rpmFileBuffer) 1040 require.NoError(t, err) 1041 1042 expectedContent, err := os.ReadFile("../testdata/{file}[") 1043 require.NoError(t, err) 1044 1045 actualContent, err := extractFileFromRpm(rpmFileBuffer.Bytes(), "/test/{file}[") 1046 require.NoError(t, err) 1047 1048 require.Equal(t, expectedContent, actualContent) 1049 } 1050 1051 func TestDirectories(t *testing.T) { 1052 info := exampleInfo() 1053 info.Contents = []*files.Content{ 1054 { 1055 Source: "../testdata/whatever.conf", 1056 Destination: "/etc/foo/file", 1057 }, 1058 { 1059 Source: "../testdata/whatever.conf", 1060 Destination: "/etc/bar/file", 1061 }, 1062 { 1063 Destination: "/etc/bar", 1064 Type: files.TypeDir, 1065 }, 1066 { 1067 Destination: "/etc/baz", 1068 Type: files.TypeDir, 1069 FileInfo: &files.ContentFileInfo{ 1070 Owner: "test", 1071 Mode: 0o700, 1072 }, 1073 }, 1074 { 1075 Destination: "/usr/lib/something/somethingelse", 1076 Type: files.TypeDir, 1077 }, 1078 } 1079 1080 var rpmFileBuffer bytes.Buffer 1081 err := DefaultRPM.Package(info, &rpmFileBuffer) 1082 require.NoError(t, err) 1083 1084 require.Equal(t, []string{ 1085 "/etc/bar", 1086 "/etc/bar/file", 1087 "/etc/baz", 1088 "/etc/foo/file", 1089 "/usr/lib/something/somethingelse", 1090 }, getTree(t, rpmFileBuffer.Bytes())) 1091 1092 // the directory /etc/foo should not be implicitly created as that 1093 // implies ownership of /etc/foo which should always be implicit 1094 _, err = extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), "/etc/foo") 1095 require.Equal(t, err, os.ErrNotExist) 1096 1097 // claiming explicit ownership of /etc/bar which already contains a file 1098 h, err := extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), "/etc/bar") 1099 require.NoError(t, err) 1100 require.NotEqual(t, 0, h.Mode()&int(tagDirectory)) 1101 1102 // creating an empty folder (which also implies ownership) 1103 h, err = extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), "/etc/baz") 1104 require.NoError(t, err) 1105 require.Equal(t, h.Mode(), int(tagDirectory|0o700)) 1106 } 1107 1108 func TestGlob(t *testing.T) { 1109 require.NoError(t, DefaultRPM.Package(nfpm.WithDefaults(&nfpm.Info{ 1110 Name: "nfpm-repro", 1111 Version: "1.0.0", 1112 Maintainer: "asdfasdf", 1113 1114 Overridables: nfpm.Overridables{ 1115 Contents: files.Contents{ 1116 { 1117 Destination: "/usr/share/nfpm-repro", 1118 Source: "../files/*", 1119 }, 1120 }, 1121 }, 1122 }), io.Discard)) 1123 } 1124 1125 func TestIgnoreUnrelatedFiles(t *testing.T) { 1126 info := exampleInfo() 1127 info.Contents = files.Contents{ 1128 { 1129 Source: "../testdata/fake", 1130 Destination: "/some/file", 1131 Type: files.TypeDebChangelog, 1132 }, 1133 } 1134 1135 var rpmFileBuffer bytes.Buffer 1136 err := DefaultRPM.Package(info, &rpmFileBuffer) 1137 require.NoError(t, err) 1138 1139 require.Empty(t, getTree(t, rpmFileBuffer.Bytes())) 1140 } 1141 1142 func extractFileFromRpm(rpm []byte, filename string) ([]byte, error) { 1143 rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm)) 1144 if err != nil { 1145 return nil, err 1146 } 1147 1148 pr, err := rpmFile.PayloadReader() 1149 if err != nil { 1150 return nil, err 1151 } 1152 1153 for { 1154 hdr, err := pr.Next() 1155 if errors.Is(err, io.EOF) { 1156 break // End of archive 1157 } 1158 if err != nil { 1159 return nil, err 1160 } 1161 1162 if hdr.Filename() != filename { 1163 continue 1164 } 1165 1166 fileContents, err := io.ReadAll(pr) 1167 if err != nil { 1168 return nil, err 1169 } 1170 1171 return fileContents, nil 1172 } 1173 1174 return nil, os.ErrNotExist 1175 } 1176 1177 func extraFileInfoSliceFromRpm(rpm []byte) ([]rpmutils.FileInfo, error) { 1178 rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm)) 1179 if err != nil { 1180 return nil, err 1181 } 1182 return rpmFile.Header.GetFiles() 1183 } 1184 1185 func getTree(tb testing.TB, rpm []byte) []string { 1186 tb.Helper() 1187 1188 rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm)) 1189 require.NoError(tb, err) 1190 pr, err := rpmFile.PayloadReader() 1191 require.NoError(tb, err) 1192 1193 var tree []string 1194 for { 1195 hdr, err := pr.Next() 1196 if errors.Is(err, io.EOF) { 1197 break // End of archive 1198 } 1199 require.NoError(tb, err) 1200 tree = append(tree, hdr.Filename()) 1201 } 1202 1203 return tree 1204 } 1205 1206 func extractFileHeaderFromRpm(rpm []byte, filename string) (*cpio.Cpio_newc_header, error) { 1207 rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm)) 1208 if err != nil { 1209 return nil, err 1210 } 1211 1212 pr, err := rpmFile.PayloadReader() 1213 if err != nil { 1214 return nil, err 1215 } 1216 1217 for { 1218 hdr, err := pr.Next() 1219 if errors.Is(err, io.EOF) { 1220 break // End of archive 1221 } 1222 if err != nil { 1223 return nil, err 1224 } 1225 1226 if hdr.Filename() != filename { 1227 continue 1228 } 1229 1230 return hdr, nil 1231 } 1232 1233 return nil, os.ErrNotExist 1234 }