mirror of https://github.com/docker/cli.git
Add docker plugin upgrade
This allows a plugin to be upgraded without requiring to uninstall/reinstall a plugin. Since plugin resources (e.g. volumes) are tied to a plugin ID, this is important to ensure resources aren't lost. The plugin must be disabled while upgrading (errors out if enabled). This does not add any convenience flags for automatically disabling/re-enabling the plugin during before/after upgrade. Since an upgrade may change requested permissions, the user is required to accept permissions just like `docker plugin install`. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
ba79205e30
commit
e301053ff5
|
@ -113,6 +113,7 @@ type PluginAPIClient interface {
|
||||||
PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
|
PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
|
||||||
PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error
|
PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error
|
||||||
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error)
|
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error)
|
||||||
|
PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error)
|
||||||
PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error)
|
PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error)
|
||||||
PluginSet(ctx context.Context, name string, args []string) error
|
PluginSet(ctx context.Context, name string, args []string) error
|
||||||
PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
|
PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
|
||||||
|
|
|
@ -20,43 +20,15 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
|
||||||
}
|
}
|
||||||
query.Set("remote", options.RemoteRef)
|
query.Set("remote", options.RemoteRef)
|
||||||
|
|
||||||
resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
|
privileges, err := cli.checkPluginPermissions(ctx, query, options)
|
||||||
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
|
|
||||||
// todo: do inspect before to check existing name before checking privileges
|
|
||||||
newAuthHeader, privilegeErr := options.PrivilegeFunc()
|
|
||||||
if privilegeErr != nil {
|
|
||||||
ensureReaderClosed(resp)
|
|
||||||
return nil, privilegeErr
|
|
||||||
}
|
|
||||||
options.RegistryAuth = newAuthHeader
|
|
||||||
resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
ensureReaderClosed(resp)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var privileges types.PluginPrivileges
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
|
|
||||||
ensureReaderClosed(resp)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ensureReaderClosed(resp)
|
|
||||||
|
|
||||||
if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 {
|
|
||||||
accept, err := options.AcceptPermissionsFunc(privileges)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !accept {
|
|
||||||
return nil, pluginPermissionDenied{options.RemoteRef}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set name for plugin pull, if empty should default to remote reference
|
// set name for plugin pull, if empty should default to remote reference
|
||||||
query.Set("name", name)
|
query.Set("name", name)
|
||||||
|
|
||||||
resp, err = cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
|
resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -103,3 +75,39 @@ func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileg
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
||||||
return cli.post(ctx, "/plugins/pull", query, privileges, headers)
|
return cli.post(ctx, "/plugins/pull", query, privileges, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options types.PluginInstallOptions) (types.PluginPrivileges, error) {
|
||||||
|
resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
|
||||||
|
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
|
||||||
|
// todo: do inspect before to check existing name before checking privileges
|
||||||
|
newAuthHeader, privilegeErr := options.PrivilegeFunc()
|
||||||
|
if privilegeErr != nil {
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return nil, privilegeErr
|
||||||
|
}
|
||||||
|
options.RegistryAuth = newAuthHeader
|
||||||
|
resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var privileges types.PluginPrivileges
|
||||||
|
if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
|
||||||
|
if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 {
|
||||||
|
accept, err := options.AcceptPermissionsFunc(privileges)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !accept {
|
||||||
|
return nil, pluginPermissionDenied{options.RemoteRef}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginUpgrade upgrades a plugin
|
||||||
|
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
|
||||||
|
query := url.Values{}
|
||||||
|
if _, err := reference.ParseNamed(options.RemoteRef); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "invalid remote reference")
|
||||||
|
}
|
||||||
|
query.Set("remote", options.RemoteRef)
|
||||||
|
|
||||||
|
privileges, err := cli.checkPluginPermissions(ctx, query, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := cli.tryPluginUpgrade(ctx, query, privileges, name, options.RegistryAuth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) {
|
||||||
|
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
||||||
|
return cli.post(ctx, fmt.Sprintf("/plugins/%s/upgrade", name), query, privileges, headers)
|
||||||
|
}
|
Loading…
Reference in New Issue