2aa90105d3
* just use an error string * Switch command to use new struct
340 lines
8.9 KiB
Go
340 lines
8.9 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// ListPluginsInput is used as input to the ListPlugins function.
|
|
type ListPluginsInput struct {
|
|
// Type of the plugin. Required.
|
|
Type consts.PluginType `json:"type"`
|
|
}
|
|
|
|
// ListPluginsResponse is the response from the ListPlugins call.
|
|
type ListPluginsResponse struct {
|
|
// PluginsByType is the list of plugins by type.
|
|
PluginsByType map[consts.PluginType][]string `json:"types"`
|
|
|
|
// Names is the list of names of the plugins.
|
|
//
|
|
// Deprecated: Newer server responses should be returning PluginsByType (json:
|
|
// "types") instead.
|
|
Names []string `json:"names"`
|
|
}
|
|
|
|
// ListPlugins lists all plugins in the catalog and returns their names as a
|
|
// list of strings.
|
|
func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
|
|
path := ""
|
|
method := ""
|
|
if i.Type == consts.PluginTypeUnknown {
|
|
path = "/v1/sys/plugins/catalog"
|
|
method = "GET"
|
|
} else {
|
|
path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Type)
|
|
method = "LIST"
|
|
}
|
|
|
|
req := c.c.NewRequest(method, path)
|
|
if method == "LIST" {
|
|
// Set this for broader compatibility, but we use LIST above to be able
|
|
// to handle the wrapping lookup function
|
|
req.Method = "GET"
|
|
req.Params.Set("list", "true")
|
|
}
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err != nil && resp == nil {
|
|
return nil, err
|
|
}
|
|
if resp == nil {
|
|
return nil, nil
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// We received an Unsupported Operation response from Vault, indicating
|
|
// Vault of an older version that doesn't support the GET method yet;
|
|
// switch it to a LIST.
|
|
if resp.StatusCode == 405 {
|
|
req.Params.Set("list", "true")
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var result struct {
|
|
Data struct {
|
|
Keys []string `json:"keys"`
|
|
} `json:"data"`
|
|
}
|
|
if err := resp.DecodeJSON(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ListPluginsResponse{Names: result.Data.Keys}, nil
|
|
}
|
|
|
|
secret, err := ParseSecret(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if secret == nil || secret.Data == nil {
|
|
return nil, errors.New("data from server response is empty")
|
|
}
|
|
|
|
result := &ListPluginsResponse{
|
|
PluginsByType: make(map[consts.PluginType][]string),
|
|
}
|
|
if i.Type == consts.PluginTypeUnknown {
|
|
for pluginTypeStr, pluginsRaw := range secret.Data {
|
|
pluginType, err := consts.ParsePluginType(pluginTypeStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pluginsIfc, ok := pluginsRaw.([]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to parse plugins for %q type", pluginTypeStr)
|
|
}
|
|
|
|
plugins := make([]string, len(pluginsIfc))
|
|
for i, nameIfc := range pluginsIfc {
|
|
name, ok := nameIfc.(string)
|
|
if !ok {
|
|
|
|
}
|
|
plugins[i] = name
|
|
}
|
|
result.PluginsByType[pluginType] = plugins
|
|
}
|
|
} else {
|
|
var respKeys []string
|
|
if err := mapstructure.Decode(secret.Data["keys"], &respKeys); err != nil {
|
|
return nil, err
|
|
}
|
|
result.PluginsByType[i.Type] = respKeys
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetPluginInput is used as input to the GetPlugin function.
|
|
type GetPluginInput struct {
|
|
Name string `json:"-"`
|
|
|
|
// Type of the plugin. Required.
|
|
Type consts.PluginType `json:"type"`
|
|
}
|
|
|
|
// GetPluginResponse is the response from the GetPlugin call.
|
|
type GetPluginResponse struct {
|
|
Args []string `json:"args"`
|
|
Builtin bool `json:"builtin"`
|
|
Command string `json:"command"`
|
|
Name string `json:"name"`
|
|
SHA256 string `json:"sha256"`
|
|
}
|
|
|
|
// GetPlugin retrieves information about the plugin.
|
|
func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) {
|
|
path := catalogPathByType(i.Type, i.Name)
|
|
req := c.c.NewRequest(http.MethodGet, path)
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result struct {
|
|
Data *GetPluginResponse
|
|
}
|
|
err = resp.DecodeJSON(&result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result.Data, err
|
|
}
|
|
|
|
// RegisterPluginInput is used as input to the RegisterPlugin function.
|
|
type RegisterPluginInput struct {
|
|
// Name is the name of the plugin. Required.
|
|
Name string `json:"-"`
|
|
|
|
// Type of the plugin. Required.
|
|
Type consts.PluginType `json:"type"`
|
|
|
|
// Args is the list of args to spawn the process with.
|
|
Args []string `json:"args,omitempty"`
|
|
|
|
// Command is the command to run.
|
|
Command string `json:"command,omitempty"`
|
|
|
|
// SHA256 is the shasum of the plugin.
|
|
SHA256 string `json:"sha256,omitempty"`
|
|
}
|
|
|
|
// RegisterPlugin registers the plugin with the given information.
|
|
func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
|
|
path := catalogPathByType(i.Type, i.Name)
|
|
req := c.c.NewRequest(http.MethodPut, path)
|
|
|
|
if err := req.SetJSONBody(i); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err == nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// DeregisterPluginInput is used as input to the DeregisterPlugin function.
|
|
type DeregisterPluginInput struct {
|
|
// Name is the name of the plugin. Required.
|
|
Name string `json:"-"`
|
|
|
|
// Type of the plugin. Required.
|
|
Type consts.PluginType `json:"type"`
|
|
}
|
|
|
|
// DeregisterPlugin removes the plugin with the given name from the plugin
|
|
// catalog.
|
|
func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
|
|
path := catalogPathByType(i.Type, i.Name)
|
|
req := c.c.NewRequest(http.MethodDelete, path)
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err == nil {
|
|
defer resp.Body.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ReloadPluginInput is used as input to the ReloadPlugin function.
|
|
type ReloadPluginInput struct {
|
|
// Plugin is the name of the plugin to reload, as registered in the plugin catalog
|
|
Plugin string `json:"plugin"`
|
|
|
|
// Mounts is the array of string mount paths of the plugin backends to reload
|
|
Mounts []string `json:"mounts"`
|
|
|
|
// Scope is the scope of the plugin reload
|
|
Scope string `json:"scope"`
|
|
}
|
|
|
|
// ReloadPlugin reloads mounted plugin backends, possibly returning
|
|
// reloadId for a cluster scoped reload
|
|
func (c *Sys) ReloadPlugin(i *ReloadPluginInput) (string, error) {
|
|
path := "/v1/sys/plugins/reload/backend"
|
|
req := c.c.NewRequest(http.MethodPut, path)
|
|
|
|
if err := req.SetJSONBody(i); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if i.Scope == "global" {
|
|
// Get the reload id
|
|
secret, parseErr := ParseSecret(resp.Body)
|
|
if parseErr != nil {
|
|
return "", parseErr
|
|
}
|
|
if _, ok := secret.Data["reload_id"]; ok {
|
|
return secret.Data["reload_id"].(string), nil
|
|
}
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
// ReloadStatus is the status of an individual node's plugin reload
|
|
type ReloadStatus struct {
|
|
Timestamp time.Time `json:"timestamp" mapstructure:"timestamp"`
|
|
Error string `json:"error" mapstructure:"error"`
|
|
}
|
|
|
|
// ReloadStatusResponse is the combined response of all known completed plugin reloads
|
|
type ReloadStatusResponse struct {
|
|
ReloadID string `mapstructure:"reload_id"`
|
|
Results map[string]*ReloadStatus `mapstructure:"results"`
|
|
}
|
|
|
|
// ReloadPluginStatusInput is used as input to the ReloadStatusPlugin function.
|
|
type ReloadPluginStatusInput struct {
|
|
// ReloadID is the ID of the reload operation
|
|
ReloadID string `json:"reload_id"`
|
|
}
|
|
|
|
// ReloadPluginStatus retrieves the status of a reload operation
|
|
func (c *Sys) ReloadPluginStatus(reloadStatusInput *ReloadPluginStatusInput) (*ReloadStatusResponse, error) {
|
|
path := "/v1/sys/plugins/reload/backend/status"
|
|
req := c.c.NewRequest(http.MethodGet, path)
|
|
req.Params.Add("reload_id", reloadStatusInput.ReloadID)
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
|
|
resp, err := c.c.RawRequestWithContext(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp != nil {
|
|
secret, parseErr := ParseSecret(resp.Body)
|
|
if parseErr != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var r ReloadStatusResponse
|
|
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339),
|
|
Result: &r,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = d.Decode(secret.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &r, nil
|
|
}
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
// catalogPathByType is a helper to construct the proper API path by plugin type
|
|
func catalogPathByType(pluginType consts.PluginType, name string) string {
|
|
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", pluginType, name)
|
|
|
|
// Backwards compat, if type is not provided then use old path
|
|
if pluginType == consts.PluginTypeUnknown {
|
|
path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", name)
|
|
}
|
|
|
|
return path
|
|
}
|