1
1
Fork 0
mirror of https://github.com/goreleaser/nfpm synced 2024-05-26 12:06:09 +02:00
nfpm/deb/deb.go

374 lines
9.7 KiB
Go
Raw Normal View History

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 (
"archive/tar"
2018-01-04 13:31:22 +01:00
"bytes"
"compress/gzip"
2018-06-02 20:22:21 +02:00
"crypto/md5" // nolint:gas
"fmt"
"io"
2018-06-02 20:22:21 +02:00
"io/ioutil"
2018-01-04 13:31:22 +01:00
"os"
2018-06-02 20:22:21 +02:00
"path/filepath"
"strings"
2018-01-04 13:31:22 +01:00
"text/template"
"time"
2018-01-04 13:31:22 +01:00
"github.com/blakesmith/ar"
2018-02-05 02:53:22 +01:00
"github.com/goreleaser/nfpm"
"github.com/goreleaser/nfpm/glob"
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
var goarchToDebian = map[string]string{
"386": "i386",
"arm": "armhf",
"arm6": "armel",
"arm7": "armhf",
"mipsle": "mipsel",
}
2018-01-10 14:16:07 +01:00
// Default deb packager
var Default = &Deb{}
// Deb is a deb packager implementation
type Deb struct{}
// 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) {
arch, ok := goarchToDebian[info.Arch]
if ok {
info.Arch = arch
}
dataTarGz, md5sums, instSize, err := createDataTarGz(info)
2018-01-04 13:31:22 +01:00
if err != nil {
return err
2018-01-04 13:31:22 +01:00
}
controlTarGz, err := createControl(instSize, md5sums, info)
2018-01-04 13:31:22 +01:00
if err != nil {
return err
}
var w = ar.NewWriter(deb)
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
}
if err := addArFile(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
}
if err := addArFile(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")
}
if err := addArFile(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")
}
return nil
2018-01-04 13:31:22 +01:00
}
func addArFile(w *ar.Writer, name string, body []byte) error {
var header = ar.Header{
Name: filepath.ToSlash(name),
Size: int64(len(body)),
Mode: 0644,
ModTime: time.Now(),
2018-01-04 13:31:22 +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")
}
_, err := w.Write(body)
2018-01-04 13:31:22 +01:00
return err
}
func createDataTarGz(info nfpm.Info) (dataTarGz, md5sums []byte, instSize int64, err error) {
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
var created = map[string]bool{}
2018-05-17 02:27:03 +02:00
if err = createEmptyFoldersInsideTarGz(info, out, created); err != nil {
2018-05-17 02:14:35 +02:00
return nil, nil, 0, err
}
md5buf, instSize, err := createFilesInsideTarGz(info, out, created)
if err != nil {
return nil, nil, 0, err
2018-05-17 01:59:24 +02:00
}
2018-05-17 02:14:35 +02:00
if err := out.Close(); err != nil {
return nil, nil, 0, errors.Wrap(err, "closing data.tar.gz")
}
if err := compress.Close(); err != nil {
return nil, nil, 0, errors.Wrap(err, "closing data.tar.gz")
}
return buf.Bytes(), md5buf.Bytes(), instSize, nil
}
func createFilesInsideTarGz(info nfpm.Info, out *tar.Writer, created map[string]bool) (bytes.Buffer, int64, error) {
var md5buf bytes.Buffer
2018-05-17 02:14:35 +02:00
var instSize int64
2018-02-16 22:11:52 +01:00
for _, files := range []map[string]string{
info.Files,
info.ConfigFiles,
} {
for srcglob, dstroot := range files {
globbed, err := glob.Glob(srcglob, dstroot)
2018-02-06 03:28:30 +01:00
if err != nil {
2018-05-17 02:14:35 +02:00
return md5buf, 0, err
2018-02-16 02:58:37 +01:00
}
for src, dst := range globbed {
if err := createTree(out, dst, created); err != nil {
2018-05-17 02:14:35 +02:00
return md5buf, 0, err
}
size, err := copyToTarAndDigest(out, &md5buf, src, dst)
if err != nil {
2018-05-17 02:14:35 +02:00
return md5buf, 0, err
}
instSize += size
}
}
}
2018-05-17 02:14:35 +02:00
return md5buf, instSize, nil
}
2018-05-17 02:14:35 +02:00
func createEmptyFoldersInsideTarGz(info nfpm.Info, out *tar.Writer, created map[string]bool) error {
for _, folder := range info.EmptyFolders {
// this .nope is actually not created, because createTree ignore the
// last part of the path, assuming it is a file.
// TODO: should probably refactor this
if err := createTree(out, filepath.Join(folder, ".nope"), created); err != nil {
return err
}
}
2018-05-17 02:14:35 +02:00
return nil
}
func copyToTarAndDigest(tarw *tar.Writer, md5w io.Writer, src, dst string) (int64, error) {
2018-02-16 03:12:54 +01:00
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() {
// TODO: this should probably return an error
2018-02-16 03:12:54 +01:00
return 0, nil
}
var header = tar.Header{
Name: filepath.ToSlash(dst[1:]),
2018-02-16 03:12:54 +01:00
Size: info.Size(),
Mode: int64(info.Mode()),
ModTime: time.Now(),
Format: tar.FormatGNU,
2018-02-16 03:12:54 +01:00
}
if err := tarw.WriteHeader(&header); err != nil {
return 0, errors.Wrapf(err, "cannot write header of %s to data.tar.gz", header)
}
2018-06-02 20:22:21 +02:00
var digest = md5.New() // nolint:gas
2018-02-16 03:12:54 +01:00
if _, err := io.Copy(tarw, io.TeeReader(file, digest)); err != nil {
return 0, errors.Wrap(err, "failed to copy")
}
2018-02-25 17:06:58 +01:00
if _, err := fmt.Fprintf(md5w, "%x %s\n", digest.Sum(nil), header.Name); err != nil {
2018-02-16 03:12:54 +01:00
return 0, errors.Wrap(err, "failed to write md5")
}
return info.Size(), nil
}
func createControl(instSize int64, md5sums []byte, info nfpm.Info) (controlTarGz []byte, err error) {
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
var body bytes.Buffer
2018-02-18 21:13:47 +01:00
if err := writeControl(&body, controlData{
Info: info,
InstalledSize: instSize / 1024,
}); err != nil {
return nil, err
2018-01-04 13:31:22 +01:00
}
for name, content := range map[string][]byte{
"control": body.Bytes(),
"md5sums": md5sums,
"conffiles": conffiles(info),
} {
if err := newFileInsideTarGz(out, name, content); err != nil {
return nil, err
}
}
2018-04-08 20:43:09 +02:00
for script, dest := range map[string]string{
info.Scripts.PreInstall: "preinst",
info.Scripts.PostInstall: "postinst",
info.Scripts.PreRemove: "prerm",
info.Scripts.PostRemove: "postrm",
} {
if script != "" {
if err := newScriptInsideTarGz(out, script, dest); err != nil {
return nil, err
}
}
}
if err := out.Close(); err != nil {
return nil, errors.Wrap(err, "closing control.tar.gz")
}
if err := compress.Close(); err != nil {
return nil, errors.Wrap(err, "closing control.tar.gz")
}
return buf.Bytes(), nil
}
2018-04-08 20:43:09 +02:00
func newItemInsideTarGz(out *tar.Writer, content []byte, header tar.Header) error {
if err := out.WriteHeader(&header); err != nil {
return errors.Wrapf(err, "cannot write header of %s file to control.tar.gz", header.Name)
}
if _, err := out.Write(content); err != nil {
return errors.Wrapf(err, "cannot write %s file to control.tar.gz", header.Name)
}
return nil
}
func newFileInsideTarGz(out *tar.Writer, name string, content []byte) error {
2018-04-08 20:43:09 +02:00
return newItemInsideTarGz(out, content, tar.Header{
Name: filepath.ToSlash(name),
Size: int64(len(content)),
Mode: 0644,
ModTime: time.Now(),
Typeflag: tar.TypeReg,
Format: tar.FormatGNU,
2018-04-08 20:43:09 +02:00
})
}
func newScriptInsideTarGz(out *tar.Writer, path string, dest string) error {
file, err := os.Open(path)
if err != nil {
return err
}
2018-04-08 20:43:09 +02:00
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
2018-04-08 20:43:09 +02:00
return newItemInsideTarGz(out, content, tar.Header{
Name: filepath.ToSlash(dest),
2018-04-08 20:43:09 +02:00
Size: int64(len(content)),
Mode: 0755,
2018-04-08 20:43:09 +02:00
ModTime: time.Now(),
Typeflag: tar.TypeReg,
Format: tar.FormatGNU,
})
}
2018-02-25 21:29:24 +01:00
// this is needed because the data.tar.gz file should have the empty folders
// as well, so we walk through the dst and create all subfolders.
func createTree(tarw *tar.Writer, dst string, created map[string]bool) error {
for _, path := range pathsToCreate(dst) {
if created[path] {
// skipping dir that was previously created inside the archive
// (eg: usr/)
continue
}
if err := tarw.WriteHeader(&tar.Header{
Name: filepath.ToSlash(path + "/"),
2018-02-25 21:29:24 +01:00
Mode: 0755,
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
ModTime: time.Now(),
}); err != nil {
return errors.Wrap(err, "failed to create folder")
}
created[path] = true
}
return nil
}
func pathsToCreate(dst string) []string {
var paths = []string{}
var base = dst[1:]
for {
base = filepath.Dir(base)
if base == "." {
break
}
paths = append(paths, base)
}
// we don't really need to create those things in order apparently, but,
2018-05-17 01:59:24 +02:00
// it looks really weird if we don't.
2018-02-25 21:29:24 +01:00
var result = []string{}
for i := len(paths) - 1; i >= 0; i-- {
result = append(result, paths[i])
}
return result
}
func conffiles(info nfpm.Info) []byte {
2018-07-09 04:20:18 +02:00
// nolint: preallocate
var confs []string
for _, dst := range info.ConfigFiles {
confs = append(confs, dst)
2018-01-04 13:31:22 +01:00
}
return []byte(strings.Join(confs, "\n") + "\n")
2018-01-04 13:31:22 +01:00
}
2018-02-25 21:29:24 +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}}
{{- with .Info.Replaces}}
Replaces: {{join .}}
{{- end }}
{{- with .Info.Provides}}
Provides: {{join .}}
{{- end }}
{{- with .Info.Depends}}
Depends: {{join .}}
{{- end }}
{{- with .Info.Recommends}}
Recommends: {{join .}}
{{- end }}
{{- with .Info.Suggests}}
Suggests: {{join .}}
{{- end }}
{{- with .Info.Conflicts}}
Conflicts: {{join .}}
{{- end }}
2018-02-25 21:29:24 +01:00
Homepage: {{.Info.Homepage}}
Description: {{.Info.Description}}
`
type controlData struct {
Info nfpm.Info
InstalledSize int64
}
func writeControl(w io.Writer, data controlData) error {
var tmpl = template.New("control")
tmpl.Funcs(template.FuncMap{
"join": func(strs []string) string {
return strings.Trim(strings.Join(strs, ", "), " ")
},
})
return template.Must(tmpl.Parse(controlTemplate)).Execute(w, data)
}