-
Notifications
You must be signed in to change notification settings - Fork 511
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
Add dependency injection of remote store for NotaryRepository initial… #1094
Conversation
82679a8
to
2ae0cf0
Compare
client/client.go
Outdated
|
||
// NewNotaryRepository is the base method that returns a new notary repository. | ||
// It takes the base directory under where all the trust files will be stored | ||
// (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling |
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.
Non-blocking: Perhaps these should be listed as examples rather than normal defaults, since in this particular function there is no default for the baseDir
(these are just defaults for the CLI tool that uses it and in docker, which uses it)
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.
(Apologies, I know this wasn't your change, I just hadn't noticed it before)
client/client.go
Outdated
// (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling | ||
// docker content trust). | ||
// It expects initiated remote store and cache. | ||
func NewNotaryRepository(baseDir, gun, baseURL string, remoteStore store.RemoteStore, rt http.RoundTripper, cache store.MetadataStore, |
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.
I think maybe the RoundTripper
object may no longer be necessary when store.RemoteStore
is passed. NotaryRepository.roundTrip
only seems to be passed to the getRemoteStore
, getRemoteKey
, and rotateRemoteKey
helper functions. getRemoteKey
and rotateRemoteKey
just use it in order to call getRemoteStore
, so they can probably just be passed the remote store instead?
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.
Had this in my todo list and skipped it somehow.. will fix.
client/client.go
Outdated
if err != nil { | ||
return err | ||
} | ||
remote := r.remoteStore |
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.
Since this is only used in the line below, would it make sense to just call r.remoteStore.SetMulti(updatedFiles)
?
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.
Actually, wondering if we should check if this is nil
, first, and error if so? Sometimes we do not require the client to be online (if we're just reading from cache, for example), but write operations on the remote store definitely require us to be online.
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.
Wait never mind, ignore me - I think we use OfflineStore
in this case. Ok, that was why I think we used getRemoteStore
everywhere - if there was an error, we return OfflineStore
+ the error, and in some cases, we fail the operation if there was an error.
In the case of downloading new data, we ignore the error and just use the OfflineStore
because we can maybe read from cache.
This means that https://github.com/docker/notary/pull/1094/files#diff-1cd41a2611da49ce63630d5a51bd82c5R78 may not be the right behavior. However, if we don't propagate the error, we'd lose it.
Every operation from OfflineStore
will result in a generic offline error, though, so wondering if we should update OfflineStore
to take an error it can wrap, and propagate the original error that way?
cc @endophage
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.
good catch @cyli: I agree we need to wrap the OfflineStore
and also have a check for nil
here
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.
Changes I've suggested to @n4ss:
- In
NewNotaryRepository
check ifremoteStore
is nil and instantiate anOfflineStore
if it is. - Create a getter for
remoteStore
that will do the same check and also initialize an OfflineStore if necessary.
re. the behaviour at https://github.com/docker/notary/pull/1094/files#diff-1cd41a2611da49ce63630d5a51bd82c5R78, I believe this is correct. The pre-existing behaviour was to directly return the error if getRemoteStore
errored (i.e. https://github.com/docker/notary/pull/1094/files#diff-1cd41a2611da49ce63630d5a51bd82c5R78`), we're now simply detecting and aborting sooner.
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.
@endophage: that sounds good 👍
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 pre-existing behaviour was to directly return the error if getRemoteStore errored...
That is true of these two previous calls to getRemoteStore
:
- https://github.com/docker/notary/pull/1094/files#diff-1cd41a2611da49ce63630d5a51bd82c5L689
- https://github.com/docker/notary/pull/1094/files#diff-1cd41a2611da49ce63630d5a51bd82c5L1149
But this one just logged the error and proceeded: https://github.com/docker/notary/pull/1094/files#diff-1cd41a2611da49ce63630d5a51bd82c5L973
client/client.go
Outdated
if err != nil { | ||
return err | ||
} | ||
remote := r.remoteStore |
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.
Similarly to above, should we check that r.remoteStore
isn't nil first, and then if it isn't, we can also just call r.remoteStore.RemoveAll()
?
client/client.go
Outdated
// It takes the base directory under where all the trust files will be stored | ||
// (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling | ||
// docker content trust). | ||
// It expects initiated remote store and cache. |
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.
s/initiated/initialized
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.
Following on from the points that @cyli mentioned - even if we expect the initialized store/cache, we should check the values in case someone accidentally calls this with nils. Would be great to have unit tests for these cases.
Also, maybe this maybe shouldn't have to be exported anymore? cc @endophage
client/client.go
Outdated
// It expects initiated remote store and cache. | ||
func NewNotaryRepository(baseDir, gun, baseURL string, remoteStore store.RemoteStore, rt http.RoundTripper, cache store.MetadataStore, | ||
trustPinning trustpinning.TrustPinConfig, cryptoService signed.CryptoService) ( | ||
*NotaryRepository, 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.
super nit: this signature is kind of hard to read - not sure if you chose it or if gofmt imposed it to be this way. Maybe we could move the return values up a line such that we have 2 lines instead of 3?
client/client.go
Outdated
if remoteErr != nil { | ||
logrus.Error(remoteErr) | ||
} else if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized { | ||
remote := r.remoteStore |
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.
same as @cyli's comment below:
Similarly to above, should we check that r.remoteStore isn't nil first, and then if it isn't, we can also just call r.remoteStore.RemoveAll()?
client/client.go
Outdated
if err != nil { | ||
return err | ||
} | ||
remote := r.remoteStore |
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.
good catch @cyli: I agree we need to wrap the OfflineStore
and also have a check for nil
here
@@ -17,7 +17,6 @@ import ( | |||
"github.com/docker/notary/client/changelist" | |||
"github.com/docker/notary/cryptoservice" | |||
store "github.com/docker/notary/storage" | |||
"github.com/docker/notary/trustmanager" |
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.
nice 👍
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.
wonder how this passed the gofmt before
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.
trustmanager
was in use at line 92 prior to this PR.
Sorry, I pushed only the first commit by mistake - pushing the rest soon. |
Made a couple of updates:
|
Also, I'd like to remove The idea is create the following struct and use it at a few key points in the tests to wrap NotaryRepository:
|
7927e96
to
7921e31
Compare
@endophage - I think that logic for a |
@endophage That would be fine - would we care about propagating the error that caused the remote store to be nil? In some cases we want to error right away, and propagating why we couldn't get a remote store would be nice. Passing just |
@cyli I feel like as long as we log the error visibly, not just when running in debug, that's probably sufficient. A user doesn't necessarily care when in the flow an error gets emitted as long as there's sufficient information for them to understand what went wrong. If that seems like a reasonable assumption, then we don't need to add the complexity of propagating the error until we might want to expose it. |
@riyazdf yeah, I was thinking it would be defined on NotaryRepository. It would just check the field and if it isn't nil, return the value, if it is nil, return an OfflineStore |
@endophage That seems reasonable - would we still need a remote store getter in that case? There is only one place where we need an offline store - in the other two cases we just seem to return an error if the remote store is nil. |
@cyli on the basis that somebody could directly initialize a |
@endophage Oh I mean returning |
@cyli I figured the OfflineStore would cause the desired behaviour by erroring when we need it to, rather than having additional preemptive checks on whether the |
7921e31
to
136a8cf
Compare
@endophage Ah good point, you're right. For some reason I was thinking we'd return a different error like "invalid remote" but "offline" would be fine so long as that other error was logged. |
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.
LGTM
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.
LGTM
client/client.go
Outdated
"github.com/docker/notary/trustpinning" | ||
"github.com/docker/notary/tuf" | ||
"github.com/docker/notary/tuf/data" | ||
"github.com/docker/notary/tuf/signed" | ||
"github.com/docker/notary/tuf/utils" | ||
"os" |
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.
super nit: weird that this got formatted to here in the import list
client/client.go
Outdated
if err != nil { | ||
return err | ||
} | ||
remote := r.remoteStore | ||
|
||
return remote.SetMulti(data.MetadataRoleMapToStringMap(updatedFiles)) |
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.
nit: return r.remoteStore.SetMulti(data.MetadataRoleMapToStringMap(updatedFiles))
(no need for the extra remote
var)
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.
Ack, sorry, I totally forgot about @endophage's suggestion about the getter and logging the remote store error.
…ization Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
…tiated Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
Signed-off-by: David Lawrence <[email protected]> (github: endophage)
Signed-off-by: David Lawrence <[email protected]> (github: endophage)
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
… or an OfflineStore Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
136a8cf
to
144a9ad
Compare
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
keyStores, trustPinning) | ||
cryptoService := cryptoservice.NewCryptoService(keyStores...) | ||
|
||
remoteStore, err := getRemoteStore(baseURL, gun, rt) |
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.
Since we still seem to be using this initializer in the CLI for both reading notary data and publishing/initializing notary data, would it make sense to log this error instead? That way later the actual NotaryRepository
object itself can decide whether to fail if it's offline (in the case of publishing and initializing) or continue reading from cache?
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.
It makes sense to me, will fix!
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.
I actually added the error logging directly inside getRemoteStore without being too sure about this decision.. Should we let caller decide on wether to logging instead? It seems more reasonable.
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, let the caller decide on logging.
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.
(but in this specific case, always log the 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.
@cyli actually we should probably log when we get an OfflineStore only from getRemoteStore I think. If we get an err from getRemoteStore
and hence from NewHTTPStore
, it means that baseURL was incorrect, not that it was unreachable.
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.
From your comment in slack:
We have the following behavior for
NewHTTPStore
:
- if we have an invalid url from baseURL+gun then we get a nil store and a non-nil err -> this should error in callers, not switched to an OfflineStore
That makes sense to change this (from master, it seem sot already do so in this PR) to error on actual invalid values.
- if we have an invalid http.roundtrip, we get an OfflineStore and a nil err -> this should be logged in callers
If we were looking just at NewHTTPStore
, I'm wondering if we should actually convert a nil
http.RoundTrip
to a http.DefaultTransport
, or count it as invalid input and return an error (I don't feel strongly either way).
But in our usage of this function, we seem to set the roundtrip
to nil when doing offline operations, namely updating the changelist
, so we don't want it to error or probably care about it logging. I'd argue that eventually we should probably just directly call NewNotaryRepository
instead with a nil
remote store or something. I think it doesn't even need the cache, or trust pinning, or a cryptoservice, so those could probably also all be nil
.
In the meantime (unless we wanted to make that change here), since our usage is specifically "a nil http.Roundtrip
means this is intended to be an offline operation", maybe we should explicitly add a parameter to denote that, or just ignore invalid roundtrips?
- we have a valid HTTPStore -> all good
We don't check if the remote is actually reachable
👍
client/client.go
Outdated
|
||
// GetRemoteStore returns the remoteStore of a repository if valid or | ||
// or an OfflineStore otherwise | ||
func (r *NotaryRepository) GetRemoteStore() (store.RemoteStore, 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.
This function doesn't seem to need to return an 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.
Also non-blocking nit: I'm not sure this needs to be exported, but if it does, would just RemoteStore()
make sense as kind of an accessor function?
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.
I agree on returning error, if it's not necessary, don't do it. Every unnecessary error return demands 3 lines of code to check it :-P
Also agree on exporting. If it doesn't need to be part of the interface, don't export it because then somebody might rely on it and we could break them if we change it in the future.
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.
Good point!
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
…it on error or not Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
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.
LGTM!!!
@cyli when you get a chance, can you do final review please. Don't want to merge while you're still an X but I think everything is now resolved. |
client/client.go
Outdated
remoteStore, err := getRemoteStore(baseURL, gun, rt) | ||
// baseURL is syntactically invalid | ||
if err != nil { | ||
logrus.Error(err) |
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.
Non-blocking: I'm not sure we have to log as well as returning the error.
Also it looks like in the CLI, tuf.go
's tokenAuth
function also parses the URL and checks for validity, which is probably a better validity check than NewHTTPStore
's, since it looks like a url like http:asdga
gets through the url.Parse
+ checking for the existence of a scheme?:
$ bin/notary -D -s https:asdgadg list repo
DEBU[0000] Configuration file not found, using defaults
DEBU[0000] Using the following trust directory: /Users/cyli/.notary
ERRO[0000] could not reach https:asdgadg: Get https:///v2/: http: no Host in request URL
INFO[0000] continuing in offline mode
DEBU[0000] No yubikey found, using alternative key storage: loaded library /usr/local/lib/libykcs11.dylib, but no HSM slots found
DEBU[0000] Making dir path: /Users/cyli/.notary/tuf/repo/changelist
* fatal: client is offline
We can probably fix that in a later PR, 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.
Just fixed it right now in a last small commit
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.
Awesome, thanks! That was my only comment! This otherwise looks good to merge on green!
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.
Other than a non-blocking nitpick about a log and returning an error both, this LGTM!
Signed-off-by: Nassim 'Nass' Eddequiouaq <[email protected]>
I'm not waiting on codecov to get worked out on this. It was OK on test coverage before and I don't see any updates that should significantly change that. |
…ization
Signed-off-by: Nassim 'Nass' Eddequiouaq [email protected]