diff --git a/cli/command/trust/cli_test.go b/cli/command/trust/cli_test.go deleted file mode 100644 index 4ebc3ba71d..0000000000 --- a/cli/command/trust/cli_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package trust - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" - - "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/trust" - "github.com/docker/docker/client" - notaryclient "github.com/docker/notary/client" -) - -type notaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) - -// FakeCli emulates the default DockerCli -type FakeCli struct { - command.DockerCli - client client.APIClient - configfile *configfile.ConfigFile - out *command.OutStream - outBuffer *bytes.Buffer - err *bytes.Buffer - in *command.InStream - server command.ServerInfo - notaryClientFunc notaryClientFuncType -} - -// NewFakeCliWithNotaryClient returns a fake for the command.Cli interface with a specified Notary Client -func NewFakeCliWithNotaryClient(client client.APIClient, notaryClientFunc notaryClientFuncType) *FakeCli { - outBuffer := new(bytes.Buffer) - errBuffer := new(bytes.Buffer) - return &FakeCli{ - client: client, - out: command.NewOutStream(outBuffer), - outBuffer: outBuffer, - err: errBuffer, - in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))), - configfile: configfile.New("configfile"), - notaryClientFunc: notaryClientFunc, - } -} - -// SetIn sets the input of the cli to the specified ReadCloser -func (c *FakeCli) SetIn(in *command.InStream) { - c.in = in -} - -// SetErr sets the stderr stream for the cli to the specified io.Writer -func (c *FakeCli) SetErr(err *bytes.Buffer) { - c.err = err -} - -// SetConfigFile sets the "fake" config file -func (c *FakeCli) SetConfigFile(configfile *configfile.ConfigFile) { - c.configfile = configfile -} - -// Client returns a docker API client -func (c *FakeCli) Client() client.APIClient { - return c.client -} - -// Out returns the output stream (stdout) the cli should write on -func (c *FakeCli) Out() *command.OutStream { - return c.out -} - -// Err returns the output stream (stderr) the cli should write on -func (c *FakeCli) Err() io.Writer { - return c.err -} - -// In returns the input stream the cli will use -func (c *FakeCli) In() *command.InStream { - return c.in -} - -// ConfigFile returns the cli configfile object (to get client configuration) -func (c *FakeCli) ConfigFile() *configfile.ConfigFile { - return c.configfile -} - -// ServerInfo returns API server information for the server used by this client -func (c *FakeCli) ServerInfo() command.ServerInfo { - return c.server -} - -// OutBuffer returns the stdout buffer -func (c *FakeCli) OutBuffer() *bytes.Buffer { - return c.outBuffer -} - -// ErrBuffer Buffer returns the stderr buffer -func (c *FakeCli) ErrBuffer() *bytes.Buffer { - return c.err -} - -// NotaryClient returns an err for testing unless defined -func (c *FakeCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { - if c.notaryClientFunc != nil { - return c.notaryClientFunc(imgRefAndAuth, actions) - } - return nil, fmt.Errorf("no notary client available unless defined") -} diff --git a/cli/command/trust/client_test.go b/cli/command/trust/client_test.go new file mode 100644 index 0000000000..4a995157ca --- /dev/null +++ b/cli/command/trust/client_test.go @@ -0,0 +1,393 @@ +package trust + +import ( + "github.com/docker/cli/cli/trust" + "github.com/docker/notary/client" + "github.com/docker/notary/client/changelist" + "github.com/docker/notary/cryptoservice" + "github.com/docker/notary/passphrase" + "github.com/docker/notary/storage" + "github.com/docker/notary/trustmanager" + "github.com/docker/notary/tuf/data" + "github.com/docker/notary/tuf/signed" +) + +// Sample mock CLI interfaces + +func getOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { + return OfflineNotaryRepository{}, nil +} + +// OfflineNotaryRepository is a mock Notary repository that is offline +type OfflineNotaryRepository struct{} + +func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { + return storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { + return storage.ErrOffline{} +} +func (o OfflineNotaryRepository) Publish() error { + return storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error { + return nil +} +func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error { + return nil +} +func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { + return nil, storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { + return nil, storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { + return nil, storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) { + return changelist.NewMemChangelist(), nil +} + +func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { + return nil, storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) { + return nil, storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error { + return nil +} + +func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error { + return nil +} + +func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error { + return nil +} + +func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error { + return nil +} + +func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error { + return nil +} + +func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error { + return nil +} + +func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error { + return nil +} + +func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error { + return nil +} + +func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) { + return nil, nil +} + +func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { + return storage.ErrOffline{} +} + +func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService { + return nil +} + +func (o OfflineNotaryRepository) SetLegacyVersions(version int) {} + +func (o OfflineNotaryRepository) GetGUN() data.GUN { + return data.GUN("gun") +} + +func getUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { + return UninitializedNotaryRepository{}, nil +} + +// UninitializedNotaryRepository is a mock Notary repository that is uninintialized +// it builds on top of the OfflineNotaryRepository, instead returning ErrRepositoryNotExist +// for any online operation +type UninitializedNotaryRepository struct { + OfflineNotaryRepository +} + +func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { + return client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { + return client.ErrRepositoryNotExist{} +} +func (u UninitializedNotaryRepository) Publish() error { + return client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { + return nil, client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { + return nil, client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { + return nil, client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { + return nil, client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { + return nil, client.ErrRepositoryNotExist{} +} + +func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { + return client.ErrRepositoryNotExist{} +} + +func getEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { + return EmptyTargetsNotaryRepository{}, nil +} + +// EmptyTargetsNotaryRepository is a mock Notary repository that is initialized +// but does not have any signed targets +type EmptyTargetsNotaryRepository struct { + OfflineNotaryRepository +} + +func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { + return nil +} + +func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { + return nil +} +func (e EmptyTargetsNotaryRepository) Publish() error { + return nil +} + +func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { + return []*client.TargetWithRole{}, nil +} + +func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { + return nil, client.ErrNoSuchTarget(name) +} + +func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { + return nil, client.ErrNoSuchTarget(name) +} + +func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { + return []client.RoleWithSignatures{}, nil +} + +func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) { + return []data.Role{}, nil +} + +func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { + return nil +} + +func getLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { + return LoadedNotaryRepository{}, nil +} + +// LoadedNotaryRepository is a mock Notary repository that is loaded with targets, delegations, and keys +type LoadedNotaryRepository struct { + EmptyTargetsNotaryRepository + statefulCryptoService signed.CryptoService +} + +// LoadedNotaryRepository has three delegations: +// - targets/releases: includes keys A and B +// - targets/alice: includes key A +// - targets/bob: includes key B +var loadedReleasesRole = data.DelegationRole{ + BaseRole: data.BaseRole{ + Name: "targets/releases", + Keys: map[string]data.PublicKey{"A": nil, "B": nil}, + Threshold: 1, + }, +} +var loadedAliceRole = data.DelegationRole{ + BaseRole: data.BaseRole{ + Name: "targets/alice", + Keys: map[string]data.PublicKey{"A": nil}, + Threshold: 1, + }, +} +var loadedBobRole = data.DelegationRole{ + BaseRole: data.BaseRole{ + Name: "targets/bob", + Keys: map[string]data.PublicKey{"B": nil}, + Threshold: 1, + }, +} +var loadedDelegationRoles = []data.Role{ + { + Name: loadedReleasesRole.Name, + RootRole: data.RootRole{ + KeyIDs: []string{"A", "B"}, + Threshold: 1, + }, + }, + { + Name: loadedAliceRole.Name, + RootRole: data.RootRole{ + KeyIDs: []string{"A"}, + Threshold: 1, + }, + }, + { + Name: loadedBobRole.Name, + RootRole: data.RootRole{ + KeyIDs: []string{"B"}, + Threshold: 1, + }, + }, +} +var loadedTargetsRole = data.DelegationRole{ + BaseRole: data.BaseRole{ + Name: data.CanonicalTargetsRole, + Keys: map[string]data.PublicKey{"C": nil}, + Threshold: 1, + }, +} + +// LoadedNotaryRepository has three targets: +// - red: signed by targets/releases, targets/alice, targets/bob +// - blue: signed by targets/releases, targets/alice +// - green: signed by targets/releases +var loadedRedTarget = client.Target{ + Name: "red", + Hashes: data.Hashes{"sha256": []byte("red-digest")}, +} +var loadedBlueTarget = client.Target{ + Name: "blue", + Hashes: data.Hashes{"sha256": []byte("blue-digest")}, +} +var loadedGreenTarget = client.Target{ + Name: "green", + Hashes: data.Hashes{"sha256": []byte("green-digest")}, +} +var loadedTargets = []client.TargetSignedStruct{ + // red is signed by all three delegations + {Target: loadedRedTarget, Role: loadedReleasesRole}, + {Target: loadedRedTarget, Role: loadedAliceRole}, + {Target: loadedRedTarget, Role: loadedBobRole}, + + // blue is signed by targets/releases, targets/alice + {Target: loadedBlueTarget, Role: loadedReleasesRole}, + {Target: loadedBlueTarget, Role: loadedAliceRole}, + + // green is signed by targets/releases + {Target: loadedGreenTarget, Role: loadedReleasesRole}, +} + +func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { + filteredTargets := []*client.TargetWithRole{} + for _, tgt := range loadedTargets { + if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) { + filteredTargets = append(filteredTargets, &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name}) + } + } + return filteredTargets, nil +} + +func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { + for _, tgt := range loadedTargets { + if name == tgt.Target.Name { + if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) { + return &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name}, nil + } + } + } + return nil, client.ErrNoSuchTarget(name) +} + +func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { + if name == "" { + return loadedTargets, nil + } + filteredTargets := []client.TargetSignedStruct{} + for _, tgt := range loadedTargets { + if name == tgt.Target.Name { + filteredTargets = append(filteredTargets, tgt) + } + } + if len(filteredTargets) == 0 { + return nil, client.ErrNoSuchTarget(name) + } + return filteredTargets, nil +} + +func (l LoadedNotaryRepository) GetGUN() data.GUN { + return data.GUN("signed-repo") +} + +func (l LoadedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { + return loadedDelegationRoles, nil +} + +func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService { + if l.statefulCryptoService == nil { + // give it an in-memory cryptoservice with a root key and targets key + l.statefulCryptoService = cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("password"))) + l.statefulCryptoService.AddKey(data.CanonicalRootRole, l.GetGUN(), nil) + l.statefulCryptoService.AddKey(data.CanonicalTargetsRole, l.GetGUN(), nil) + } + return l.statefulCryptoService +} + +func getLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { + return LoadedWithNoSignersNotaryRepository{}, nil +} + +// LoadedWithNoSignersNotaryRepository is a mock Notary repository that is loaded with targets but no delegations +// it only contains the green target +type LoadedWithNoSignersNotaryRepository struct { + LoadedNotaryRepository +} + +func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { + filteredTargets := []*client.TargetWithRole{} + for _, tgt := range loadedTargets { + if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) { + filteredTargets = append(filteredTargets, &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name}) + } + } + return filteredTargets, nil +} + +func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { + if name == "" || name == loadedGreenTarget.Name { + return &client.TargetWithRole{Target: loadedGreenTarget, Role: data.CanonicalTargetsRole}, nil + } + return nil, client.ErrNoSuchTarget(name) +} + +func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { + if name == "" || name == loadedGreenTarget.Name { + return []client.TargetSignedStruct{{Target: loadedGreenTarget, Role: loadedTargetsRole}}, nil + } + return nil, client.ErrNoSuchTarget(name) +} + +func (l LoadedWithNoSignersNotaryRepository) GetDelegationRoles() ([]data.Role, error) { + return []data.Role{}, nil +} diff --git a/cli/command/trust/inspect_test.go b/cli/command/trust/inspect_test.go index 62778f00fe..1ea15551e8 100644 --- a/cli/command/trust/inspect_test.go +++ b/cli/command/trust/inspect_test.go @@ -11,10 +11,7 @@ import ( dockerClient "github.com/docker/docker/client" "github.com/docker/notary" "github.com/docker/notary/client" - "github.com/docker/notary/client/changelist" - "github.com/docker/notary/storage" "github.com/docker/notary/tuf/data" - "github.com/docker/notary/tuf/signed" "github.com/stretchr/testify/assert" ) @@ -58,13 +55,15 @@ func TestTrustInspectCommandErrors(t *testing.T) { } func TestTrustInspectCommandOfflineErrors(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getOfflineNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getOfflineNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"nonexistent-reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getOfflineNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getOfflineNotaryRepository) cmd = newInspectCommand(cli) cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -72,13 +71,15 @@ func TestTrustInspectCommandOfflineErrors(t *testing.T) { } func TestTrustInspectCommandUninitializedErrors(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getUninitializedNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getUninitializedNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"reg/unsigned-img"}) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getUninitializedNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getUninitializedNotaryRepository) cmd = newInspectCommand(cli) cmd.SetArgs([]string{"reg/unsigned-img:tag"}) cmd.SetOutput(ioutil.Discard) @@ -86,7 +87,8 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) { } func TestTrustInspectCommandEmptyNotaryRepoErrors(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getEmptyTargetsNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"reg/img:unsigned-tag"}) cmd.SetOutput(ioutil.Discard) @@ -94,7 +96,8 @@ func TestTrustInspectCommandEmptyNotaryRepoErrors(t *testing.T) { assert.Contains(t, cli.OutBuffer().String(), "No signatures for reg/img:unsigned-tag") assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for reg/img:") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getEmptyTargetsNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cmd = newInspectCommand(cli) cmd.SetArgs([]string{"reg/img"}) cmd.SetOutput(ioutil.Discard) @@ -105,8 +108,9 @@ func TestTrustInspectCommandEmptyNotaryRepoErrors(t *testing.T) { func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) cmd := newInspectCommand(cli) - cmd.SetArgs([]string{"alpine"}) + cmd.SetArgs([]string{"signed-repo"}) assert.NoError(t, cmd.Execute()) // Check for the signed tag headers @@ -114,7 +118,7 @@ func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { assert.Contains(t, cli.OutBuffer().String(), "DIGEST") assert.Contains(t, cli.OutBuffer().String(), "SIGNERS") // Check for the signer headers - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for alpine:") + assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") assert.Contains(t, cli.OutBuffer().String(), "(Repo Admin)") // no delegations on this repo assert.NotContains(t, cli.OutBuffer().String(), "List of signers and their keys:") @@ -122,66 +126,70 @@ func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) cmd := newInspectCommand(cli) - cmd.SetArgs([]string{"alpine:3.5"}) + cmd.SetArgs([]string{"signed-repo:green"}) assert.NoError(t, cmd.Execute()) assert.Contains(t, cli.OutBuffer().String(), "SIGNED TAG") assert.Contains(t, cli.OutBuffer().String(), "DIGEST") assert.Contains(t, cli.OutBuffer().String(), "SIGNERS") // Check for the signer headers - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for alpine:") + assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") // make sure the tag isn't included - assert.NotContains(t, cli.OutBuffer().String(), "Administrative keys for alpine:3.5") - assert.Contains(t, cli.OutBuffer().String(), "3.5") + assert.NotContains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:green") + assert.Contains(t, cli.OutBuffer().String(), "green") assert.Contains(t, cli.OutBuffer().String(), "(Repo Admin)") // no delegations on this repo - assert.NotContains(t, cli.OutBuffer().String(), "3.6") + assert.NotContains(t, cli.OutBuffer().String(), "alice") + assert.NotContains(t, cli.OutBuffer().String(), "bob") assert.NotContains(t, cli.OutBuffer().String(), "List of signers and their keys:") } func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getLoadedNotaryRepository) cmd := newInspectCommand(cli) - cmd.SetArgs([]string{"dockerorcadev/trust-fixture"}) + cmd.SetArgs([]string{"signed-repo"}) assert.NoError(t, cmd.Execute()) - // Check for the signed tag headers + // Check for the signed tag headers and contents assert.Contains(t, cli.OutBuffer().String(), "SIGNED TAG") assert.Contains(t, cli.OutBuffer().String(), "DIGEST") assert.Contains(t, cli.OutBuffer().String(), "SIGNERS") - // Check for the signer headers + assert.Contains(t, cli.OutBuffer().String(), "blue 626c75652d646967657374 alice") + assert.Contains(t, cli.OutBuffer().String(), "green 677265656e2d646967657374 (Repo Admin)") + assert.Contains(t, cli.OutBuffer().String(), "red 7265642d646967657374 alice, bob") + + // Check for the signer headers and contents assert.Contains(t, cli.OutBuffer().String(), "List of signers and their keys:") assert.Contains(t, cli.OutBuffer().String(), "SIGNER") assert.Contains(t, cli.OutBuffer().String(), "KEYS") - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for dockerorcadev/trust-fixture:") - assert.Contains(t, cli.OutBuffer().String(), "Repository Key") - assert.Contains(t, cli.OutBuffer().String(), "Root Key") - // all signers have names - assert.NotContains(t, cli.OutBuffer().String(), "(Repo Admin)") + assert.Contains(t, cli.OutBuffer().String(), "alice A") + assert.Contains(t, cli.OutBuffer().String(), "bob B") + assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") } func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getLoadedNotaryRepository) cmd := newInspectCommand(cli) - cmd.SetArgs([]string{"dockerorcadev/trust-fixture:unsigned"}) + cmd.SetArgs([]string{"signed-repo:unsigned"}) assert.NoError(t, cmd.Execute()) // Check that the signatures table does not show up, and instead we get the message - assert.Contains(t, cli.OutBuffer().String(), "No signatures for dockerorcadev/trust-fixture:unsigned") + assert.Contains(t, cli.OutBuffer().String(), "No signatures for signed-repo:unsigned") assert.NotContains(t, cli.OutBuffer().String(), "SIGNED TAG") assert.NotContains(t, cli.OutBuffer().String(), "DIGEST") assert.NotContains(t, cli.OutBuffer().String(), "SIGNERS") - // Check for the signer headers + + // Check for the signer headers and contents assert.Contains(t, cli.OutBuffer().String(), "List of signers and their keys:") assert.Contains(t, cli.OutBuffer().String(), "SIGNER") assert.Contains(t, cli.OutBuffer().String(), "KEYS") - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for dockerorcadev/trust-fixture:") - // make sure the tag isn't included - assert.NotContains(t, cli.OutBuffer().String(), "Administrative keys for dockerorcadev/trust-fixture:unsigned") - assert.Contains(t, cli.OutBuffer().String(), "Repository Key") - assert.Contains(t, cli.OutBuffer().String(), "Root Key") - // all signers have names - assert.NotContains(t, cli.OutBuffer().String(), "(Repo Admin)") + assert.Contains(t, cli.OutBuffer().String(), "alice A") + assert.Contains(t, cli.OutBuffer().String(), "bob B") + assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") + assert.NotContains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:unsigned") } func TestNotaryRoleToSigner(t *testing.T) { @@ -468,194 +476,3 @@ func TestFormatAdminRole(t *testing.T) { targetsRoleWithSigs := client.RoleWithSignatures{Role: targetsRole, Signatures: nil} assert.Equal(t, "Repository Key:\tabc, key11, key99\n", formatAdminRole(targetsRoleWithSigs)) } - -// Sample mock CLI interfaces - -func getOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { - return OfflineNotaryRepository{}, nil -} - -// OfflineNotaryRepository is a mock Notary repository that is offline -type OfflineNotaryRepository struct{} - -func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { - return storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { - return storage.ErrOffline{} -} -func (o OfflineNotaryRepository) Publish() error { - return storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error { - return nil -} -func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error { - return nil -} -func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { - return nil, storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { - return nil, storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { - return nil, storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) { - return changelist.NewMemChangelist(), nil -} - -func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { - return nil, storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) { - return nil, storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error { - return nil -} - -func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error { - return nil -} - -func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error { - return nil -} - -func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error { - return nil -} - -func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error { - return nil -} - -func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error { - return nil -} - -func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error { - return nil -} - -func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error { - return nil -} - -func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) { - return nil, nil -} - -func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { - return storage.ErrOffline{} -} - -func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService { - return nil -} - -func (o OfflineNotaryRepository) SetLegacyVersions(version int) {} - -func (o OfflineNotaryRepository) GetGUN() data.GUN { - return data.GUN("gun") -} - -func getUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { - return UninitializedNotaryRepository{}, nil -} - -// UninitializedNotaryRepository is a mock Notary repository that is uninintialized -// it builds on top of the OfflineNotaryRepository, instead returning ErrRepoNotInitialized -// for any online operation -type UninitializedNotaryRepository struct { - OfflineNotaryRepository -} - -func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { - return client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { - return client.ErrRepositoryNotExist{} -} -func (u UninitializedNotaryRepository) Publish() error { - return client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { - return nil, client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { - return nil, client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { - return nil, client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { - return nil, client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { - return nil, client.ErrRepositoryNotExist{} -} - -func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { - return client.ErrRepositoryNotExist{} -} - -func getEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { - return EmptyTargetsNotaryRepository{}, nil -} - -// EmptyTargetsNotaryRepository is a mock Notary repository that is initialized -// but does not have any signed targets -type EmptyTargetsNotaryRepository struct { - OfflineNotaryRepository -} - -func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { - return nil -} - -func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { - return nil -} -func (e EmptyTargetsNotaryRepository) Publish() error { - return nil -} - -func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { - return []*client.TargetWithRole{}, nil -} - -func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { - return nil, client.ErrNoSuchTarget(name) -} - -func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { - return nil, client.ErrNoSuchTarget(name) -} - -func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { - return []client.RoleWithSignatures{}, nil -} - -func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) { - return []data.Role{}, nil -} - -func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { - return nil -} diff --git a/cli/command/trust/revoke_test.go b/cli/command/trust/revoke_test.go index 92824e70bc..077ee86b1e 100644 --- a/cli/command/trust/revoke_test.go +++ b/cli/command/trust/revoke_test.go @@ -38,11 +38,6 @@ func TestTrustRevokeCommandErrors(t *testing.T) { args: []string{"ALPINE"}, expectedError: "invalid reference format", }, - // { - // name: "no-signing-keys-for-image", - // args: []string{"alpine", "-y"}, - // expectedError: "could not remove signature for alpine: could not find necessary signing keys", - // }, { name: "digest-reference", args: []string{"ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"}, @@ -59,20 +54,23 @@ func TestTrustRevokeCommandErrors(t *testing.T) { } func TestTrustRevokeCommandOfflineErrors(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getOfflineNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getOfflineNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) assert.NoError(t, cmd.Execute()) assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getOfflineNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getOfflineNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: client is offline") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getOfflineNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getOfflineNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -80,20 +78,23 @@ func TestTrustRevokeCommandOfflineErrors(t *testing.T) { } func TestTrustRevokeCommandUninitializedErrors(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getUninitializedNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getUninitializedNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) assert.NoError(t, cmd.Execute()) assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getUninitializedNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getUninitializedNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: does not have trust data for") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getUninitializedNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getUninitializedNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -101,21 +102,24 @@ func TestTrustRevokeCommandUninitializedErrors(t *testing.T) { } func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getEmptyTargetsNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) assert.NoError(t, cmd.Execute()) assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getEmptyTargetsNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetOutput(ioutil.Discard) assert.NoError(t, cmd.Execute()) assert.Contains(t, cli.OutBuffer().String(), "Successfully deleted signature for reg-name.io/image") - cli = NewFakeCliWithNotaryClient(&fakeClient{}, getEmptyTargetsNotaryRepository) + cli = test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -125,6 +129,7 @@ func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) { func TestNewRevokeTrustAllSigConfirmation(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"alpine"}) assert.NoError(t, cmd.Execute()) diff --git a/cli/command/trust/sign_test.go b/cli/command/trust/sign_test.go index 98b5ba58ec..d75e212a91 100644 --- a/cli/command/trust/sign_test.go +++ b/cli/command/trust/sign_test.go @@ -41,11 +41,6 @@ func TestTrustSignCommandErrors(t *testing.T) { args: []string{"870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"}, expectedError: "invalid repository name", }, - { - name: "nonexistent-reg", - args: []string{"nonexistent-reg-name.io/image:tag"}, - expectedError: "no such host", - }, { name: "invalid-img-reference", args: []string{"ALPINE:latest"}, @@ -77,7 +72,8 @@ func TestTrustSignCommandErrors(t *testing.T) { } func TestTrustSignCommandOfflineErrors(t *testing.T) { - cli := NewFakeCliWithNotaryClient(&fakeClient{}, getOfflineNotaryRepository) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getOfflineNotaryRepository) cmd := newSignCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) diff --git a/internal/test/cli.go b/internal/test/cli.go index 0b3ee28978..1002c166d6 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -2,25 +2,31 @@ package test import ( "bytes" + "fmt" "io" "io/ioutil" "strings" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/trust" "github.com/docker/docker/client" + notaryclient "github.com/docker/notary/client" ) +type notaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) + // FakeCli emulates the default DockerCli type FakeCli struct { command.DockerCli - client client.APIClient - configfile *configfile.ConfigFile - out *command.OutStream - outBuffer *bytes.Buffer - err *bytes.Buffer - in *command.InStream - server command.ServerInfo + client client.APIClient + configfile *configfile.ConfigFile + out *command.OutStream + outBuffer *bytes.Buffer + err *bytes.Buffer + in *command.InStream + server command.ServerInfo + notaryClientFunc notaryClientFuncType } // NewFakeCli returns a fake for the command.Cli interface @@ -91,3 +97,16 @@ func (c *FakeCli) OutBuffer() *bytes.Buffer { func (c *FakeCli) ErrBuffer() *bytes.Buffer { return c.err } + +// SetNotaryClient sets the internal getter for retrieving a NotaryClient +func (c *FakeCli) SetNotaryClient(notaryClientFunc notaryClientFuncType) { + c.notaryClientFunc = notaryClientFunc +} + +// NotaryClient returns an err for testing unless defined +func (c *FakeCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { + if c.notaryClientFunc != nil { + return c.notaryClientFunc(imgRefAndAuth, actions) + } + return nil, fmt.Errorf("no notary client available unless defined") +}