From 909e32b27d64bd626deff398df59931a753a789b Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Tue, 21 Oct 2025 19:13:45 -0500 Subject: [PATCH] client: refactor plugin api client functions to define options/results structs Co-authored-by: Claude Signed-off-by: Austin Vazquez Signed-off-by: Sebastiaan van Stijn --- client/client_interfaces.go | 19 +++-- client/plugin_create.go | 9 ++- client/plugin_disable.go | 11 ++- client/plugin_disable_test.go | 8 +- client/plugin_enable.go | 11 ++- client/plugin_enable_test.go | 8 +- client/plugin_install.go | 73 ++++++++++++++----- client/plugin_list.go | 15 ++-- client/plugin_list_test.go | 4 +- client/plugin_push.go | 20 +++-- client/plugin_push_test.go | 8 +- client/plugin_remove.go | 11 ++- client/plugin_remove_test.go | 8 +- client/plugin_set.go | 18 ++++- client/plugin_set_test.go | 8 +- client/plugin_upgrade.go | 49 ++++++++++++- .../plugin/authz/authz_plugin_v2_test.go | 2 +- integration/plugin/common/plugin_test.go | 29 ++++---- .../plugin/logging/logging_linux_test.go | 7 +- integration/plugin/logging/read_test.go | 2 +- integration/plugin/logging/validation_test.go | 6 +- integration/plugin/volumes/mounts_test.go | 4 +- integration/service/plugin_test.go | 8 +- internal/testutil/environment/clean.go | 6 +- internal/testutil/environment/protect.go | 4 +- internal/testutil/fixtures/plugin/plugin.go | 5 +- .../moby/moby/client/client_interfaces.go | 19 +++-- .../moby/moby/client/plugin_create.go | 9 ++- .../moby/moby/client/plugin_disable.go | 11 ++- .../moby/moby/client/plugin_enable.go | 11 ++- .../moby/moby/client/plugin_install.go | 73 ++++++++++++++----- .../moby/moby/client/plugin_list.go | 15 ++-- .../moby/moby/client/plugin_push.go | 20 +++-- .../moby/moby/client/plugin_remove.go | 11 ++- .../github.com/moby/moby/client/plugin_set.go | 18 ++++- .../moby/moby/client/plugin_upgrade.go | 49 ++++++++++++- 36 files changed, 427 insertions(+), 162 deletions(-) diff --git a/client/client_interfaces.go b/client/client_interfaces.go index f121c3e14c..2ab3c85788 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -9,7 +9,6 @@ import ( "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/network" - "github.com/moby/moby/api/types/plugin" "github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/system" @@ -145,16 +144,16 @@ type NodeAPIClient interface { // PluginAPIClient defines API client methods for the plugins type PluginAPIClient interface { - PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error) - PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error - PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error - PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error - PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) - PluginUpgrade(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) - PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) - PluginSet(ctx context.Context, name string, args []string) error + PluginList(ctx context.Context, options PluginListOptions) (PluginListResult, error) + PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) (PluginRemoveResult, error) + PluginEnable(ctx context.Context, name string, options PluginEnableOptions) (PluginEnableResult, error) + PluginDisable(ctx context.Context, name string, options PluginDisableOptions) (PluginDisableResult, error) + PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (PluginInstallResult, error) + PluginUpgrade(ctx context.Context, name string, options PluginUpgradeOptions) (PluginUpgradeResult, error) + PluginPush(ctx context.Context, name string, options PluginPushOptions) (PluginPushResult, error) + PluginSet(ctx context.Context, name string, options PluginSetOptions) (PluginSetResult, error) PluginInspect(ctx context.Context, name string, options PluginInspectOptions) (PluginInspectResult, error) - PluginCreate(ctx context.Context, createContext io.Reader, options PluginCreateOptions) error + PluginCreate(ctx context.Context, createContext io.Reader, options PluginCreateOptions) (PluginCreateResult, error) } // ServiceAPIClient defines API client methods for the services diff --git a/client/plugin_create.go b/client/plugin_create.go index c143db7061..c1a2dd5a6c 100644 --- a/client/plugin_create.go +++ b/client/plugin_create.go @@ -12,8 +12,13 @@ type PluginCreateOptions struct { RepoName string } +// PluginCreateResult represents the result of a plugin create operation. +type PluginCreateResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginCreate creates a plugin -func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions PluginCreateOptions) error { +func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions PluginCreateOptions) (PluginCreateResult, error) { headers := http.Header(make(map[string][]string)) headers.Set("Content-Type", "application/x-tar") @@ -22,5 +27,5 @@ func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, cr resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers) defer ensureReaderClosed(resp) - return err + return PluginCreateResult{}, err } diff --git a/client/plugin_disable.go b/client/plugin_disable.go index b368dc6e58..65ab0aa004 100644 --- a/client/plugin_disable.go +++ b/client/plugin_disable.go @@ -10,11 +10,16 @@ type PluginDisableOptions struct { Force bool } +// PluginDisableResult represents the result of a plugin disable operation. +type PluginDisableResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginDisable disables a plugin -func (cli *Client) PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error { +func (cli *Client) PluginDisable(ctx context.Context, name string, options PluginDisableOptions) (PluginDisableResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginDisableResult{}, err } query := url.Values{} if options.Force { @@ -22,5 +27,5 @@ func (cli *Client) PluginDisable(ctx context.Context, name string, options Plugi } resp, err := cli.post(ctx, "/plugins/"+name+"/disable", query, nil, nil) defer ensureReaderClosed(resp) - return err + return PluginDisableResult{}, err } diff --git a/client/plugin_disable_test.go b/client/plugin_disable_test.go index 9e626c4794..677661b0e7 100644 --- a/client/plugin_disable_test.go +++ b/client/plugin_disable_test.go @@ -16,14 +16,14 @@ func TestPluginDisableError(t *testing.T) { client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) - err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{}) + _, err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - err = client.PluginDisable(context.Background(), "", PluginDisableOptions{}) + _, err = client.PluginDisable(context.Background(), "", PluginDisableOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - err = client.PluginDisable(context.Background(), " ", PluginDisableOptions{}) + _, err = client.PluginDisable(context.Background(), " ", PluginDisableOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -42,6 +42,6 @@ func TestPluginDisable(t *testing.T) { })) assert.NilError(t, err) - err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{}) + _, err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{}) assert.NilError(t, err) } diff --git a/client/plugin_enable.go b/client/plugin_enable.go index c79361a473..7c3e26b67a 100644 --- a/client/plugin_enable.go +++ b/client/plugin_enable.go @@ -11,16 +11,21 @@ type PluginEnableOptions struct { Timeout int } +// PluginEnableResult represents the result of a plugin enable operation. +type PluginEnableResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginEnable enables a plugin -func (cli *Client) PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error { +func (cli *Client) PluginEnable(ctx context.Context, name string, options PluginEnableOptions) (PluginEnableResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginEnableResult{}, err } query := url.Values{} query.Set("timeout", strconv.Itoa(options.Timeout)) resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil) defer ensureReaderClosed(resp) - return err + return PluginEnableResult{}, err } diff --git a/client/plugin_enable_test.go b/client/plugin_enable_test.go index 3bf067b187..8ff946cf85 100644 --- a/client/plugin_enable_test.go +++ b/client/plugin_enable_test.go @@ -16,14 +16,14 @@ func TestPluginEnableError(t *testing.T) { client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) - err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{}) + _, err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - err = client.PluginEnable(context.Background(), "", PluginEnableOptions{}) + _, err = client.PluginEnable(context.Background(), "", PluginEnableOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - err = client.PluginEnable(context.Background(), " ", PluginEnableOptions{}) + _, err = client.PluginEnable(context.Background(), " ", PluginEnableOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -42,6 +42,6 @@ func TestPluginEnable(t *testing.T) { })) assert.NilError(t, err) - err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{}) + _, err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{}) assert.NilError(t, err) } diff --git a/client/plugin_install.go b/client/plugin_install.go index 2ec6f062e7..a589b2e1fd 100644 --- a/client/plugin_install.go +++ b/client/plugin_install.go @@ -33,17 +33,23 @@ type PluginInstallOptions struct { Args []string } +// PluginInstallResult holds the result of a plugin install operation. +// It is an io.ReadCloser from which the caller can read installation progress or result. +type PluginInstallResult struct { + io.ReadCloser +} + // PluginInstall installs a plugin -func (cli *Client) PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (_ io.ReadCloser, retErr error) { +func (cli *Client) PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (_ PluginInstallResult, retErr error) { query := url.Values{} if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil { - return nil, fmt.Errorf("invalid remote reference: %w", err) + return PluginInstallResult{}, fmt.Errorf("invalid remote reference: %w", err) } query.Set("remote", options.RemoteRef) - privileges, err := cli.checkPluginPermissions(ctx, query, options) + privileges, err := cli.checkPluginPermissions(ctx, query, &options) if err != nil { - return nil, err + return PluginInstallResult{}, err } // set name for plugin pull, if empty should default to remote reference @@ -51,7 +57,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth) if err != nil { - return nil, err + return PluginInstallResult{}, err } name = resp.Header.Get("Docker-Plugin-Name") @@ -70,7 +76,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi } }() if len(options.Args) > 0 { - if err := cli.PluginSet(ctx, name, options.Args); err != nil { + if _, err := cli.PluginSet(ctx, name, PluginSetOptions{Args: options.Args}); err != nil { _ = pw.CloseWithError(err) return } @@ -81,10 +87,10 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi return } - enableErr := cli.PluginEnable(ctx, name, PluginEnableOptions{Timeout: 0}) + _, enableErr := cli.PluginEnable(ctx, name, PluginEnableOptions{Timeout: 0}) _ = pw.CloseWithError(enableErr) }() - return pr, nil + return PluginInstallResult{pr}, nil } func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { @@ -99,17 +105,17 @@ func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileg }) } -func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options PluginInstallOptions) (plugin.Privileges, error) { - resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) - if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { +func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options pluginOptions) (plugin.Privileges, error) { + resp, err := cli.tryPluginPrivileges(ctx, query, options.getRegistryAuth()) + if cerrdefs.IsUnauthorized(err) && options.getPrivilegeFunc() != nil { // TODO: do inspect before to check existing name before checking privileges - newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) + newAuthHeader, privilegeErr := options.getPrivilegeFunc()(ctx) if privilegeErr != nil { ensureReaderClosed(resp) return nil, privilegeErr } - options.RegistryAuth = newAuthHeader - resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) + options.setRegistryAuth(newAuthHeader) + resp, err = cli.tryPluginPrivileges(ctx, query, options.getRegistryAuth()) } if err != nil { ensureReaderClosed(resp) @@ -123,14 +129,47 @@ func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, } ensureReaderClosed(resp) - if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { - accept, err := options.AcceptPermissionsFunc(ctx, privileges) + if !options.getAcceptAllPermissions() && options.getAcceptPermissionsFunc() != nil && len(privileges) > 0 { + accept, err := options.getAcceptPermissionsFunc()(ctx, privileges) if err != nil { return nil, err } if !accept { - return nil, errors.New("permission denied while installing plugin " + options.RemoteRef) + return nil, errors.New("permission denied while installing plugin " + options.getRemoteRef()) } } return privileges, nil } + +type pluginOptions interface { + getRegistryAuth() string + setRegistryAuth(string) + getPrivilegeFunc() func(context.Context) (string, error) + getAcceptAllPermissions() bool + getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) + getRemoteRef() string +} + +func (o *PluginInstallOptions) getRegistryAuth() string { + return o.RegistryAuth +} + +func (o *PluginInstallOptions) setRegistryAuth(auth string) { + o.RegistryAuth = auth +} + +func (o *PluginInstallOptions) getPrivilegeFunc() func(context.Context) (string, error) { + return o.PrivilegeFunc +} + +func (o *PluginInstallOptions) getAcceptAllPermissions() bool { + return o.AcceptAllPermissions +} + +func (o *PluginInstallOptions) getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) { + return o.AcceptPermissionsFunc +} + +func (o *PluginInstallOptions) getRemoteRef() string { + return o.RemoteRef +} diff --git a/client/plugin_list.go b/client/plugin_list.go index 39b15ad82f..d613b88453 100644 --- a/client/plugin_list.go +++ b/client/plugin_list.go @@ -13,18 +13,23 @@ type PluginListOptions struct { Filters Filters } +// PluginListResult represents the result of a plugin list operation. +type PluginListResult struct { + Items []*plugin.Plugin +} + // PluginList returns the installed plugins -func (cli *Client) PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error) { - var plugins plugin.ListResponse +func (cli *Client) PluginList(ctx context.Context, options PluginListOptions) (PluginListResult, error) { query := url.Values{} - opts.Filters.updateURLValues(query) + options.Filters.updateURLValues(query) resp, err := cli.get(ctx, "/plugins", query, nil) defer ensureReaderClosed(resp) if err != nil { - return plugins, err + return PluginListResult{}, err } + var plugins plugin.ListResponse err = json.NewDecoder(resp.Body).Decode(&plugins) - return plugins, err + return PluginListResult{Items: plugins}, err } diff --git a/client/plugin_list_test.go b/client/plugin_list_test.go index 359bf6b60d..32d47232ee 100644 --- a/client/plugin_list_test.go +++ b/client/plugin_list_test.go @@ -85,10 +85,10 @@ func TestPluginList(t *testing.T) { })) assert.NilError(t, err) - plugins, err := client.PluginList(context.Background(), PluginListOptions{ + list, err := client.PluginList(context.Background(), PluginListOptions{ Filters: listCase.filters, }) assert.NilError(t, err) - assert.Check(t, is.Len(plugins, 2)) + assert.Check(t, is.Len(list.Items, 2)) } } diff --git a/client/plugin_push.go b/client/plugin_push.go index 778a9b99b1..4ba25d1336 100644 --- a/client/plugin_push.go +++ b/client/plugin_push.go @@ -8,17 +8,27 @@ import ( "github.com/moby/moby/api/types/registry" ) +// PluginPushOptions holds parameters to push a plugin. +type PluginPushOptions struct { + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry +} + +// PluginPushResult is the result of a plugin push operation +type PluginPushResult struct { + io.ReadCloser +} + // PluginPush pushes a plugin to a registry -func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) { +func (cli *Client) PluginPush(ctx context.Context, name string, options PluginPushOptions) (PluginPushResult, error) { name, err := trimID("plugin", name) if err != nil { - return nil, err + return PluginPushResult{}, err } resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{ - registry.AuthHeader: {registryAuth}, + registry.AuthHeader: {options.RegistryAuth}, }) if err != nil { - return nil, err + return PluginPushResult{}, err } - return resp.Body, nil + return PluginPushResult{resp.Body}, nil } diff --git a/client/plugin_push_test.go b/client/plugin_push_test.go index eeca6cdf9c..619573f36c 100644 --- a/client/plugin_push_test.go +++ b/client/plugin_push_test.go @@ -18,14 +18,14 @@ func TestPluginPushError(t *testing.T) { client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) - _, err = client.PluginPush(context.Background(), "plugin_name", "") + _, err = client.PluginPush(context.Background(), "plugin_name", PluginPushOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - _, err = client.PluginPush(context.Background(), "", "") + _, err = client.PluginPush(context.Background(), "", PluginPushOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - _, err = client.PluginPush(context.Background(), " ", "") + _, err = client.PluginPush(context.Background(), " ", PluginPushOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -48,6 +48,6 @@ func TestPluginPush(t *testing.T) { })) assert.NilError(t, err) - _, err = client.PluginPush(context.Background(), "plugin_name", "authtoken") + _, err = client.PluginPush(context.Background(), "plugin_name", PluginPushOptions{RegistryAuth: "authtoken"}) assert.NilError(t, err) } diff --git a/client/plugin_remove.go b/client/plugin_remove.go index fee1a7ad36..229f408582 100644 --- a/client/plugin_remove.go +++ b/client/plugin_remove.go @@ -10,11 +10,16 @@ type PluginRemoveOptions struct { Force bool } +// PluginRemoveResult represents the result of a plugin removal. +type PluginRemoveResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginRemove removes a plugin -func (cli *Client) PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error { +func (cli *Client) PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) (PluginRemoveResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginRemoveResult{}, err } query := url.Values{} @@ -24,5 +29,5 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options Plugin resp, err := cli.delete(ctx, "/plugins/"+name, query, nil) defer ensureReaderClosed(resp) - return err + return PluginRemoveResult{}, err } diff --git a/client/plugin_remove_test.go b/client/plugin_remove_test.go index 99d123f0d1..cf1c013c80 100644 --- a/client/plugin_remove_test.go +++ b/client/plugin_remove_test.go @@ -16,14 +16,14 @@ func TestPluginRemoveError(t *testing.T) { client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) - err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{}) + _, err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - err = client.PluginRemove(context.Background(), "", PluginRemoveOptions{}) + _, err = client.PluginRemove(context.Background(), "", PluginRemoveOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - err = client.PluginRemove(context.Background(), " ", PluginRemoveOptions{}) + _, err = client.PluginRemove(context.Background(), " ", PluginRemoveOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -42,6 +42,6 @@ func TestPluginRemove(t *testing.T) { })) assert.NilError(t, err) - err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{}) + _, err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{}) assert.NilError(t, err) } diff --git a/client/plugin_set.go b/client/plugin_set.go index f606311602..c1f6bb5fac 100644 --- a/client/plugin_set.go +++ b/client/plugin_set.go @@ -4,14 +4,24 @@ import ( "context" ) +// PluginSetOptions defines options for modifying a plugin's settings. +type PluginSetOptions struct { + Args []string +} + +// PluginSetResult represents the result of a plugin set operation. +type PluginSetResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginSet modifies settings for an existing plugin -func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { +func (cli *Client) PluginSet(ctx context.Context, name string, options PluginSetOptions) (PluginSetResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginSetResult{}, err } - resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) + resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, options.Args, nil) defer ensureReaderClosed(resp) - return err + return PluginSetResult{}, err } diff --git a/client/plugin_set_test.go b/client/plugin_set_test.go index 25908273f1..f4ddc949f8 100644 --- a/client/plugin_set_test.go +++ b/client/plugin_set_test.go @@ -16,14 +16,14 @@ func TestPluginSetError(t *testing.T) { client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) - err = client.PluginSet(context.Background(), "plugin_name", []string{}) + _, err = client.PluginSet(context.Background(), "plugin_name", PluginSetOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - err = client.PluginSet(context.Background(), "", []string{}) + _, err = client.PluginSet(context.Background(), "", PluginSetOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - err = client.PluginSet(context.Background(), " ", []string{}) + _, err = client.PluginSet(context.Background(), " ", PluginSetOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -42,6 +42,6 @@ func TestPluginSet(t *testing.T) { })) assert.NilError(t, err) - err = client.PluginSet(context.Background(), "plugin_name", []string{"arg1"}) + _, err = client.PluginSet(context.Background(), "plugin_name", PluginSetOptions{Args: []string{"arg1"}}) assert.NilError(t, err) } diff --git a/client/plugin_upgrade.go b/client/plugin_upgrade.go index c2cff48ead..f9df6e5843 100644 --- a/client/plugin_upgrade.go +++ b/client/plugin_upgrade.go @@ -12,8 +12,29 @@ import ( "github.com/moby/moby/api/types/registry" ) +// PluginUpgradeOptions holds parameters to upgrade a plugin. +type PluginUpgradeOptions struct { + Disabled bool + AcceptAllPermissions bool + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + RemoteRef string // RemoteRef is the plugin name on the registry + + // PrivilegeFunc is a function that clients can supply to retry operations + // after getting an authorization error. This function returns the registry + // authentication header value in base64 encoded format, or an error if the + // privilege request fails. + // + // For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig]. + PrivilegeFunc func(context.Context) (string, error) + AcceptPermissionsFunc func(context.Context, plugin.Privileges) (bool, error) + Args []string +} + +// PluginUpgradeResult holds the result of a plugin upgrade operation. +type PluginUpgradeResult io.ReadCloser + // PluginUpgrade upgrades a plugin -func (cli *Client) PluginUpgrade(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) { +func (cli *Client) PluginUpgrade(ctx context.Context, name string, options PluginUpgradeOptions) (PluginUpgradeResult, error) { name, err := trimID("plugin", name) if err != nil { return nil, err @@ -25,7 +46,7 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options Plugi } query.Set("remote", options.RemoteRef) - privileges, err := cli.checkPluginPermissions(ctx, query, options) + privileges, err := cli.checkPluginPermissions(ctx, query, &options) if err != nil { return nil, err } @@ -42,3 +63,27 @@ func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privi registry.AuthHeader: {registryAuth}, }) } + +func (o *PluginUpgradeOptions) getRegistryAuth() string { + return o.RegistryAuth +} + +func (o *PluginUpgradeOptions) setRegistryAuth(auth string) { + o.RegistryAuth = auth +} + +func (o *PluginUpgradeOptions) getPrivilegeFunc() func(context.Context) (string, error) { + return o.PrivilegeFunc +} + +func (o *PluginUpgradeOptions) getAcceptAllPermissions() bool { + return o.AcceptAllPermissions +} + +func (o *PluginUpgradeOptions) getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) { + return o.AcceptPermissionsFunc +} + +func (o *PluginUpgradeOptions) getRemoteRef() string { + return o.RemoteRef +} diff --git a/integration/plugin/authz/authz_plugin_v2_test.go b/integration/plugin/authz/authz_plugin_v2_test.go index d460527966..c42f30ea51 100644 --- a/integration/plugin/authz/authz_plugin_v2_test.go +++ b/integration/plugin/authz/authz_plugin_v2_test.go @@ -71,7 +71,7 @@ func TestAuthZPluginV2Disable(t *testing.T) { assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)) // disable the plugin - err = c.PluginDisable(ctx, authzPluginNameWithTag, client.PluginDisableOptions{}) + _, err = c.PluginDisable(ctx, authzPluginNameWithTag, client.PluginDisableOptions{}) assert.NilError(t, err) // now test to see if the docker api works. diff --git a/integration/plugin/common/plugin_test.go b/integration/plugin/common/plugin_test.go index c3e76e0618..dfade3bf58 100644 --- a/integration/plugin/common/plugin_test.go +++ b/integration/plugin/common/plugin_test.go @@ -135,14 +135,14 @@ func TestPluginInstall(t *testing.T) { err := plugin.Create(ctx, apiclient, repo) assert.NilError(t, err) - rdr, err := apiclient.PluginPush(ctx, repo, "") + pushResult, err := apiclient.PluginPush(ctx, repo, client.PluginPushOptions{}) assert.NilError(t, err) - defer rdr.Close() + defer pushResult.Close() buf := &strings.Builder{} assert.NilError(t, err) var digest string - assert.NilError(t, jsonmessage.DisplayJSONMessagesStream(rdr, buf, 0, false, func(j jsonmessage.JSONMessage) { + assert.NilError(t, jsonmessage.DisplayJSONMessagesStream(pushResult, buf, 0, false, func(j jsonmessage.JSONMessage) { if j.Aux != nil { var r types.PushResult assert.NilError(t, json.Unmarshal(*j.Aux, &r)) @@ -150,17 +150,17 @@ func TestPluginInstall(t *testing.T) { } }), buf) - err = apiclient.PluginRemove(ctx, repo, client.PluginRemoveOptions{Force: true}) + _, err = apiclient.PluginRemove(ctx, repo, client.PluginRemoveOptions{Force: true}) assert.NilError(t, err) - rdr, err = apiclient.PluginInstall(ctx, repo, client.PluginInstallOptions{ + installResult, err := apiclient.PluginInstall(ctx, repo, client.PluginInstallOptions{ Disabled: true, RemoteRef: repo + "@" + digest, }) assert.NilError(t, err) - defer rdr.Close() + defer installResult.Close() - _, err = io.Copy(io.Discard, rdr) + _, err = io.Copy(io.Discard, installResult) assert.NilError(t, err) _, err = apiclient.PluginInspect(ctx, repo, client.PluginInspectOptions{}) @@ -268,9 +268,12 @@ func TestPluginsWithRuntimes(t *testing.T) { apiclient := d.NewClientT(t) assert.NilError(t, plugin.Create(ctx, apiclient, "test:latest")) - defer apiclient.PluginRemove(ctx, "test:latest", client.PluginRemoveOptions{Force: true}) + defer func() { + _, _ = apiclient.PluginRemove(ctx, "test:latest", client.PluginRemoveOptions{Force: true}) + }() - assert.NilError(t, apiclient.PluginEnable(ctx, "test:latest", client.PluginEnableOptions{Timeout: 30})) + _, err = apiclient.PluginEnable(ctx, "test:latest", client.PluginEnableOptions{Timeout: 30}) + assert.NilError(t, err) p := filepath.Join(dir, "myrt") script := fmt.Sprintf(`#!/bin/sh @@ -331,12 +334,12 @@ func TestPluginBackCompatMediaTypes(t *testing.T) { assert.NilError(t, plugin.Create(ctx, apiclient, repo)) - rdr, err := apiclient.PluginPush(ctx, repo, "") + res, err := apiclient.PluginPush(ctx, repo, client.PluginPushOptions{}) assert.NilError(t, err) - defer rdr.Close() + defer res.Close() buf := &strings.Builder{} - assert.NilError(t, jsonmessage.DisplayJSONMessagesStream(rdr, buf, 0, false, nil), buf) + assert.NilError(t, jsonmessage.DisplayJSONMessagesStream(res, buf, 0, false, nil), buf) // Use custom header here because older versions of the registry do not // parse the accept header correctly and does not like the accept header @@ -356,7 +359,7 @@ func TestPluginBackCompatMediaTypes(t *testing.T) { fetcher, err := resolver.Fetcher(ctx, n) assert.NilError(t, err) - rdr, err = fetcher.Fetch(ctx, desc) + rdr, err := fetcher.Fetch(ctx, desc) assert.NilError(t, err) defer rdr.Close() diff --git a/integration/plugin/logging/logging_linux_test.go b/integration/plugin/logging/logging_linux_test.go index 654d49571c..dd7caa1df6 100644 --- a/integration/plugin/logging/logging_linux_test.go +++ b/integration/plugin/logging/logging_linux_test.go @@ -31,9 +31,12 @@ func TestContinueAfterPluginCrash(t *testing.T) { ctxT, cancel := context.WithTimeout(ctx, 60*time.Second) defer cancel() - assert.Assert(t, apiclient.PluginEnable(ctxT, "test", client.PluginEnableOptions{Timeout: 30})) + _, err := apiclient.PluginEnable(ctxT, "test", client.PluginEnableOptions{Timeout: 30}) + assert.NilError(t, err) cancel() - defer apiclient.PluginRemove(ctx, "test", client.PluginRemoveOptions{Force: true}) + defer func() { + _, _ = apiclient.PluginRemove(ctx, "test", client.PluginRemoveOptions{Force: true}) + }() ctxT, cancel = context.WithTimeout(ctx, 60*time.Second) defer cancel() diff --git a/integration/plugin/logging/read_test.go b/integration/plugin/logging/read_test.go index 2844879332..ef800789a1 100644 --- a/integration/plugin/logging/read_test.go +++ b/integration/plugin/logging/read_test.go @@ -34,7 +34,7 @@ func TestReadPluginNoRead(t *testing.T) { assert.Assert(t, err) createPlugin(ctx, t, apiclient, "test", "discard", asLogDriver) - err = apiclient.PluginEnable(ctx, "test", client.PluginEnableOptions{Timeout: 30}) + _, err = apiclient.PluginEnable(ctx, "test", client.PluginEnableOptions{Timeout: 30}) assert.Check(t, err) d.Stop(t) diff --git a/integration/plugin/logging/validation_test.go b/integration/plugin/logging/validation_test.go index b98940d1cc..f01dad689d 100644 --- a/integration/plugin/logging/validation_test.go +++ b/integration/plugin/logging/validation_test.go @@ -29,9 +29,11 @@ func TestDaemonStartWithLogOpt(t *testing.T) { c := d.NewClientT(t) createPlugin(ctx, t, c, "test", "dummy", asLogDriver) - err := c.PluginEnable(ctx, "test", client.PluginEnableOptions{Timeout: 30}) + _, err := c.PluginEnable(ctx, "test", client.PluginEnableOptions{Timeout: 30}) assert.Check(t, err) - defer c.PluginRemove(ctx, "test", client.PluginRemoveOptions{Force: true}) + defer func() { + _, _ = c.PluginRemove(ctx, "test", client.PluginRemoveOptions{Force: true}) + }() d.Stop(t) d.Start(t, "--iptables=false", "--ip6tables=false", "--log-driver=test", "--log-opt=foo=bar") diff --git a/integration/plugin/volumes/mounts_test.go b/integration/plugin/volumes/mounts_test.go index aa818e0314..23a882d14e 100644 --- a/integration/plugin/volumes/mounts_test.go +++ b/integration/plugin/volumes/mounts_test.go @@ -47,10 +47,10 @@ func TestPluginWithDevMounts(t *testing.T) { c.IpcHost = true }) - err = c.PluginEnable(ctx, "test", client.PluginEnableOptions{Timeout: 30}) + _, err = c.PluginEnable(ctx, "test", client.PluginEnableOptions{Timeout: 30}) assert.NilError(t, err) defer func() { - err := c.PluginRemove(ctx, "test", client.PluginRemoveOptions{Force: true}) + _, err := c.PluginRemove(ctx, "test", client.PluginRemoveOptions{Force: true}) assert.Check(t, err) }() diff --git a/integration/service/plugin_test.go b/integration/service/plugin_test.go index 770ab4eb09..c5711f2b8d 100644 --- a/integration/service/plugin_test.go +++ b/integration/service/plugin_test.go @@ -35,19 +35,19 @@ func TestServicePlugin(t *testing.T) { apiclient := d.NewClientT(t) err := plugin.Create(ctx, apiclient, repo) assert.NilError(t, err) - r, err := apiclient.PluginPush(ctx, repo, "") + r, err := apiclient.PluginPush(ctx, repo, client.PluginPushOptions{}) assert.NilError(t, err) _, err = io.Copy(io.Discard, r) assert.NilError(t, err) - err = apiclient.PluginRemove(ctx, repo, client.PluginRemoveOptions{}) + _, err = apiclient.PluginRemove(ctx, repo, client.PluginRemoveOptions{}) assert.NilError(t, err) err = plugin.Create(ctx, apiclient, repo2) assert.NilError(t, err) - r, err = apiclient.PluginPush(ctx, repo2, "") + r, err = apiclient.PluginPush(ctx, repo2, client.PluginPushOptions{}) assert.NilError(t, err) _, err = io.Copy(io.Discard, r) assert.NilError(t, err) - err = apiclient.PluginRemove(ctx, repo2, client.PluginRemoveOptions{}) + _, err = apiclient.PluginRemove(ctx, repo2, client.PluginRemoveOptions{}) assert.NilError(t, err) d.Stop(t) diff --git a/internal/testutil/environment/clean.go b/internal/testutil/environment/clean.go index ef2b990145..cef84930e8 100644 --- a/internal/testutil/environment/clean.go +++ b/internal/testutil/environment/clean.go @@ -164,18 +164,18 @@ func deleteAllNetworks(ctx context.Context, t testing.TB, c client.NetworkAPICli func deleteAllPlugins(ctx context.Context, t testing.TB, c client.PluginAPIClient, protectedPlugins map[string]struct{}) { t.Helper() - plugins, err := c.PluginList(ctx, client.PluginListOptions{}) + res, err := c.PluginList(ctx, client.PluginListOptions{}) // Docker EE does not allow cluster-wide plugin management. if cerrdefs.IsNotImplemented(err) { return } assert.Check(t, err, "failed to list plugins") - for _, p := range plugins { + for _, p := range res.Items { if _, ok := protectedPlugins[p.Name]; ok { continue } - err := c.PluginRemove(ctx, p.Name, client.PluginRemoveOptions{Force: true}) + _, err := c.PluginRemove(ctx, p.Name, client.PluginRemoveOptions{Force: true}) assert.Check(t, err, "failed to remove plugin %s", p.ID) } } diff --git a/internal/testutil/environment/protect.go b/internal/testutil/environment/protect.go index 073659f4bc..f683ac287a 100644 --- a/internal/testutil/environment/protect.go +++ b/internal/testutil/environment/protect.go @@ -194,7 +194,7 @@ func ProtectPlugins(ctx context.Context, t testing.TB, testEnv *Execution) { func getExistingPlugins(ctx context.Context, t testing.TB, testEnv *Execution) []string { t.Helper() apiClient := testEnv.APIClient() - pluginList, err := apiClient.PluginList(ctx, client.PluginListOptions{}) + res, err := apiClient.PluginList(ctx, client.PluginListOptions{}) // Docker EE does not allow cluster-wide plugin management. if cerrdefs.IsNotImplemented(err) { return []string{} @@ -202,7 +202,7 @@ func getExistingPlugins(ctx context.Context, t testing.TB, testEnv *Execution) [ assert.NilError(t, err, "failed to list plugins") var plugins []string - for _, plugin := range pluginList { + for _, plugin := range res.Items { plugins = append(plugins, plugin.Name) } return plugins diff --git a/internal/testutil/fixtures/plugin/plugin.go b/internal/testutil/fixtures/plugin/plugin.go index 6a3addf084..782c9ee5dc 100644 --- a/internal/testutil/fixtures/plugin/plugin.go +++ b/internal/testutil/fixtures/plugin/plugin.go @@ -51,7 +51,7 @@ func WithBinary(bin string) CreateOpt { // CreateClient is the interface used for `BuildPlugin` to interact with the // daemon. type CreateClient interface { - PluginCreate(context.Context, io.Reader, client.PluginCreateOptions) error + PluginCreate(context.Context, io.Reader, client.PluginCreateOptions) (client.PluginCreateResult, error) } // Create creates a new plugin with the specified name @@ -71,7 +71,8 @@ func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.PluginCreate(ctx, tar, client.PluginCreateOptions{RepoName: name}) + _, err = c.PluginCreate(ctx, tar, client.PluginCreateOptions{RepoName: name}) + return err } // CreateInRegistry makes a plugin (locally) and pushes it to a registry. diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index f121c3e14c..2ab3c85788 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -9,7 +9,6 @@ import ( "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/network" - "github.com/moby/moby/api/types/plugin" "github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/system" @@ -145,16 +144,16 @@ type NodeAPIClient interface { // PluginAPIClient defines API client methods for the plugins type PluginAPIClient interface { - PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error) - PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error - PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error - PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error - PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) - PluginUpgrade(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) - PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) - PluginSet(ctx context.Context, name string, args []string) error + PluginList(ctx context.Context, options PluginListOptions) (PluginListResult, error) + PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) (PluginRemoveResult, error) + PluginEnable(ctx context.Context, name string, options PluginEnableOptions) (PluginEnableResult, error) + PluginDisable(ctx context.Context, name string, options PluginDisableOptions) (PluginDisableResult, error) + PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (PluginInstallResult, error) + PluginUpgrade(ctx context.Context, name string, options PluginUpgradeOptions) (PluginUpgradeResult, error) + PluginPush(ctx context.Context, name string, options PluginPushOptions) (PluginPushResult, error) + PluginSet(ctx context.Context, name string, options PluginSetOptions) (PluginSetResult, error) PluginInspect(ctx context.Context, name string, options PluginInspectOptions) (PluginInspectResult, error) - PluginCreate(ctx context.Context, createContext io.Reader, options PluginCreateOptions) error + PluginCreate(ctx context.Context, createContext io.Reader, options PluginCreateOptions) (PluginCreateResult, error) } // ServiceAPIClient defines API client methods for the services diff --git a/vendor/github.com/moby/moby/client/plugin_create.go b/vendor/github.com/moby/moby/client/plugin_create.go index c143db7061..c1a2dd5a6c 100644 --- a/vendor/github.com/moby/moby/client/plugin_create.go +++ b/vendor/github.com/moby/moby/client/plugin_create.go @@ -12,8 +12,13 @@ type PluginCreateOptions struct { RepoName string } +// PluginCreateResult represents the result of a plugin create operation. +type PluginCreateResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginCreate creates a plugin -func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions PluginCreateOptions) error { +func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions PluginCreateOptions) (PluginCreateResult, error) { headers := http.Header(make(map[string][]string)) headers.Set("Content-Type", "application/x-tar") @@ -22,5 +27,5 @@ func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, cr resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers) defer ensureReaderClosed(resp) - return err + return PluginCreateResult{}, err } diff --git a/vendor/github.com/moby/moby/client/plugin_disable.go b/vendor/github.com/moby/moby/client/plugin_disable.go index b368dc6e58..65ab0aa004 100644 --- a/vendor/github.com/moby/moby/client/plugin_disable.go +++ b/vendor/github.com/moby/moby/client/plugin_disable.go @@ -10,11 +10,16 @@ type PluginDisableOptions struct { Force bool } +// PluginDisableResult represents the result of a plugin disable operation. +type PluginDisableResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginDisable disables a plugin -func (cli *Client) PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error { +func (cli *Client) PluginDisable(ctx context.Context, name string, options PluginDisableOptions) (PluginDisableResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginDisableResult{}, err } query := url.Values{} if options.Force { @@ -22,5 +27,5 @@ func (cli *Client) PluginDisable(ctx context.Context, name string, options Plugi } resp, err := cli.post(ctx, "/plugins/"+name+"/disable", query, nil, nil) defer ensureReaderClosed(resp) - return err + return PluginDisableResult{}, err } diff --git a/vendor/github.com/moby/moby/client/plugin_enable.go b/vendor/github.com/moby/moby/client/plugin_enable.go index c79361a473..7c3e26b67a 100644 --- a/vendor/github.com/moby/moby/client/plugin_enable.go +++ b/vendor/github.com/moby/moby/client/plugin_enable.go @@ -11,16 +11,21 @@ type PluginEnableOptions struct { Timeout int } +// PluginEnableResult represents the result of a plugin enable operation. +type PluginEnableResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginEnable enables a plugin -func (cli *Client) PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error { +func (cli *Client) PluginEnable(ctx context.Context, name string, options PluginEnableOptions) (PluginEnableResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginEnableResult{}, err } query := url.Values{} query.Set("timeout", strconv.Itoa(options.Timeout)) resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil) defer ensureReaderClosed(resp) - return err + return PluginEnableResult{}, err } diff --git a/vendor/github.com/moby/moby/client/plugin_install.go b/vendor/github.com/moby/moby/client/plugin_install.go index 2ec6f062e7..a589b2e1fd 100644 --- a/vendor/github.com/moby/moby/client/plugin_install.go +++ b/vendor/github.com/moby/moby/client/plugin_install.go @@ -33,17 +33,23 @@ type PluginInstallOptions struct { Args []string } +// PluginInstallResult holds the result of a plugin install operation. +// It is an io.ReadCloser from which the caller can read installation progress or result. +type PluginInstallResult struct { + io.ReadCloser +} + // PluginInstall installs a plugin -func (cli *Client) PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (_ io.ReadCloser, retErr error) { +func (cli *Client) PluginInstall(ctx context.Context, name string, options PluginInstallOptions) (_ PluginInstallResult, retErr error) { query := url.Values{} if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil { - return nil, fmt.Errorf("invalid remote reference: %w", err) + return PluginInstallResult{}, fmt.Errorf("invalid remote reference: %w", err) } query.Set("remote", options.RemoteRef) - privileges, err := cli.checkPluginPermissions(ctx, query, options) + privileges, err := cli.checkPluginPermissions(ctx, query, &options) if err != nil { - return nil, err + return PluginInstallResult{}, err } // set name for plugin pull, if empty should default to remote reference @@ -51,7 +57,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth) if err != nil { - return nil, err + return PluginInstallResult{}, err } name = resp.Header.Get("Docker-Plugin-Name") @@ -70,7 +76,7 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi } }() if len(options.Args) > 0 { - if err := cli.PluginSet(ctx, name, options.Args); err != nil { + if _, err := cli.PluginSet(ctx, name, PluginSetOptions{Args: options.Args}); err != nil { _ = pw.CloseWithError(err) return } @@ -81,10 +87,10 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options Plugi return } - enableErr := cli.PluginEnable(ctx, name, PluginEnableOptions{Timeout: 0}) + _, enableErr := cli.PluginEnable(ctx, name, PluginEnableOptions{Timeout: 0}) _ = pw.CloseWithError(enableErr) }() - return pr, nil + return PluginInstallResult{pr}, nil } func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { @@ -99,17 +105,17 @@ func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileg }) } -func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options PluginInstallOptions) (plugin.Privileges, error) { - resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) - if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { +func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options pluginOptions) (plugin.Privileges, error) { + resp, err := cli.tryPluginPrivileges(ctx, query, options.getRegistryAuth()) + if cerrdefs.IsUnauthorized(err) && options.getPrivilegeFunc() != nil { // TODO: do inspect before to check existing name before checking privileges - newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) + newAuthHeader, privilegeErr := options.getPrivilegeFunc()(ctx) if privilegeErr != nil { ensureReaderClosed(resp) return nil, privilegeErr } - options.RegistryAuth = newAuthHeader - resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) + options.setRegistryAuth(newAuthHeader) + resp, err = cli.tryPluginPrivileges(ctx, query, options.getRegistryAuth()) } if err != nil { ensureReaderClosed(resp) @@ -123,14 +129,47 @@ func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, } ensureReaderClosed(resp) - if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { - accept, err := options.AcceptPermissionsFunc(ctx, privileges) + if !options.getAcceptAllPermissions() && options.getAcceptPermissionsFunc() != nil && len(privileges) > 0 { + accept, err := options.getAcceptPermissionsFunc()(ctx, privileges) if err != nil { return nil, err } if !accept { - return nil, errors.New("permission denied while installing plugin " + options.RemoteRef) + return nil, errors.New("permission denied while installing plugin " + options.getRemoteRef()) } } return privileges, nil } + +type pluginOptions interface { + getRegistryAuth() string + setRegistryAuth(string) + getPrivilegeFunc() func(context.Context) (string, error) + getAcceptAllPermissions() bool + getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) + getRemoteRef() string +} + +func (o *PluginInstallOptions) getRegistryAuth() string { + return o.RegistryAuth +} + +func (o *PluginInstallOptions) setRegistryAuth(auth string) { + o.RegistryAuth = auth +} + +func (o *PluginInstallOptions) getPrivilegeFunc() func(context.Context) (string, error) { + return o.PrivilegeFunc +} + +func (o *PluginInstallOptions) getAcceptAllPermissions() bool { + return o.AcceptAllPermissions +} + +func (o *PluginInstallOptions) getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) { + return o.AcceptPermissionsFunc +} + +func (o *PluginInstallOptions) getRemoteRef() string { + return o.RemoteRef +} diff --git a/vendor/github.com/moby/moby/client/plugin_list.go b/vendor/github.com/moby/moby/client/plugin_list.go index 39b15ad82f..d613b88453 100644 --- a/vendor/github.com/moby/moby/client/plugin_list.go +++ b/vendor/github.com/moby/moby/client/plugin_list.go @@ -13,18 +13,23 @@ type PluginListOptions struct { Filters Filters } +// PluginListResult represents the result of a plugin list operation. +type PluginListResult struct { + Items []*plugin.Plugin +} + // PluginList returns the installed plugins -func (cli *Client) PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error) { - var plugins plugin.ListResponse +func (cli *Client) PluginList(ctx context.Context, options PluginListOptions) (PluginListResult, error) { query := url.Values{} - opts.Filters.updateURLValues(query) + options.Filters.updateURLValues(query) resp, err := cli.get(ctx, "/plugins", query, nil) defer ensureReaderClosed(resp) if err != nil { - return plugins, err + return PluginListResult{}, err } + var plugins plugin.ListResponse err = json.NewDecoder(resp.Body).Decode(&plugins) - return plugins, err + return PluginListResult{Items: plugins}, err } diff --git a/vendor/github.com/moby/moby/client/plugin_push.go b/vendor/github.com/moby/moby/client/plugin_push.go index 778a9b99b1..4ba25d1336 100644 --- a/vendor/github.com/moby/moby/client/plugin_push.go +++ b/vendor/github.com/moby/moby/client/plugin_push.go @@ -8,17 +8,27 @@ import ( "github.com/moby/moby/api/types/registry" ) +// PluginPushOptions holds parameters to push a plugin. +type PluginPushOptions struct { + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry +} + +// PluginPushResult is the result of a plugin push operation +type PluginPushResult struct { + io.ReadCloser +} + // PluginPush pushes a plugin to a registry -func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) { +func (cli *Client) PluginPush(ctx context.Context, name string, options PluginPushOptions) (PluginPushResult, error) { name, err := trimID("plugin", name) if err != nil { - return nil, err + return PluginPushResult{}, err } resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{ - registry.AuthHeader: {registryAuth}, + registry.AuthHeader: {options.RegistryAuth}, }) if err != nil { - return nil, err + return PluginPushResult{}, err } - return resp.Body, nil + return PluginPushResult{resp.Body}, nil } diff --git a/vendor/github.com/moby/moby/client/plugin_remove.go b/vendor/github.com/moby/moby/client/plugin_remove.go index fee1a7ad36..229f408582 100644 --- a/vendor/github.com/moby/moby/client/plugin_remove.go +++ b/vendor/github.com/moby/moby/client/plugin_remove.go @@ -10,11 +10,16 @@ type PluginRemoveOptions struct { Force bool } +// PluginRemoveResult represents the result of a plugin removal. +type PluginRemoveResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginRemove removes a plugin -func (cli *Client) PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error { +func (cli *Client) PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) (PluginRemoveResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginRemoveResult{}, err } query := url.Values{} @@ -24,5 +29,5 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options Plugin resp, err := cli.delete(ctx, "/plugins/"+name, query, nil) defer ensureReaderClosed(resp) - return err + return PluginRemoveResult{}, err } diff --git a/vendor/github.com/moby/moby/client/plugin_set.go b/vendor/github.com/moby/moby/client/plugin_set.go index f606311602..c1f6bb5fac 100644 --- a/vendor/github.com/moby/moby/client/plugin_set.go +++ b/vendor/github.com/moby/moby/client/plugin_set.go @@ -4,14 +4,24 @@ import ( "context" ) +// PluginSetOptions defines options for modifying a plugin's settings. +type PluginSetOptions struct { + Args []string +} + +// PluginSetResult represents the result of a plugin set operation. +type PluginSetResult struct { + // Currently empty; can be extended in the future if needed. +} + // PluginSet modifies settings for an existing plugin -func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { +func (cli *Client) PluginSet(ctx context.Context, name string, options PluginSetOptions) (PluginSetResult, error) { name, err := trimID("plugin", name) if err != nil { - return err + return PluginSetResult{}, err } - resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) + resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, options.Args, nil) defer ensureReaderClosed(resp) - return err + return PluginSetResult{}, err } diff --git a/vendor/github.com/moby/moby/client/plugin_upgrade.go b/vendor/github.com/moby/moby/client/plugin_upgrade.go index c2cff48ead..f9df6e5843 100644 --- a/vendor/github.com/moby/moby/client/plugin_upgrade.go +++ b/vendor/github.com/moby/moby/client/plugin_upgrade.go @@ -12,8 +12,29 @@ import ( "github.com/moby/moby/api/types/registry" ) +// PluginUpgradeOptions holds parameters to upgrade a plugin. +type PluginUpgradeOptions struct { + Disabled bool + AcceptAllPermissions bool + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + RemoteRef string // RemoteRef is the plugin name on the registry + + // PrivilegeFunc is a function that clients can supply to retry operations + // after getting an authorization error. This function returns the registry + // authentication header value in base64 encoded format, or an error if the + // privilege request fails. + // + // For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig]. + PrivilegeFunc func(context.Context) (string, error) + AcceptPermissionsFunc func(context.Context, plugin.Privileges) (bool, error) + Args []string +} + +// PluginUpgradeResult holds the result of a plugin upgrade operation. +type PluginUpgradeResult io.ReadCloser + // PluginUpgrade upgrades a plugin -func (cli *Client) PluginUpgrade(ctx context.Context, name string, options PluginInstallOptions) (io.ReadCloser, error) { +func (cli *Client) PluginUpgrade(ctx context.Context, name string, options PluginUpgradeOptions) (PluginUpgradeResult, error) { name, err := trimID("plugin", name) if err != nil { return nil, err @@ -25,7 +46,7 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options Plugi } query.Set("remote", options.RemoteRef) - privileges, err := cli.checkPluginPermissions(ctx, query, options) + privileges, err := cli.checkPluginPermissions(ctx, query, &options) if err != nil { return nil, err } @@ -42,3 +63,27 @@ func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privi registry.AuthHeader: {registryAuth}, }) } + +func (o *PluginUpgradeOptions) getRegistryAuth() string { + return o.RegistryAuth +} + +func (o *PluginUpgradeOptions) setRegistryAuth(auth string) { + o.RegistryAuth = auth +} + +func (o *PluginUpgradeOptions) getPrivilegeFunc() func(context.Context) (string, error) { + return o.PrivilegeFunc +} + +func (o *PluginUpgradeOptions) getAcceptAllPermissions() bool { + return o.AcceptAllPermissions +} + +func (o *PluginUpgradeOptions) getAcceptPermissionsFunc() func(context.Context, plugin.Privileges) (bool, error) { + return o.AcceptPermissionsFunc +} + +func (o *PluginUpgradeOptions) getRemoteRef() string { + return o.RemoteRef +}