From 02bd32057775a14d89eba4b52e0dd5244201de02 Mon Sep 17 00:00:00 2001 From: Jerry Jacobs Date: Thu, 10 Aug 2017 23:27:26 +0200 Subject: [PATCH 1/2] Initial work on control file extra --- .travis.yml | 6 +- Makefile | 15 ++-- control.go | 24 +++--- control_test.go | 55 +++++++++++++- data.go | 2 +- debpkg.go | 55 +++++++++++--- debpkg_test.go | 115 +++++++++++++++++++++++------ digest_test.go | 33 ++------- errors.go | 27 +++++++ {lib => internal}/targzip/targz.go | 6 +- internal/test/test.go | 53 +++++++++++++ 11 files changed, 306 insertions(+), 85 deletions(-) create mode 100644 errors.go rename {lib => internal}/targzip/targz.go (94%) create mode 100644 internal/test/test.go diff --git a/.travis.yml b/.travis.yml index 774a20f..ff9e7fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ notifications: language: go go: - - 1.6 - - 1.7 + # TODO: We only test for go 1.8 or later because testing.Name() is not supported. + # There must be a way to check if non-test builds work for go before 1.8 - 1.8 - tip @@ -18,5 +18,5 @@ before_install: - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi script: - - make test + - make ci - $GOPATH/bin/goveralls -service=travis-ci diff --git a/Makefile b/Makefile index 715fd3e..5b5bd01 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ export GOBIN?=$(DESTDIR) all: build ci: test +dep: + go get -u ./ + build: go build go install github.com/xor-gate/debpkg/cmd/debpkg @@ -13,13 +16,11 @@ test: go test -v lint: - go tool vet . - -fmt: - gofmt -s -w . + go get -u github.com/golang/lint/golint + golint ./... | grep -v '^vendor\/' | grep -v ".pb.*.go:" || true clean: - rm -Rf *.deb - rm -Rf *.tar.gz + rm -Rf $(TMPDIR)/debpkg* -.PHONY: clean +fmt: + gofmt -s -w . diff --git a/control.go b/control.go index 0690044..7dff333 100644 --- a/control.go +++ b/control.go @@ -5,18 +5,19 @@ package debpkg import ( + "io" "fmt" "math" "strings" - "github.com/xor-gate/debpkg/lib/targzip" + "github.com/xor-gate/debpkg/internal/targzip" ) type control struct { tgz *targzip.TarGzip info controlInfo - extra []string // Extra files added to the control.tar.gz. Typical usage is for conffiles, postinst, postrm, prerm. - conffiles []string // Conffiles which must be treated as configuration files + extra map[string][]byte // Extra files added to the control.tar.gz. Typical usage is for preinst, postinst, prerm, postrm. + conffiles map[string][]byte // Conffiles which must be treated as configuration files } type controlInfoVersion struct { @@ -190,16 +191,21 @@ func (deb *DebPkg) SetBuiltUsing(info string) { deb.control.info.builtUsing = info } +// AddControlExtraString is the same as AddControlExtra except it uses a string input +func (deb *DebPkg) AddControlExtraString(name, s string) error { + return deb.control.tgz.AddFileFromBuffer(name, []byte(s)); +} + // AddControlExtra allows the advanced user to add custom script to the control.tar.gz Typical usage is -// for conffiles, postinst, postrm, prerm: https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html +// for preinst, postinst, postrm, prerm: https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html // And: https://www.debian.org/doc/manuals/maint-guide/dother.en.html#maintscripts -func (deb *DebPkg) AddControlExtra(filename string) { - deb.control.extra = append(deb.control.extra, filename) +func (deb *DebPkg) AddControlExtra(name, filename string) error { + return deb.control.tgz.AddFile(filename, name); } -// AddConffile adds a file to the conffiles so it is treated as configuration files. Configuration files are not overwritten during an update unless specified. -func (deb *DebPkg) AddConffile(filename string) { - deb.control.conffiles = append(deb.control.conffiles, filename) +// AddConffile adds a file to the conffiles so it is treated as configuration files. Configuration files are not +// overwritten during an update unless specified. +func (deb *DebPkg) AddConffile(filename string, r io.Reader) { } // verify the control file for validity diff --git a/control_test.go b/control_test.go index 30744fa..9207f80 100644 --- a/control_test.go +++ b/control_test.go @@ -5,7 +5,10 @@ package debpkg import ( + "os" + "io/ioutil" "testing" + "github.com/xor-gate/debpkg/internal/test" "github.com/stretchr/testify/assert" ) @@ -133,7 +136,6 @@ Description: Golang package for creating (gpg signed) debian packages deb.SetHomepage("https://github.com/xor-gate/debpkg") deb.SetShortDescription("Golang package for creating (gpg signed) debian packages") deb.SetDescription(controlDescr) - // architecture is auto-set when empty, this makes sure it is always set to amd64 deb.SetArchitecture("amd64") assert.Equal(t, controlExpect, deb.control.String(0)) @@ -169,3 +171,54 @@ Description: assert.Equal(t, controlExpect2K, deb.control.String(1025)) assert.Equal(t, controlExpect2K, deb.control.String(2048)) } + +func TestControlFileExtraString(t *testing.T) { + deb := New() + defer deb.Close() + + deb.SetName("debpkg-control-file-extra-string") + deb.SetArchitecture("all") + deb.SetDescription("bla bla\n") + + // BUG SetDescription should add the newline itself, and must not be left empty + // dpkg: error processing archive /tmp/TestControlFileExtra.deb (--install): + // parsing file '/var/lib/dpkg/tmp.ci/control' near line 6 package 'debpkg-control-file-extra:any': + // end of file during value of field 'Description' (missing final newline) + + deb.AddControlExtraString("preinst", `#!/bin/sh + echo "preinst: hello world from debpkg!"`) + deb.AddControlExtraString("postinst", `#!/bin/sh + echo "postinst: hello world from debpkg!"`) + deb.AddControlExtraString("prerm", `#!/bin/sh + echo "prerm: hello world from debpkg!"`) + deb.AddControlExtraString("postrm", `#!/bin/sh + echo "postrm: hello world from debpkg!"`) + + assert.Nil(t, testWrite(t, deb)) +} + +func TestControlFileExtra(t *testing.T) { + deb := New() + defer deb.Close() + + const script = `#!/bin/sh +echo "hello world from debpkg" +` + filename := test.TempDir() + string(os.PathSeparator) + t.Name() + ".sh" + assert.Nil(t, ioutil.WriteFile(filename, []byte(script), 0644)) + + deb.SetName("debpkg-control-file-extra") + deb.SetArchitecture("all") + deb.SetDescription("bla bla\n") + + // BUG SetDescription should add the newline itself, and must not be left empty + // dpkg: error processing archive /tmp/TestControlFileExtra.deb (--install): + // parsing file '/var/lib/dpkg/tmp.ci/control' near line 6 package 'debpkg-control-file-extra:any': + // end of file during value of field 'Description' (missing final newline) + + deb.AddControlExtra("preinst", filename) + deb.AddControlExtra("postinst", filename) + deb.AddControlExtraString("prerm", filename) + deb.AddControlExtraString("postrm", filename) + assert.Nil(t, testWrite(t, deb)) +} diff --git a/data.go b/data.go index 96a7d36..8c3e75c 100644 --- a/data.go +++ b/data.go @@ -11,7 +11,7 @@ import ( "os" "strings" - "github.com/xor-gate/debpkg/lib/targzip" + "github.com/xor-gate/debpkg/internal/targzip" ) type data struct { diff --git a/debpkg.go b/debpkg.go index 04d0e11..485125e 100644 --- a/debpkg.go +++ b/debpkg.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/xor-gate/debpkg/lib/targzip" + "github.com/xor-gate/debpkg/internal/targzip" ) // DebPkg holds data for a single debian package @@ -19,6 +19,7 @@ type DebPkg struct { control control data data digest digest + err error } var debpkgTempDir = os.TempDir() // default temporary directory is os.TempDir @@ -27,14 +28,13 @@ var debpkgTempDir = os.TempDir() // default temporary directory is os.TempDir // exist it is automaticly created (but not removed). func SetTempDir(dir string) error { if dir == "" { - debpkgTempDir = os.TempDir() + dir = os.TempDir() } - finfo, err := os.Stat(dir) - if os.IsExist(err) && finfo.IsDir() { + stat, err := os.Stat(dir) + if err == nil && stat.IsDir() { + debpkgTempDir = dir return nil - } else if !finfo.IsDir() { - return fmt.Errorf("not a directory") } if err := os.MkdirAll(dir, 0700); err != nil { @@ -45,6 +45,15 @@ func SetTempDir(dir string) error { return nil } +// RemoveTempDir removes the temporary directory recursive. This is safe against +// when TempDir() is set to os.TempDir() then it does nothing +func RemoveTempDir() error { + if TempDir() == os.TempDir() { + return nil + } + return os.RemoveAll(TempDir()) +} + // TempDir returns the directory to use for temporary files. func TempDir() string { return debpkgTempDir @@ -64,9 +73,14 @@ func New() *DebPkg { return deb } +// Close closes the File (and removes the intermediate files), rendering it unusable for I/O. It returns an error, if any. func (deb *DebPkg) Close() error { + if deb.err == ErrClosed { + return deb.err + } deb.control.tgz.Remove() deb.data.tgz.Remove() + deb.err = ErrClosed // FIXME make deb.SetError work... return nil } @@ -93,13 +107,20 @@ func (deb *DebPkg) writeControlData() error { // Write the debian package to the filename func (deb *DebPkg) Write(filename string) error { + if deb.err != nil { + return deb.err + } if err := deb.writeControlData(); err != nil { + deb.setError(err) return err } if filename == "" { filename = deb.GetFilename() } - return deb.createDebAr(filename) + err := deb.createDebAr(filename) + deb.setError(err) + deb.Close() + return err } // GetFilename calculates the filename based on name, version and architecture @@ -117,16 +138,26 @@ func (deb *DebPkg) GetFilename() string { // AddFile adds a file by filename to the package func (deb *DebPkg) AddFile(filename string, dest ...string) error { - return deb.data.addFile(filename, dest...) + if deb.err != nil { + return deb.err + } + return deb.setError(deb.data.addFile(filename, dest...)) } // AddEmptyDirectory adds a empty directory to the package func (deb *DebPkg) AddEmptyDirectory(dir string) error { - return deb.data.addEmptyDirectory(dir) + if deb.err != nil { + return deb.err + } + return deb.setError(deb.data.addEmptyDirectory(dir)) } // AddDirectory adds a directory recursive to the package func (deb *DebPkg) AddDirectory(dir string) error { + if deb.err != nil { + return deb.err + } + deb.data.addDirectory(dir) return filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { @@ -138,16 +169,16 @@ func (deb *DebPkg) AddDirectory(dir string) error { } if f.IsDir() { if err := deb.data.addDirectory(path); err != nil { - return err + return deb.setError(err) } return deb.AddDirectory(path) } - return deb.AddFile(path) + return deb.setError(deb.AddFile(path)) }) } -// GetArchitecture gets the current local CPU architecture in debian-form +// GetArchitecture gets the current build.Default.GOARCH in debian-form func GetArchitecture() string { arch := build.Default.GOARCH if arch == "386" { diff --git a/debpkg_test.go b/debpkg_test.go index 352ada7..cda4ffa 100644 --- a/debpkg_test.go +++ b/debpkg_test.go @@ -6,13 +6,75 @@ package debpkg import ( "fmt" + "go/build" "os/exec" - "path/filepath" "testing" "os" + "github.com/xor-gate/debpkg/internal/test" "github.com/stretchr/testify/assert" ) +// testWrite writes the deb package to a temporary file +func testWrite(t *testing.T, deb *DebPkg) error { + f := test.TempFile(t) + err := deb.Write(f) + if err == nil { + testReadWithNativeDpkg(t, f) + } + return err +} + +// testReadWithNativeDpkg tests a single debian package with the dpkg tool when present +func testReadWithNativeDpkg(t *testing.T, filename string) { + dpkgCmd, err := exec.LookPath("dpkg") + if err != nil || dpkgCmd == "" { + return + } + + dpkg := func(action, filename string) error { + return exec.Command(dpkgCmd, "--"+action, filename).Run() + } + + // TODO test dry-run install... + assert.Nil(t, dpkg("info", filename)) + assert.Nil(t, dpkg("contents", filename)) +} + +// TestTempDir verifies the correct working of TempDir and SetTempDir +func TestTempDir(t *testing.T) { + dirExists := func(path string) bool { + if stat, err := os.Stat(path); err == nil && stat.IsDir() { + return true + } + return false + } + + // Default the TempDir points to os.TempDir() + assert.Equal(t, os.TempDir(), TempDir()) + + // Unset debpkgTempDir and verify it is set to os.TempDir() when SetTempDir received a empty string + debpkgTempDir = "" + assert.Nil(t, SetTempDir("")) + assert.Equal(t, os.TempDir(), TempDir()) + + // Check if custom test tempdir is created + tempdir := os.TempDir() + "/debpkg-test-tempdir" + + assert.Nil(t, SetTempDir(tempdir)) + assert.True(t, dirExists(tempdir)) + assert.Nil(t, RemoveTempDir()) + assert.False(t, dirExists(tempdir)) + assert.Nil(t, SetTempDir("")) + + // Check if TempDir() == os.TempDir() is not removed and RemoveTempDir() returns nil on os.TempDir() + assert.True(t, dirExists(TempDir())) + assert.Nil(t, RemoveTempDir()) + assert.True(t, dirExists(TempDir())) + + // Restore to os.TempDir() + assert.Nil(t, SetTempDir("")) +} + // TestDirectory verifies adding a single directory recursive to the package func TestAddDirectory(t *testing.T) { deb := New() @@ -21,9 +83,10 @@ func TestAddDirectory(t *testing.T) { deb.SetArchitecture("all") assert.Nil(t, deb.AddDirectory("vendor")) - assert.Nil(t, deb.Write(os.TempDir() + "/debpkg-test-add-directory.deb")) + assert.Nil(t, testWrite(t, deb)) } +// TestWrite verifies Write works as expected with adding just one datafile func TestWrite(t *testing.T) { deb := New() defer deb.Close() @@ -43,19 +106,26 @@ func TestWrite(t *testing.T) { deb.AddFile("debpkg.go") - assert.Nil(t, deb.Write(os.TempDir() + "/debpkg-test.deb")) + assert.Nil(t, testWrite(t, deb)) + + // Try to Write again on implicit closed package + assert.Equal(t, ErrClosed, testWrite(t, deb)) } +// TestWriteError tests if the Write fails with the correct errors func TestWriteError(t *testing.T) { deb := New() defer deb.Close() assert.NotNil(t, deb.Write(""), "deb.Write should return nil") deb.control.info.name = "pkg" - assert.Equal(t, fmt.Errorf("empty architecture"), deb.Write("")) + assert.Equal(t, fmt.Errorf("empty package name"), deb.Write("")) } +// ExampleWrite demonstrates generating a simple package func ExampleWrite() { + tempfile := TempDir() + "/foobar.deb" + deb := New() defer deb.Close() @@ -70,29 +140,12 @@ func ExampleWrite() { deb.SetDescription("Foo bar package doesn't do anything") deb.AddFile("debpkg.go") - fmt.Println(deb.Write(os.TempDir() + "/foobar.deb")) - - // Output: -} - -func dpkg(cmd, action, filename string) error { - return exec.Command(cmd, "--"+action, filename).Run() -} + fmt.Println(deb.Write(tempfile)) -func TestReadWithNativeDpkg(t *testing.T) { - dpkgCmd, err := exec.LookPath("dpkg") - if err != nil || dpkgCmd == "" { - return - } - - debs, err := filepath.Glob("*.deb") - assert.Nil(t, err) - for _, deb := range debs { - assert.Nil(t, dpkg(dpkgCmd, "info", deb)) - assert.Nil(t, dpkg(dpkgCmd, "contents", deb)) - } + // Do something with tempfile other than removing it... } +// TestFilenameFromFullVersion verifies if the whole version string is correctly concatinated func TestFilenameFromFullVersion(t *testing.T) { deb := New() defer deb.Close() @@ -103,3 +156,17 @@ func TestFilenameFromFullVersion(t *testing.T) { assert.Equal(t, "foo-1.33.7_amd64.deb", deb.GetFilename()) } + +// TestGetArchitecture checks the current build.Default.GOARCH compatible debian architecture +func TestGetArchitecture(t *testing.T) { + // On debian 386 GOARCH is presented as i386 + goarch := build.Default.GOARCH + build.Default.GOARCH = "386" + assert.Equal(t, "i386", GetArchitecture()) + build.Default.GOARCH = goarch + + // Check current build GOARCH + if build.Default.GOARCH != "386" { + assert.Equal(t, build.Default.GOARCH, GetArchitecture()) + } +} diff --git a/digest_test.go b/digest_test.go index 9922ebe..b4c517c 100644 --- a/digest_test.go +++ b/digest_test.go @@ -5,40 +5,19 @@ package debpkg import ( - "fmt" - "os" "testing" - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/armor" - + "github.com/xor-gate/debpkg/internal/test" "github.com/stretchr/testify/assert" ) var e *openpgp.Entity func init() { - // Create random new GPG identity for signage - e, _ = openpgp.NewEntity("Debpkg Authors", "", "debpkg-authors@xor-gate.org", nil) - - // Sign all the identities - for _, id := range e.Identities { - err := id.SelfSignature.SignUserId(id.UserId.Id, e.PrimaryKey, e.PrivateKey, nil) - if err != nil { - fmt.Println(err) - return - } - } - - // TODO write to tempfile - f, _ := os.Create("digest_test.key") - w, _ := armor.Encode(f, openpgp.PublicKeyType, nil) - devnull, _ := os.Open(os.DevNull) - e.SerializePrivate(devnull, nil) - devnull.Close() - e.Serialize(w) - w.Close() - f.Close() + e, _ = test.TempOpenPGPIdentity() + if e == nil { + panic("e == nil") + } } func TestDigestCreateEmpty(t *testing.T) { @@ -82,5 +61,5 @@ func TestWriteSigned(t *testing.T) { deb.AddFile("debpkg.go") - assert.Nil(t, deb.WriteSigned(os.TempDir() + "/debpkg-test-signed.deb", e)) + assert.Nil(t, deb.WriteSigned(test.TempFile(t), e)) } diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..5705efe --- /dev/null +++ b/errors.go @@ -0,0 +1,27 @@ +// Copyright 2017 Debpkg authors. All rights reserved. +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE file. + +package debpkg + +import ( + "errors" +) + +var ErrClosed = errors.New("debpkg: Closed") +var ErrIO = errors.New("debpkg: I/O failed") + +// setError sets the package error when not nil +// setting an error when the current error is ErrClosed it will panic +func (deb *DebPkg) setError(err error) error { + if err == nil { + return nil + } + if deb.err == ErrClosed { + panic("debpkg: Trying to overwrite ErrClosed") + } + if err != ErrClosed { + deb.err = err + } + return err +} diff --git a/lib/targzip/targz.go b/internal/targzip/targz.go similarity index 94% rename from lib/targzip/targz.go rename to internal/targzip/targz.go index c1712dc..9a37576 100644 --- a/lib/targzip/targz.go +++ b/internal/targzip/targz.go @@ -66,7 +66,9 @@ func (t *TarGzip) AddFile(filename string, dest ...string) error { dirname := filepath.Dir(filename) if dirname != "." { - dirname = strings.Replace(dirname, "\\", "/", -1) + if os.PathSeparator != '/' { + dirname = strings.Replace(dirname, string(os.PathSeparator), "/", -1) + } dirs := strings.Split(dirname, "/") var current string for _, dir := range dirs { @@ -167,10 +169,12 @@ func (t *TarGzip) Close() error { return nil } +// Name returns the name of the file as presented to Open. func (t *TarGzip) Name() string { return t.fileName } +// Size returns the length in bytes for the closed file func (t *TarGzip) Size() int64 { fi, err := os.Stat(t.Name()) if err != nil { diff --git a/internal/test/test.go b/internal/test/test.go new file mode 100644 index 0000000..4396f8d --- /dev/null +++ b/internal/test/test.go @@ -0,0 +1,53 @@ +// Copyright 2017 Debpkg authors. All rights reserved. +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE file. + +package test + +import ( + "os" + "io/ioutil" + "testing" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/armor" +) + +var tempdir string + +func init() { + tempdir, _ = ioutil.TempDir("", "debpkg-test") +} + +// TempDir returns the current tempdir +func TempDir() string { + return tempdir +} + +// TempFile calculates the debian package filename based on os.TempDir() and t.Name() +func TempFile(t *testing.T) string { + return TempDir() + string(os.PathSeparator) + t.Name() + ".deb" +} + +// TempOpenPGPIdentity creates a new identity in TempDir() +func TempOpenPGPIdentity() (e *openpgp.Entity, err error) { + // Create random new GPG identity for signage + e, _ = openpgp.NewEntity("Debpkg Authors", "", "debpkg-authors@xor-gate.org", nil) + + // Sign all the identities + for _, id := range e.Identities { + if err = id.SelfSignature.SignUserId(id.UserId.Id, e.PrimaryKey, e.PrivateKey, nil); err != nil { + return + } + } + + f, _ := os.Create(TempDir() + string(os.PathSeparator) + "openpgp-testkey.asc") + w, _ := armor.Encode(f, openpgp.PublicKeyType, nil) + devnull, _ := os.Open(os.DevNull) + e.SerializePrivate(devnull, nil) + devnull.Close() + e.Serialize(w) + w.Close() + f.Close() + + return +} From 753bdf355cadab7c0b1cd67b3bb5193a371538eb Mon Sep 17 00:00:00 2001 From: Jerry Jacobs Date: Fri, 11 Aug 2017 10:51:20 +0200 Subject: [PATCH 2/2] Enable HoundCI checking for Go --- .hound.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .hound.yml diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 0000000..e5c719d --- /dev/null +++ b/.hound.yml @@ -0,0 +1,2 @@ +go: + enabled: true