feat: abstract interactions with archive/store - #311
Conversation
Signed-off-by: Paul Mars <[email protected]>
Signed-off-by: Paul Mars <[email protected]>
Signed-off-by: Paul Mars <[email protected]>
| // the package comes from an archive or a store. | ||
| type pkgSource struct { | ||
| arch string | ||
| fetch func() (io.ReadSeekCloser, *archive.PackageInfo, error) |
There was a problem hiding this comment.
[Note to reviewer]: If we adopt this approach, PackageInfo should likely be extracted to its own package as it will not be specific to the archive anymore and should also be usable by the future store package. This is slightly tangential to changes of this PR so I deffered this change for now.
There was a problem hiding this comment.
I'm not completely sure it makes sense to have PackageInfo itself to be extracted to a different package, because by the end of the day an apt package archive has package info data, and this needs to be concretely somewhere. The alternative is that we create a common ground type, but these often end up very messy because the become the union of all needs of all backends. It seems better to extend the approach that we are already pursuing here: interfaces that encapsulate only what actually needs to be common across them.
I'll take this comment to continue into a more general direction: this PR as a whole is playing with the right ideas and going into the right direction, but it's not yet nailing a proper encapsulation and abstraction approach to implement what we must. It doesn't make sense for the slider to be the one defining how to fetch packages and how to fetch stores, by injecting a lambda with custom code for either. The slider is precisely the thing that shouldn't care about the detais, and should instead be calling out in a single way towards multiple implementations.
So again, you're probably in the right ground, but needs some tuning still.
There was a problem hiding this comment.
The alternative is that we create a common ground type
This is what I experimented on in #316. Today I think it looks reasonable and not too messy thanks to the introduction of the Digest/DigestKind fields. We end up with a struct that is partially empty in the two existing cases (package from the archive or bin from the store) and the only place consuming it, manifestutil knows how to write/read it from/to a manifest.
I also tried using an interface and keeping 2 separate concrete types in #320 (the one for bins will be added in the PR fetching bins from the store). It seems more consistent with the overall approach. The main drawback is that as this time the added Pkg... methods seems a bit overkill, but this is an investment.
I'll take this comment to continue into a more general direction: this PR as a whole is playing with the right ideas and going into the right direction, but it's not yet nailing a proper encapsulation and abstraction approach to implement what we must. It doesn't make sense for the slider to be the one defining how to fetch packages and how to fetch stores, by injecting a lambda with custom code for either. The slider is precisely the thing that shouldn't care about the detais, and should instead be calling out in a single way towards multiple implementations.
I have reworked the approach following your suggestion. It can be further adapted if we proceed with #320.
Signed-off-by: Paul Mars <[email protected]>
Signed-off-by: Paul Mars <[email protected]>
| // the package comes from an archive or a store. | ||
| type pkgSource struct { | ||
| arch string | ||
| fetch func() (io.ReadSeekCloser, *archive.PackageInfo, error) |
There was a problem hiding this comment.
I'm not completely sure it makes sense to have PackageInfo itself to be extracted to a different package, because by the end of the day an apt package archive has package info data, and this needs to be concretely somewhere. The alternative is that we create a common ground type, but these often end up very messy because the become the union of all needs of all backends. It seems better to extend the approach that we are already pursuing here: interfaces that encapsulate only what actually needs to be common across them.
I'll take this comment to continue into a more general direction: this PR as a whole is playing with the right ideas and going into the right direction, but it's not yet nailing a proper encapsulation and abstraction approach to implement what we must. It doesn't make sense for the slider to be the one defining how to fetch packages and how to fetch stores, by injecting a lambda with custom code for either. The slider is precisely the thing that shouldn't care about the detais, and should instead be calling out in a single way towards multiple implementations.
So again, you're probably in the right ground, but needs some tuning still.
Add a source package with a Source interface to abstract over the archive and store interface. This is needed because fetching from the store requires the architecture, track and risk.
| // Source is a resolved package source, abstracting over archives and stores. | ||
| type Source interface { | ||
| Arch() string | ||
| Fetch() (io.ReadSeekCloser, *archive.PackageInfo, error) |
There was a problem hiding this comment.
[Note to reviewer]: If the approach of #320 is adopted, then this method will change to Fetch() (io.ReadSeekCloser, manifestutil.PackageInfo, error)
There was a problem hiding this comment.
That's mostly okay, but let's talk about whether manifestutil is the right place for this.
There was a problem hiding this comment.
Yes, this place is questionable. See my other note #311 (comment) for the options considered.
niemeyer
left a comment
There was a problem hiding this comment.
This seems close, but I think it needs a once over with some additional care for the concepts being represented. Some hints below, but please consider the overall design a bit further.
| @@ -0,0 +1,110 @@ | |||
| package source | |||
There was a problem hiding this comment.
This doesn't seem like a great name or place for this. We have several concepts of "source", and this doesn't seem to match cleanly with people's expectations of what they'd find here. It's also weird that this is completely detached from other packages that actually makes this relevant.
There was a problem hiding this comment.
The revised approach addresses these concerns:
- The
Fetcherconcept better express what we want to achieve and what the interface offers. - This implementation is now located in the
slicerpackage, but in a dedicated file to still express this is a related but adjacent concern. - If we ever need to expose more common behavior (for example an interface to give info on packages gathered from the archive/store) another interface (ex.
Explainer) could be defined.
A case could be made to move this fetcher.go out of the slicer package as in the future cmd_debug_check_release_archive.go could consume it. But at this point this is hypothetical and it seems too weak of a reason to justify a dedicated package for now.
Note on the "fetch" term: in setup we already have a FetchRelease (accepting FetchOptions) but this is a different concern, clearly stated in the name. So I think the risk of confusion is limited. If we want to be completely safe from that, "retrieve" and "Retriever" (for the interface) might be a suitable alternative.
| } | ||
|
|
||
| // archiveSource adapts an archive.Archive to the Source interface for a | ||
| // specific package. |
There was a problem hiding this comment.
That sounds unusual as well. Adapt an "archive" to a "package source"? It doesn't feel natural to transform one into the other.
There was a problem hiding this comment.
Yes it sounded wrong. In the revised approach this adapter is now a debFetcher, and the other one a binFetcher. Their purpose and what they offer is clearer that way.
Signed-off-by: Paul Mars <[email protected]>
Signed-off-by: Paul Mars <[email protected]>
| "slices" | ||
|
|
||
| "github.com/canonical/chisel/internal/archive" | ||
| "github.com/canonical/chisel/internal/manifestutil" |
There was a problem hiding this comment.
[Note to reviewer]: This is a smell that having PackageInfo in manifestutil shows its limit, as already discussed.
I considered moving it to the slicer pkg but that would be wrong because manifestutil (a leaf package) would need to import it.
I also considered creating a standalone package with fetch.go and PackageInfo but that would couple manifestutil with this fetch package and it looks wrong too.
So the next best solution looks to be a small pkginfo leaf package, holding the interface. Let me know what you think about it and I will proceed with it in a follow-up PR (to not mix refactor and feature in this one) if we agree on a refactor.
| // package slices file. For packages from a store it selects the store | ||
| // named in the package slices file. It returns a map of Fetcher indexed | ||
| // by package names. | ||
| func selectPkgFetchers(archives map[string]archive.Archive, selection *setup.Selection) (map[string]Fetcher, error) { |
There was a problem hiding this comment.
[Note to reviewer]: I think it makes sense to have this function here, but it is a refactor (and rename) of selectPkgArchives so moving it right now make it a bit difficult to see the diff. The actual changes in this function are very local but if it is too hard to read I can move it back to slicer.go in this PR and do the move to this file in a follow-up.
The slicer previously resolved packages to archives directly (
selectPkgArchivesreturning
map[string]archive.Archive), which cannot represent packagesdistributed from stores. This change introduces a
Fetcherinterface in the slicerthat abstracts over where a package comes from:
resolveFetchersbinds thesource-specific arguments at resolution time, so
Runcalls a uniformzero-argument
Fetch()without knowing whether the package is a deb from anarchive or a bin from a store.
Fetchreturns themanifestutil.PackageInfointerface, letting each fetcher produce its own metadata type (the store will use
revisions and SHA-384 digests). Store packages resolve successfully but fail with
a clear "not implemented" error at fetch time, so the upcoming store support
lands entirely inside
binFetcherwithout touching the slicer again. Resolutionlogic is covered by dedicated tests, exported to the test package via
export_test.go.