Skip to content
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

Refactor trust view command into a --pretty flag on trust inspect #934

Merged
merged 2 commits into from
Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cli/command/trust/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
Annotations: map[string]string{"experimentalCLI": ""},
}
cmd.AddCommand(
newViewCommand(dockerCli),
newRevokeCommand(dockerCli),
newSignCommand(dockerCli),
newTrustKeyCommand(dockerCli),
Expand Down
38 changes: 35 additions & 3 deletions cli/command/trust/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package trust

import (
"encoding/json"
"fmt"
"sort"

"github.com/docker/cli/cli"
Expand All @@ -11,24 +12,55 @@ import (
"github.com/theupdateframework/notary/tuf/data"
)

type inspectOptions struct {
remotes []string
// FIXME(n4ss): this is consistent with `docker service inspect` but we should provide
// a `--format` flag too. (format and pretty-print should be exclusive)
prettyPrint bool
}

func newInspectCommand(dockerCli command.Cli) *cobra.Command {
options := inspectOptions{}
cmd := &cobra.Command{
Use: "inspect IMAGE[:TAG] [IMAGE[:TAG]...]",
Short: "Return low-level information about keys and signatures",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runInspect(dockerCli, args)
options.remotes = args

return runInspect(dockerCli, options)
},
}

flags := cmd.Flags()
flags.BoolVar(&options.prettyPrint, "pretty", false, "Print the information in a human friendly format")

return cmd
}

func runInspect(dockerCli command.Cli, remotes []string) error {
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
if opts.prettyPrint {
var err error

for index, remote := range opts.remotes {
if err = prettyPrintTrustInfo(dockerCli, remote); err != nil {
return err
}

// Additional separator between the inspection output of each image
if index < len(opts.remotes)-1 {
fmt.Fprint(dockerCli.Out(), "\n\n")
}
}

return err
}

getRefFunc := func(ref string) (interface{}, []byte, error) {
i, err := getRepoTrustInfo(dockerCli, ref)
return nil, i, err
}
return inspect.Inspect(dockerCli.Out(), remotes, "", getRefFunc)
return inspect.Inspect(dockerCli.Out(), opts.remotes, "", getRefFunc)
}

func getRepoTrustInfo(cli command.Cli, remote string) ([]byte, error) {
Expand Down
27 changes: 8 additions & 19 deletions cli/command/trust/view.go → cli/command/trust/inspect_pretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,21 @@ import (
"fmt"
"io"
"sort"
"strings"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/spf13/cobra"
"github.com/theupdateframework/notary/client"
)

func newViewCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "view IMAGE[:TAG]",
Short: "Display detailed information about keys and signatures",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return viewTrustInfo(dockerCli, args[0])
},
}
return cmd
}

func viewTrustInfo(cli command.Cli, remote string) error {
func prettyPrintTrustInfo(cli command.Cli, remote string) error {
signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote)
if err != nil {
return err
}

if len(signatureRows) > 0 {
fmt.Fprintf(cli.Out(), "\nSignatures for %s\n\n", remote)

if err := printSignatures(cli.Out(), signatureRows); err != nil {
return err
}
Expand All @@ -42,22 +29,24 @@ func viewTrustInfo(cli command.Cli, remote string) error {

// If we do not have additional signers, do not display
if len(signerRoleToKeyIDs) > 0 {
fmt.Fprintf(cli.Out(), "\nList of signers and their keys for %s:\n\n", strings.Split(remote, ":")[0])
fmt.Fprintf(cli.Out(), "\nList of signers and their keys for %s\n\n", remote)
if err := printSignerInfo(cli.Out(), signerRoleToKeyIDs); err != nil {
return err
}
}

// This will always have the root and targets information
fmt.Fprintf(cli.Out(), "\nAdministrative keys for %s:\n", strings.Split(remote, ":")[0])
fmt.Fprintf(cli.Out(), "\nAdministrative keys for %s\n\n", remote)
printSortedAdminKeys(cli.Out(), adminRolesWithSigs)
return nil
}

func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) {
sort.Slice(adminRoles, func(i, j int) bool { return adminRoles[i].Name > adminRoles[j].Name })
for _, adminRole := range adminRoles {
fmt.Fprintf(out, "%s", formatAdminRole(adminRole))
if formattedAdminRole := formatAdminRole(adminRole); formattedAdminRole != "" {
fmt.Fprintf(out, " %s", formattedAdminRole)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,21 @@ import (
"github.com/theupdateframework/notary/tuf/data"
)

// TODO(n4ss): remove common tests with the regular inspect command

type fakeClient struct {
dockerClient.Client
}

func TestTrustViewCommandErrors(t *testing.T) {
func TestTrustInspectPrettyCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "not-enough-args",
expectedError: "requires exactly 1 argument",
},
{
name: "too-many-args",
args: []string{"remote1", "remote2"},
expectedError: "requires exactly 1 argument",
expectedError: "requires at least 1 argument",
},
{
name: "sha-reference",
Expand All @@ -48,104 +45,115 @@ func TestTrustViewCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := newViewCommand(
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
cmd.Flags().Set("pretty", "true")
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

func TestTrustViewCommandOfflineErrors(t *testing.T) {
func TestTrustInspectPrettyCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"nonexistent-reg-name.io/image"})
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")

cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd = newViewCommand(cli)
cmd = newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
}

func TestTrustViewCommandUninitializedErrors(t *testing.T) {
func TestTrustInspectPrettyCommandUninitializedErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"reg/unsigned-img"})
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img")

cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository)
cmd = newViewCommand(cli)
cmd = newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"reg/unsigned-img:tag"})
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img:tag")
}

