-
Notifications
You must be signed in to change notification settings - Fork 651
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
image: Refactor to use cas/ref engines instead of walkers #159
Conversation
7efffe9
to
b4482da
Compare
} | ||
} | ||
|
||
func (state *casGetCmd) Run(cmd *cobra.Command, args []string) { |
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.
just call this cmd
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.
On Mon, Jun 20, 2016 at 05:30:34AM -0700, Sergiusz Urbaniak wrote:
+func (state *casGetCmd) Run(cmd *cobra.Command, args []string) {
just call this
cmd
There are a few things in that line. Do you mean ‘Run’ → ‘cmd’?
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.
Sorry for being inprecise. I meant the state
variable, I'd call this cmd
.
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.
On Mon, Jun 20, 2016 at 02:09:20PM -0700, Sergiusz Urbaniak wrote:
+func (state *casGetCmd) Run(cmd *cobra.Command, args []string) {
Sorry for being inprecise. I meant the
state
variable, I'd call thiscmd
.
What do you want me to use for *cobra.Command?
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.
Haha, sorry, it's 11pm here in old Europe, my eyes are starting to blur ;-) In this case I'd recommend calling it getCmd
, or even cgc
which is quite idiomatic.
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.
On Mon, Jun 20, 2016 at 02:19:47PM -0700, Sergiusz Urbaniak wrote:
+func (state *casGetCmd) Run(cmd *cobra.Command, args []string) {
Haha, sorry, it's 11pm here in old Europe, my eyes are starting to
blur ;-) In this case I'd recommend calling itgetCmd
, or even
cgc
which is quite idiomatic.
getCmd would cause collisions between the current casGetCmd and
refsGetCmd. Do you really prefer types ‘cgc’ and ‘rgc’ to casGetCmt
and refsGetCmd? I can reroll (it's not my project ;), but the short
versions are a bit dense for my taste ;).
Signed-off-by: W. Trevor King <wking@tremily.us>
I've pushed c071139 → 7e96d03, rebasing around #212, #229, and #230 On Tue, Aug 30, 2016 at 11:08:41PM -0700, W. Trevor King wrote 1:
I'm expecting something like one of: a. “We agree that the current PR's tar handling isn't performant, but b. “You need to use temporary files to avoid holding blobs in memory c. “Everything from (b), except you cannot rewrite the whole tar file [2]: Is there really no truncate(2) in Go's high-level file ops? More [5]: E.g. if TempFile and the original tar path are on different |
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <c8e4433a-53c0-4ee6-9dc5-98f62eea06d2@googlegroups.com> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
Moved to opencontainers/image-tools#5. |
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <c8e4433a-53c0-4ee6-9dc5-98f62eea06d2@googlegroups.com> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <c8e4433a-53c0-4ee6-9dc5-98f62eea06d2@googlegroups.com> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <c8e4433a-53c0-4ee6-9dc5-98f62eea06d2@googlegroups.com> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <c8e4433a-53c0-4ee6-9dc5-98f62eea06d2@googlegroups.com> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. This replaces the simpler interface from image/reader.go with something more robust. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <c8e4433a-53c0-4ee6-9dc5-98f62eea06d2@googlegroups.com> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. This replaces the simpler interface from image/reader.go with something more robust. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). Also replace the old methods like: func (d *descriptor) validateContent(r io.Reader) error with functions like: validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error to avoid local types that duplicate the image-spec types. This saves an extra instantiation for folks who want to validate (or whatever) a specs.Descriptor they have obtained elsewhere. I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <wking@tremily.us>
Abstract out the details of the image-layout format, since the
validators and unpackers shouldn't care about that sort of backend
stuff. This should make it easy to build backends based on zip files,
HTTP, the Docker registry format, etc. See #140.
I ran out of time on the final commit, but wanted to push this in case
folks had time for early feedback. I should have the last commit
fixed up today, but there may be some more rerolling while I do that.
Ping @s-urbaniak.