Repository navigation
feat: support extraction of multiple package types #318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
upils
wants to merge
29
commits into
canonical:main
Choose a base branch
from
upils:support-bin-extract
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
f91d449
feat: extract bins
upils 1663446
refactor: rework approach
upils 3435cee
refactor: refining
upils 09a01bd
refactor: refining
upils 8d87fae
docs: clean comments
upils 1a38ccd
docs: remove mentions of packages in tarball pkg
upils ca8d6e8
Merge branch 'main' into support-bin-extract
upils ee8732a
style: refine naming and docs
upils ca72fd6
refactor: rework approach
upils dc8e94b
fix: clean superfluous import
upils f882d47
test: remove unused field for now
upils 116752e
refactor: put deb DataReader back in deb package
upils e9c4d88
style: add missing trailing newline
upils b1ec390
style: refine naming
upils a8ad813
style: improve naming consistency
upils 6ad029e
style: avoid ambiguity
upils 150e904
refactor: rework approach with interface
upils 791224b
refactor: refine interface
upils 50b03d1
tests: polish tests
upils 798ce59
docs: fix misleading docs
upils ec2c67c
refactor: rename to fit new abstraction
upils 6f2fe07
docs: refine docs
upils 436a161
fix: clarify interface following review
upils 7afa609
fix: use a single default test package
upils 95a45c1
fix: use better names
upils 1a0d381
ci: rerun
upils eee0a4e
ci: rerun
upils 1fc0c45
feat: rework heavily the approach
upils 37df805
fix: clean unwanted change coming from main
upils File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package bin | ||
|
|
||
| import ( | ||
| "io" | ||
|
|
||
| "github.com/ulikunitz/xz" | ||
| ) | ||
|
|
||
| // DataReader takes a Reader for a bin package (an XZ-compressed tarball) and | ||
| // returns a Reader to the inner tarball. | ||
| func DataReader(pkgReader io.Reader) (io.ReadCloser, error) { | ||
| xzReader, err := xz.NewReader(pkgReader) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return io.NopCloser(xzReader), nil | ||
| } | ||
|
|
||
| func OpenTar(pkgReader io.ReadSeeker) func() (io.ReadCloser, error) { | ||
| return func() (io.ReadCloser, error) { | ||
| _, err := pkgReader.Seek(0, io.SeekStart) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return DataReader(pkgReader) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package bin_test | ||
|
|
||
| import ( | ||
| "archive/tar" | ||
| "bytes" | ||
|
|
||
| . "gopkg.in/check.v1" | ||
|
|
||
| "github.com/canonical/chisel/internal/bin" | ||
| "github.com/canonical/chisel/internal/testutil" | ||
| ) | ||
|
|
||
| func (s *S) TestDataReader(c *C) { | ||
| data := testutil.MustMakeBin([]testutil.TarEntry{ | ||
| testutil.Dir(0o755, "./"), | ||
| testutil.Reg(0o644, "./file", "content"), | ||
| }) | ||
|
|
||
| dataReader, err := bin.DataReader(bytes.NewReader(data)) | ||
| c.Assert(err, IsNil) | ||
| tarReader := tar.NewReader(dataReader) | ||
| _, err = tarReader.Next() | ||
| c.Assert(err, IsNil) | ||
| } | ||
|
|
||
| func (s *S) TestDataReaderInvalid(c *C) { | ||
| _, err := bin.DataReader(bytes.NewReader([]byte("not an xz stream"))) | ||
| c.Assert(err, ErrorMatches, "xz.*") | ||
| } | ||
|
|
||
| func (s *S) TestOpenTar(c *C) { | ||
| data := testutil.MustMakeBin([]testutil.TarEntry{ | ||
| testutil.Dir(0o755, "./"), | ||
| testutil.Reg(0o644, "./file", "content"), | ||
| }) | ||
| pkgReader := testutil.ReadSeekNopCloser(bytes.NewReader(data)) | ||
| openTar := bin.OpenTar(pkgReader) | ||
|
|
||
| // Each call must return a fresh stream over the same content. | ||
| testutil.CheckOpenTar(c, openTar) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package bin | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "sync" | ||
| ) | ||
|
|
||
| // Avoid importing the log type information unnecessarily. There's a small cost | ||
| // associated with using an interface rather than the type. Depending on how | ||
| // often the logger is plugged in, it would be worth using the type instead. | ||
| type log_Logger interface { | ||
| Output(calldepth int, s string) error | ||
| } | ||
|
|
||
| var globalLoggerLock sync.Mutex | ||
| var globalLogger log_Logger | ||
| var globalDebug bool | ||
|
|
||
| // Specify the *log.Logger object where log messages should be sent to. | ||
| func SetLogger(logger log_Logger) { | ||
| globalLoggerLock.Lock() | ||
| globalLogger = logger | ||
| globalLoggerLock.Unlock() | ||
| } | ||
|
|
||
| // Enable the delivery of debug messages to the logger. Only meaningful | ||
| // if a logger is also set. | ||
| func SetDebug(debug bool) { | ||
| globalLoggerLock.Lock() | ||
| globalDebug = debug | ||
| globalLoggerLock.Unlock() | ||
| } | ||
|
|
||
| // logf sends to the logger registered via SetLogger the string resulting | ||
| // from running format and args through Sprintf. | ||
| func logf(format string, args ...any) { | ||
| globalLoggerLock.Lock() | ||
| defer globalLoggerLock.Unlock() | ||
| if globalLogger != nil { | ||
| globalLogger.Output(2, fmt.Sprintf(format, args...)) | ||
| } | ||
| } | ||
|
|
||
| // debugf sends to the logger registered via SetLogger the string resulting | ||
| // from running format and args through Sprintf, but only if debugging was | ||
| // enabled via SetDebug. | ||
| func debugf(format string, args ...any) { | ||
| globalLoggerLock.Lock() | ||
| defer globalLoggerLock.Unlock() | ||
| if globalDebug && globalLogger != nil { | ||
| globalLogger.Output(2, fmt.Sprintf(format, args...)) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package bin_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| . "gopkg.in/check.v1" | ||
| ) | ||
|
|
||
| func Test(t *testing.T) { TestingT(t) } | ||
|
|
||
| type S struct{} | ||
|
|
||
| var _ = Suite(&S{}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package deb_test | ||
|
|
||
| import ( | ||
| "bytes" | ||
|
|
||
| . "gopkg.in/check.v1" | ||
|
|
||
| "github.com/canonical/chisel/internal/deb" | ||
| "github.com/canonical/chisel/internal/testutil" | ||
| ) | ||
|
|
||
| func (s *S) TestOpenTar(c *C) { | ||
| pkgReader := testutil.ReadSeekNopCloser(bytes.NewReader(testutil.PackageData["test-package"])) | ||
| openTar := deb.OpenTar(pkgReader) | ||
|
|
||
| // Each call must return a fresh stream over the same content. | ||
| testutil.CheckOpenTar(c, openTar) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,6 @@ import ( | |
| "strings" | ||
| "syscall" | ||
|
|
||
| "github.com/canonical/chisel/internal/deb" | ||
| "github.com/canonical/chisel/internal/fsutil" | ||
| "github.com/canonical/chisel/internal/strdist" | ||
| ) | ||
|
|
@@ -58,7 +57,11 @@ func getValidOptions(options *ExtractOptions) (*ExtractOptions, error) { | |
| return options, nil | ||
| } | ||
|
|
||
| func Extract(pkgReader io.ReadSeeker, options *ExtractOptions) (err error) { | ||
| // OpenTarFunc opens a fresh reader over a package's inner tar stream. | ||
| // Each call must return a stream from its start. | ||
| type OpenTarFunc func() (io.ReadCloser, error) | ||
|
|
||
| func Extract(openTar OpenTarFunc, options *ExtractOptions) (err error) { | ||
| defer func() { | ||
| if err != nil { | ||
| err = fmt.Errorf("cannot extract from package %q: %w", options.Package, err) | ||
|
|
@@ -79,15 +82,15 @@ func Extract(pkgReader io.ReadSeeker, options *ExtractOptions) (err error) { | |
| return err | ||
| } | ||
|
|
||
| return extractData(pkgReader, validOpts) | ||
| return extractEntries(openTar, validOpts) | ||
| } | ||
|
|
||
| func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error { | ||
| dataReader, err := deb.DataReader(pkgReader) | ||
| func extractEntries(openTar OpenTarFunc, options *ExtractOptions) error { | ||
| tarStream, err := openTar() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer dataReader.Close() | ||
| defer tarStream.Close() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the difference in clarity between the original version of this method and the new one. We used to take a boring io.ReadSeeker, which any Go developer knows, and now we take a PkgReader that has a strange interface. |
||
|
|
||
| oldUmask := syscall.Umask(0) | ||
| defer func() { | ||
|
|
@@ -117,10 +120,10 @@ func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error { | |
| // create them with the permissions defined in the tarball. | ||
| // | ||
| // The assumption is that the tar entries of the parent directories appear | ||
| // before the entry for the file itself. This is the case for .deb files but | ||
| // not for all tarballs. | ||
| // before the entry for the file itself. This is the case for the tarballs | ||
| // produced by common packaging tools but not for all tarballs. | ||
| tarDirMode := make(map[string]fs.FileMode) | ||
| tarReader := tar.NewReader(dataReader) | ||
| tarReader := tar.NewReader(tarStream) | ||
| for { | ||
| tarHeader, err := tarReader.Next() | ||
| if err == io.EOF { | ||
|
|
@@ -261,11 +264,7 @@ func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error { | |
| ExtractOptions: options, | ||
| pendingLinks: pendingHardLinks, | ||
| } | ||
| _, err := pkgReader.Seek(0, io.SeekStart) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| err = extractHardLinks(pkgReader, extractHardLinkOptions) | ||
| err := extractHardLinks(openTar, extractHardLinkOptions) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
@@ -299,14 +298,14 @@ type extractHardLinkOptions struct { | |
|
|
||
| // extractHardLinks iterates through the tarball a second time to extract the | ||
| // hard links that were not extracted in the first pass. | ||
| func extractHardLinks(pkgReader io.ReadSeeker, opts *extractHardLinkOptions) error { | ||
| dataReader, err := deb.DataReader(pkgReader) | ||
| func extractHardLinks(openTar OpenTarFunc, opts *extractHardLinkOptions) error { | ||
| tarStream, err := openTar() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer dataReader.Close() | ||
| defer tarStream.Close() | ||
|
|
||
| tarReader := tar.NewReader(dataReader) | ||
| tarReader := tar.NewReader(tarStream) | ||
| for { | ||
| tarHeader, err := tarReader.Next() | ||
| if err == io.EOF { | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Note to reviewer]: The introduction of this package can be done in a follow up PR if we want this one to focus on the reworked interface. I added it as this is rather small and it illustrates the work in this PR is not artificial.