-
Notifications
You must be signed in to change notification settings - Fork 379
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
*: pluggable transports #216
Conversation
ed1136e
to
b33e9eb
Compare
b33e9eb
to
f755409
Compare
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.
Broadly, ACK.
It is annoying that registeredtransports.ImageName
is so long and ImageName
is really unrelated… but I don’t have a good idea how to solve that.
We could cheat and put ImageName
into types/commonutils.go
or something.
Or we could have containers/image/transports
(contains ImageName
), containers/image/transports/registry
(contains KnownTransports
) and containers/image/transports/alltransports
(contains the full list of transports, and ParseImageName
). But that feels like overengineering it.
copy/copy.go
Outdated
"github.com/containers/image/signature" | ||
"github.com/containers/image/transports" | ||
_ "github.com/containers/image/transports" |
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.
Why this? This would force any user of copy.Image
to import all transports.
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.
right, my bad, skopeo should actually import transports
to register them all
signature/policy_config.go
Outdated
@@ -123,7 +123,7 @@ func (m *policyTransportsMap) UnmarshalJSON(data []byte) error { | |||
// So, use a temporary map of pointers-to-slices and convert. | |||
tmpMap := map[string]*PolicyTransportScopes{} | |||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} { | |||
transport, ok := transports.KnownTransports[key] | |||
transport, ok := registeredtransports.KnownTransports[key] |
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.
This will need more work; otherwise an user who imports only a subset of transports would refuse to use a policy.json
which defines policies for other transports.
I suppose unmarshaling and remarshaling a policy.json
should always keep it unmodified, so we can’t just drop the sections for unknown transports; instead, policyTransportScopesWithTransport
will need to be taught to deal with a nil
transport, and silently accept any key. It seems that requirementsForImageRef
does the right thing, or close enough to it, even in such a situation.
It bothers me a bit that we would accept any typo in transport names, though… perhaps we could add a registeredtransports.AllExistingTransportsRegistered bool = false
, with the “all-transports” transports.init()
seting it to true
, and refusing unknown values if AllExistingTransportsRegistered
? The downside is that that would make it impossible to add policies private transports not known to containers/image/transports
for a single-application-use.
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.
This will need more work; otherwise an user who imports only a subset of transports would refuse to use a policy.json which defines policies for other transports.
I suppose unmarshaling and remarshaling a policy.json should always keep it unmodified, so we can’t just drop the sections for unknown transports; instead, policyTransportScopesWithTransport will need to be taught to deal with a nil transport, and silently accept any key. It seems that requirementsForImageRef does the right thing, or close enough to it, even in such a situation.
Could you shed some light on how you want this to be done in the code?
It bothers me a bit that we would accept any typo in transport names, though… perhaps we could add a registeredtransports.AllExistingTransportsRegistered bool = false, with the “all-transports” transports.init() seting it to true, and refusing unknown values if AllExistingTransportsRegistered? The downside is that that would make it impossible to add policies private transports not known to containers/image/transports for a single-application-use.
Not sure about this, it bothers me that ppl will need to know about this and set/unset this. It could be fine though.
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.
This will need more work; otherwise an user who imports only a subset of transports would refuse to use a policy.json which defines policies for other transports. …policyTransportScopesWithTransport will need to be taught to deal with a nil transport, and silently accept any key. It seems that requirementsForImageRef does the right thing, or close enough to it, even in such a situation.
Could you shed some light on how you want this to be done in the code?
- In
policyTransportsMap.UnmarshalJSON
, ifKnownTransports[key]
does not exist, just usenil
. - In
policyTransportScopesWithTransport.UnmarshalJSON
, ifm.transport
isnil
(from the change above), skip theValidatePolicyConfigurationScope
call (accept any scope string) - For
requirementsForImageRef
, add a test that when a transport is not inKnownTransports
when parsing the policy, but it is inKnownTransports
at the time ofrequirementsForImageRef
, the correct set of scopes is used (more or less re-run the existing test, except creating the policy in a different way). - Plus as close to 100% test coverage as possible.
I hope that makes some sense.
It bothers me a bit that we would accept any typo in transport names, though… perhaps we could add a registeredtransports.AllExistingTransportsRegistered bool = false, with the “all-transports” transports.init() seting it to true, and refusing unknown values if AllExistingTransportsRegistered? The downside is that that would make it impossible to add policies private transports not known to containers/image/transports for a single-application-use.
Not sure about this, it bothers me that ppl will need to know about this and set/unset this. It could be fine though.
The idea is that this would work automatically for users who use both all of the known AllTransports
, and any subset of them. The other side of this is that there would be nothing to manage, no extra work but also no flexibility; if skopeo
thought it knows about all possible transport names, then it still possible to create a local tool with mypinkponytransport
, but the system-wide /etc/containers/policy.json
must not contain a policy for mypinkponytransport
because skopeo
would reject that policy file.
Yeah, not sure about this either.
signature/signature_test.go
Outdated
@@ -5,6 +5,7 @@ import ( | |||
"io/ioutil" | |||
"testing" | |||
|
|||
_ "github.com/containers/image/openshift" |
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.
What is this needed for?
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.
wrong, file this is needed in policy_config_test.go
where we use the atomic
transport in https://github.com/containers/image/blob/master/signature/policy_config_test.go#L242. docker
and dir
are already imported in that test file so we just need this or it fails with:
--- FAIL: TestPolicyUnmarshalJSON (0.00s)
Error Trace: policy_config_test.go:255
Error: Received unexpected error "Unknown key \"atomic\""
Btw, fixed
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.
Thanks. Add a comment perhaps?
transports/transports.go
Outdated
} | ||
KnownTransports[name] = t | ||
registeredtransports.Register(t) |
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.
These transports should have been registered merely by the fact of being imported here, shouldn’t they? So this could just verify their presence and panic if they are not already in KnownTransports
. Or is the import and initialization order uncertain?
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.
no need to panic I guess, I dropped this altogether.
Actually, we only need two packages. Perhaps ( |
f755409
to
20454d9
Compare
fix containers/image progress
9edee29
to
9f73252
Compare
done this, just have a question around signatures. |
61dccbe
to
80f83ad
Compare
@cyphar could you also take a look at this? We are mainly trying to decouple each transports wrt the signature pkg. Right now, if you use the signature package, you are required to pull every other transport c/image has (that means, stuff like the storage transport which pulls in many files). This change aims at making easy to use the signature package with just a subset of interested transports (i.e. just docker, or oci and docker ...). Skopeo will keep pulling in all transports via the new |
80f83ad
to
a39304a
Compare
) | ||
|
||
// ParseImageName converts a URL-like image name to a types.ImageReference. | ||
func ParseImageName(imgName string) (types.ImageReference, error) { |
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.
IMO this should be done in transports
. This package should just be doing dumb imports and not providing any other functionality -- though I understand why you'd want to do this (so you don't have an annoying _
import).
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.
The underlying idea is that the strings are exposed to the users, so they should always mean the same thing; we should not have skopeo
and whateverothertool
use strings which look the same but actually each accepting a different subset of transports. So, if any user of the library uses the strings in the UI and wants to use ParseImageName
, they automatically get all transports: they can’t forget the _
import, as you say, and they can’t even opt out — this automatically tries to do the right thing for users. I understand that this may feel annoyingly heavy-handed; is it actually problematic? I think that on balance this does the right thing for users but I can of course be mistaken.
(More specialized users of containers/image
can use somepackage.Transport.ParseReference
to get a part of the parsing functionality, or somepackage.NewReference
if working with the native values directly.)
transports/transports.go
Outdated
"github.com/containers/image/types" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// KnownTransports is a registry of known ImageTransport instances. | ||
var KnownTransports map[string]types.ImageTransport |
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.
IMO this should be hidden (with some sort of Get
function) and have a lock around it internally. It isn't a good idea to expose a map
globally.
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.
(No strong opinion on this; conceptually this is a separate idea from this PR; why not I guess.)
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.
yeah, I'll do that
transports/transports.go
Outdated
if !ok { | ||
return nil, errors.Errorf(`Invalid image name "%s", unknown transport "%s"`, imgName, parts[0]) | ||
// Register TODO(runcom) | ||
func Register(t types.ImageTransport) { |
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.
As above, add a lock to this.
transports/transports.go
Outdated
} | ||
return transport.ParseReference(parts[1]) | ||
KnownTransports[name] = t | ||
} | ||
|
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.
Can we add a Deregister
function? Also it would be nice to have a simple Get
function (as the above comment noted), so that we can hide KnownTransports
?
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.
When would it be useful to call Deregister
? Lock or not, it is global state, and there would be a risk of one package deregistering a transport which a completely unrelated package has also imported and expects to be present.
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.
If someone wants to switch out transports -- or even for mocking? Maybe an atomic Replace
or something would reduce the issue?
transports/transports.go
Outdated
) | ||
|
||
// KnownTransports is a registry of known ImageTransport instances. | ||
var KnownTransports map[string]types.ImageTransport | ||
|
||
func init() { | ||
KnownTransports = make(map[string]types.ImageTransport) | ||
// NOTE: Make sure docs/policy.json.md is updated when adding or updating | ||
// a transport. |
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.
Please preserve this comment (in the import block of alltransports.go
probably).
a39304a
to
add4ea6
Compare
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.
Broadly ACK, the policy part still needs more tests
@@ -269,9 +270,6 @@ func TestPolicyUnmarshalJSON(t *testing.T) { | |||
func(v mSI) { v["transports"] = []string{} }, | |||
// "default" is an invalid PolicyRequirements | |||
func(v mSI) { v["default"] = PolicyRequirements{} }, | |||
// A key in "transports" is an invalid transport name | |||
func(v mSI) { x(v, "transports")["this is unknown"] = x(v, "transports")["docker"] }, | |||
func(v mSI) { x(v, "transports")[""] = x(v, "transports")["docker"] }, |
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.
There should be a test for an unknown transport name (that it is not rejected).
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.
done
@@ -174,7 +172,7 @@ func (m *policyTransportScopesWithTransport) UnmarshalJSON(data []byte) error { | |||
if _, ok := tmpMap[key]; ok { | |||
return nil | |||
} | |||
if key != "" { | |||
if key != "" && m.transport != nil { |
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.
This needs a test.
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.
And another test:
For requirementsForImageRef
, add a test that when a transport is not in KnownTransports
when parsing the policy, but it is in KnownTransports
at the time of requirementsForImageRef
, the correct set of scopes is used (more or less re-run the existing test, except creating the policy in a different way).
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.
isn't this https://github.com/containers/image/pull/216/files#diff-38040ba06b5a45464a80ca2fb88302c2R249 testing this code path?
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.
That tests parsing the policy into a data structure, not using the data structure to look up the relevant rules to evaluate for a particular image (requirementsForImageRef
). Right now, by inspection, it seems clear enough that requirementsForImageRef
can correctly handle a transport which exists but is unregistered, or a transport which was not registered when parsing the policy but is registered afterwards—but the transport registry is a very non-obvious hidden state in the parsing/evaluation interaction, so it would be nice to have a test which ensures that the code keeps working in these corner cases.
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.
added a test, hopefully done correctly (pretty late here now)
if !ok { | ||
return nil | ||
} | ||
// transport can be nil |
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.
Also please update the comment at policyTransportScopesWithTransport
: while validating using a specific ImageTransport
if not nil
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.
done
signature/policy_config_test.go
Outdated
@@ -9,6 +9,7 @@ import ( | |||
|
|||
"github.com/containers/image/directory" | |||
"github.com/containers/image/docker" | |||
_ "github.com/containers/image/openshift" |
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.
Add a comment explaining the need for this please.
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.
transports/transports.go
Outdated
if !ok { | ||
return nil, errors.Errorf(`Invalid image name "%s", unknown transport "%s"`, imgName, parts[0]) | ||
kt.transports[name] = t | ||
kt.mu.Unlock() |
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.
Shouldn’t this use defer
? This way, if we panic
above, the lock is permanently locked. Or perhaps it would be better to explicitly unlock it before that panic
, randomly unlocking in the middle of some other code path could be risky… not that there are that many code paths here :)
// KnownTransports is a registry of known ImageTransport instances. | ||
var KnownTransports map[string]types.ImageTransport | ||
// knownTransports is a registry of known ImageTransport instances. | ||
type knownTransports struct { |
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.
With this being a private variable of a private type, it’s really not obvious to me what defining the type and methods adds right now.
I guess it allows writing tests? Except that those tests don’t exist.
(But this does work, feel free to leave it as it is.)
dd69424
to
3631acd
Compare
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.
Thanks for putting up with me!
LGTM pending a successful test in updated skopeo.
signature/policy_eval_test.go
Outdated
|
||
assert.Nil(t, transports.Get("docker")) | ||
transports.Register(docker.Transport) | ||
assert.NotNil(t, transports.Get("docker")) |
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.
Please move this cleanup into a defer
block right after unregistering trhe transport; otherwise one of the require
s aborting would leave transports
in an unexpected state.
3631acd
to
3c3d25c
Compare
@mtrmac fixed but your LGTM didn't work |
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
3c3d25c
to
29da2bc
Compare
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
@mtrmac containers/skopeo#288 it's green over at skopeo 👍 (https://travis-ci.org/projectatomic/skopeo/builds/207004846) |
Close #125
As discussed in #125, this patch creates a new subpkg to import transports independently. projectatomic/docker would greatly benefit from this as it needs just the docker transport (right now).
Those will be the stats in projectatomic/docker + this PR:
@mtrmac PTAL (far from being ready, just a start...)
Signed-off-by: Antonio Murdaca runcom@redhat.com