client: refactor plugin api client functions to define options/results structs

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Austin Vazquez
2025-10-21 19:13:45 -05:00
committed by Sebastiaan van Stijn
parent eddf1a1ad6
commit 909e32b27d
36 changed files with 427 additions and 162 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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")

View File

@@ -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)
}()

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}