Add deprecation status to plugin api and cli (#17077)

* api: Add deprecation status to plugin endpoints

* cli: Add -detailed flag to `plugin list`

* docs: Update plugin list/info docs
This commit is contained in:
Mike Palmiotto 2022-09-09 16:03:07 -04:00 committed by GitHub
parent 102f5f6832
commit 9849af8663
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 153 additions and 50 deletions

View File

@ -32,10 +32,11 @@ type ListPluginsResponse struct {
} }
type PluginDetails struct { type PluginDetails struct {
Type string `json:"string"` Type string `json:"string"`
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
Builtin bool `json:"builtin"` Builtin bool `json:"builtin"`
DeprecationStatus string `json:"deprecation_status,omitempty" mapstructure:"deprecation_status"`
} }
// ListPlugins wraps ListPluginsWithContext using context.Background. // ListPlugins wraps ListPluginsWithContext using context.Background.
@ -158,11 +159,12 @@ type GetPluginInput struct {
// GetPluginResponse is the response from the GetPlugin call. // GetPluginResponse is the response from the GetPlugin call.
type GetPluginResponse struct { type GetPluginResponse struct {
Args []string `json:"args"` Args []string `json:"args"`
Builtin bool `json:"builtin"` Builtin bool `json:"builtin"`
Command string `json:"command"` Command string `json:"command"`
Name string `json:"name"` Name string `json:"name"`
SHA256 string `json:"sha256"` SHA256 string `json:"sha256"`
DeprecationStatus string `json:"deprecation_status,omitempty"`
} }
// GetPlugin wraps GetPluginWithContext using context.Background. // GetPlugin wraps GetPluginWithContext using context.Background.

12
changelog/17077.txt Normal file
View File

@ -0,0 +1,12 @@
```release-note:change
plugins: `GET /sys/plugins/catalog/:type/:name` endpoint contains deprecation status for builtin plugins.
```
```release-note:change
plugins: `GET /sys/plugins/catalog/` endpoint contains deprecation status in `detailed` list.
```
```release-note:change
plugins: `plugin list` now accepts a `-detailed` flag, which display deprecation status and version info.
```
```release-note:change
plugins: `plugin info` displays deprecation status for builtin plugins.
```

View File

@ -107,11 +107,12 @@ func (c *PluginInfoCommand) Run(args []string) int {
} }
data := map[string]interface{}{ data := map[string]interface{}{
"args": resp.Args, "args": resp.Args,
"builtin": resp.Builtin, "builtin": resp.Builtin,
"command": resp.Command, "command": resp.Command,
"name": resp.Name, "name": resp.Name,
"sha256": resp.SHA256, "sha256": resp.SHA256,
"deprecation_status": resp.DeprecationStatus,
} }
if c.flagField != "" { if c.flagField != "" {

View File

@ -18,6 +18,8 @@ var (
type PluginListCommand struct { type PluginListCommand struct {
*BaseCommand *BaseCommand
flagDetailed bool
} }
func (c *PluginListCommand) Synopsis() string { func (c *PluginListCommand) Synopsis() string {
@ -40,13 +42,30 @@ Usage: vault plugin list [options] [TYPE]
$ vault plugin list database $ vault plugin list database
List all available plugins with detailed output:
$ vault plugin list -detailed
` + c.Flags().Help() ` + c.Flags().Help()
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *PluginListCommand) Flags() *FlagSets { func (c *PluginListCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat) set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
f.BoolVar(&BoolVar{
Name: "detailed",
Target: &c.flagDetailed,
Default: false,
Usage: "Print detailed plugin information such as plugin type, " +
"version, and deprecation status for each plugin. This option " +
"is only applicable to table-formatted output.",
})
return set
} }
func (c *PluginListCommand) AutocompleteArgs() complete.Predictor { func (c *PluginListCommand) AutocompleteArgs() complete.Predictor {
@ -105,19 +124,11 @@ func (c *PluginListCommand) Run(args []string) int {
switch Format(c.UI) { switch Format(c.UI) {
case "table": case "table":
var flattenedNames []string if c.flagDetailed {
namesAdded := make(map[string]bool) c.UI.Output(tableOutput(c.detailedResponse(resp), nil))
for _, names := range resp.PluginsByType { return 0
for _, name := range names {
if ok := namesAdded[name]; !ok {
flattenedNames = append(flattenedNames, name)
namesAdded[name] = true
}
}
sort.Strings(flattenedNames)
} }
list := append([]string{"Plugins"}, flattenedNames...) c.UI.Output(tableOutput(c.simpleResponse(resp), nil))
c.UI.Output(tableOutput(list, nil))
return 0 return 0
default: default:
res := make(map[string]interface{}) res := make(map[string]interface{})
@ -127,3 +138,28 @@ func (c *PluginListCommand) Run(args []string) int {
return OutputData(c.UI, res) return OutputData(c.UI, res)
} }
} }
func (c *PluginListCommand) simpleResponse(plugins *api.ListPluginsResponse) []string {
var flattenedNames []string
namesAdded := make(map[string]bool)
for _, names := range plugins.PluginsByType {
for _, name := range names {
if ok := namesAdded[name]; !ok {
flattenedNames = append(flattenedNames, name)
namesAdded[name] = true
}
}
sort.Strings(flattenedNames)
}
list := append([]string{"Plugins"}, flattenedNames...)
return list
}
func (c *PluginListCommand) detailedResponse(plugins *api.ListPluginsResponse) []string {
out := []string{"Name | Type | Version | Deprecation Status"}
for _, plugin := range plugins.Details {
out = append(out, fmt.Sprintf("%s | %s | %s | %s", plugin.Name, plugin.Type, plugin.Version, plugin.DeprecationStatus))
}
return out
}

View File

@ -88,11 +88,12 @@ func (r *PluginRunner) RunMetadataMode(ctx context.Context, wrapper RunnerUtil,
// VersionedPlugin holds any versioning information stored about a plugin in the // VersionedPlugin holds any versioning information stored about a plugin in the
// plugin catalog. // plugin catalog.
type VersionedPlugin struct { type VersionedPlugin struct {
Type string `json:"type"` // string instead of consts.PluginType so that we get the string form in API responses. Type string `json:"type"` // string instead of consts.PluginType so that we get the string form in API responses.
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
SHA256 string `json:"sha256,omitempty"` SHA256 string `json:"sha256,omitempty"`
Builtin bool `json:"builtin"` Builtin bool `json:"builtin"`
DeprecationStatus string `json:"deprecation_status,omitempty"`
// Pre-parsed semver struct of the Version field // Pre-parsed semver struct of the Version field
SemanticVersion *version.Version `json:"-"` SemanticVersion *version.Version `json:"-"`

View File

@ -572,6 +572,11 @@ func (b *SystemBackend) handlePluginCatalogRead(ctx context.Context, _ *logical.
"version": plugin.Version, "version": plugin.Version,
} }
if plugin.Builtin {
status, _ := b.Core.builtinRegistry.DeprecationStatus(plugin.Name, plugin.Type)
data["deprecation_status"] = status.String()
}
return &logical.Response{ return &logical.Response{
Data: data, Data: data,
}, nil }, nil

View File

@ -2989,14 +2989,18 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// Get deprecation status directly from the registry so we can compare it to the API response
deprecationStatus, _ := c.builtinRegistry.DeprecationStatus("mysql-database-plugin", consts.PluginTypeDatabase)
actualRespData := resp.Data actualRespData := resp.Data
expectedRespData := map[string]interface{}{ expectedRespData := map[string]interface{}{
"name": "mysql-database-plugin", "name": "mysql-database-plugin",
"command": "", "command": "",
"args": []string(nil), "args": []string(nil),
"sha256": "", "sha256": "",
"builtin": true, "builtin": true,
"version": c.pluginCatalog.getBuiltinVersion(consts.PluginTypeDatabase, "mysql-database-plugin"), "version": c.pluginCatalog.getBuiltinVersion(consts.PluginTypeDatabase, "mysql-database-plugin"),
"deprecation_status": deprecationStatus.String(),
} }
if !reflect.DeepEqual(actualRespData, expectedRespData) { if !reflect.DeepEqual(actualRespData, expectedRespData) {
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData) t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData)

View File

@ -853,15 +853,17 @@ func (c *PluginCatalog) listInternal(ctx context.Context, pluginType consts.Plug
version := c.getBuiltinVersion(pluginType, plugin) version := c.getBuiltinVersion(pluginType, plugin)
semanticVersion, err := semver.NewVersion(version) semanticVersion, err := semver.NewVersion(version)
deprecationStatus, _ := c.builtinRegistry.DeprecationStatus(plugin, pluginType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = append(result, pluginutil.VersionedPlugin{ result = append(result, pluginutil.VersionedPlugin{
Name: plugin, Name: plugin,
Type: pluginType.String(), Type: pluginType.String(),
Version: version, Version: version,
Builtin: true, Builtin: true,
SemanticVersion: semanticVersion, SemanticVersion: semanticVersion,
DeprecationStatus: deprecationStatus.String(),
}) })
} }

View File

@ -9,6 +9,12 @@ description: The "plugin info" command displays information about a plugin in th
The `plugin info` displays information about a plugin in the catalog. The `plugin info` displays information about a plugin in the catalog.
The plugin's type of "auth", "database", or "secret" must be included. The plugin's type of "auth", "database", or "secret" must be included.
## deprecation_status field
As of 1.12, all builtin plugins will have an associated Deprecation
Status. This status will be reflected in the `deprecation_status` key/value
pair, seen below.
## Examples ## Examples
Display information about a plugin Display information about a plugin
@ -16,13 +22,26 @@ Display information about a plugin
```shell-session ```shell-session
$ vault plugin info auth my-custom-plugin $ vault plugin info auth my-custom-plugin
Key Value Key Value
--- ----- --- -----
args [] args []
builtin false builtin false
command my-custom-plugin command my-custom-plugin
name my-custom-plugin deprecation_status n/a
sha256 d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 name my-custom-plugin
sha256 d3f0a8be02f6c074cf38c9c99d4d04c9c6466249
```
```shell-session
$ vault plugin info database postgresql-database-plugin
Key Value
--- -----
args []
builtin true
command n/a
deprecation_status supported
name postgresql-database-plugin
sha256 n/a
``` ```
## Usage ## Usage

View File

@ -9,6 +9,12 @@ description: The "plugin list" command lists all available plugins in the plugin
The `plugin list` command lists all available plugins in the plugin catalog. The `plugin list` command lists all available plugins in the plugin catalog.
It can be used alone or with a type such as "auth", "database", or "secret". It can be used alone or with a type such as "auth", "database", or "secret".
## Deprecation Status Column
As of 1.12, all builtin plugins will have an associated Deprecation
Status. This status will be reflected in the `Deprecation Status` column, seen
below. All non-builtin plugins will show a `Deprecation Status` of "n/a".
## Examples ## Examples
List all available plugins in the catalog. List all available plugins in the catalog.
@ -28,6 +34,16 @@ cassandra-database-plugin
# ... # ...
``` ```
List detailed plugin information:
```shell-session
$ vault plugin list -detailed
Name Type Version Deprecation Status
---- ---- ------- ------------------
alicloud auth v0.12.0+builtin supported
app-id auth v1.12.0+builtin.vault pending removal
# ...
```
## Usage ## Usage
The following flags are available in addition to the [standard set of The following flags are available in addition to the [standard set of
@ -38,3 +54,8 @@ flags](/docs/commands) included on all commands.
- `-format` `(string: "table")` - Print the output in the given format. Valid - `-format` `(string: "table")` - Print the output in the given format. Valid
formats are "table", "json", or "yaml". This can also be specified via the formats are "table", "json", or "yaml". This can also be specified via the
`VAULT_FORMAT` environment variable. `VAULT_FORMAT` environment variable.
### Command Options
- `-detailed` `(bool: false)` - Print detailed information such as version and
deprecation status about each plugin.