2018-02-05 02:53:22 +01:00
|
|
|
// Package deb implements nfpm.Packager providing .deb bindings.
|
2018-01-04 13:31:22 +01:00
|
|
|
package deb
|
|
|
|
|
|
|
|
import (
|
2018-01-10 00:03:01 +01:00
|
|
|
"archive/tar"
|
2018-01-04 13:31:22 +01:00
|
|
|
"bytes"
|
2018-01-10 00:03:01 +01:00
|
|
|
"compress/gzip"
|
2018-02-16 03:16:44 +01:00
|
|
|
// #nosec
|
2018-01-10 00:03:01 +01:00
|
|
|
"crypto/md5"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2018-01-04 13:31:22 +01:00
|
|
|
"os"
|
2018-01-10 00:03:01 +01:00
|
|
|
"strings"
|
2018-01-04 13:31:22 +01:00
|
|
|
"text/template"
|
2018-01-10 00:03:01 +01:00
|
|
|
"time"
|
2018-01-04 13:31:22 +01:00
|
|
|
|
2018-01-10 00:03:01 +01:00
|
|
|
"github.com/blakesmith/ar"
|
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-01-04 13:31:22 +01:00
|
|
|
)
|
|
|
|
|
2018-02-12 16:50:25 +01:00
|
|
|
func init() {
|
|
|
|
nfpm.Register("deb", Default)
|
|
|
|
}
|
2018-01-10 14:16:07 +01:00
|
|
|
|
|
|
|
// Default deb packager
|
|
|
|
var Default = &Deb{}
|
|
|
|
|
|
|
|
// Deb is a deb packager implementation
|
|
|
|
type Deb struct{}
|
|
|
|
|
2018-01-10 00:03:01 +01:00
|
|
|
// Package writes a new deb package to the given writer using the given info
|
2018-02-05 02:53:22 +01:00
|
|
|
func (*Deb) Package(info nfpm.Info, deb io.Writer) (err error) {
|
2018-01-10 00:03:01 +01:00
|
|
|
var now = time.Now()
|
2018-01-10 14:16:07 +01:00
|
|
|
dataTarGz, md5sums, instSize, err := createDataTarGz(now, info)
|
2018-01-04 13:31:22 +01:00
|
|
|
if err != nil {
|
2018-01-10 00:03:01 +01:00
|
|
|
return err
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
2018-01-10 00:03:01 +01:00
|
|
|
controlTarGz, err := createControl(now, instSize, md5sums, info)
|
2018-01-04 13:31:22 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-10 14:05:54 +01:00
|
|
|
var w = ar.NewWriter(deb)
|
2018-01-10 00:03:01 +01:00
|
|
|
if err := w.WriteGlobalHeader(); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "cannot write ar header to deb file")
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
2018-01-10 00:03:01 +01:00
|
|
|
if err := addArFile(now, w, "debian-binary", []byte("2.0\n")); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "cannot pack debian-binary")
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
2018-01-10 00:03:01 +01:00
|
|
|
if err := addArFile(now, w, "control.tar.gz", controlTarGz); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "cannot add control.tar.gz to deb")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
if err := addArFile(now, w, "data.tar.gz", dataTarGz); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "cannot add data.tar.gz to deb")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
return nil
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 00:03:01 +01:00
|
|
|
func addArFile(now time.Time, w *ar.Writer, name string, body []byte) error {
|
|
|
|
var header = ar.Header{
|
|
|
|
Name: name,
|
|
|
|
Size: int64(len(body)),
|
|
|
|
Mode: 0644,
|
|
|
|
ModTime: now,
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
2018-01-10 00:03:01 +01:00
|
|
|
if err := w.WriteHeader(&header); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return errors.Wrap(err, "cannot write file header")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
_, err := w.Write(body)
|
2018-01-04 13:31:22 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-02-05 02:53:22 +01:00
|
|
|
func createDataTarGz(now time.Time, info nfpm.Info) (dataTarGz, md5sums []byte, instSize int64, err error) {
|
2018-01-10 00:03:01 +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-01-10 00:03:01 +01:00
|
|
|
|
|
|
|
var md5buf bytes.Buffer
|
2018-02-16 22:11:52 +01:00
|
|
|
for _, files := range []map[string]string{
|
|
|
|
info.Files,
|
|
|
|
info.ConfigFiles,
|
|
|
|
} {
|
2018-02-06 03:28:30 +01:00
|
|
|
for src, dst := range files {
|
2018-02-16 03:12:54 +01:00
|
|
|
size, err := copyToTarAndDigest(out, &md5buf, now, src, dst)
|
2018-02-06 03:28:30 +01:00
|
|
|
if err != nil {
|
2018-02-16 03:12:54 +01:00
|
|
|
return nil, nil, 0, err
|
2018-02-16 02:58:37 +01:00
|
|
|
}
|
2018-02-16 03:12:54 +01:00
|
|
|
instSize += size
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := out.Close(); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, nil, 0, errors.Wrap(err, "closing data.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
if err := compress.Close(); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, nil, 0, errors.Wrap(err, "closing data.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return buf.Bytes(), md5buf.Bytes(), instSize, nil
|
|
|
|
}
|
|
|
|
|
2018-02-16 03:12:54 +01:00
|
|
|
func copyToTarAndDigest(tarw *tar.Writer, md5w io.Writer, now time.Time, src, dst string) (int64, error) {
|
|
|
|
file, err := os.OpenFile(src, os.O_RDONLY, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return 0, errors.Wrap(err, "could not add file to the archive")
|
|
|
|
}
|
|
|
|
// don't care if it errs while closing...
|
|
|
|
defer file.Close() // nolint: errcheck
|
|
|
|
info, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if info.IsDir() {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
var header = tar.Header{
|
|
|
|
Name: dst,
|
|
|
|
Size: info.Size(),
|
|
|
|
Mode: int64(info.Mode()),
|
|
|
|
ModTime: now,
|
|
|
|
}
|
|
|
|
if err := tarw.WriteHeader(&header); err != nil {
|
|
|
|
return 0, errors.Wrapf(err, "cannot write header of %s to data.tar.gz", header)
|
|
|
|
}
|
|
|
|
if _, err := io.Copy(tarw, file); err != nil {
|
|
|
|
return 0, errors.Wrapf(err, "cannot write %s to data.tar.gz", header)
|
|
|
|
}
|
2018-02-16 03:16:44 +01:00
|
|
|
// #nosec
|
2018-02-16 03:12:54 +01:00
|
|
|
var digest = md5.New()
|
|
|
|
if _, err := io.Copy(tarw, io.TeeReader(file, digest)); err != nil {
|
|
|
|
return 0, errors.Wrap(err, "failed to copy")
|
|
|
|
}
|
|
|
|
if _, err := fmt.Fprintf(md5w, "%x %s\n", digest.Sum(nil), header.Name[2:]); err != nil {
|
|
|
|
return 0, errors.Wrap(err, "failed to write md5")
|
|
|
|
}
|
|
|
|
return info.Size(), nil
|
|
|
|
}
|
|
|
|
|
2018-01-10 00:03:01 +01:00
|
|
|
var controlTemplate = `Package: {{.Info.Name}}
|
|
|
|
Version: {{.Info.Version}}
|
|
|
|
Section: {{.Info.Section}}
|
|
|
|
Priority: {{.Info.Priority}}
|
|
|
|
Architecture: {{.Info.Arch}}
|
|
|
|
Maintainer: {{.Info.Maintainer}}
|
|
|
|
Vendor: {{.Info.Vendor}}
|
|
|
|
Installed-Size: {{.InstalledSize}}
|
2018-02-05 02:42:03 +01:00
|
|
|
Replaces: {{join .Info.Replaces}}
|
|
|
|
Provides: {{join .Info.Provides}}
|
2018-01-10 00:03:01 +01:00
|
|
|
Depends: {{join .Info.Depends}}
|
|
|
|
Conflicts: {{join .Info.Conflicts}}
|
|
|
|
Homepage: {{.Info.Homepage}}
|
|
|
|
Description: {{.Info.Description}}
|
2018-01-04 13:31:22 +01:00
|
|
|
`
|
|
|
|
|
2018-01-10 00:03:01 +01:00
|
|
|
type controlData struct {
|
2018-02-05 02:53:22 +01:00
|
|
|
Info nfpm.Info
|
2018-01-10 00:03:01 +01:00
|
|
|
InstalledSize int64
|
|
|
|
}
|
|
|
|
|
2018-02-05 02:53:22 +01:00
|
|
|
func createControl(now time.Time, instSize int64, md5sums []byte, info nfpm.Info) (controlTarGz []byte, err error) {
|
2018-01-10 00:03:01 +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-01-10 00:03:01 +01:00
|
|
|
|
|
|
|
var body bytes.Buffer
|
|
|
|
var tmpl = template.New("control")
|
|
|
|
tmpl.Funcs(template.FuncMap{
|
|
|
|
"join": func(strs []string) string {
|
|
|
|
return strings.Trim(strings.Join(strs, ", "), " ")
|
|
|
|
},
|
2018-01-04 13:31:22 +01:00
|
|
|
})
|
2018-01-10 00:03:01 +01:00
|
|
|
if err := template.Must(tmpl.Parse(controlTemplate)).Execute(&body, controlData{
|
|
|
|
Info: info,
|
|
|
|
InstalledSize: instSize / 1024,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
2018-01-10 00:03:01 +01:00
|
|
|
var header = tar.Header{
|
|
|
|
Name: "control",
|
|
|
|
Size: int64(body.Len()),
|
|
|
|
Mode: 0644,
|
|
|
|
ModTime: now,
|
|
|
|
Typeflag: tar.TypeReg,
|
|
|
|
}
|
|
|
|
if err := out.WriteHeader(&header); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, errors.Wrap(err, "cannot write header of control file to control.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
if _, err := out.Write(body.Bytes()); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, errors.Wrap(err, "cannot write control file to control.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
header = tar.Header{
|
|
|
|
Name: "md5sums",
|
|
|
|
Size: int64(len(md5sums)),
|
|
|
|
Mode: 0644,
|
|
|
|
ModTime: now,
|
|
|
|
Typeflag: tar.TypeReg,
|
|
|
|
}
|
|
|
|
if err := out.WriteHeader(&header); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, errors.Wrap(err, "cannot write header of md5sums file to control.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
if _, err := out.Write(md5sums); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, errors.Wrap(err, "cannot write md5sums file to control.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := out.Close(); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, errors.Wrap(err, "closing control.tar.gz")
|
2018-01-10 00:03:01 +01:00
|
|
|
}
|
|
|
|
if err := compress.Close(); err != nil {
|
2018-02-12 17:09:59 +01:00
|
|
|
return nil, errors.Wrap(err, "closing control.tar.gz")
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|
2018-01-10 00:03:01 +01:00
|
|
|
return buf.Bytes(), nil
|
2018-01-04 13:31:22 +01:00
|
|
|
}
|