blob: fe16121ce7ba53007779ceb12032576259eec358 [file] [edit]
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.26
package trust
import (
"cmp"
"context"
"encoding/json"
"fmt"
"slices"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/inspect"
"github.com/spf13/cobra"
"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 {
options.remotes = args
return runInspect(cmd.Context(), dockerCLI, options)
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.BoolVar(&options.prettyPrint, "pretty", false, "Print the information in a human friendly format")
return cmd
}
func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error {
if opts.prettyPrint {
var err error
for index, remote := range opts.remotes {
if err = prettyPrintTrustInfo(ctx, 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) (any, []byte, error) {
i, err := getRepoTrustInfo(ctx, dockerCLI, ref)
return nil, i, err
}
return inspect.Inspect(dockerCLI.Out(), opts.remotes, "", getRefFunc)
}
func getRepoTrustInfo(ctx context.Context, dockerCLI command.Cli, remote string) ([]byte, error) {
signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(ctx, dockerCLI, remote)
if err != nil {
return []byte{}, err
}
// process the signatures to include repo admin if signed by the base targets role
for idx, sig := range signatureRows {
if len(sig.Signers) == 0 {
signatureRows[idx].Signers = []string{releasedRoleName}
}
}
signerList := []trustSigner{}
for signerName, signerKeys := range getDelegationRoleToKeyMap(delegationRoles) {
signerKeyList := make([]trustKey, 0, len(signerKeys))
for _, keyID := range signerKeys {
signerKeyList = append(signerKeyList, trustKey{ID: keyID})
}
signerList = append(signerList, trustSigner{signerName, signerKeyList})
}
// Sort by name in descending order.
slices.SortFunc(signerList, func(a, b trustSigner) int { return cmp.Compare(b.Name, a.Name) })
var adminList []trustSigner
for _, adminRole := range adminRolesWithSigs {
var name string
switch adminRole.Name {
case data.CanonicalRootRole:
name = "Root"
case data.CanonicalTargetsRole:
name = "Repository"
default:
continue
}
keys := make([]trustKey, 0, len(adminRole.KeyIDs))
for _, keyID := range adminRole.KeyIDs {
keys = append(keys, trustKey{ID: keyID})
}
adminList = append(adminList, trustSigner{name, keys})
}
// Sort by name in descending order.
slices.SortFunc(adminList, func(a, b trustSigner) int { return cmp.Compare(b.Name, a.Name) })
return json.Marshal(trustRepo{
Name: remote,
SignedTags: signatureRows,
Signers: signerList,
AdministrativeKeys: adminList,
})
}