Repository: goreleaser/nfpm
Pull request #245 (2020-11-07 21:04:38)

feat: Support RPM ghost files
From the [Maximum RPM] guide:

    The %ghost Directive

    As we mentioned in the Section called The %files List, if a file
    is specified in the %files list, that file will automatically be
    included in the package. There are times when a file should be
    owned by the package but not installed - log files and state files
    are good examples of cases you might desire this to happen.

    The way to achieve this, is to use the %ghost directive. By adding
    this directive to the line containing a file, RPM will know about
    the ghosted file, but will not add it to the package.

Ghost files are specified using `Type: rpmpack.GhostFile` when
constructing the output package.

The test ensures that we have:
 - The target file present in the RPM.
 - The specified mode attributes (although not really important,
 either).
 - An empty file *[1]* in the RPM for the named ghost, 
see [google/rpmpack #51].

*[1] Instead of an empty file, no file should be created, but this is
not possible until the upstream issue is resolved.*

[Maximum RPM]:
http://ftp.rpm.org/max-rpm/s1-rpm-inside-files-list-directives.html

[google/rpmpack #51]: https://github.com/google/rpmpack/issues/51

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/goreleaser/nfpm/p64tjvsfn](https://vercel.com/goreleaser/nfpm/p64tjvsfn)  
✅ Preview: *In Progress*


codecov[bot]: # [Codecov](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=h1) Report
> Merging [#245](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=desc) (650d7fc) into [master](https://codecov.io/gh/goreleaser/nfpm/commit/a2ffae7a5dd9ad38ee4ed6fcb13bbf96fd9fd56d?el=desc) (a2ffae7) will **increase** coverage by `0.25%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/nfpm/pull/245/graphs/tree.svg?width=650&height=150&src=pr&token=9QKGIxn7wg)](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #245      +/-   ##
==========================================
+ Coverage   66.86%   67.11%   +0.25%     
==========================================
  Files           9        9              
  Lines        1023     1031       +8     
==========================================
+ Hits          684      692       +8     
  Misses        204      204              
  Partials      135      135              
```


| [Impacted Files](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [nfpm.go](https://codecov.io/gh/goreleaser/nfpm/pull/245/diff?src=pr&el=tree#diff-bmZwbS5nbw==) | `78.04% <ø> (ø)` | |
| [rpm/rpm.go](https://codecov.io/gh/goreleaser/nfpm/pull/245/diff?src=pr&el=tree#diff-cnBtL3JwbS5nbw==) | `72.94% <100.00%> (+1.08%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=footer). Last update [a2ffae7...650d7fc](https://codecov.io/gh/goreleaser/nfpm/pull/245?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/go.mod b/go.mod
index 0e82df4..66e14bc 100644
--- a/go.mod
+++ b/go.mod
@@ -11,8 +11,8 @@ require (
 	github.com/golangci/golangci-lint v1.31.0
 	github.com/google/rpmpack v0.0.0-20200919095143-1c1eea455332
 	github.com/goreleaser/chglog v0.1.1
+	github.com/goreleaser/fileglob v0.2.1
 	github.com/imdario/mergo v0.3.11
-	github.com/mattn/go-zglob v0.0.4-0.20201013150311-602f75124917
 	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
 	github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b
 	github.com/stretchr/objx v0.2.0 // indirect
diff --git a/go.sum b/go.sum
index 581f4a9..db7c5cb 100644
--- a/go.sum
+++ b/go.sum
@@ -203,6 +203,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/goreleaser/chglog v0.1.1 h1:UaY3enMEVeavOaZyCraLn+2iM7/2T0yji8Mh7ZFsDp4=
 github.com/goreleaser/chglog v0.1.1/go.mod h1:xSDa/73C0TxBcLvoT2JHh47QyXpCx5rrNVzJKyeFGPw=
+github.com/goreleaser/fileglob v0.2.1 h1:tnKYHrWm00afuZpyEKGzgHWCaEExeT7jiwdVOTbKNXk=
+github.com/goreleaser/fileglob v0.2.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
@@ -293,8 +295,6 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
 github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/mattn/go-zglob v0.0.4-0.20201013150311-602f75124917 h1:dYf27OGvVV8wGxrOg8lxJ+gE2IGWD7s6J3qvrze8w/k=
-github.com/mattn/go-zglob v0.0.4-0.20201013150311-602f75124917/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -408,6 +408,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU=
 github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
+github.com/spf13/afero v1.4.1 h1:asw9sl74539yqavKaglDM5hFpdJVK0Y5Dr/JOgQ89nQ=
+github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
diff --git a/internal/glob/glob.go b/internal/glob/glob.go
index 29834fe..f107e0e 100644
--- a/internal/glob/glob.go
+++ b/internal/glob/glob.go
@@ -7,7 +7,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/mattn/go-zglob"
+	"github.com/goreleaser/fileglob"
 )
 
 // longestCommonPrefix returns the longest prefix of all strings the argument
@@ -44,24 +44,25 @@ type ErrGlobNoMatch struct {
 }
 
 func (e ErrGlobNoMatch) Error() string {
-	return fmt.Sprintf("%s: no matching files", e.glob)
+	return fmt.Sprintf("glob failed: %s: no matching files", e.glob)
 }
 
 // Glob returns a map with source file path as keys and destination as values.
 // First the longest common prefix (lcp) of all globbed files is found. The destination
 // for each globbed file is then dst joined with src with the lcp trimmed off.
-func Glob(glob, dst string) (map[string]string, error) {
-	matches, err := zglob.Glob(glob)
+func Glob(pattern, dst string) (map[string]string, error) {
+	matches, err := fileglob.Glob(pattern)
 	if err != nil {
-		return nil, fmt.Errorf("glob failed: %s: %w", glob, err)
+		return nil, fmt.Errorf("glob failed: %s: %w", pattern, err)
 	}
+
 	if len(matches) == 0 {
-		return nil, ErrGlobNoMatch{glob}
+		return nil, ErrGlobNoMatch{pattern}
 	}
 	files := make(map[string]string)
 	prefix := longestCommonPrefix(matches)
 	// the prefix may not be a complete path or may use glob patterns, in that case use the parent directory
-	if _, err := os.Stat(prefix); os.IsNotExist(err) || strings.ContainsAny(glob, "*") {
+	if _, err := os.Stat(prefix); os.IsNotExist(err) || strings.ContainsAny(pattern, "*") {
 		prefix = filepath.Dir(prefix)
 	}
 	for _, src := range matches {
diff --git a/nfpm.go b/nfpm.go
index 3dc3557..05bbb43 100644
--- a/nfpm.go
+++ b/nfpm.go
@@ -182,6 +182,7 @@ type RPM struct {
 	// https://www.cl.cam.ac.uk/~jw35/docs/rpm_config.html
 	ConfigNoReplaceFiles map[string]string `yaml:"config_noreplace_files,omitempty"`
 	Signature            RPMSignature      `yaml:"signature,omitempty"`
+	GhostFiles           []string          `yaml:"ghost_files,omitempty"`
 }
 
 type RPMSignature struct {
diff --git a/rpm/rpm.go b/rpm/rpm.go
index a56d8f4..5438efe 100644
--- a/rpm/rpm.go
+++ b/rpm/rpm.go
@@ -350,6 +350,19 @@ func createFilesInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) error {
 		}
 	}
 
+	// note: the ghost files will be created as empty files when the package is installed, which is not
+	// correct: https://github.com/google/rpmpack/issues/51
+	for _, destName := range info.RPM.GhostFiles {
+		rpm.AddFile(rpmpack.RPMFile{
+			Name:  destName,
+			Mode:  0644,
+			MTime: uint32(time.Now().UTC().Unix()),
+			Owner: "root",
+			Group: "root",
+			Type:  rpmpack.GhostFile,
+		})
+	}
+
 	return nil
 }
 
diff --git a/www/docs/configuration.md b/www/docs/configuration.md
index 3590656..69dcaa1 100644
--- a/www/docs/configuration.md
+++ b/www/docs/configuration.md
@@ -131,7 +131,7 @@ overrides:
   apk:
     # ...
 
-# Custon configuration applied only to the RPM packager.
+# Custom configuration applied only to the RPM packager.
 rpm:
   # The package group. This option is deprecated by most distros
   # but required by old distros like CentOS 5 / EL 5 and earlier.
@@ -149,6 +149,20 @@ rpm:
   config_noreplace_files:
     path/to/local/bar.con: /etc/bar.conf
 
+  # These files are not actually present in the package, but the file names
+  # are added to the package header. From the RPM directives documentation:
+  #
+  # "There are times when a file should be owned by the package but not
+  # installed - log files and state files are good examples of cases you might
+  # desire this to happen."
+  #
+  # "The way to achieve this, is to use the %ghost directive. By adding this
+  # directive to the line containing a file, RPM will know about the ghosted
+  # file, but will not add it to the package."
+  ghost_files:
+    - /etc/casper.conf
+    - /var/log/boo.log
+
   # The package is signed if a key_file is set
   signature:
     # PGP secret key (can also be ASCII-armored), the passphrase is taken


Test patch:
diff --git a/acceptance/testdata/complex.386.yaml b/acceptance/testdata/complex.386.yaml
index 50f8cfd..d6dc028 100644
--- a/acceptance/testdata/complex.386.yaml
+++ b/acceptance/testdata/complex.386.yaml
@@ -19,7 +19,7 @@ homepage: "https://foobar.org"
 license: "MIT"
 files:
   ../testdata/fake: "/usr/local/bin/fake"
-  ./testdata/folder/**/*: "/usr/share/whatever/folder/"
+  ./testdata/folder/*: "/usr/share/whatever/folder/"
 config_files:
   ../testdata/whatever.conf: "/etc/foo/whatever.conf"
 empty_folders:
diff --git a/acceptance/testdata/complex.yaml b/acceptance/testdata/complex.yaml
index 850508c..8659bde 100644
--- a/acceptance/testdata/complex.yaml
+++ b/acceptance/testdata/complex.yaml
@@ -21,7 +21,7 @@ homepage: "https://foobar.org"
 license: "MIT"
 files:
   ../testdata/fake: "/usr/local/bin/fake"
-  ./testdata/folder/**/*: "/usr/share/whatever/folder/"
+  ./testdata/folder/*: "/usr/share/whatever/folder/"
 config_files:
   ../testdata/whatever.conf: "/etc/foo/whatever.conf"
 empty_folders:
diff --git a/acceptance/testdata/overrides.yaml b/acceptance/testdata/overrides.yaml
index a0f1f3c..c519a51 100644
--- a/acceptance/testdata/overrides.yaml
+++ b/acceptance/testdata/overrides.yaml
@@ -19,7 +19,7 @@ homepage: "https://foobar.org"
 license: "MIT"
 files:
   ../testdata/fake: "/usr/local/bin/fake"
-  ./testdata/folder/**/*: "/usr/share/whatever/folder/"
+  ./testdata/folder/*: "/usr/share/whatever/folder/"
 config_files:
   ../testdata/whatever.conf: "/etc/foo/whatever.conf"
 overrides:
diff --git a/apk/apk_test.go b/apk/apk_test.go
index 8a6684e..86a0257 100644
--- a/apk/apk_test.go
+++ b/apk/apk_test.go
@@ -168,7 +168,7 @@ func TestFileDoesNotExist(t *testing.T) {
 					"bash",
 				},
 				Files: map[string]string{
-					"../testdata/": "/usr/local/bin/fake",
+					"../testdata/fake": "/usr/local/bin/fake",
 				},
 				ConfigFiles: map[string]string{
 					"../testdata/whatever.confzzz": "/etc/fake/fake.conf",
@@ -177,7 +177,7 @@ func TestFileDoesNotExist(t *testing.T) {
 		}),
 		ioutil.Discard,
 	)
-	assert.EqualError(t, err, "glob failed: ../testdata/whatever.confzzz: file does not exist")
+	assert.EqualError(t, err, "glob failed: ../testdata/whatever.confzzz: no matching files")
 }
 
 func TestNoFiles(t *testing.T) {
diff --git a/deb/deb_test.go b/deb/deb_test.go
index 2995497..1d5fc0d 100644
--- a/deb/deb_test.go
+++ b/deb/deb_test.go
@@ -269,7 +269,7 @@ func TestDebFileDoesNotExist(t *testing.T) {
 					"bash",
 				},
 				Files: map[string]string{
-					"../testdata/": "/usr/local/bin/fake",
+					"../testdata/fake": "/usr/local/bin/fake",
 				},
 				ConfigFiles: map[string]string{
 					"../testdata/whatever.confzzz": "/etc/fake/fake.conf",
@@ -278,7 +278,7 @@ func TestDebFileDoesNotExist(t *testing.T) {
 		}),
 		ioutil.Discard,
 	)
-	assert.EqualError(t, err, "glob failed: ../testdata/whatever.confzzz: file does not exist")
+	assert.EqualError(t, err, "glob failed: ../testdata/whatever.confzzz: no matching files")
 }
 
 func TestDebNoFiles(t *testing.T) {
diff --git a/internal/files/files_test.go b/internal/files/files_test.go
index 4879c90..4161d40 100644
--- a/internal/files/files_test.go
+++ b/internal/files/files_test.go
@@ -15,7 +15,7 @@ func TestListFilesToCopy(t *testing.T) {
 				"../../testdata/whatever.conf": "/whatever",
 			},
 			Files: map[string]string{
-				"../../testdata/scripts/**/*": "/test",
+				"../../testdata/scripts/*": "/test",
 			},
 		},
 	}
diff --git a/internal/glob/glob_test.go b/internal/glob/glob_test.go
index 714dc6f..1c2c910 100644
--- a/internal/glob/glob_test.go
+++ b/internal/glob/glob_test.go
@@ -3,7 +3,7 @@ package glob
 import (
 	"testing"
 
-	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestLongestCommonPrefix(t *testing.T) {
@@ -17,11 +17,11 @@ func TestLongestCommonPrefix(t *testing.T) {
 	}
 
 	lcp1 := longestCommonPrefix(strings)
-	assert.Equal(t, "long", lcp1)
+	require.Equal(t, "long", lcp1)
 
 	empty := []string{}
 	lcp2 := longestCommonPrefix(empty)
-	assert.Equal(t, "", lcp2)
+	require.Equal(t, "", lcp2)
 
 	unique := []string{
 		"every",
@@ -34,38 +34,55 @@ func TestLongestCommonPrefix(t *testing.T) {
 	}
 
 	lcp3 := longestCommonPrefix(unique)
-	assert.Equal(t, "", lcp3)
+	require.Equal(t, "", lcp3)
 }
 
 func TestGlob(t *testing.T) {
-	files, err := Glob("testdata/dir_a/dir_*/*", "/foo/bar")
-	assert.NoError(t, err)
-	assert.Len(t, files, 2)
-	assert.Equal(t, "/foo/bar/dir_b/test_b.txt", files["testdata/dir_a/dir_b/test_b.txt"])
-	assert.Equal(t, "/foo/bar/dir_c/test_c.txt", files["testdata/dir_a/dir_c/test_c.txt"])
+	t.Run("simple", func(t *testing.T) {
+		files, err := Glob("./testdata/dir_a/dir_*/*", "/foo/bar")
+		require.NoError(t, err)
+		require.Len(t, files, 2)
+		require.Equal(t, "/foo/bar/dir_b/test_b.txt", files["testdata/dir_a/dir_b/test_b.txt"])
+		require.Equal(t, "/foo/bar/dir_c/test_c.txt", files["testdata/dir_a/dir_c/test_c.txt"])
+	})
 
-	singleFile, err := Glob("testdata/dir_a/dir_b/*", "/foo/bar")
-	assert.NoError(t, err)
-	assert.Len(t, singleFile, 1)
-	assert.Equal(t, "/foo/bar/test_b.txt", singleFile["testdata/dir_a/dir_b/test_b.txt"])
+	t.Run("single file", func(t *testing.T) {
+		files, err := Glob("testdata/dir_a/dir_b/*", "/foo/bar")
+		require.NoError(t, err)
+		require.Len(t, files, 1)
+		require.Equal(t, "/foo/bar/test_b.txt", files["testdata/dir_a/dir_b/test_b.txt"])
+	})
 
-	nilvalue, err := Glob("does/not/exist", "/foo/bar")
-	assert.EqualError(t, err, "glob failed: does/not/exist: file does not exist")
-	assert.Nil(t, nilvalue)
+	t.Run("double star", func(t *testing.T) {
+		files, err := Glob("testdata/**/test*.txt", "/foo/bar")
+		require.NoError(t, err)
+		require.Len(t, files, 3)
+		require.Equal(t, "/foo/bar/dir_a/dir_b/test_b.txt", files["testdata/dir_a/dir_b/test_b.txt"])
+	})
 
-	nomatches, err := Glob("testdata/nothing*", "/foo/bar")
-	assert.Nil(t, nomatches)
-	assert.EqualError(t, err, "testdata/nothing*: no matching files")
+	t.Run("nil value", func(t *testing.T) {
+		files, err := Glob("does/not/exist", "/foo/bar")
+		require.EqualError(t, err, "glob failed: does/not/exist: no matching files")
+		require.Nil(t, files)
+	})
 
-	escapedBrace, err := Glob("testdata/\\{dir_d\\}/*", "/foo/bar")
-	assert.NoError(t, err)
-	assert.Len(t, escapedBrace, 1)
-	assert.Equal(t, "/foo/bar/test_brace.txt", escapedBrace["testdata/{dir_d}/test_brace.txt"])
-}
+	t.Run("no matches", func(t *testing.T) {
+		files, err := Glob("testdata/nothing*", "/foo/bar")
+		require.Nil(t, files)
+		require.EqualError(t, err, "glob failed: testdata/nothing*: no matching files")
+	})
+
+	t.Run("escaped brace", func(t *testing.T) {
+		files, err := Glob("testdata/\\{dir_d\\}/*", "/foo/bar")
+		require.NoError(t, err)
+		require.Len(t, files, 1)
+		require.Equal(t, "/foo/bar/test_brace.txt", files["testdata/{dir_d}/test_brace.txt"])
+	})
 
-func TestSingleGlob(t *testing.T) {
-	files, err := Glob("testdata/dir_a/dir_b/test_b.txt", "/foo/bar/dest.dat")
-	assert.NoError(t, err)
-	assert.Len(t, files, 1)
-	assert.Equal(t, "/foo/bar/dest.dat", files["testdata/dir_a/dir_b/test_b.txt"])
+	t.Run("no glob", func(t *testing.T) {
+		files, err := Glob("testdata/dir_a/dir_b/test_b.txt", "/foo/bar/dest.dat")
+		require.NoError(t, err)
+		require.Len(t, files, 1)
+		require.Equal(t, "/foo/bar/dest.dat", files["testdata/dir_a/dir_b/test_b.txt"])
+	})
 }
diff --git a/rpm/rpm_test.go b/rpm/rpm_test.go
index 94a17d8..b65dc52 100644
--- a/rpm/rpm_test.go
+++ b/rpm/rpm_test.go
@@ -359,13 +359,13 @@ echo "Postremove" > /dev/null
 func TestRPMFileDoesNotExist(t *testing.T) {
 	info := exampleInfo()
 	info.Files = map[string]string{
-		"../testdata/": "/usr/local/bin/fake",
+		"../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, "glob failed: ../testdata/whatever.confzzz: file does not exist")
+	assert.EqualError(t, err, "glob failed: ../testdata/whatever.confzzz: no matching files")
 }
 
 func TestRPMMultiArch(t *testing.T) {
@@ -615,6 +615,34 @@ func TestRPMSignatureError(t *testing.T) {
 	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)
+
+	packagedFileHeader, err := extractFileHeaderFromRpm(rpmFileBuffer.Bytes(), filename)
+	require.NoError(t, err)
+
+	packagedFile, err := extractFileFromRpm(rpmFileBuffer.Bytes(), filename)
+	require.NoError(t, err)
+
+	assert.Equal(t, filename, packagedFileHeader.Filename())
+	assert.Equal(t, cpio.S_ISREG|0644, packagedFileHeader.Mode())
+	assert.Equal(t, "", string(packagedFile))
+}
+
 func extractFileFromRpm(rpm []byte, filename string) ([]byte, error) {
 	rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm))
 	if err != nil {