mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 12:07:54 +00:00
Plugins: Add -version flag to 'vault plugin info' (#17454)
* Add -version flag to 'vault plugin info' * Allow specifying a builtin tag when reading a single plugin from the catalog
This commit is contained in:
@@ -134,6 +134,7 @@ type GetPluginInput struct {
|
|||||||
|
|
||||||
// Type of the plugin. Required.
|
// Type of the plugin. Required.
|
||||||
Type consts.PluginType `json:"type"`
|
Type consts.PluginType `json:"type"`
|
||||||
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPluginResponse is the response from the GetPlugin call.
|
// GetPluginResponse is the response from the GetPlugin call.
|
||||||
@@ -144,6 +145,7 @@ type GetPluginResponse struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
SHA256 string `json:"sha256"`
|
SHA256 string `json:"sha256"`
|
||||||
DeprecationStatus string `json:"deprecation_status,omitempty"`
|
DeprecationStatus string `json:"deprecation_status,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlugin wraps GetPluginWithContext using context.Background.
|
// GetPlugin wraps GetPluginWithContext using context.Background.
|
||||||
@@ -158,6 +160,9 @@ func (c *Sys) GetPluginWithContext(ctx context.Context, i *GetPluginInput) (*Get
|
|||||||
|
|
||||||
path := catalogPathByType(i.Type, i.Name)
|
path := catalogPathByType(i.Type, i.Name)
|
||||||
req := c.c.NewRequest(http.MethodGet, path)
|
req := c.c.NewRequest(http.MethodGet, path)
|
||||||
|
if i.Version != "" {
|
||||||
|
req.Params.Set("version", i.Version)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := c.c.rawRequestWithContext(ctx, req)
|
resp, err := c.c.rawRequestWithContext(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
@@ -115,6 +116,141 @@ func TestListPlugins(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPlugin(t *testing.T) {
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
version string
|
||||||
|
body string
|
||||||
|
expected GetPluginResponse
|
||||||
|
}{
|
||||||
|
"builtin": {
|
||||||
|
body: getResponse,
|
||||||
|
expected: GetPluginResponse{
|
||||||
|
Args: nil,
|
||||||
|
Builtin: true,
|
||||||
|
Command: "",
|
||||||
|
Name: "azure",
|
||||||
|
SHA256: "",
|
||||||
|
DeprecationStatus: "supported",
|
||||||
|
Version: "v0.14.0+builtin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"external": {
|
||||||
|
version: "v1.0.0",
|
||||||
|
body: getResponseExternal,
|
||||||
|
expected: GetPluginResponse{
|
||||||
|
Args: []string{},
|
||||||
|
Builtin: false,
|
||||||
|
Command: "azure-plugin",
|
||||||
|
Name: "azure",
|
||||||
|
SHA256: "8ba442dba253803685b05e35ad29dcdebc48dec16774614aa7a4ebe53c1e90e1",
|
||||||
|
DeprecationStatus: "",
|
||||||
|
Version: "v1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"old server": {
|
||||||
|
body: getResponseOldServerVersion,
|
||||||
|
expected: GetPluginResponse{
|
||||||
|
Args: nil,
|
||||||
|
Builtin: true,
|
||||||
|
Command: "",
|
||||||
|
Name: "azure",
|
||||||
|
SHA256: "",
|
||||||
|
DeprecationStatus: "",
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerInfo(tc.body)))
|
||||||
|
defer mockVaultServer.Close()
|
||||||
|
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
cfg.Address = mockVaultServer.URL
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := GetPluginInput{
|
||||||
|
Name: "azure",
|
||||||
|
Type: consts.PluginTypeSecrets,
|
||||||
|
}
|
||||||
|
if tc.version != "" {
|
||||||
|
input.Version = tc.version
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := client.Sys().GetPluginWithContext(context.Background(), &input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.expected, *info) {
|
||||||
|
t.Errorf("expected: %#v\ngot: %#v", tc.expected, info)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockVaultHandlerInfo(body string) func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
_, _ = w.Write([]byte(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getResponse = `{
|
||||||
|
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c495241",
|
||||||
|
"lease_id": "",
|
||||||
|
"renewable": false,
|
||||||
|
"lease_duration": 0,
|
||||||
|
"data": {
|
||||||
|
"args": null,
|
||||||
|
"builtin": true,
|
||||||
|
"command": "",
|
||||||
|
"deprecation_status": "supported",
|
||||||
|
"name": "azure",
|
||||||
|
"sha256": "",
|
||||||
|
"version": "v0.14.0+builtin"
|
||||||
|
},
|
||||||
|
"wrap_info": null,
|
||||||
|
"warnings": null,
|
||||||
|
"auth": null
|
||||||
|
}`
|
||||||
|
|
||||||
|
const getResponseExternal = `{
|
||||||
|
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c495241",
|
||||||
|
"lease_id": "",
|
||||||
|
"renewable": false,
|
||||||
|
"lease_duration": 0,
|
||||||
|
"data": {
|
||||||
|
"args": [],
|
||||||
|
"builtin": false,
|
||||||
|
"command": "azure-plugin",
|
||||||
|
"name": "azure",
|
||||||
|
"sha256": "8ba442dba253803685b05e35ad29dcdebc48dec16774614aa7a4ebe53c1e90e1",
|
||||||
|
"version": "v1.0.0"
|
||||||
|
},
|
||||||
|
"wrap_info": null,
|
||||||
|
"warnings": null,
|
||||||
|
"auth": null
|
||||||
|
}`
|
||||||
|
|
||||||
|
const getResponseOldServerVersion = `{
|
||||||
|
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c495241",
|
||||||
|
"lease_id": "",
|
||||||
|
"renewable": false,
|
||||||
|
"lease_duration": 0,
|
||||||
|
"data": {
|
||||||
|
"args": null,
|
||||||
|
"builtin": true,
|
||||||
|
"command": "",
|
||||||
|
"name": "azure",
|
||||||
|
"sha256": ""
|
||||||
|
},
|
||||||
|
"wrap_info": null,
|
||||||
|
"warnings": null,
|
||||||
|
"auth": null
|
||||||
|
}`
|
||||||
|
|
||||||
func mockVaultHandlerList(w http.ResponseWriter, _ *http.Request) {
|
func mockVaultHandlerList(w http.ResponseWriter, _ *http.Request) {
|
||||||
_, _ = w.Write([]byte(listUntypedResponse))
|
_, _ = w.Write([]byte(listUntypedResponse))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func TestPluginDeregisterCommand_Run(t *testing.T) {
|
|||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
pluginName := "my-plugin"
|
pluginName := "my-plugin"
|
||||||
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
|
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential, "")
|
||||||
|
|
||||||
ui, cmd := testPluginDeregisterCommand(t)
|
ui, cmd := testPluginDeregisterCommand(t)
|
||||||
cmd.client = client
|
cmd.client = client
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ var (
|
|||||||
|
|
||||||
type PluginInfoCommand struct {
|
type PluginInfoCommand struct {
|
||||||
*BaseCommand
|
*BaseCommand
|
||||||
|
|
||||||
|
flagVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PluginInfoCommand) Synopsis() string {
|
func (c *PluginInfoCommand) Synopsis() string {
|
||||||
@@ -41,7 +43,18 @@ Usage: vault plugin info [options] TYPE NAME
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *PluginInfoCommand) Flags() *FlagSets {
|
func (c *PluginInfoCommand) Flags() *FlagSets {
|
||||||
return c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||||
|
|
||||||
|
f := set.NewFlagSet("Command Options")
|
||||||
|
|
||||||
|
f.StringVar(&StringVar{
|
||||||
|
Name: "version",
|
||||||
|
Target: &c.flagVersion,
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Semantic version of the plugin. Optional.",
|
||||||
|
})
|
||||||
|
|
||||||
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PluginInfoCommand) AutocompleteArgs() complete.Predictor {
|
func (c *PluginInfoCommand) AutocompleteArgs() complete.Predictor {
|
||||||
@@ -95,6 +108,7 @@ func (c *PluginInfoCommand) Run(args []string) int {
|
|||||||
resp, err := client.Sys().GetPlugin(&api.GetPluginInput{
|
resp, err := client.Sys().GetPlugin(&api.GetPluginInput{
|
||||||
Name: pluginName,
|
Name: pluginName,
|
||||||
Type: pluginType,
|
Type: pluginType,
|
||||||
|
Version: c.flagVersion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error reading plugin named %s: %s", pluginName, err))
|
c.UI.Error(fmt.Sprintf("Error reading plugin named %s: %s", pluginName, err))
|
||||||
@@ -113,6 +127,7 @@ func (c *PluginInfoCommand) Run(args []string) int {
|
|||||||
"name": resp.Name,
|
"name": resp.Name,
|
||||||
"sha256": resp.SHA256,
|
"sha256": resp.SHA256,
|
||||||
"deprecation_status": resp.DeprecationStatus,
|
"deprecation_status": resp.DeprecationStatus,
|
||||||
|
"version": resp.Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.flagField != "" {
|
if c.flagField != "" {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/helper/versions"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
@@ -81,7 +82,7 @@ func TestPluginInfoCommand_Run(t *testing.T) {
|
|||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
pluginName := "my-plugin"
|
pluginName := "my-plugin"
|
||||||
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
|
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential, "")
|
||||||
|
|
||||||
ui, cmd := testPluginInfoCommand(t)
|
ui, cmd := testPluginInfoCommand(t)
|
||||||
cmd.client = client
|
cmd.client = client
|
||||||
@@ -102,6 +103,52 @@ func TestPluginInfoCommand_Run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("version flag", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pluginDir, cleanup := vault.MakeTestPluginDir(t)
|
||||||
|
defer cleanup(t)
|
||||||
|
|
||||||
|
client, _, closer := testVaultServerPluginDir(t, pluginDir)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
const pluginName = "azure"
|
||||||
|
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential, "v1.0.0")
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
version string
|
||||||
|
expectedSHA string
|
||||||
|
}{
|
||||||
|
"versioned": {"v1.0.0", sha256Sum},
|
||||||
|
"builtin version": {versions.GetBuiltinVersion(consts.PluginTypeSecrets, pluginName), ""},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
ui, cmd := testPluginInfoCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
code := cmd.Run([]string{
|
||||||
|
"-version=" + tc.version,
|
||||||
|
consts.PluginTypeCredential.String(), pluginName,
|
||||||
|
})
|
||||||
|
|
||||||
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||||
|
if exp := 0; code != exp {
|
||||||
|
t.Errorf("expected %d to be %d: %s", code, exp, combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(combined, pluginName) {
|
||||||
|
t.Errorf("expected %q to contain %q", combined, pluginName)
|
||||||
|
}
|
||||||
|
if !strings.Contains(combined, tc.expectedSHA) {
|
||||||
|
t.Errorf("expected %q to contain %q", combined, tc.expectedSHA)
|
||||||
|
}
|
||||||
|
if !strings.Contains(combined, tc.version) {
|
||||||
|
t.Errorf("expected %q to contain %q", combined, tc.version)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("field", func(t *testing.T) {
|
t.Run("field", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -112,7 +159,7 @@ func TestPluginInfoCommand_Run(t *testing.T) {
|
|||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
pluginName := "my-plugin"
|
pluginName := "my-plugin"
|
||||||
testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
|
testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential, "")
|
||||||
|
|
||||||
ui, cmd := testPluginInfoCommand(t)
|
ui, cmd := testPluginInfoCommand(t)
|
||||||
cmd.client = client
|
cmd.client = client
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func TestPluginReloadCommand_Run(t *testing.T) {
|
|||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
pluginName := "my-plugin"
|
pluginName := "my-plugin"
|
||||||
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
|
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential, "")
|
||||||
|
|
||||||
ui, cmd := testPluginReloadCommand(t)
|
ui, cmd := testPluginReloadCommand(t)
|
||||||
cmd.client = client
|
cmd.client = client
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func testPluginCreate(tb testing.TB, dir, name string) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// testPluginCreateAndRegister creates a plugin and registers it in the catalog.
|
// testPluginCreateAndRegister creates a plugin and registers it in the catalog.
|
||||||
func testPluginCreateAndRegister(tb testing.TB, client *api.Client, dir, name string, pluginType consts.PluginType) (string, string) {
|
func testPluginCreateAndRegister(tb testing.TB, client *api.Client, dir, name string, pluginType consts.PluginType, version string) (string, string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
pth, sha256Sum := testPluginCreate(tb, dir, name)
|
pth, sha256Sum := testPluginCreate(tb, dir, name)
|
||||||
@@ -48,6 +48,7 @@ func testPluginCreateAndRegister(tb testing.TB, client *api.Client, dir, name st
|
|||||||
Type: pluginType,
|
Type: pluginType,
|
||||||
Command: name,
|
Command: name,
|
||||||
SHA256: sha256Sum,
|
SHA256: sha256Sum,
|
||||||
|
Version: version,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
tb.Fatal(err)
|
tb.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -473,10 +473,13 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginVersion, err := getVersion(d)
|
pluginVersion, builtin, err := getVersion(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse(err.Error()), nil
|
return logical.ErrorResponse(err.Error()), nil
|
||||||
}
|
}
|
||||||
|
if builtin {
|
||||||
|
return logical.ErrorResponse("version %q is not allowed because 'builtin' is a reserved metadata identifier", pluginVersion), nil
|
||||||
|
}
|
||||||
|
|
||||||
sha256 := d.Get("sha256").(string)
|
sha256 := d.Get("sha256").(string)
|
||||||
if sha256 == "" {
|
if sha256 == "" {
|
||||||
@@ -546,7 +549,7 @@ func (b *SystemBackend) handlePluginCatalogRead(ctx context.Context, _ *logical.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginVersion, err := getVersion(d)
|
pluginVersion, _, err := getVersion(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse(err.Error()), nil
|
return logical.ErrorResponse(err.Error()), nil
|
||||||
}
|
}
|
||||||
@@ -592,10 +595,13 @@ func (b *SystemBackend) handlePluginCatalogDelete(ctx context.Context, _ *logica
|
|||||||
return logical.ErrorResponse("missing plugin name"), nil
|
return logical.ErrorResponse("missing plugin name"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginVersion, err := getVersion(d)
|
pluginVersion, builtin, err := getVersion(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse(err.Error()), nil
|
return logical.ErrorResponse(err.Error()), nil
|
||||||
}
|
}
|
||||||
|
if builtin {
|
||||||
|
return logical.ErrorResponse("version %q cannot be deleted", pluginVersion), nil
|
||||||
|
}
|
||||||
|
|
||||||
var resp *logical.Response
|
var resp *logical.Response
|
||||||
pluginTypeStr := d.Get("type").(string)
|
pluginTypeStr := d.Get("type").(string)
|
||||||
@@ -620,18 +626,19 @@ func (b *SystemBackend) handlePluginCatalogDelete(ctx context.Context, _ *logica
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getVersion(d *framework.FieldData) (string, error) {
|
func getVersion(d *framework.FieldData) (version string, builtin bool, err error) {
|
||||||
version := d.Get("version").(string)
|
version = d.Get("version").(string)
|
||||||
if version != "" {
|
if version != "" {
|
||||||
semanticVersion, err := semver.NewSemver(version)
|
semanticVersion, err := semver.NewSemver(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("version %q is not a valid semantic version: %w", version, err)
|
return "", false, fmt.Errorf("version %q is not a valid semantic version: %w", version, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataIdentifiers := strings.Split(semanticVersion.Metadata(), ".")
|
metadataIdentifiers := strings.Split(semanticVersion.Metadata(), ".")
|
||||||
for _, identifier := range metadataIdentifiers {
|
for _, identifier := range metadataIdentifiers {
|
||||||
if identifier == "builtin" {
|
if identifier == "builtin" {
|
||||||
return "", fmt.Errorf("version %q is not allowed because 'builtin' is a reserved metadata identifier", version)
|
builtin = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,7 +647,7 @@ func getVersion(d *framework.FieldData) (string, error) {
|
|||||||
version = "v" + semanticVersion.String()
|
version = "v" + semanticVersion.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return version, nil
|
return version, builtin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user