1
1
Fork 0
mirror of https://github.com/goreleaser/nfpm synced 2024-05-24 18:26:13 +02:00

feat: support symlinks for deb and rpm. (#185)

* feat: Support symlinks.

* fix: Fix symlink creation for deb.

* fix: Remove magic number in rpm symlink creation.

* test: Add symlink unit tests.

* test: Add symlink acceptance tests.

* doc: Add documentation for symlinks.

* fix: Fix acceptance test name.

* fix: Make symlink headers more consistent.
This commit is contained in:
Erik G 2020-07-30 04:20:50 +02:00 committed by GitHub
parent 42f29b9d7a
commit 0000a2fe8d
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 214 additions and 0 deletions

View File

@ -210,6 +210,21 @@ func TestDebTriggers(t *testing.T) {
})
}
func TestSymlink(t *testing.T) {
for _, format := range formats {
format := format
t.Run(fmt.Sprintf("symlink-%s", format), func(t *testing.T) {
t.Parallel()
accept(t, acceptParms{
Name: fmt.Sprintf("symlink_%s", format),
Conf: "symlink.yaml",
Format: format,
Dockerfile: fmt.Sprintf("%s.symlink.dockerfile", format),
})
})
}
}
type acceptParms struct {
Name string
Conf string

View File

@ -0,0 +1,6 @@
FROM ubuntu
ARG package
COPY ${package} /tmp/foo.deb
RUN dpkg -i /tmp/foo.deb
RUN ls -l /path/to/symlink | grep "/path/to/symlink -> /etc/foo/whatever.conf"
RUN dpkg -r foo

View File

@ -0,0 +1,6 @@
FROM fedora
ARG package
COPY ${package} /tmp/foo.rpm
RUN rpm -ivh /tmp/foo.rpm
RUN ls -l /path/to/symlink | grep "/path/to/symlink -> /etc/foo/whatever.conf"
RUN rpm -e foo

12
acceptance/testdata/symlink.yaml vendored Normal file
View File

@ -0,0 +1,12 @@
name: "foo"
arch: "amd64"
platform: "linux"
version: "v1.2.3"
maintainer: "Foo Bar"
vendor: "foobar"
homepage: "https://foobar.org"
license: "MIT"
files:
../testdata/whatever.conf: "/etc/foo/whatever.conf"
symlinks:
/path/to/symlink: "/etc/foo/whatever.conf"

View File

@ -154,6 +154,8 @@ files:
./bar: "/usr/local/bin/bar"
config_files:
./foobar.conf: "/etc/foobar.conf"
symlinks:
/sbin/foo: "/usr/local/bin/foo"
overrides:
rpm:
scripts:

View File

@ -131,6 +131,10 @@ func createDataTarGz(info *nfpm.Info) (dataTarGz, md5sums []byte, instSize int64
return nil, nil, 0, err
}
if err := createSymlinksInsideTarGz(info, out, created); err != nil {
return nil, nil, 0, err
}
if err := out.Close(); err != nil {
return nil, nil, 0, errors.Wrap(err, "closing data.tar.gz")
}
@ -141,6 +145,27 @@ func createDataTarGz(info *nfpm.Info) (dataTarGz, md5sums []byte, instSize int64
return buf.Bytes(), md5buf.Bytes(), instSize, nil
}
func createSymlinksInsideTarGz(info *nfpm.Info, out *tar.Writer, created map[string]bool) error {
for src, dst := range info.Symlinks {
if err := createTree(out, src, created); err != nil {
return err
}
err := newItemInsideTarGz(out, []byte{}, &tar.Header{
Name: src,
Linkname: dst,
Typeflag: tar.TypeSymlink,
ModTime: time.Now(),
Format: tar.FormatGNU,
})
if err != nil {
return err
}
}
return nil
}
func createFilesInsideTarGz(info *nfpm.Info, out *tar.Writer, created map[string]bool) (bytes.Buffer, int64, error) {
var md5buf bytes.Buffer
var instSize int64

View File

@ -591,6 +591,39 @@ func TestSymlinkInFiles(t *testing.T) {
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,
},
},
}
dataTarGz, _, _, err := createDataTarGz(info)
assert.NoError(t, err)
packagedSymlinkHeader, err := extractFileHeaderFromTarGz(dataTarGz, symlink)
assert.NoError(t, err)
assert.Equal(t, symlink, packagedSymlinkHeader.Name)
assert.Equal(t, uint8(tar.TypeSymlink), packagedSymlinkHeader.Typeflag)
assert.Equal(t, symlinkTarget, packagedSymlinkHeader.Linkname)
}
func extractFileFromTarGz(tarGzFile []byte, filename string) ([]byte, error) {
tarFile, err := gzipInflate(tarGzFile)
if err != nil {
@ -622,6 +655,32 @@ func extractFileFromTarGz(tarGzFile []byte, filename string) ([]byte, error) {
return nil, os.ErrNotExist
}
func extractFileHeaderFromTarGz(tarGzFile []byte, filename string) (*tar.Header, error) {
tarFile, err := gzipInflate(tarGzFile)
if err != nil {
return nil, err
}
tr := tar.NewReader(bytes.NewReader(tarFile))
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
return nil, err
}
if path.Join("/", hdr.Name) != path.Join("/", filename) {
continue
}
return hdr, nil
}
return nil, os.ErrNotExist
}
func gzipInflate(data []byte) ([]byte, error) {
gzr, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {

View File

@ -153,6 +153,7 @@ type Overridables struct {
Conflicts []string `yaml:"conflicts,omitempty"`
Files map[string]string `yaml:"files,omitempty"`
ConfigFiles map[string]string `yaml:"config_files,omitempty"`
Symlinks map[string]string `yaml:"symlinks,omitempty"`
EmptyFolders []string `yaml:"empty_folders,omitempty"`
Scripts Scripts `yaml:"scripts,omitempty"`
RPM RPM `yaml:"rpm,omitempty"`

View File

@ -14,6 +14,7 @@ import (
"github.com/google/rpmpack"
"github.com/pkg/errors"
"github.com/sassoftware/go-rpmutils/cpio"
"github.com/goreleaser/chglog"
@ -103,6 +104,8 @@ func (*RPM) Package(info *nfpm.Info, w io.Writer) error {
return err
}
addSymlinksInsideRPM(info, rpm)
if err = addScriptFiles(info, rpm); err != nil {
return err
}
@ -315,6 +318,19 @@ func createFilesInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) error {
return nil
}
func addSymlinksInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) {
for src, dst := range info.Symlinks {
rpm.AddFile(rpmpack.RPMFile{
Name: src,
Body: []byte(dst),
Mode: uint(cpio.S_ISLNK),
MTime: uint32(time.Now().UTC().Unix()),
Owner: "root",
Group: "root",
})
}
}
func copyToRPM(rpm *rpmpack.RPM, src, dst string, config bool) error {
file, err := os.OpenFile(src, os.O_RDONLY, 0600) //nolint:gosec
if err != nil {

View File

@ -13,6 +13,7 @@ import (
"time"
"github.com/sassoftware/go-rpmutils"
"github.com/sassoftware/go-rpmutils/cpio"
"github.com/stretchr/testify/assert"
"github.com/goreleaser/chglog"
@ -435,6 +436,43 @@ func TestSymlinkInFiles(t *testing.T) {
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)
assert.NoError(t, err)
packagedSymlinkHeader, err := extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), symlink)
assert.NoError(t, err)
packagedSymlink, err := extractFileFromRpm(rpmFileBuffer.Bytes(), symlink)
assert.NoError(t, err)
assert.Equal(t, symlink, packagedSymlinkHeader.Filename())
assert.Equal(t, cpio.S_ISLNK, packagedSymlinkHeader.Mode())
assert.Equal(t, symlinkTarget, string(packagedSymlink))
}
func extractFileFromRpm(rpm []byte, filename string) ([]byte, error) {
rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm))
if err != nil {
@ -470,6 +508,36 @@ func extractFileFromRpm(rpm []byte, filename string) ([]byte, error) {
return nil, os.ErrNotExist
}
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 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)

View File

@ -97,6 +97,10 @@ config_files:
empty_folders:
- /var/log/foo
# Symlinks mapping from symlink name inside package to target inside package (overridable)
symlinks:
/sbin/foo: /usr/local/bin/foo
# Scripts to run at specific stages. (overridable)
scripts:
preinstall: ./scripts/preinstall.sh