-
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
Changes from 12 commits
ddfe727
e4cacd0
ae43610
7d8ec9f
1070d54
e8d8eae
144a9ad
1d03a50
9b5aeb7
10577ed
1afae72
0b3f3cb
b70d671
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
"github.com/docker/notary/trustpinning" | ||
"github.com/docker/notary/tuf" | ||
"github.com/docker/notary/tuf/data" | ||
|
@@ -42,8 +41,9 @@ type NotaryRepository struct { | |
baseDir string | ||
gun data.GUN | ||
baseURL string | ||
tufRepoPath string | ||
changelist changelist.Changelist | ||
cache store.MetadataStore | ||
remoteStore store.RemoteStore | ||
CryptoService signed.CryptoService | ||
tufRepo *tuf.Repo | ||
invalid *tuf.Repo // known data that was parsable but deemed invalid | ||
|
@@ -53,7 +53,9 @@ type NotaryRepository struct { | |
} | ||
|
||
// NewFileCachedNotaryRepository is a wrapper for NewNotaryRepository that initializes | ||
// a file cache from the provided repository and local config information | ||
// a file cache from the provided repository, local config information and a crypto service. | ||
// It also retrieves the remote store associated to the base directory under where all the | ||
// trust files will be stored and the specified GUN. | ||
func NewFileCachedNotaryRepository(baseDir string, gun data.GUN, baseURL string, rt http.RoundTripper, | ||
retriever notary.PassRetriever, trustPinning trustpinning.TrustPinConfig) ( | ||
*NotaryRepository, error) { | ||
|
@@ -65,47 +67,57 @@ func NewFileCachedNotaryRepository(baseDir string, gun data.GUN, baseURL string, | |
if err != nil { | ||
return nil, err | ||
} | ||
return NewNotaryRepository(baseDir, gun, baseURL, rt, cache, retriever, trustPinning) | ||
} | ||
|
||
// NewNotaryRepository is a helper 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 | ||
// docker content trust). | ||
func NewNotaryRepository(baseDir string, gun data.GUN, baseURL string, rt http.RoundTripper, cache store.MetadataStore, | ||
retriever notary.PassRetriever, trustPinning trustpinning.TrustPinConfig) ( | ||
*NotaryRepository, error) { | ||
|
||
keyStores, err := getKeyStores(baseDir, retriever) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return repositoryFromKeystores(baseDir, gun, baseURL, rt, cache, | ||
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 commentThe 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 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. It makes sense to me, will fix! 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. 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 commentThe 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 commentThe 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 commentThe 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 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. From your comment in slack:
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 were looking just at But in our usage of this function, we seem to set the In the meantime (unless we wanted to make that change here), since our usage is specifically "a nil
👍 |
||
// baseURL is syntactically invalid | ||
if err != nil { | ||
logrus.Error(err) | ||
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. Non-blocking: I'm not sure we have to log as well as returning the error. Also it looks like in the CLI,
We can probably fix that in a later PR, though. 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. Just fixed it right now in a last small commit 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. Awesome, thanks! That was my only comment! This otherwise looks good to merge on green! |
||
return nil, err | ||
} | ||
|
||
cl, err := changelist.NewFileChangelist(filepath.Join( | ||
filepath.Join(baseDir, tufDir, filepath.FromSlash(gun.String()), "changelist"), | ||
)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return NewNotaryRepository(baseDir, gun, baseURL, remoteStore, cache, trustPinning, cryptoService, cl) | ||
} | ||
|
||
// repositoryFromKeystores is a helper function for NewNotaryRepository that | ||
// takes some basic NotaryRepository parameters as well as keystores (in order | ||
// of usage preference), and returns a NotaryRepository. | ||
func repositoryFromKeystores(baseDir string, gun data.GUN, baseURL string, rt http.RoundTripper, cache store.MetadataStore, | ||
keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) { | ||
// 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 commentThe 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 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. (Apologies, I know this wasn't your change, I just hadn't noticed it before) |
||
// docker content trust). | ||
// It expects an initialized remote store and cache. | ||
func NewNotaryRepository(baseDir string, gun data.GUN, baseURL string, remoteStore store.RemoteStore, cache store.MetadataStore, | ||
trustPinning trustpinning.TrustPinConfig, cryptoService signed.CryptoService, cl changelist.Changelist) ( | ||
*NotaryRepository, error) { | ||
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. 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? |
||
|
||
cryptoService := cryptoservice.NewCryptoService(keyStores...) | ||
// Repo's remote store is either a valid remote store or an OfflineStore | ||
if remoteStore == nil { | ||
remoteStore = store.OfflineStore{} | ||
} | ||
|
||
nRepo := &NotaryRepository{ | ||
gun: gun, | ||
baseDir: baseDir, | ||
baseURL: baseURL, | ||
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun.String())), | ||
baseDir: baseDir, | ||
changelist: cl, | ||
cache: cache, | ||
remoteStore: remoteStore, | ||
CryptoService: cryptoService, | ||
roundTrip: rt, | ||
trustPinning: trustPin, | ||
trustPinning: trustPinning, | ||
LegacyVersions: 0, // By default, don't sign with legacy roles | ||
} | ||
|
||
nRepo.cache = cache | ||
|
||
return nRepo, nil | ||
} | ||
|
||
|
@@ -274,10 +286,13 @@ func (r *NotaryRepository) initializeRoles(rootKeys []data.PublicKey, localRoles | |
) | ||
} | ||
} | ||
|
||
remote := r.getRemoteStore() | ||
|
||
for _, role := range remoteRoles { | ||
// This key is generated by the remote server. | ||
var key data.PublicKey | ||
key, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip) | ||
key, err = getRemoteKey(role, remote) | ||
if err != nil { | ||
return | ||
} | ||
|
@@ -302,8 +317,7 @@ func (r *NotaryRepository) initializeRoles(rootKeys []data.PublicKey, localRoles | |
} | ||
|
||
// adds a TUF Change template to the given roles | ||
func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...data.RoleName) error { | ||
|
||
func addChange(cl changelist.Changelist, c changelist.Change, roles ...data.RoleName) error { | ||
if len(roles) == 0 { | ||
roles = []data.RoleName{data.CanonicalTargetsRole} | ||
} | ||
|
@@ -344,11 +358,6 @@ func (r *NotaryRepository) AddTarget(target *Target, roles ...data.RoleName) err | |
if len(target.Hashes) == 0 { | ||
return fmt.Errorf("no hashes specified for target \"%s\"", target.Name) | ||
} | ||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) | ||
if err != nil { | ||
return err | ||
} | ||
defer cl.Close() | ||
logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) | ||
|
||
meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} | ||
|
@@ -360,22 +369,17 @@ func (r *NotaryRepository) AddTarget(target *Target, roles ...data.RoleName) err | |
template := changelist.NewTUFChange( | ||
changelist.ActionCreate, "", changelist.TypeTargetsTarget, | ||
target.Name, metaJSON) | ||
return addChange(cl, template, roles...) | ||
return addChange(r.changelist, template, roles...) | ||
} | ||
|
||
// RemoveTarget creates new changelist entries to remove a target from the given | ||
// roles in the repository when the changelist gets applied at publish time. | ||
// If roles are unspecified, the default role is "target". | ||
func (r *NotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error { | ||
|
||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) | ||
if err != nil { | ||
return err | ||
} | ||
logrus.Debugf("Removing target \"%s\"", targetName) | ||
template := changelist.NewTUFChange(changelist.ActionDelete, "", | ||
changelist.TypeTargetsTarget, targetName, nil) | ||
return addChange(cl, template, roles...) | ||
return addChange(r.changelist, template, roles...) | ||
} | ||
|
||
// ListTargets lists all targets for the current repository. The list of | ||
|
@@ -533,13 +537,19 @@ func (r *NotaryRepository) GetAllTargetMetadataByName(name string) ([]TargetSign | |
|
||
// GetChangelist returns the list of the repository's unpublished changes | ||
func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) { | ||
changelistDir := filepath.Join(r.tufRepoPath, "changelist") | ||
cl, err := changelist.NewFileChangelist(changelistDir) | ||
if err != nil { | ||
logrus.Debug("Error initializing changelist") | ||
return nil, err | ||
return r.changelist, nil | ||
} | ||
|
||
// getRemoteStore returns the remoteStore of a repository if valid or | ||
// or an OfflineStore otherwise | ||
func (r *NotaryRepository) getRemoteStore() store.RemoteStore { | ||
if r.remoteStore != nil { | ||
return r.remoteStore | ||
} | ||
return cl, nil | ||
|
||
r.remoteStore = &store.OfflineStore{} | ||
|
||
return r.remoteStore | ||
} | ||
|
||
// RoleWithSignatures is a Role with its associated signatures | ||
|
@@ -590,18 +600,14 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) { | |
// Publish pushes the local changes in signed material to the remote notary-server | ||
// Conceptually it performs an operation similar to a `git rebase` | ||
func (r *NotaryRepository) Publish() error { | ||
cl, err := r.GetChangelist() | ||
if err != nil { | ||
if err := r.publish(r.changelist); err != nil { | ||
return err | ||
} | ||
if err = r.publish(cl); err != nil { | ||
return err | ||
} | ||
if err = cl.Clear(""); err != nil { | ||
if err := r.changelist.Clear(""); err != nil { | ||
// This is not a critical problem when only a single host is pushing | ||
// but will cause weird behaviour if changelist cleanup is failing | ||
// and there are multiple hosts writing to the repo. | ||
logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist")) | ||
logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", r.changelist.Location()) | ||
} | ||
return nil | ||
} | ||
|
@@ -686,10 +692,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error { | |
return err | ||
} | ||
|
||
remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) | ||
if err != nil { | ||
return err | ||
} | ||
remote := r.getRemoteStore() | ||
|
||
return remote.SetMulti(data.MetadataRoleMapToStringMap(updatedFiles)) | ||
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. nit: (no need for the extra |
||
} | ||
|
@@ -970,10 +973,9 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*TUFClient, e | |
} | ||
} | ||
|
||
remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) | ||
if remoteErr != nil { | ||
logrus.Error(remoteErr) | ||
} else if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized { | ||
remote := r.getRemoteStore() | ||
|
||
if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized { | ||
// remoteErr was nil and we were not able to load a root from cache or | ||
// are specifically checking for initialization of the repo. | ||
|
||
|
@@ -1037,7 +1039,8 @@ func (r *NotaryRepository) pubKeyListForRotation(role data.RoleName, serverManag | |
|
||
// If server manages the key being rotated, request a rotation and return the new key | ||
if serverManaged { | ||
pubKey, err = rotateRemoteKey(r.baseURL, r.gun, role, r.roundTrip) | ||
remote := r.getRemoteStore() | ||
pubKey, err = rotateRemoteKey(role, remote) | ||
pubKeyList = make(data.KeyList, 0, 1) | ||
pubKeyList = append(pubKeyList, pubKey) | ||
if err != nil { | ||
|
@@ -1062,7 +1065,7 @@ func (r *NotaryRepository) pubKeyListForRotation(role data.RoleName, serverManag | |
for _, keyID := range newKeys { | ||
pubKey = r.CryptoService.GetKey(keyID) | ||
if pubKey == nil { | ||
return nil, fmt.Errorf("unable to find key: %s") | ||
return nil, fmt.Errorf("unable to find key: %s", keyID) | ||
} | ||
pubKeyList = append(pubKeyList, pubKey) | ||
} | ||
|
@@ -1139,15 +1142,17 @@ func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role data | |
|
||
// DeleteTrustData removes the trust data stored for this repo in the TUF cache on the client side | ||
// Note that we will not delete any private key material from local storage | ||
func (r *NotaryRepository) DeleteTrustData(deleteRemote bool) error { | ||
func DeleteTrustData(baseDir string, gun data.GUN, URL string, rt http.RoundTripper, deleteRemote bool) error { | ||
localRepo := filepath.Join(baseDir, tufDir, filepath.FromSlash(gun.String())) | ||
// Remove the tufRepoPath directory, which includes local TUF metadata files and changelist information | ||
if err := os.RemoveAll(r.tufRepoPath); err != nil { | ||
if err := os.RemoveAll(localRepo); err != nil { | ||
return fmt.Errorf("error clearing TUF repo data: %v", err) | ||
} | ||
// Note that this will require admin permission in this NotaryRepository's roundtripper | ||
if deleteRemote { | ||
remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) | ||
remote, err := getRemoteStore(URL, gun, rt) | ||
if err != nil { | ||
logrus.Error("unable to instantiate a remote store: %v", err) | ||
return err | ||
} | ||
if err := remote.RemoveAll(); err != 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.
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.