mirror of https://github.com/docker/cli.git
Implement content addressability for plugins
Move plugins to shared distribution stack with images. Create immutable plugin config that matches schema2 requirements. Ensure data being pushed is same as pulled/created. Store distribution artifacts in a blobstore. Run init layer setup for every plugin start. Fix breakouts from unsafe file accesses. Add support for `docker plugin install --alias` Uses normalized references for default names to avoid collisions when using default hosts/tags. Some refactoring of the plugin manager to support the change, like removing the singleton manager and adding manager config struct. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
parent
4b933cc26d
commit
66f7194250
|
@ -111,8 +111,8 @@ type PluginAPIClient interface {
|
|||
PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
|
||||
PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
|
||||
PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error
|
||||
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
|
||||
PluginPush(ctx context.Context, name string, registryAuth string) error
|
||||
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error)
|
||||
PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error)
|
||||
PluginSet(ctx context.Context, name string, args []string) error
|
||||
PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
|
||||
PluginCreate(ctx context.Context, createContext io.Reader, options types.PluginCreateOptions) error
|
||||
|
|
|
@ -2,73 +2,96 @@ package client
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// PluginInstall installs a plugin
|
||||
func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (err error) {
|
||||
// FIXME(vdemeester) name is a ref, we might want to parse/validate it here.
|
||||
func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
|
||||
query := url.Values{}
|
||||
query.Set("name", name)
|
||||
if _, err := reference.ParseNamed(options.RemoteRef); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid remote reference")
|
||||
}
|
||||
query.Set("remote", options.RemoteRef)
|
||||
|
||||
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 privilegeErr
|
||||
return nil, privilegeErr
|
||||
}
|
||||
options.RegistryAuth = newAuthHeader
|
||||
resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
|
||||
}
|
||||
if err != nil {
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var privileges types.PluginPrivileges
|
||||
if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
ensureReaderClosed(resp)
|
||||
|
||||
if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 {
|
||||
accept, err := options.AcceptPermissionsFunc(privileges)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if !accept {
|
||||
return pluginPermissionDenied{name}
|
||||
return nil, pluginPermissionDenied{options.RemoteRef}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
|
||||
// set name for plugin pull, if empty should default to remote reference
|
||||
query.Set("name", name)
|
||||
|
||||
resp, err = cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name = resp.header.Get("Docker-Plugin-Name")
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() { // todo: the client should probably be designed more around the actual api
|
||||
_, err := io.Copy(pw, resp.body)
|
||||
if err != nil {
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
|
||||
ensureReaderClosed(delResp)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(options.Args) > 0 {
|
||||
if err := cli.PluginSet(ctx, name, options.Args); err != nil {
|
||||
return err
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if options.Disabled {
|
||||
return nil
|
||||
pw.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
|
||||
err = cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
|
||||
pw.CloseWithError(err)
|
||||
}()
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// PluginPush pushes a plugin to a registry
|
||||
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) error {
|
||||
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
|
||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
||||
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func TestPluginPushError(t *testing.T) {
|
|||
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
||||
}
|
||||
|
||||
err := client.PluginPush(context.Background(), "plugin_name", "")
|
||||
_, err := client.PluginPush(context.Background(), "plugin_name", "")
|
||||
if err == nil || err.Error() != "Error response from daemon: Server error" {
|
||||
t.Fatalf("expected a Server Error, got %v", err)
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func TestPluginPush(t *testing.T) {
|
|||
}),
|
||||
}
|
||||
|
||||
err := client.PluginPush(context.Background(), "plugin_name", "authtoken")
|
||||
_, err := client.PluginPush(context.Background(), "plugin_name", "authtoken")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue