Skip to content

Commit

Permalink
Merge pull request #8 from goreleaser/fix-subfolders
Browse files Browse the repository at this point in the history
fix: deb: create subfolders and use GNU tar format
  • Loading branch information
caarlos0 committed Feb 25, 2018
2 parents 3fb90ad + 24b75ec commit 3c6c7d3
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 52 deletions.
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ TEST_OPTIONS?=
setup:
go get -u github.com/alecthomas/gometalinter
go get -u github.com/golang/dep/cmd/dep
go get -u github.com/pierrre/gotestcover
go get -u golang.org/x/tools/cmd/cover
go get -u github.com/caarlos0/bandep
go get -u github.com/gobuffalo/packr/...
Expand All @@ -22,7 +21,7 @@ check:

# Run all the tests
test:
gotestcover $(TEST_OPTIONS) -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
go test $(TEST_OPTIONS) -v -coverpkg=./... -race -failfast -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
.PHONY: cover

# Run all the tests and opens the coverage report
Expand Down
148 changes: 99 additions & 49 deletions deb/deb.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"archive/tar"
"bytes"
"compress/gzip"
"path/filepath"
// #nosec
"crypto/md5"
"fmt"
Expand All @@ -31,37 +32,36 @@ type Deb struct{}

// Package writes a new deb package to the given writer using the given info
func (*Deb) Package(info nfpm.Info, deb io.Writer) (err error) {
var now = time.Now()
dataTarGz, md5sums, instSize, err := createDataTarGz(now, info)
dataTarGz, md5sums, instSize, err := createDataTarGz(info)
if err != nil {
return err
}
controlTarGz, err := createControl(now, instSize, md5sums, info)
controlTarGz, err := createControl(instSize, md5sums, info)
if err != nil {
return err
}
var w = ar.NewWriter(deb)
if err := w.WriteGlobalHeader(); err != nil {
return errors.Wrap(err, "cannot write ar header to deb file")
}
if err := addArFile(now, w, "debian-binary", []byte("2.0\n")); err != nil {
if err := addArFile(w, "debian-binary", []byte("2.0\n")); err != nil {
return errors.Wrap(err, "cannot pack debian-binary")
}
if err := addArFile(now, w, "control.tar.gz", controlTarGz); err != nil {
if err := addArFile(w, "control.tar.gz", controlTarGz); err != nil {
return errors.Wrap(err, "cannot add control.tar.gz to deb")
}
if err := addArFile(now, w, "data.tar.gz", dataTarGz); err != nil {
if err := addArFile(w, "data.tar.gz", dataTarGz); err != nil {
return errors.Wrap(err, "cannot add data.tar.gz to deb")
}
return nil
}

func addArFile(now time.Time, w *ar.Writer, name string, body []byte) error {
func addArFile(w *ar.Writer, name string, body []byte) error {
var header = ar.Header{
Name: name,
Size: int64(len(body)),
Mode: 0644,
ModTime: now,
ModTime: time.Now(),
}
if err := w.WriteHeader(&header); err != nil {
return errors.Wrap(err, "cannot write file header")
Expand All @@ -70,7 +70,7 @@ func addArFile(now time.Time, w *ar.Writer, name string, body []byte) error {
return err
}

func createDataTarGz(now time.Time, info nfpm.Info) (dataTarGz, md5sums []byte, instSize int64, err error) {
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)
Expand All @@ -80,13 +80,18 @@ func createDataTarGz(now time.Time, info nfpm.Info) (dataTarGz, md5sums []byte,
defer out.Close() // nolint: errcheck
defer compress.Close() // nolint: errcheck

var created = map[string]bool{}

var md5buf bytes.Buffer
for _, files := range []map[string]string{
info.Files,
info.ConfigFiles,
} {
for src, dst := range files {
size, err := copyToTarAndDigest(out, &md5buf, now, src, dst)
if err := createTree(out, dst, created); err != nil {
return nil, nil, 0, err
}
size, err := copyToTarAndDigest(out, &md5buf, src, dst)
if err != nil {
return nil, nil, 0, err
}
Expand All @@ -104,7 +109,7 @@ func createDataTarGz(now time.Time, info nfpm.Info) (dataTarGz, md5sums []byte,
return buf.Bytes(), md5buf.Bytes(), instSize, nil
}

func copyToTarAndDigest(tarw *tar.Writer, md5w io.Writer, now time.Time, src, dst string) (int64, error) {
func copyToTarAndDigest(tarw *tar.Writer, md5w io.Writer, 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")
Expand All @@ -116,13 +121,15 @@ func copyToTarAndDigest(tarw *tar.Writer, md5w io.Writer, now time.Time, src, ds
return 0, err
}
if info.IsDir() {
// TODO: this should probably return an error
return 0, nil
}
var header = tar.Header{
Name: dst[1:],
Size: info.Size(),
Mode: int64(info.Mode()),
ModTime: now,
ModTime: time.Now(),
Format: tar.FormatGNU,
}
if err := tarw.WriteHeader(&header); err != nil {
return 0, errors.Wrapf(err, "cannot write header of %s to data.tar.gz", header)
Expand All @@ -138,30 +145,7 @@ func copyToTarAndDigest(tarw *tar.Writer, md5w io.Writer, now time.Time, src, ds
return info.Size(), nil
}

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}}
Replaces: {{join .Info.Replaces}}
Provides: {{join .Info.Provides}}
Depends: {{join .Info.Depends}}
Recommends: {{join .Info.Recommends}}
Suggests: {{join .Info.Suggests}}
Conflicts: {{join .Info.Conflicts}}
Homepage: {{.Info.Homepage}}
Description: {{.Info.Description}}
`

type controlData struct {
Info nfpm.Info
InstalledSize int64
}

func createControl(now time.Time, instSize int64, md5sums []byte, info nfpm.Info) (controlTarGz []byte, err error) {
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)
Expand All @@ -183,7 +167,7 @@ func createControl(now time.Time, instSize int64, md5sums []byte, info nfpm.Info
"md5sums": md5sums,
"conffiles": conffiles(info),
} {
if err := newFileInsideTarGz(out, name, content, now); err != nil {
if err := newFileInsideTarGz(out, name, content); err != nil {
return nil, err
}
}
Expand All @@ -197,23 +181,14 @@ func createControl(now time.Time, instSize int64, md5sums []byte, info nfpm.Info
return buf.Bytes(), nil
}

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)
}

func newFileInsideTarGz(out *tar.Writer, name string, content []byte, now time.Time) error {
func newFileInsideTarGz(out *tar.Writer, name string, content []byte) error {
var header = tar.Header{
Name: name,
Size: int64(len(content)),
Mode: 0644,
ModTime: now,
ModTime: time.Now(),
Typeflag: tar.TypeReg,
Format: tar.FormatGNU,
}
if err := out.WriteHeader(&header); err != nil {
return errors.Wrapf(err, "cannot write header of %s file to control.tar.gz", name)
Expand All @@ -224,10 +199,85 @@ func newFileInsideTarGz(out *tar.Writer, name string, content []byte, now time.T
return nil
}

// 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: path + "/",
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,
// it looks really weird if we do.
var result = []string{}
for i := len(paths) - 1; i >= 0; i-- {
result = append(result, paths[i])
}
return result
}

func conffiles(info nfpm.Info) []byte {
var confs []string
for _, dst := range info.ConfigFiles {
confs = append(confs, dst)
}
return []byte(strings.Join(confs, "\n") + "\n")
}

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}}
Replaces: {{join .Info.Replaces}}
Provides: {{join .Info.Provides}}
Depends: {{join .Info.Depends}}
Recommends: {{join .Info.Recommends}}
Suggests: {{join .Info.Suggests}}
Conflicts: {{join .Info.Conflicts}}
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)
}
16 changes: 15 additions & 1 deletion deb/deb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package deb
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"testing"

Expand Down Expand Up @@ -41,7 +42,8 @@ var info = nfpm.WithDefaults(nfpm.Info{
Homepage: "http://carlosbecker.com",
Vendor: "nope",
Files: map[string]string{
"../testdata/fake": "/usr/local/bin/fake",
"../testdata/fake": "/usr/local/bin/fake",
"../testdata/whatever.conf": "/usr/share/doc/fake/fake.txt",
},
ConfigFiles: map[string]string{
"../testdata/whatever.conf": "/etc/fake/fake.conf",
Expand Down Expand Up @@ -129,3 +131,15 @@ func TestConffiles(t *testing.T) {
})
assert.Equal(t, "/etc/fake\n", string(out), "should have a trailing empty line")
}

func TestPathsToCreate(t *testing.T) {
for path, parts := range map[string][]string{
"/usr/share/doc/whatever/foo.md": []string{"usr", "usr/share", "usr/share/doc", "usr/share/doc/whatever"},
"/var/moises": []string{"var"},
"/": []string{},
} {
t.Run(fmt.Sprintf("path: '%s'", path), func(t *testing.T) {
assert.Equal(t, parts, pathsToCreate(path))
})
}
}

0 comments on commit 3c6c7d3

Please sign in to comment.