2019-10-09 21:30:17 +02:00
|
|
|
// Package rpm implements nfpm.Packager providing .rpm bindings using
|
|
|
|
// google/rpmpack.
|
2018-02-03 20:42:56 +01:00
|
|
|
package rpm
|
|
|
|
|
|
|
|
import (
|
2020-07-13 17:10:03 +02:00
|
|
|
"bytes"
|
2019-11-11 22:57:10 +01:00
|
|
|
"fmt"
|
2018-02-03 20:42:56 +01:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2019-12-31 15:55:19 +01:00
|
|
|
"strconv"
|
2019-11-05 03:56:18 +01:00
|
|
|
"strings"
|
2019-10-30 21:56:34 +01:00
|
|
|
"time"
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2019-09-10 20:01:43 +02:00
|
|
|
"github.com/google/rpmpack"
|
2020-07-30 04:20:50 +02:00
|
|
|
"github.com/sassoftware/go-rpmutils/cpio"
|
2019-10-11 22:11:28 +02:00
|
|
|
|
2020-12-23 14:25:57 +01:00
|
|
|
"github.com/goreleaser/nfpm/v2/files"
|
|
|
|
"github.com/goreleaser/nfpm/v2/internal/sign"
|
2020-08-04 15:11:03 +02:00
|
|
|
|
2020-07-13 17:10:03 +02:00
|
|
|
"github.com/goreleaser/chglog"
|
|
|
|
|
2020-12-23 14:25:57 +01:00
|
|
|
"github.com/goreleaser/nfpm/v2"
|
2018-02-03 20:42:56 +01:00
|
|
|
)
|
|
|
|
|
2020-07-13 17:10:03 +02:00
|
|
|
const (
|
|
|
|
// https://github.com/rpm-software-management/rpm/blob/master/lib/rpmtag.h#L152
|
|
|
|
tagChangelogTime = 1080
|
|
|
|
// https://github.com/rpm-software-management/rpm/blob/master/lib/rpmtag.h#L153
|
|
|
|
tagChangelogName = 1081
|
|
|
|
// https://github.com/rpm-software-management/rpm/blob/master/lib/rpmtag.h#L154
|
|
|
|
tagChangelogText = 1082
|
|
|
|
|
|
|
|
changelogNotesTemplate = `
|
2020-07-15 14:21:52 +02:00
|
|
|
{{- range .Changes }}{{$note := splitList "\n" .Note}}
|
|
|
|
- {{ first $note }}
|
|
|
|
{{- range $i,$n := (rest $note) }}{{- if ne (trim $n) ""}}
|
|
|
|
{{$n}}{{end}}
|
|
|
|
{{- end}}{{- end}}`
|
2020-07-13 17:10:03 +02:00
|
|
|
)
|
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
const packagerName = "rpm"
|
|
|
|
|
2019-03-04 14:14:05 +01:00
|
|
|
// nolint: gochecknoinits
|
2018-02-12 16:50:25 +01:00
|
|
|
func init() {
|
2020-12-15 17:47:00 +01:00
|
|
|
nfpm.RegisterPackager(packagerName, Default)
|
2018-02-12 16:50:25 +01:00
|
|
|
}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2020-11-27 03:17:14 +01:00
|
|
|
// Default RPM packager.
|
2019-03-04 14:14:05 +01:00
|
|
|
// nolint: gochecknoglobals
|
2018-02-03 20:42:56 +01:00
|
|
|
var Default = &RPM{}
|
|
|
|
|
2020-05-13 21:24:06 +02:00
|
|
|
// RPM is a RPM packager implementation.
|
2018-02-03 20:42:56 +01:00
|
|
|
type RPM struct{}
|
|
|
|
|
2019-03-20 01:48:14 +01:00
|
|
|
// nolint: gochecknoglobals
|
2019-03-20 01:34:06 +01:00
|
|
|
var archToRPM = map[string]string{
|
2020-07-09 14:49:20 +02:00
|
|
|
"all": "noarch",
|
2019-03-20 01:34:06 +01:00
|
|
|
"amd64": "x86_64",
|
|
|
|
"386": "i386",
|
2019-06-28 19:14:45 +02:00
|
|
|
"arm64": "aarch64",
|
2019-03-20 00:47:02 +01:00
|
|
|
}
|
|
|
|
|
2019-10-11 22:11:28 +02:00
|
|
|
func ensureValidArch(info *nfpm.Info) *nfpm.Info {
|
2019-03-20 01:34:06 +01:00
|
|
|
arch, ok := archToRPM[info.Arch]
|
|
|
|
if ok {
|
|
|
|
info.Arch = arch
|
|
|
|
}
|
2019-06-28 19:14:45 +02:00
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
2020-07-09 15:16:04 +02:00
|
|
|
// ConventionalFileName returns a file name according
|
|
|
|
// to the conventions for RPM packages. See:
|
|
|
|
// http://ftp.rpm.org/max-rpm/ch-rpm-file-format.html
|
|
|
|
func (*RPM) ConventionalFileName(info *nfpm.Info) string {
|
|
|
|
info = ensureValidArch(info)
|
2020-07-15 15:10:29 +02:00
|
|
|
|
2020-08-19 15:20:02 +02:00
|
|
|
version := formatVersion(info)
|
2020-07-15 15:10:29 +02:00
|
|
|
if info.Release != "" {
|
|
|
|
version += "-" + info.Release
|
|
|
|
}
|
|
|
|
|
2020-07-09 15:16:04 +02:00
|
|
|
// name-version-release.architecture.rpm
|
2020-07-15 15:10:29 +02:00
|
|
|
return fmt.Sprintf("%s-%s.%s.rpm", info.Name, version, info.Arch)
|
2020-07-09 15:16:04 +02:00
|
|
|
}
|
|
|
|
|
2020-05-13 21:24:06 +02:00
|
|
|
// Package writes a new RPM package to the given writer using the given info.
|
2020-12-15 17:47:00 +01:00
|
|
|
func (*RPM) Package(info *nfpm.Info, w io.Writer) (err error) {
|
2019-10-09 21:10:05 +02:00
|
|
|
var (
|
|
|
|
meta *rpmpack.RPMMetaData
|
|
|
|
rpm *rpmpack.RPM
|
|
|
|
)
|
2019-06-28 19:14:45 +02:00
|
|
|
info = ensureValidArch(info)
|
2020-12-15 17:47:00 +01:00
|
|
|
if err = info.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2019-10-09 21:10:05 +02:00
|
|
|
if meta, err = buildRPMMeta(info); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if rpm, err = rpmpack.NewRPM(*meta); err != nil {
|
2019-09-10 20:01:43 +02:00
|
|
|
return err
|
2018-02-18 22:41:32 +01:00
|
|
|
}
|
2018-03-06 01:02:26 +01:00
|
|
|
|
2020-09-17 14:18:44 +02:00
|
|
|
if info.RPM.Signature.KeyFile != "" {
|
|
|
|
rpm.SetPGPSigner(sign.PGPSigner(info.RPM.Signature.KeyFile, info.RPM.Signature.KeyPassphrase))
|
|
|
|
}
|
|
|
|
|
2019-10-09 21:10:05 +02:00
|
|
|
addEmptyDirsRPM(info, rpm)
|
2019-09-10 20:01:43 +02:00
|
|
|
if err = createFilesInsideRPM(info, rpm); err != nil {
|
|
|
|
return err
|
2018-02-18 22:41:32 +01:00
|
|
|
}
|
|
|
|
|
2019-09-10 20:01:43 +02:00
|
|
|
if err = addScriptFiles(info, rpm); err != nil {
|
2018-02-18 22:41:32 +01:00
|
|
|
return err
|
|
|
|
}
|
2018-04-08 23:37:25 +02:00
|
|
|
|
2020-07-13 17:10:03 +02:00
|
|
|
if info.Changelog != "" {
|
|
|
|
if err = addChangeLog(info, rpm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-10 20:01:43 +02:00
|
|
|
if err = rpm.Write(w); err != nil {
|
2018-04-08 23:37:25 +02:00
|
|
|
return err
|
2018-02-18 22:41:32 +01:00
|
|
|
}
|
2019-09-10 20:01:43 +02:00
|
|
|
|
2018-02-18 21:13:47 +01:00
|
|
|
return nil
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
|
2020-07-13 17:10:03 +02:00
|
|
|
func addChangeLog(info *nfpm.Info, rpm *rpmpack.RPM) error {
|
|
|
|
changelog, err := info.GetChangeLog()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("reading changelog: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(changelog.Entries) == 0 {
|
|
|
|
// no nothing because creating empty tags
|
|
|
|
// would result in an invalid package
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tpl, err := chglog.LoadTemplateData(changelogNotesTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parsing RPM changelog template: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
changes := make([]string, len(changelog.Entries))
|
|
|
|
titles := make([]string, len(changelog.Entries))
|
|
|
|
times := make([]uint32, len(changelog.Entries))
|
|
|
|
for idx, entry := range changelog.Entries {
|
|
|
|
var formattedNotes bytes.Buffer
|
|
|
|
|
|
|
|
err := tpl.Execute(&formattedNotes, entry)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("formatting changlog notes: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
changes[idx] = strings.TrimSpace(formattedNotes.String())
|
|
|
|
times[idx] = uint32(entry.Date.Unix())
|
|
|
|
titles[idx] = fmt.Sprintf("%s - %s", entry.Packager, entry.Semver)
|
|
|
|
}
|
|
|
|
|
|
|
|
rpm.AddCustomTag(tagChangelogTime, rpmpack.EntryUint32(times))
|
|
|
|
rpm.AddCustomTag(tagChangelogName, rpmpack.EntryStringSlice(titles))
|
|
|
|
rpm.AddCustomTag(tagChangelogText, rpmpack.EntryStringSlice(changes))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-01 22:18:18 +02:00
|
|
|
//nolint:funlen
|
2019-10-11 22:11:28 +02:00
|
|
|
func buildRPMMeta(info *nfpm.Info) (*rpmpack.RPMMetaData, error) {
|
2019-10-09 21:10:05 +02:00
|
|
|
var (
|
2019-12-31 15:55:19 +01:00
|
|
|
err error
|
|
|
|
epoch uint64
|
2019-10-09 21:10:05 +02:00
|
|
|
provides,
|
|
|
|
depends,
|
2020-07-01 22:18:18 +02:00
|
|
|
recommends,
|
2019-10-09 21:10:05 +02:00
|
|
|
replaces,
|
|
|
|
suggests,
|
|
|
|
conflicts rpmpack.Relations
|
|
|
|
)
|
2019-12-31 15:55:19 +01:00
|
|
|
if epoch, err = strconv.ParseUint(defaultTo(info.Epoch, "0"), 10, 32); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-10-09 21:10:05 +02:00
|
|
|
if provides, err = toRelation(info.Provides); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if depends, err = toRelation(info.Depends); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-01 22:18:18 +02:00
|
|
|
if recommends, err = toRelation(info.Recommends); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-10-09 21:10:05 +02:00
|
|
|
if replaces, err = toRelation(info.Replaces); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if suggests, err = toRelation(info.Suggests); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if conflicts, err = toRelation(info.Conflicts); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-24 15:24:43 +01:00
|
|
|
hostname, err := os.Hostname()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-09 21:10:05 +02:00
|
|
|
return &rpmpack.RPMMetaData{
|
|
|
|
Name: info.Name,
|
2020-11-08 18:49:40 +01:00
|
|
|
Summary: defaultTo(info.RPM.Summary, strings.Split(info.Description, "\n")[0]),
|
2019-10-09 21:10:05 +02:00
|
|
|
Description: info.Description,
|
2020-08-19 15:20:02 +02:00
|
|
|
Version: formatVersion(info),
|
|
|
|
Release: defaultTo(info.Release, "1"),
|
2019-12-31 15:55:19 +01:00
|
|
|
Epoch: uint32(epoch),
|
2019-10-09 21:10:05 +02:00
|
|
|
Arch: info.Arch,
|
|
|
|
OS: info.Platform,
|
|
|
|
Licence: info.License,
|
|
|
|
URL: info.Homepage,
|
|
|
|
Vendor: info.Vendor,
|
|
|
|
Packager: info.Maintainer,
|
2020-08-04 20:41:54 +02:00
|
|
|
Group: info.RPM.Group,
|
2019-10-09 21:10:05 +02:00
|
|
|
Provides: provides,
|
2020-07-01 22:18:18 +02:00
|
|
|
Recommends: recommends,
|
2019-10-09 21:10:05 +02:00
|
|
|
Requires: depends,
|
|
|
|
Obsoletes: replaces,
|
|
|
|
Suggests: suggests,
|
|
|
|
Conflicts: conflicts,
|
|
|
|
Compressor: info.RPM.Compression,
|
2019-11-24 15:24:43 +01:00
|
|
|
BuildTime: time.Now(),
|
|
|
|
BuildHost: hostname,
|
2019-10-09 21:10:05 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-08-19 15:20:02 +02:00
|
|
|
func formatVersion(info *nfpm.Info) string {
|
2020-08-20 06:00:17 +02:00
|
|
|
version := info.Version
|
|
|
|
|
|
|
|
if info.Prerelease != "" {
|
|
|
|
version += "~" + info.Prerelease
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.VersionMetadata != "" {
|
|
|
|
version += "+" + info.VersionMetadata
|
2020-02-18 14:41:15 +01:00
|
|
|
}
|
2020-08-19 15:20:02 +02:00
|
|
|
|
2020-08-20 06:00:17 +02:00
|
|
|
return version
|
2020-02-18 14:41:15 +01:00
|
|
|
}
|
|
|
|
|
2019-10-22 05:55:29 +02:00
|
|
|
func defaultTo(in, def string) string {
|
|
|
|
if in == "" {
|
|
|
|
return def
|
|
|
|
}
|
|
|
|
return in
|
|
|
|
}
|
|
|
|
|
2019-10-09 21:10:05 +02:00
|
|
|
func toRelation(items []string) (rpmpack.Relations, error) {
|
|
|
|
relations := make(rpmpack.Relations, 0)
|
|
|
|
for idx := range items {
|
|
|
|
if err := relations.Set(items[idx]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return relations, nil
|
|
|
|
}
|
|
|
|
|
2019-10-11 22:11:28 +02:00
|
|
|
func addScriptFiles(info *nfpm.Info, rpm *rpmpack.RPM) error {
|
2019-09-10 20:01:43 +02:00
|
|
|
if info.Scripts.PreInstall != "" {
|
|
|
|
data, err := ioutil.ReadFile(info.Scripts.PreInstall)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-04-08 23:37:25 +02:00
|
|
|
}
|
2019-09-10 20:01:43 +02:00
|
|
|
rpm.AddPrein(string(data))
|
2018-04-08 23:37:25 +02:00
|
|
|
}
|
|
|
|
|
2019-09-10 20:01:43 +02:00
|
|
|
if info.Scripts.PreRemove != "" {
|
|
|
|
data, err := ioutil.ReadFile(info.Scripts.PreRemove)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rpm.AddPreun(string(data))
|
2018-02-16 02:58:37 +01:00
|
|
|
}
|
2019-09-10 20:01:43 +02:00
|
|
|
|
|
|
|
if info.Scripts.PostInstall != "" {
|
|
|
|
data, err := ioutil.ReadFile(info.Scripts.PostInstall)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rpm.AddPostin(string(data))
|
2018-02-16 02:58:37 +01:00
|
|
|
}
|
|
|
|
|
2019-09-10 20:01:43 +02:00
|
|
|
if info.Scripts.PostRemove != "" {
|
2019-10-09 21:10:05 +02:00
|
|
|
data, err := ioutil.ReadFile(info.Scripts.PostRemove)
|
2019-09-10 20:01:43 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
2019-09-10 20:01:43 +02:00
|
|
|
rpm.AddPostun(string(data))
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
2019-09-10 20:01:43 +02:00
|
|
|
|
2018-02-06 03:28:30 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-11 22:11:28 +02:00
|
|
|
func addEmptyDirsRPM(info *nfpm.Info, rpm *rpmpack.RPM) {
|
2019-10-09 21:10:05 +02:00
|
|
|
for _, dir := range info.EmptyFolders {
|
|
|
|
rpm.AddFile(
|
|
|
|
rpmpack.RPMFile{
|
2019-10-30 21:56:34 +01:00
|
|
|
Name: dir,
|
|
|
|
Mode: uint(040755),
|
|
|
|
MTime: uint32(time.Now().Unix()),
|
2020-01-15 20:23:05 +01:00
|
|
|
Owner: "root",
|
|
|
|
Group: "root",
|
2019-10-22 05:56:11 +02:00
|
|
|
},
|
|
|
|
)
|
2019-10-09 21:10:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
func createFilesInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) (err error) {
|
|
|
|
var symlinks files.Contents
|
|
|
|
for _, file := range info.Contents {
|
|
|
|
if file.Packager != "" && file.Packager != packagerName {
|
|
|
|
continue
|
2020-08-04 19:44:13 +02:00
|
|
|
}
|
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
var rpmFileType rpmpack.FileType
|
|
|
|
switch file.Type {
|
|
|
|
case "config":
|
|
|
|
rpmFileType = rpmpack.ConfigFile
|
|
|
|
case "config|noreplace":
|
|
|
|
rpmFileType = rpmpack.ConfigFile | rpmpack.NoReplaceFile
|
|
|
|
case "ghost":
|
|
|
|
rpmFileType = rpmpack.GhostFile
|
|
|
|
if file.FileInfo.Mode == 0 {
|
|
|
|
file.FileInfo.Mode = os.FileMode(0644)
|
|
|
|
}
|
|
|
|
case "doc":
|
|
|
|
rpmFileType = rpmpack.DocFile
|
|
|
|
case "licence", "license":
|
|
|
|
rpmFileType = rpmpack.LicenceFile
|
|
|
|
case "readme":
|
|
|
|
rpmFileType = rpmpack.ReadmeFile
|
|
|
|
case "symlink":
|
|
|
|
symlinks = append(symlinks, file)
|
|
|
|
continue
|
|
|
|
default:
|
|
|
|
rpmFileType = rpmpack.GenericFile
|
2020-08-04 19:44:13 +02:00
|
|
|
}
|
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
if err = copyToRPM(rpm, file, rpmFileType); err != nil {
|
2020-05-14 14:46:46 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
2020-08-04 19:44:13 +02:00
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
addSymlinksInsideRPM(symlinks, rpm)
|
2020-11-08 21:10:27 +01:00
|
|
|
|
2018-02-16 02:58:37 +01:00
|
|
|
return nil
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
func addSymlinksInsideRPM(symlinks files.Contents, rpm *rpmpack.RPM) {
|
|
|
|
for _, file := range symlinks {
|
2020-07-30 04:20:50 +02:00
|
|
|
rpm.AddFile(rpmpack.RPMFile{
|
2020-12-15 17:47:00 +01:00
|
|
|
Name: file.Destination,
|
|
|
|
Body: []byte(file.Source),
|
2020-07-30 04:20:50 +02:00
|
|
|
Mode: uint(cpio.S_ISLNK),
|
2020-12-15 17:47:00 +01:00
|
|
|
MTime: uint32(file.FileInfo.MTime.Unix()),
|
|
|
|
Owner: file.FileInfo.Owner,
|
|
|
|
Group: file.FileInfo.Group,
|
2020-07-30 04:20:50 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-15 17:47:00 +01:00
|
|
|
func copyToRPM(rpm *rpmpack.RPM, file *files.Content, fileType rpmpack.FileType) (err error) {
|
|
|
|
data, err := ioutil.ReadFile(file.Source)
|
|
|
|
if err != nil && file.Type != "ghost" {
|
2019-09-10 20:01:43 +02:00
|
|
|
return err
|
2018-02-16 03:12:54 +01:00
|
|
|
}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2020-11-09 04:54:37 +01:00
|
|
|
rpm.AddFile(rpmpack.RPMFile{
|
2020-12-15 17:47:00 +01:00
|
|
|
Name: file.Destination,
|
2019-10-30 21:56:34 +01:00
|
|
|
Body: data,
|
2020-12-15 17:47:00 +01:00
|
|
|
Mode: uint(file.FileInfo.Mode),
|
|
|
|
MTime: uint32(file.FileInfo.MTime.Unix()),
|
|
|
|
Owner: file.FileInfo.Owner,
|
|
|
|
Group: file.FileInfo.Group,
|
2020-08-04 19:44:13 +02:00
|
|
|
Type: fileType,
|
2020-11-09 04:54:37 +01:00
|
|
|
})
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2019-09-10 20:01:43 +02:00
|
|
|
return nil
|
|
|
|
}
|