| // 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, |
| }) |
| } |