2018-02-03 20:42:56 +01:00
|
|
|
package rpm
|
|
|
|
|
|
|
|
import (
|
2018-02-06 03:28:30 +01:00
|
|
|
"archive/tar"
|
2018-02-05 00:00:20 +01:00
|
|
|
"bytes"
|
2018-02-06 03:28:30 +01:00
|
|
|
"compress/gzip"
|
2018-02-03 20:42:56 +01:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2018-02-05 00:00:20 +01:00
|
|
|
"os/exec"
|
2018-02-03 20:42:56 +01:00
|
|
|
"path/filepath"
|
2018-02-05 00:00:20 +01:00
|
|
|
"strings"
|
2018-02-05 03:54:03 +01:00
|
|
|
"text/template"
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2018-02-05 02:53:22 +01:00
|
|
|
"github.com/goreleaser/nfpm"
|
2018-02-12 17:09:59 +01:00
|
|
|
"github.com/pkg/errors"
|
2018-02-03 20:42:56 +01:00
|
|
|
)
|
|
|
|
|
2018-02-12 16:50:25 +01:00
|
|
|
func init() {
|
|
|
|
nfpm.Register("rpm", Default)
|
|
|
|
}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
|
|
|
// Default deb packager
|
|
|
|
var Default = &RPM{}
|
|
|
|
|
|
|
|
// RPM is a RPM packager implementation
|
|
|
|
type RPM struct{}
|
|
|
|
|
|
|
|
// Package writes a new RPM package to the given writer using the given info
|
2018-02-05 02:53:22 +01:00
|
|
|
func (*RPM) Package(info nfpm.Info, w io.Writer) error {
|
2018-02-05 00:19:00 +01:00
|
|
|
if info.Arch == "amd64" {
|
|
|
|
info.Arch = "x86_64"
|
|
|
|
}
|
2018-02-16 02:58:37 +01:00
|
|
|
temps, err := setupTempFiles(info)
|
2018-02-03 20:42:56 +01:00
|
|
|
if err != nil {
|
2018-02-16 02:58:37 +01:00
|
|
|
return err
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
2018-02-16 03:15:41 +01:00
|
|
|
if err = createTarGz(info, temps.Folder, temps.Source); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "failed to create tar.gz")
|
2018-02-03 20:42:56 +01:00
|
|
|
}
|
2018-02-16 03:15:41 +01:00
|
|
|
if err = createSpec(info, temps.Spec); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "failed to create rpm spec file")
|
2018-02-05 00:00:20 +01:00
|
|
|
}
|
|
|
|
|
2018-02-03 20:42:56 +01:00
|
|
|
var args = []string{
|
2018-02-16 02:58:37 +01:00
|
|
|
"--define", fmt.Sprintf("_topdir %s", temps.Root),
|
|
|
|
"--define", fmt.Sprintf("_tmppath %s/tmp", temps.Root),
|
2018-02-05 02:28:36 +01:00
|
|
|
"--target", fmt.Sprintf("%s-unknown-%s", info.Arch, info.Platform),
|
2018-02-05 00:00:20 +01:00
|
|
|
"-ba",
|
|
|
|
"SPECS/" + info.Name + ".spec",
|
2018-02-03 20:42:56 +01:00
|
|
|
}
|
2018-02-16 03:15:41 +01:00
|
|
|
// #nosec
|
2018-02-05 00:00:20 +01:00
|
|
|
cmd := exec.Command("rpmbuild", args...)
|
2018-02-16 02:58:37 +01:00
|
|
|
cmd.Dir = temps.Root
|
2018-02-05 00:00:20 +01:00
|
|
|
out, err := cmd.CombinedOutput()
|
2018-02-06 03:28:30 +01:00
|
|
|
if err != nil {
|
2018-02-16 23:50:13 +01:00
|
|
|
var msg = "rpmbuild failed"
|
|
|
|
if string(out) != "" {
|
|
|
|
msg += ": " + string(out)
|
|
|
|
}
|
|
|
|
return errors.Wrap(err, msg)
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
|
2018-02-16 02:58:37 +01:00
|
|
|
rpm, err := os.Open(temps.RPM)
|
2018-02-05 00:19:00 +01:00
|
|
|
if err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "failed open rpm file")
|
2018-02-05 00:19:00 +01:00
|
|
|
}
|
|
|
|
_, err = io.Copy(w, rpm)
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "failed to copy rpm file to writer")
|
2018-02-03 20:42:56 +01:00
|
|
|
}
|
|
|
|
|
2018-02-06 03:28:30 +01:00
|
|
|
func createSpec(info nfpm.Info, path string) error {
|
2018-02-18 21:13:47 +01:00
|
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0655)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to create spec")
|
|
|
|
}
|
|
|
|
return writeSpec(file, info)
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeSpec(w io.Writer, info nfpm.Info) error {
|
2018-02-06 03:28:30 +01:00
|
|
|
var tmpl = template.New("spec")
|
|
|
|
tmpl.Funcs(template.FuncMap{
|
|
|
|
"join": func(strs []string) string {
|
|
|
|
return strings.Trim(strings.Join(strs, ", "), " ")
|
|
|
|
},
|
|
|
|
"first_line": func(str string) string {
|
|
|
|
return strings.Split(str, "\n")[0]
|
|
|
|
},
|
|
|
|
})
|
2018-02-18 21:13:47 +01:00
|
|
|
if err := template.Must(tmpl.Parse(specTemplate)).Execute(w, info); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "failed to parse spec template")
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
2018-02-18 21:13:47 +01:00
|
|
|
return nil
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
|
2018-02-16 02:58:37 +01:00
|
|
|
type tempFiles struct {
|
|
|
|
// Root folder - topdir on rpm's slang
|
|
|
|
Root string
|
|
|
|
// Folder is the name of subfolders and etc, in the `name-version` format
|
|
|
|
Folder string
|
|
|
|
// Source is the path the .tar.gz file should be in
|
|
|
|
Source string
|
|
|
|
// Spec is the path the .spec file should be in
|
|
|
|
Spec string
|
|
|
|
// RPM is the path where the .rpm file should be generated
|
|
|
|
RPM string
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupTempFiles(info nfpm.Info) (tempFiles, error) {
|
|
|
|
root, err := ioutil.TempDir("", info.Name)
|
|
|
|
if err != nil {
|
|
|
|
return tempFiles{}, errors.Wrap(err, "failed to create temp dir")
|
|
|
|
}
|
|
|
|
if err := createDirs(root); err != nil {
|
|
|
|
return tempFiles{}, errors.Wrap(err, "failed to rpm dir structure")
|
|
|
|
}
|
|
|
|
folder := fmt.Sprintf("%s-%s", info.Name, info.Version)
|
|
|
|
return tempFiles{
|
|
|
|
Root: root,
|
|
|
|
Folder: folder,
|
|
|
|
Source: filepath.Join(root, "SOURCES", folder+".tar.gz"),
|
|
|
|
Spec: filepath.Join(root, "SPECS", info.Name+".spec"),
|
|
|
|
RPM: filepath.Join(root, "RPMS", info.Arch, fmt.Sprintf("%s-1.%s.rpm", folder, info.Arch)),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-02-06 03:28:30 +01:00
|
|
|
func createDirs(root string) error {
|
|
|
|
for _, folder := range []string{
|
|
|
|
"RPMS",
|
|
|
|
"SRPMS",
|
|
|
|
"BUILD",
|
|
|
|
"SOURCES",
|
|
|
|
"SPECS",
|
|
|
|
"tmp",
|
|
|
|
} {
|
|
|
|
path := filepath.Join(root, folder)
|
2018-02-16 03:15:41 +01:00
|
|
|
if err := os.Mkdir(path, 0700); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrapf(err, "failed to create %s", path)
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-16 02:58:37 +01:00
|
|
|
func createTarGz(info nfpm.Info, root, file string) error {
|
2018-02-06 03:28:30 +01:00
|
|
|
var buf bytes.Buffer
|
|
|
|
var compress = gzip.NewWriter(&buf)
|
|
|
|
var out = tar.NewWriter(compress)
|
2018-02-16 02:58:37 +01:00
|
|
|
// the writers are properly closed later, this is just in case that we have
|
|
|
|
// an error in another part of the code.
|
|
|
|
defer out.Close() // nolint: errcheck
|
|
|
|
defer compress.Close() // nolint: errcheck
|
2018-02-06 03:28:30 +01:00
|
|
|
|
|
|
|
for _, files := range []map[string]string{info.Files, info.ConfigFiles} {
|
|
|
|
for src, dst := range files {
|
2018-02-16 03:12:54 +01:00
|
|
|
if err := copyToTarGz(out, root, src, dst); err != nil {
|
|
|
|
return err
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := out.Close(); err != nil {
|
2018-02-16 02:58:37 +01:00
|
|
|
return errors.Wrap(err, "failed to close data.tar.gz writer")
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
if err := compress.Close(); err != nil {
|
2018-02-16 02:58:37 +01:00
|
|
|
return errors.Wrap(err, "failed to close data.tar.gz gzip writer")
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
2018-02-16 02:58:37 +01:00
|
|
|
if err := ioutil.WriteFile(file, buf.Bytes(), 0666); err != nil {
|
|
|
|
return errors.Wrap(err, "could not write to .tar.gz file")
|
|
|
|
}
|
|
|
|
return nil
|
2018-02-06 03:28:30 +01:00
|
|
|
}
|
|
|
|
|
2018-02-16 03:12:54 +01:00
|
|
|
func copyToTarGz(out *tar.Writer, root, src, dst string) error {
|
|
|
|
file, err := os.OpenFile(src, os.O_RDONLY, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not add file to the archive")
|
|
|
|
}
|
|
|
|
// don't really care if Close() errs
|
|
|
|
defer file.Close() // nolint: errcheck
|
|
|
|
info, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var header = tar.Header{
|
|
|
|
Name: filepath.Join(root, dst),
|
|
|
|
Size: info.Size(),
|
|
|
|
Mode: int64(info.Mode()),
|
|
|
|
ModTime: info.ModTime(),
|
|
|
|
}
|
|
|
|
if err := out.WriteHeader(&header); err != nil {
|
|
|
|
return errors.Wrapf(err, "cannot write header of %s to data.tar.gz", header.Name)
|
|
|
|
}
|
|
|
|
if _, err := io.Copy(out, file); err != nil {
|
|
|
|
return errors.Wrapf(err, "cannot write %s to data.tar.gz", header.Name)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-03 20:42:56 +01:00
|
|
|
const specTemplate = `
|
2018-02-05 02:28:36 +01:00
|
|
|
%define __spec_install_post %{nil}
|
|
|
|
%define debug_package %{nil}
|
|
|
|
%define __os_install_post %{_dbpath}/brp-compress
|
|
|
|
%define _arch {{.Arch}}
|
2018-02-12 19:29:44 +01:00
|
|
|
%define _bindir {{.Bindir}}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2018-02-05 00:00:20 +01:00
|
|
|
Name: {{ .Name }}
|
|
|
|
Summary: {{ first_line .Description }}
|
|
|
|
Version: {{ .Version }}
|
2018-02-03 20:42:56 +01:00
|
|
|
Release: 1
|
2018-02-05 00:00:20 +01:00
|
|
|
License: {{ .License }}
|
2018-02-03 20:42:56 +01:00
|
|
|
Group: Development/Tools
|
2018-02-05 00:00:20 +01:00
|
|
|
SOURCE0 : %{name}-%{version}.tar.gz
|
|
|
|
URL: {{ .Homepage }}
|
2018-02-03 20:42:56 +01:00
|
|
|
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
|
|
|
|
2018-02-05 02:42:03 +01:00
|
|
|
{{ range $index, $element := .Replaces }}
|
2018-02-18 21:13:47 +01:00
|
|
|
Obsoletes: {{ . }}
|
2018-02-05 02:42:03 +01:00
|
|
|
{{ end }}
|
|
|
|
|
|
|
|
{{ range $index, $element := .Conflicts }}
|
|
|
|
Conflicts: {{ . }}
|
|
|
|
{{ end }}
|
|
|
|
|
|
|
|
{{ range $index, $element := .Provides }}
|
|
|
|
Provides: {{ . }}
|
|
|
|
{{ end }}
|
|
|
|
|
|
|
|
{{ range $index, $element := .Depends }}
|
|
|
|
Requires: {{ . }}
|
|
|
|
{{ end }}
|
|
|
|
|
2018-02-18 21:13:47 +01:00
|
|
|
{{ range $index, $element := .Recommends }}
|
2018-02-18 21:54:07 +01:00
|
|
|
Recommends: {{ . }}
|
2018-02-18 21:13:47 +01:00
|
|
|
{{ end }}
|
|
|
|
|
2018-02-03 20:42:56 +01:00
|
|
|
%description
|
2018-02-05 00:00:20 +01:00
|
|
|
{{ .Description }}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
|
|
|
%prep
|
|
|
|
%setup -q
|
|
|
|
|
|
|
|
%build
|
|
|
|
# Empty section.
|
|
|
|
|
|
|
|
%install
|
|
|
|
rm -rf %{buildroot}
|
|
|
|
mkdir -p %{buildroot}
|
|
|
|
|
|
|
|
# in builddir
|
|
|
|
cp -a * %{buildroot}
|
|
|
|
|
|
|
|
%clean
|
|
|
|
rm -rf %{buildroot}
|
|
|
|
|
|
|
|
%files
|
|
|
|
%defattr(-,root,root,-)
|
2018-02-05 02:28:36 +01:00
|
|
|
{{ range $index, $element := .Files }}
|
|
|
|
{{ . }}
|
|
|
|
{{ end }}
|
2018-02-12 19:29:44 +01:00
|
|
|
%{_bindir}/*
|
2018-02-06 03:28:30 +01:00
|
|
|
{{ range $index, $element := .ConfigFiles }}
|
|
|
|
{{ . }}
|
|
|
|
{{ end }}
|
|
|
|
{{ range $index, $element := .ConfigFiles }}
|
2018-02-12 19:29:44 +01:00
|
|
|
%config(noreplace) {{ . }}
|
2018-02-06 03:28:30 +01:00
|
|
|
{{ end }}
|
2018-02-03 20:42:56 +01:00
|
|
|
|
2018-02-05 00:00:20 +01:00
|
|
|
%changelog
|
|
|
|
# noop
|
2018-02-03 20:42:56 +01:00
|
|
|
`
|