From 4c1e1b7301cbe84eb493ae7fadc4a1bd6d2429e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Virtus?= Date: Mon, 9 Jan 2023 21:15:10 +0100 Subject: [PATCH] testutil/pkgdata: Create deb programatically Currently we only test with base-files deb which is embedded as base64 encoded binary in testutil/pkgdata.go. If we want to change the contents of the data files, we have to un-ar it, un-tar it, change the data files, tar it and ar it. Furthermore, these operations modify attributes based on the workstation on which this process is run. So we end up with arbitrary user/group ids/names names and dates. Fix it by creating debs programatically. The new interface provides way to create debs with arbitrary content of data tarball. --- internal/testutil/pkgdata.go | 114 +++++++++++++++++++++++++ internal/testutil/pkgdata_test.go | 58 +++++++++++++ internal/testutil/testdata/pkgdata.deb | Bin 0 -> 536 bytes 3 files changed, 172 insertions(+) create mode 100644 internal/testutil/pkgdata_test.go create mode 100644 internal/testutil/testdata/pkgdata.deb diff --git a/internal/testutil/pkgdata.go b/internal/testutil/pkgdata.go index 7dc9dc20..56ce7ec4 100644 --- a/internal/testutil/pkgdata.go +++ b/internal/testutil/pkgdata.go @@ -1,7 +1,13 @@ package testutil import ( + "archive/tar" + "bytes" "encoding/base64" + "time" + + "github.com/blakesmith/ar" + "github.com/klauspost/compress/zstd" ) var PackageData = map[string][]byte{} @@ -150,3 +156,111 @@ BIsAGAJD+LGgIzDiD2ntudH6JU80wD0rklyRGBV2dEx8VJG5LVwpcXb8PI0lMdi+wwUPoBkQGEIN 7UwLQKMDjg8YkdmAmRrEgCSRRhMWBgBBSuI7S4BVs7zODfj1ja0TF+KbjdQDGANIcRlZhwzQSlla abj2Z7KoYMBXz9dwNNP2Aw13FguKkogezW5cqy4lCg== ` + +type TarEntry struct { + Header tar.Header + NoFixup bool + Content []byte +} + +var zeroTime time.Time +var epochStartTime time.Time = time.Unix(0, 0) + +func fixupEntry(entry *TarEntry) { + hdr := &entry.Header + if hdr.Typeflag == 0 { + if hdr.Linkname != "" { + hdr.Typeflag = tar.TypeSymlink + } else if hdr.Name[len(hdr.Name)-1] == '/' { + hdr.Typeflag = tar.TypeDir + } else { + hdr.Typeflag = tar.TypeReg + } + } + if hdr.Mode == 0 { + switch hdr.Typeflag { + case tar.TypeDir: + hdr.Mode = 0755 + case tar.TypeSymlink: + hdr.Mode = 0777 + default: + hdr.Mode = 0644 + } + } + if hdr.Size == 0 && entry.Content != nil { + hdr.Size = int64(len(entry.Content)) + } + if hdr.Uid == 0 && hdr.Uname == "" { + hdr.Uname = "root" + } + if hdr.Gid == 0 && hdr.Gname == "" { + hdr.Gname = "root" + } + if hdr.ModTime == zeroTime { + hdr.ModTime = epochStartTime + } + if hdr.Format == 0 { + hdr.Format = tar.FormatGNU + } +} + +func makeTar(entries []TarEntry) ([]byte, error) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + for _, entry := range entries { + if !entry.NoFixup { + fixupEntry(&entry) + } + if err := tw.WriteHeader(&entry.Header); err != nil { + return nil, err + } + if entry.Content != nil { + if _, err := tw.Write(entry.Content); err != nil { + return nil, err + } + } + } + return buf.Bytes(), nil +} + +func compressBytesZstd(input []byte) ([]byte, error) { + var buf bytes.Buffer + writer, err := zstd.NewWriter(&buf) + if _, err = writer.Write(input); err != nil { + return nil, err + } + if err = writer.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func MakeDeb(entries []TarEntry) ([]byte, error) { + var buf bytes.Buffer + + tarData, err := makeTar(entries) + if err != nil { + return nil, err + } + compTarData, err := compressBytesZstd(tarData) + if err != nil { + return nil, err + } + + writer := ar.NewWriter(&buf) + if err := writer.WriteGlobalHeader(); err != nil { + return nil, err + } + dataHeader := ar.Header{ + Name: "data.tar.zst", + Mode: 0644, + Size: int64(len(compTarData)), + } + if err := writer.WriteHeader(&dataHeader); err != nil { + return nil, err + } + if _, err = writer.Write(compTarData); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/internal/testutil/pkgdata_test.go b/internal/testutil/pkgdata_test.go new file mode 100644 index 00000000..23ddb396 --- /dev/null +++ b/internal/testutil/pkgdata_test.go @@ -0,0 +1,58 @@ +package testutil_test + +import ( + "archive/tar" + + _ "embed" + + "github.com/canonical/chisel/internal/testutil" + "gopkg.in/check.v1" +) + +type pkgdataSuite struct{} + +var _ = check.Suite(&pkgdataSuite{}) + +var pkgdataTestEntries = []testutil.TarEntry{ + { + Header: tar.Header{Name: "./"}, + }, { + Header: tar.Header{Name: "./admin/", Mode: 00700, Uname: "admin"}, + }, { + Header: tar.Header{Name: "./admin/password", Mode: 00600, Uname: "admin"}, Content: []byte("swordf1sh"), + }, { + Header: tar.Header{Name: "./admin/setpassword", Mode: 04711, Uname: "admin"}, Content: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01}, + }, { + Header: tar.Header{Name: "./data/"}, + }, { + Header: tar.Header{Name: "./data/invoice.txt"}, Content: []byte("$ 10"), + }, { + Header: tar.Header{Name: "./data/logs/"}, + }, { + Header: tar.Header{Name: "./data/logs/task.log"}, Content: []byte("starting\nfinished\n"), + }, { + Header: tar.Header{Name: "./data/shared/", Mode: 02777}, + }, { + Header: tar.Header{Name: "./home/"}, + }, { + Header: tar.Header{Name: "./home/alice/", Uid: 1000}, + }, { + Header: tar.Header{Name: "./home/alice/notes", Uid: 1000}, Content: []byte("check the cat"), + }, { + Header: tar.Header{Name: "./home/bob/", Uname: "bob", Uid: 1001}, + }, { + Header: tar.Header{Name: "./home/bob/task.sh", Mode: 00700, Uname: "bob", Uid: 1001}, Content: []byte("#!/bin/sh\n"), + }, { + Header: tar.Header{Name: "./logs/", Linkname: "data/logs"}, + }, +} + +//go:embed testdata/pkgdata.deb +var pkgdataExpectedDeb []byte + +func (s *pkgdataSuite) TestMakeDeb(c *check.C) { + deb, err := testutil.MakeDeb(pkgdataTestEntries) + c.Assert(err, check.IsNil) + //os.WriteFile("/tmp/pkdata.deb", deb, 0644) + c.Assert(deb, check.DeepEquals, pkgdataExpectedDeb) +} diff --git a/internal/testutil/testdata/pkgdata.deb b/internal/testutil/testdata/pkgdata.deb new file mode 100644 index 0000000000000000000000000000000000000000..e73e990c4106d1ff56af85312b8acf18edadcfa1 GIT binary patch literal 536 zcmV+z0_Xi9JYjNUXg&&LVRT_GbYXHXdUJFjARr(uHZn0YH8nXlI503UARr(hATS^x zARr(yFfcYWG$0@}HaH+4ARr(hAYck8wJ-f-04X&N0K!i%A7DxAv=K9DKDLNn@CI0`p&dPgB_slodrV5oaE{<(C_q4!I#xBcvdCCunyf6cU&v|hj{<29$)w&s z!aO#$B5$#lvmHd&dvy1rq%F%iB5BSadBjzEuI**1e^nESr#e=Aa>)#f4W(9z=U?9$Wu3lnu{FJ{bI&|?bW7Y zz#^<0f|v)0*~&zPP@5Fp)YyQfS~sCHGzM&^Y}E`>TR<25s4PaGTATun) zeoDsqZ88He4;TrFN##_CJ`^8;gHfrDVM3kUl`3q*rZBt@Zop`Pim6SJLQ~Bg%e&!m aT8^7QHiu8o!~0y8Q7m6^CP4<`aoFz_2;GSQ literal 0 HcmV?d00001