From 2793731977bd2fb19941147912b2d0aec920762e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 1 Mar 2025 01:34:23 +0100 Subject: [PATCH 1/2] cli/command: internalize constructing Notary client The CLI.NotaryClient method is a shallow wrapper around trust.GetNotaryRepository and only depends on the CLI itself to pass its StdErr/StrOut streams. - This patch inlines the code to produce the client, skipping the wrapper. - Define a local interface for some tests where a dummy notary client was used. Signed-off-by: Sebastiaan van Stijn --- cli/command/image/trust.go | 18 ++++++++++++++++-- cli/command/trust/common.go | 16 +++++++++++++++- cli/command/trust/revoke.go | 2 +- cli/command/trust/sign.go | 2 +- cli/command/trust/signer_add.go | 2 +- cli/command/trust/signer_remove.go | 2 +- 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index c47e174737..89d6e54eae 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -30,6 +30,20 @@ type target struct { size int64 } +// notaryClientProvider is used in tests to provide a dummy notary client. +type notaryClientProvider interface { + NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) +} + +// newNotaryClient provides a Notary Repository to interact with signed metadata for an image. +func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (client.Repository, error) { + if ncp, ok := cli.(notaryClientProvider); ok { + // notaryClientProvider is used in tests to provide a dummy notary client. + return ncp.NotaryClient(imgRefAndAuth, []string{"pull"}) + } + return trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), "pull") +} + // TrustedPush handles content trust pushing of an image func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, options image.PushOptions) error { responseBody, err := cli.Client().ImagePush(ctx, reference.FamiliarString(ref), options) @@ -200,7 +214,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image } func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) ([]target, error) { - notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly) + notaryRepo, err := newNotaryClient(cli, imgRefAndAuth) if err != nil { return nil, errors.Wrap(err, "error establishing connection to trust repository") } @@ -280,7 +294,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT return nil, err } - notaryRepo, err := cli.NotaryClient(imgRefAndAuth, []string{"pull"}) + notaryRepo, err := newNotaryClient(cli, imgRefAndAuth) if err != nil { return nil, errors.Wrap(err, "error establishing connection to trust repository") } diff --git a/cli/command/trust/common.go b/cli/command/trust/common.go index 1248bb487e..7d1d50e296 100644 --- a/cli/command/trust/common.go +++ b/cli/command/trust/common.go @@ -49,6 +49,20 @@ type trustKey struct { ID string `json:",omitempty"` } +// notaryClientProvider is used in tests to provide a dummy notary client. +type notaryClientProvider interface { + NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) +} + +// newNotaryClient provides a Notary Repository to interact with signed metadata for an image. +func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { + if ncp, ok := cli.(notaryClientProvider); ok { + // notaryClientProvider is used in tests to provide a dummy notary client. + return ncp.NotaryClient(imgRefAndAuth, actions) + } + return trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...) +} + // lookupTrustInfo returns processed signature and role information about a notary repository. // This information is to be pretty printed or serialized into a machine-readable format. func lookupTrustInfo(ctx context.Context, cli command.Cli, remote string) ([]trustTagRow, []client.RoleWithSignatures, []data.Role, error) { @@ -57,7 +71,7 @@ func lookupTrustInfo(ctx context.Context, cli command.Cli, remote string) ([]tru return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err } tag := imgRefAndAuth.Tag() - notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly) + notaryRepo, err := newNotaryClient(cli, imgRefAndAuth, trust.ActionsPullOnly) if err != nil { return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err) } diff --git a/cli/command/trust/revoke.go b/cli/command/trust/revoke.go index a30d6cccc6..7d1651679e 100644 --- a/cli/command/trust/revoke.go +++ b/cli/command/trust/revoke.go @@ -53,7 +53,7 @@ func revokeTrust(ctx context.Context, dockerCLI command.Cli, remote string, opti } } - notaryRepo, err := dockerCLI.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) + notaryRepo, err := newNotaryClient(dockerCLI, imgRefAndAuth, trust.ActionsPushAndPull) if err != nil { return err } diff --git a/cli/command/trust/sign.go b/cli/command/trust/sign.go index bf974d0dc0..1875e257b5 100644 --- a/cli/command/trust/sign.go +++ b/cli/command/trust/sign.go @@ -52,7 +52,7 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption return err } - notaryRepo, err := dockerCLI.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) + notaryRepo, err := newNotaryClient(dockerCLI, imgRefAndAuth, trust.ActionsPushAndPull) if err != nil { return trust.NotaryError(imgRefAndAuth.Reference().Name(), err) } diff --git a/cli/command/trust/signer_add.go b/cli/command/trust/signer_add.go index 1593a591ad..9822df3d17 100644 --- a/cli/command/trust/signer_add.go +++ b/cli/command/trust/signer_add.go @@ -85,7 +85,7 @@ func addSignerToRepo(ctx context.Context, dockerCLI command.Cli, signerName stri return err } - notaryRepo, err := dockerCLI.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) + notaryRepo, err := newNotaryClient(dockerCLI, imgRefAndAuth, trust.ActionsPushAndPull) if err != nil { return trust.NotaryError(imgRefAndAuth.Reference().Name(), err) } diff --git a/cli/command/trust/signer_remove.go b/cli/command/trust/signer_remove.go index d2c5e9c711..ff6a29d5c5 100644 --- a/cli/command/trust/signer_remove.go +++ b/cli/command/trust/signer_remove.go @@ -103,7 +103,7 @@ func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, si if signerDelegation == releasesRoleTUFName { return false, errors.Errorf("releases is a reserved keyword and cannot be removed") } - notaryRepo, err := dockerCLI.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) + notaryRepo, err := newNotaryClient(dockerCLI, imgRefAndAuth, trust.ActionsPushAndPull) if err != nil { return false, trust.NotaryError(imgRefAndAuth.Reference().Name(), err) } From 9bc16bbde085c618d3bddfb633560ab9af83a58d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 4 Mar 2025 14:29:20 +0100 Subject: [PATCH 2/2] cli/command: deprecate Cli.NotaryClient This method is a shallow wrapper around trust.GetNotaryRepository, but due to its signature resulted in the trust package, and notary dependencies to become a dependency of the CLI. Consequence of this was that cli-plugins, which need the cli/command package, would also get notary and its dependencies as a dependency. It is no longer used in our code, which constructs the client in packages that need it, so we can deprecate this method. Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 9 +-------- cli/command/cli_deprecated.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 cli/command/cli_deprecated.go diff --git a/cli/command/cli.go b/cli/command/cli.go index 1452e1a240..dfb4fba69a 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -25,7 +25,6 @@ import ( manifeststore "github.com/docker/cli/cli/manifest/store" registryclient "github.com/docker/cli/cli/registry/client" "github.com/docker/cli/cli/streams" - "github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/version" dopts "github.com/docker/cli/opts" "github.com/docker/docker/api" @@ -36,7 +35,6 @@ import ( "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" "github.com/spf13/cobra" - notaryclient "github.com/theupdateframework/notary/client" ) const defaultInitTimeout = 2 * time.Second @@ -56,7 +54,6 @@ type Cli interface { Apply(ops ...CLIOption) error ConfigFile() *configfile.ConfigFile ServerInfo() ServerInfo - NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) DefaultVersion() string CurrentVersion() string ManifestStore() manifeststore.Store @@ -67,6 +64,7 @@ type Cli interface { CurrentContext() string DockerEndpoint() docker.Endpoint TelemetryClient + DeprecatedNotaryClient } // DockerCli is an instance the docker command line client. @@ -405,11 +403,6 @@ func (cli *DockerCli) initializeFromClient() { cli.client.NegotiateAPIVersionPing(ping) } -// NotaryClient provides a Notary Repository to interact with signed metadata for an image -func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { - return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...) -} - // ContextStore returns the ContextStore func (cli *DockerCli) ContextStore() store.Store { return cli.contextStore diff --git a/cli/command/cli_deprecated.go b/cli/command/cli_deprecated.go new file mode 100644 index 0000000000..d179f42f22 --- /dev/null +++ b/cli/command/cli_deprecated.go @@ -0,0 +1,18 @@ +package command + +import ( + "github.com/docker/cli/cli/trust" + notaryclient "github.com/theupdateframework/notary/client" +) + +type DeprecatedNotaryClient interface { + // NotaryClient provides a Notary Repository to interact with signed metadata for an image + // + // Deprecated: use [trust.GetNotaryRepository] instead. This method is no longer used and will be removed in the next release. + NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) +} + +// NotaryClient provides a Notary Repository to interact with signed metadata for an image +func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { + return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...) +}