func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) {
func TestTrustInspectPrettyCommandEmptyNotaryRepoErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"reg/img:unsigned-tag"})
cmd.SetOutput(ioutil.Discard)
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Contains(cli.OutBuffer().String(), "No signatures for reg/img:unsigned-tag"))
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img:"))
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img"))

cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd = newViewCommand(cli)
cmd = newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"reg/img"})
cmd.SetOutput(ioutil.Discard)
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Contains(cli.OutBuffer().String(), "No signatures for reg/img"))
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img:"))
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img"))
}

func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) {
func TestTrustInspectPrettyCommandFullRepoWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetLoadedWithNoSignersNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"signed-repo"})
assert.NilError(t, cmd.Execute())

golden.Assert(t, cli.OutBuffer().String(), "trust-view-full-repo-no-signers.golden")
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-pretty-full-repo-no-signers.golden")
}

func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) {
func TestTrustInspectPrettyCommandOneTagWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetLoadedWithNoSignersNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"signed-repo:green"})
assert.NilError(t, cmd.Execute())

golden.Assert(t, cli.OutBuffer().String(), "trust-view-one-tag-no-signers.golden")
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-pretty-one-tag-no-signers.golden")
}

func TestTrustViewCommandFullRepoWithSigners(t *testing.T) {
func TestTrustInspectPrettyCommandFullRepoWithSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"signed-repo"})
assert.NilError(t, cmd.Execute())

golden.Assert(t, cli.OutBuffer().String(), "trust-view-full-repo-with-signers.golden")
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-pretty-full-repo-with-signers.golden")
}

func TestTrustViewCommandUnsignedTagInSignedRepo(t *testing.T) {
func TestTrustInspectPrettyCommandUnsignedTagInSignedRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
cmd := newViewCommand(cli)
cmd := newInspectCommand(cli)
cmd.Flags().Set("pretty", "true")
cmd.SetArgs([]string{"signed-repo:unsigned"})
assert.NilError(t, cmd.Execute())

golden.Assert(t, cli.OutBuffer().String(), "trust-view-unsigned-tag-with-signers.golden")
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-pretty-unsigned-tag-with-signers.golden")
}

func TestNotaryRoleToSigner(t *testing.T) {
Expand Down
10 changes: 3 additions & 7 deletions cli/command/trust/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@ func TestTrustInspectCommandErrors(t *testing.T) {
}{
{
name: "not-enough-args",
expectedError: "requires exactly 1 argument",
},
{
name: "too-many-args",
args: []string{"remote1", "remote2"},
expectedError: "requires exactly 1 argument",
expectedError: "requires at least 1 argument",
},
{
name: "sha-reference",
Expand All @@ -37,8 +32,9 @@ func TestTrustInspectCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := newViewCommand(
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{}))
cmd.Flags().Set("pretty", "true")
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

Signatures for signed-repo

SIGNED TAG DIGEST SIGNERS
green 677265656e2d646967657374 (Repo Admin)

Administrative keys for signed-repo:
Repository Key: targetsID
Root Key: rootID
Administrative keys for signed-repo

Repository Key: targetsID
Root Key: rootID
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@

Signatures for signed-repo

SIGNED TAG DIGEST SIGNERS
blue 626c75652d646967657374 alice
green 677265656e2d646967657374 (Repo Admin)
red 7265642d646967657374 alice, bob

List of signers and their keys for signed-repo:
List of signers and their keys for signed-repo

SIGNER KEYS
alice A
bob B

Administrative keys for signed-repo:
Repository Key: targetsID
Root Key: rootID
Administrative keys for signed-repo

Repository Key: targetsID
Root Key: rootID
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

Signatures for signed-repo:green

SIGNED TAG DIGEST SIGNERS
green 677265656e2d646967657374 (Repo Admin)

Administrative keys for signed-repo:green

Repository Key: targetsID
Root Key: rootID
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

No signatures for signed-repo:unsigned


List of signers and their keys for signed-repo:unsigned

SIGNER KEYS
alice A
bob B

Administrative keys for signed-repo:unsigned

Repository Key: targetsID
Root Key: rootID
Loading