1package client // import "github.com/docker/docker/client" 2 3import ( 4 "context" 5 "encoding/json" 6 "io" 7 "net/url" 8 9 "github.com/docker/distribution/reference" 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/errdefs" 12 "github.com/pkg/errors" 13) 14 15// PluginInstall installs a plugin 16func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) { 17 query := url.Values{} 18 if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil { 19 return nil, errors.Wrap(err, "invalid remote reference") 20 } 21 query.Set("remote", options.RemoteRef) 22 23 privileges, err := cli.checkPluginPermissions(ctx, query, options) 24 if err != nil { 25 return nil, err 26 } 27 28 // set name for plugin pull, if empty should default to remote reference 29 query.Set("name", name) 30 31 resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth) 32 if err != nil { 33 return nil, err 34 } 35 36 name = resp.header.Get("Docker-Plugin-Name") 37 38 pr, pw := io.Pipe() 39 go func() { // todo: the client should probably be designed more around the actual api 40 _, err := io.Copy(pw, resp.body) 41 if err != nil { 42 pw.CloseWithError(err) 43 return 44 } 45 defer func() { 46 if err != nil { 47 delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) 48 ensureReaderClosed(delResp) 49 } 50 }() 51 if len(options.Args) > 0 { 52 if err := cli.PluginSet(ctx, name, options.Args); err != nil { 53 pw.CloseWithError(err) 54 return 55 } 56 } 57 58 if options.Disabled { 59 pw.Close() 60 return 61 } 62 63 enableErr := cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0}) 64 pw.CloseWithError(enableErr) 65 }() 66 return pr, nil 67} 68 69func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { 70 headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 71 return cli.get(ctx, "/plugins/privileges", query, headers) 72} 73 74func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) { 75 headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 76 return cli.post(ctx, "/plugins/pull", query, privileges, headers) 77} 78 79func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options types.PluginInstallOptions) (types.PluginPrivileges, error) { 80 resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) 81 if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { 82 // todo: do inspect before to check existing name before checking privileges 83 newAuthHeader, privilegeErr := options.PrivilegeFunc() 84 if privilegeErr != nil { 85 ensureReaderClosed(resp) 86 return nil, privilegeErr 87 } 88 options.RegistryAuth = newAuthHeader 89 resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) 90 } 91 if err != nil { 92 ensureReaderClosed(resp) 93 return nil, err 94 } 95 96 var privileges types.PluginPrivileges 97 if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil { 98 ensureReaderClosed(resp) 99 return nil, err 100 } 101 ensureReaderClosed(resp) 102 103 if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { 104 accept, err := options.AcceptPermissionsFunc(privileges) 105 if err != nil { 106 return nil, err 107 } 108 if !accept { 109 return nil, pluginPermissionDenied{options.RemoteRef} 110 } 111 } 112 return privileges, nil 113} 114