1
1
Fork 0
mirror of https://github.com/goreleaser/nfpm synced 2024-05-24 05:56:16 +02:00
nfpm/rpm/rpm_test.go
wwade 738ac8b236
test: Intergate and test rpmpack ghost file fix (#261)
Bump github.com/google/rpmpack to integrate the ghost file handling
fix [google/rpmpack 52].

Updated the TestRPMGhostFiles fix to test the updated
functionality. Now, we expect that the files are listed in the RPM
header, but without any cpio content.

This is an extension of PR #246.

[google/rpmpack 52]: https://github.com/google/rpmpack/pull/52

Co-authored-by: wwade <wwade@users.noreply.github.com>
2020-12-08 18:37:51 -03:00

770 lines
19 KiB
Go

package rpm
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/sassoftware/go-rpmutils"
"github.com/sassoftware/go-rpmutils/cpio"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/openpgp"
"github.com/goreleaser/chglog"
"github.com/goreleaser/nfpm"
)
func exampleInfo() *nfpm.Info {
return nfpm.WithDefaults(&nfpm.Info{
Name: "foo",
Arch: "amd64",
Description: "Foo does things",
Priority: "extra",
Maintainer: "Carlos A Becker <pkg@carlosbecker.com>",
Version: "1.0.0",
Section: "default",
Homepage: "http://carlosbecker.com",
Vendor: "nope",
License: "MIT",
Overridables: nfpm.Overridables{
Depends: []string{
"bash",
},
Recommends: []string{
"git",
},
Suggests: []string{
"bash",
},
Replaces: []string{
"svn",
},
Provides: []string{
"bzr",
},
Conflicts: []string{
"zsh",
},
Files: map[string]string{
"../testdata/fake": "/usr/local/bin/fake",
},
ConfigFiles: map[string]string{
"../testdata/whatever.conf": "/etc/fake/fake.conf",
},
EmptyFolders: []string{
"/var/log/whatever",
"/usr/share/whatever",
},
Scripts: nfpm.Scripts{
PreInstall: "../testdata/scripts/preinstall.sh",
PostInstall: "../testdata/scripts/postinstall.sh",
PreRemove: "../testdata/scripts/preremove.sh",
PostRemove: "../testdata/scripts/postremove.sh",
},
},
})
}
func TestRPM(t *testing.T) {
f, err := ioutil.TempFile("", "test.rpm")
defer func() {
_ = f.Close()
err = os.Remove(f.Name())
assert.NoError(t, err)
}()
require.NoError(t, Default.Package(exampleInfo(), f))
file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0600) //nolint:gosec
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(file)
require.NoError(t, err)
version, err := rpm.Header.GetString(rpmutils.VERSION)
require.NoError(t, err)
assert.Equal(t, "1.0.0", version)
release, err := rpm.Header.GetString(rpmutils.RELEASE)
require.NoError(t, err)
assert.Equal(t, "1", release)
epoch, err := rpm.Header.Get(rpmutils.EPOCH)
require.NoError(t, err)
epochUint32, ok := epoch.([]uint32)
require.True(t, ok)
assert.Len(t, epochUint32, 1)
assert.Equal(t, uint32(0), epochUint32[0])
group, err := rpm.Header.GetString(rpmutils.GROUP)
require.NoError(t, err)
assert.Equal(t, "", group)
summary, err := rpm.Header.GetString(rpmutils.SUMMARY)
require.NoError(t, err)
assert.Equal(t, "Foo does things", summary)
description, err := rpm.Header.GetString(rpmutils.DESCRIPTION)
require.NoError(t, err)
assert.Equal(t, "Foo does things", description)
}
func TestRPMGroup(t *testing.T) {
f, err := ioutil.TempFile("", "test.rpm")
defer func() {
_ = f.Close()
err = os.Remove(f.Name())
assert.NoError(t, err)
}()
info := exampleInfo()
info.RPM.Group = "Unspecified"
require.NoError(t, Default.Package(info, f))
file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0600) //nolint:gosec
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(file)
require.NoError(t, err)
group, err := rpm.Header.GetString(rpmutils.GROUP)
require.NoError(t, err)
assert.Equal(t, "Unspecified", group)
}
func TestRPMSummary(t *testing.T) {
f, err := ioutil.TempFile("", "test.rpm")
defer func() {
_ = f.Close()
err = os.Remove(f.Name())
assert.NoError(t, err)
}()
var customSummary = "This is my custom summary"
info := exampleInfo()
info.RPM.Group = "Unspecified"
info.RPM.Summary = customSummary
require.NoError(t, Default.Package(info, f))
file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0600) //nolint:gosec
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(file)
require.NoError(t, err)
summary, err := rpm.Header.GetString(rpmutils.SUMMARY)
require.NoError(t, err)
assert.Equal(t, customSummary, summary)
}
func TestWithRPMTags(t *testing.T) {
f, err := ioutil.TempFile("", "test.rpm")
defer func() {
_ = f.Close()
err = os.Remove(f.Name())
assert.NoError(t, err)
}()
var info = exampleInfo()
info.Release = "3"
info.Epoch = "42"
info.RPM = nfpm.RPM{
Group: "default",
}
info.Description = "first line\nsecond line\nthird line"
require.NoError(t, Default.Package(info, f))
file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0600) //nolint:gosec
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(file)
require.NoError(t, err)
version, err := rpm.Header.GetString(rpmutils.VERSION)
require.NoError(t, err)
assert.Equal(t, "1.0.0", version)
release, err := rpm.Header.GetString(rpmutils.RELEASE)
require.NoError(t, err)
assert.Equal(t, "3", release)
epoch, err := rpm.Header.Get(rpmutils.EPOCH)
require.NoError(t, err)
epochUint32, ok := epoch.([]uint32)
assert.Len(t, epochUint32, 1)
assert.True(t, ok)
assert.Equal(t, uint32(42), epochUint32[0])
group, err := rpm.Header.GetString(rpmutils.GROUP)
require.NoError(t, err)
assert.Equal(t, "default", group)
summary, err := rpm.Header.GetString(rpmutils.SUMMARY)
require.NoError(t, err)
assert.Equal(t, "first line", summary)
description, err := rpm.Header.GetString(rpmutils.DESCRIPTION)
require.NoError(t, err)
assert.Equal(t, info.Description, description)
}
func TestRPMVersion(t *testing.T) {
info := exampleInfo()
info.Version = "1.0.0" //nolint:golint,goconst
meta, err := buildRPMMeta(info)
require.NoError(t, err)
assert.Equal(t, "1.0.0", meta.Version)
assert.Equal(t, "1", meta.Release)
}
func TestRPMVersionWithRelease(t *testing.T) {
info := exampleInfo()
info.Version = "1.0.0" //nolint:golint,goconst
info.Release = "2"
meta, err := buildRPMMeta(info)
require.NoError(t, err)
assert.Equal(t, "1.0.0", meta.Version)
assert.Equal(t, "2", meta.Release)
}
func TestRPMVersionWithPrerelease(t *testing.T) {
// https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples
info := exampleInfo()
info.Version = "1.0.0"
info.Prerelease = "rc1" // nolint:goconst
meta, err := buildRPMMeta(info)
require.NoError(t, err)
assert.Equal(t, "1.0.0~rc1", meta.Version)
assert.Equal(t, "1", meta.Release)
info.Version = "1.0.0~rc1"
info.Prerelease = ""
meta, err = buildRPMMeta(info)
require.NoError(t, err)
assert.Equal(t, "1.0.0~rc1", meta.Version)
assert.Equal(t, "1", meta.Release)
}
func TestRPMVersionWithReleaseAndPrerelease(t *testing.T) {
// https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples
info := exampleInfo()
info.Version = "1.0.0"
info.Release = "0.2"
info.Prerelease = "rc1"
meta, err := buildRPMMeta(info)
require.NoError(t, err)
assert.Equal(t, "1.0.0~rc1", meta.Version)
assert.Equal(t, "0.2", meta.Release)
info.Version = "1.0.0~rc1"
info.Release = "0.2"
info.Prerelease = ""
meta, err = buildRPMMeta(info)
require.NoError(t, err)
assert.Equal(t, "1.0.0~rc1", meta.Version)
assert.Equal(t, "0.2", meta.Release)
}
func TestRPMVersionWithVersionMetadata(t *testing.T) {
// https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples
info := exampleInfo()
info.Version = "1.0.0+meta"
info.VersionMetadata = ""
meta, err := buildRPMMeta(nfpm.WithDefaults(info))
require.NoError(t, err)
assert.Equal(t, "1.0.0+meta", meta.Version)
info.Version = "1.0.0"
info.VersionMetadata = "meta"
meta, err = buildRPMMeta(nfpm.WithDefaults(info))
require.NoError(t, err)
assert.Equal(t, "1.0.0+meta", meta.Version)
}
func TestWithInvalidEpoch(t *testing.T) {
f, err := ioutil.TempFile("", "test.rpm")
defer func() {
_ = f.Close()
err = os.Remove(f.Name())
assert.NoError(t, err)
}()
var info = exampleInfo()
info.Release = "3"
info.Epoch = "-1"
info.RPM = nfpm.RPM{
Group: "default",
}
info.Description = "first line\nsecond line\nthird line"
assert.Error(t, Default.Package(info, f))
}
func TestRPMScripts(t *testing.T) {
info := exampleInfo()
f, err := ioutil.TempFile(".", fmt.Sprintf("%s-%s-*.rpm", info.Name, info.Version))
defer func() {
_ = f.Close()
err = os.Remove(f.Name())
require.NoError(t, err)
}()
require.NoError(t, err)
err = Default.Package(info, f)
require.NoError(t, err)
file, err := os.OpenFile(f.Name(), os.O_RDONLY, 0600) //nolint:gosec
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(file)
require.NoError(t, err)
data, err := rpm.Header.GetString(rpmutils.PREIN)
require.NoError(t, err)
assert.Equal(t, `#!/bin/bash
echo "Preinstall" > /dev/null
`, data, "Preinstall script does not match")
data, err = rpm.Header.GetString(rpmutils.PREUN)
require.NoError(t, err)
assert.Equal(t, `#!/bin/bash
echo "Preremove" > /dev/null
`, data, "Preremove script does not match")
data, err = rpm.Header.GetString(rpmutils.POSTIN)
require.NoError(t, err)
assert.Equal(t, `#!/bin/bash
echo "Postinstall" > /dev/null
`, data, "Postinstall script does not match")
data, err = rpm.Header.GetString(rpmutils.POSTUN)
require.NoError(t, err)
assert.Equal(t, `#!/bin/bash
echo "Postremove" > /dev/null
`, data, "Postremove script does not match")
}
func TestRPMFileDoesNotExist(t *testing.T) {
info := exampleInfo()
info.Files = map[string]string{
"../testdata/fake": "/usr/local/bin/fake",
}
info.ConfigFiles = map[string]string{
"../testdata/whatever.confzzz": "/etc/fake/fake.conf",
}
var err = Default.Package(info, ioutil.Discard)
assert.EqualError(t, err, "matching \"../testdata/whatever.confzzz\": file does not exist")
}
func TestRPMMultiArch(t *testing.T) {
info := exampleInfo()
for k := range archToRPM {
info.Arch = k
info = ensureValidArch(info)
assert.Equal(t, archToRPM[k], info.Arch)
}
}
func TestConfigNoReplace(t *testing.T) {
var (
buildConfigFile = "../testdata/whatever.conf"
packageConfigFile = "/etc/fake/fake.conf"
)
info := &nfpm.Info{
Name: "symlink-in-files",
Arch: "amd64",
Description: "This package's config references a file via symlink.",
Version: "1.0.0",
Overridables: nfpm.Overridables{
RPM: nfpm.RPM{
ConfigNoReplaceFiles: map[string]string{
buildConfigFile: packageConfigFile,
},
},
},
}
var rpmFileBuffer bytes.Buffer
err := Default.Package(info, &rpmFileBuffer)
assert.NoError(t, err)
expectedConfigContent, err := ioutil.ReadFile(buildConfigFile)
assert.NoError(t, err)
packageConfigContent, err := extractFileFromRpm(rpmFileBuffer.Bytes(), packageConfigFile)
assert.NoError(t, err)
assert.Equal(t, expectedConfigContent, packageConfigContent)
}
func TestRPMConventionalFileName(t *testing.T) {
info := &nfpm.Info{
Name: "testpkg",
Arch: "noarch",
}
testCases := []struct {
Version string
Release string
Prerelease string
Expected string
Metadata string
}{
{Version: "1.2.3", Release: "", Prerelease: "", Metadata: "",
Expected: fmt.Sprintf("%s-1.2.3.%s.rpm", info.Name, info.Arch)},
{Version: "1.2.3", Release: "4", Prerelease: "", Metadata: "",
Expected: fmt.Sprintf("%s-1.2.3-4.%s.rpm", info.Name, info.Arch)},
{Version: "1.2.3", Release: "4", Prerelease: "5", Metadata: "",
Expected: fmt.Sprintf("%s-1.2.3~5-4.%s.rpm", info.Name, info.Arch)},
{Version: "1.2.3", Release: "", Prerelease: "5", Metadata: "",
Expected: fmt.Sprintf("%s-1.2.3~5.%s.rpm", info.Name, info.Arch)},
{Version: "1.2.3", Release: "1", Prerelease: "5", Metadata: "git",
Expected: fmt.Sprintf("%s-1.2.3~5+git-1.%s.rpm", info.Name, info.Arch)},
}
for _, testCase := range testCases {
info.Version = testCase.Version
info.Release = testCase.Release
info.Prerelease = testCase.Prerelease
info.VersionMetadata = testCase.Metadata
assert.Equal(t, testCase.Expected, Default.ConventionalFileName(info))
}
}
func TestRPMChangelog(t *testing.T) {
info := exampleInfo()
info.Changelog = "../testdata/changelog.yaml"
var rpmFileBuffer bytes.Buffer
err := Default.Package(info, &rpmFileBuffer)
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(bytes.NewReader(rpmFileBuffer.Bytes()))
require.NoError(t, err)
changelog, err := chglog.Parse(info.Changelog)
require.NoError(t, err)
_times, err := rpm.Header.Get(tagChangelogTime)
require.NoError(t, err)
times, ok := _times.([]uint32)
require.True(t, ok)
assert.Equal(t, len(changelog), len(times))
_titles, err := rpm.Header.Get(tagChangelogName)
require.NoError(t, err)
titles, ok := _titles.([]string)
require.True(t, ok)
assert.Equal(t, len(changelog), len(titles))
_notes, err := rpm.Header.Get(tagChangelogText)
require.NoError(t, err)
allNotes, ok := _notes.([]string)
require.True(t, ok)
assert.Equal(t, len(changelog), len(allNotes))
for i, entry := range changelog {
timestamp := time.Unix(int64(times[i]), 0).UTC()
title := titles[i]
notes := strings.Split(allNotes[i], "\n")
assert.Equal(t, entry.Date, timestamp)
assert.True(t, strings.Contains(title, entry.Packager))
assert.True(t, strings.Contains(title, entry.Semver))
assert.Equal(t, len(entry.Changes), len(notes))
for j, change := range entry.Changes {
assert.True(t, strings.Contains(notes[j], change.Note))
}
}
}
func TestRPMNoChangelogTagsWithoutChangelogConfigured(t *testing.T) {
info := exampleInfo()
var rpmFileBuffer bytes.Buffer
err := Default.Package(info, &rpmFileBuffer)
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(bytes.NewReader(rpmFileBuffer.Bytes()))
require.NoError(t, err)
_, err = rpm.Header.Get(tagChangelogTime)
assert.Error(t, err)
_, err = rpm.Header.Get(tagChangelogName)
assert.Error(t, err)
_, err = rpm.Header.Get(tagChangelogText)
assert.Error(t, err)
}
func TestSymlinkInFiles(t *testing.T) {
var (
symlinkTarget = "../testdata/whatever.conf"
packagedTarget = "/etc/fake/whatever.conf"
)
info := &nfpm.Info{
Name: "symlink-in-files",
Arch: "amd64",
Description: "This package's config references a file via symlink.",
Version: "1.0.0",
Overridables: nfpm.Overridables{
Files: map[string]string{
symlinkTo(t, symlinkTarget): packagedTarget,
},
},
}
realSymlinkTarget, err := ioutil.ReadFile(symlinkTarget)
require.NoError(t, err)
var rpmFileBuffer bytes.Buffer
err = Default.Package(info, &rpmFileBuffer)
require.NoError(t, err)
packagedSymlinkTarget, err := extractFileFromRpm(rpmFileBuffer.Bytes(), packagedTarget)
require.NoError(t, err)
assert.Equal(t, string(realSymlinkTarget), string(packagedSymlinkTarget))
}
func TestSymlink(t *testing.T) {
var (
configFilePath = "/usr/share/doc/fake/fake.txt"
symlink = "/path/to/symlink"
symlinkTarget = configFilePath
)
info := &nfpm.Info{
Name: "symlink-in-files",
Arch: "amd64",
Description: "This package's config references a file via symlink.",
Version: "1.0.0",
Overridables: nfpm.Overridables{
Files: map[string]string{
"../testdata/whatever.conf": configFilePath,
},
Symlinks: map[string]string{
symlink: symlinkTarget,
},
},
}
var rpmFileBuffer bytes.Buffer
err := Default.Package(info, &rpmFileBuffer)
require.NoError(t, err)
packagedSymlinkHeader, err := extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), symlink)
require.NoError(t, err)
packagedSymlink, err := extractFileFromRpm(rpmFileBuffer.Bytes(), symlink)
require.NoError(t, err)
assert.Equal(t, symlink, packagedSymlinkHeader.Filename())
assert.Equal(t, cpio.S_ISLNK, packagedSymlinkHeader.Mode())
assert.Equal(t, symlinkTarget, string(packagedSymlink))
}
func TestRPMSignature(t *testing.T) {
info := exampleInfo()
info.RPM.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
info.RPM.Signature.KeyPassphrase = "hunter2"
pubkeyFileContent, err := ioutil.ReadFile("../internal/sign/testdata/pubkey.gpg")
require.NoError(t, err)
keyring, err := openpgp.ReadKeyRing(bytes.NewReader(pubkeyFileContent))
require.NoError(t, err)
var rpmBuffer bytes.Buffer
err = Default.Package(info, &rpmBuffer)
require.NoError(t, err)
_, sigs, err := rpmutils.Verify(bytes.NewReader(rpmBuffer.Bytes()), keyring)
require.NoError(t, err)
require.Len(t, sigs, 1)
}
func TestRPMSignatureError(t *testing.T) {
info := exampleInfo()
info.RPM.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
info.RPM.Signature.KeyPassphrase = "wrongpass"
var rpmBuffer bytes.Buffer
err := Default.Package(info, &rpmBuffer)
require.Error(t, err)
var expectedError *nfpm.ErrSigningFailure
require.True(t, errors.As(err, &expectedError))
}
func TestRPMGhostFiles(t *testing.T) {
var (
filename = "/usr/lib/casper.a"
)
info := &nfpm.Info{
Name: "rpm-ghost",
Arch: "amd64",
Description: "This RPM contains ghost files.",
Version: "1.0.0",
Overridables: nfpm.Overridables{RPM: nfpm.RPM{GhostFiles: []string{filename}}},
}
var rpmFileBuffer bytes.Buffer
err := Default.Package(info, &rpmFileBuffer)
require.NoError(t, err)
headerFiles, err := extraFileInfoSliceFromRpm(rpmFileBuffer.Bytes())
require.NoError(t, err)
type headerFileInfo struct {
Name string
Size int64
Mode int
}
expected := []headerFileInfo{
{filename, 0, cpio.S_ISREG | 0644},
}
actual := make([]headerFileInfo, 0)
for _, fileInfo := range headerFiles {
actual = append(actual, headerFileInfo{fileInfo.Name(), fileInfo.Size(), fileInfo.Mode()})
}
assert.Equal(t, expected, actual)
_, err = extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), filename)
require.Error(t, err)
_, err = extractFileFromRpm(rpmFileBuffer.Bytes(), filename)
require.Error(t, err)
}
func TestDisableGlobbing(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Files = map[string]string{
"../testdata/{file}[": "/test/{file}[",
}
var rpmFileBuffer bytes.Buffer
err := Default.Package(info, &rpmFileBuffer)
require.NoError(t, err)
expectedContent, err := ioutil.ReadFile("../testdata/{file}[")
require.NoError(t, err)
actualContent, err := extractFileFromRpm(rpmFileBuffer.Bytes(), "/test/{file}[")
require.NoError(t, err)
assert.Equal(t, expectedContent, actualContent)
}
func extractFileFromRpm(rpm []byte, filename string) ([]byte, error) {
rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm))
if err != nil {
return nil, err
}
pr, err := rpmFile.PayloadReader()
if err != nil {
return nil, err
}
for {
hdr, err := pr.Next()
if errors.Is(err, io.EOF) {
break // End of archive
}
if err != nil {
return nil, err
}
if hdr.Filename() != filename {
continue
}
fileContents, err := ioutil.ReadAll(pr)
if err != nil {
return nil, err
}
return fileContents, nil
}
return nil, os.ErrNotExist
}
func extraFileInfoSliceFromRpm(rpm []byte) ([]rpmutils.FileInfo, error) {
rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm))
if err != nil {
return nil, err
}
return rpmFile.Header.GetFiles()
}
func extractFileHeaderFromRpm(rpm []byte, filename string) (*cpio.Cpio_newc_header, error) {
rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm))
if err != nil {
return nil, err
}
pr, err := rpmFile.PayloadReader()
if err != nil {
return nil, err
}
for {
hdr, err := pr.Next()
if errors.Is(err, io.EOF) {
break // End of archive
}
if err != nil {
return nil, err
}
if hdr.Filename() != filename {
continue
}
return hdr, nil
}
return nil, os.ErrNotExist
}
func symlinkTo(tb testing.TB, fileName string) string {
target, err := filepath.Abs(fileName)
assert.NoError(tb, err)
tempDir, err := ioutil.TempDir("", "nfpm_rpm_test")
assert.NoError(tb, err)
symlinkName := path.Join(tempDir, "symlink")
err = os.Symlink(target, symlinkName)
assert.NoError(tb, err)
tb.Cleanup(func() {
err = os.RemoveAll(tempDir)
assert.NoError(tb, err)
})
return symlinkName
}