mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
add CLI commands for plugin runtime VAULT-18181 (#22819)
--------- Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
@@ -64,8 +64,8 @@ type RegisterPluginRuntimeInput struct {
|
||||
|
||||
OCIRuntime string `json:"oci_runtime,omitempty"`
|
||||
CgroupParent string `json:"cgroup_parent,omitempty"`
|
||||
CPU int64 `json:"cpu,omitempty"`
|
||||
Memory int64 `json:"memory,omitempty"`
|
||||
CPU int64 `json:"cpu_nanos,omitempty"`
|
||||
Memory int64 `json:"memory_bytes,omitempty"`
|
||||
}
|
||||
|
||||
// RegisterPluginRuntime registers the plugin with the given information.
|
||||
|
||||
@@ -604,6 +604,31 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) map[string]cli.Co
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin runtime": func() (cli.Command, error) {
|
||||
return &PluginRuntimeCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin runtime register": func() (cli.Command, error) {
|
||||
return &PluginRuntimeRegisterCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin runtime deregister": func() (cli.Command, error) {
|
||||
return &PluginRuntimeDeregisterCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin runtime info": func() (cli.Command, error) {
|
||||
return &PluginRuntimeInfoCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin runtime list": func() (cli.Command, error) {
|
||||
return &PluginRuntimeListCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"proxy": func() (cli.Command, error) {
|
||||
return &ProxyCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
|
||||
@@ -6,14 +6,11 @@ package command
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
@@ -338,40 +335,3 @@ func TestFlagParsing(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mockClient(t *testing.T) (*api.Client, *recordingRoundTripper) {
|
||||
t.Helper()
|
||||
|
||||
config := api.DefaultConfig()
|
||||
httpClient := cleanhttp.DefaultClient()
|
||||
roundTripper := &recordingRoundTripper{}
|
||||
httpClient.Transport = roundTripper
|
||||
config.HttpClient = httpClient
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return client, roundTripper
|
||||
}
|
||||
|
||||
var _ http.RoundTripper = (*recordingRoundTripper)(nil)
|
||||
|
||||
type recordingRoundTripper struct {
|
||||
path string
|
||||
body []byte
|
||||
}
|
||||
|
||||
func (r *recordingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
r.path = req.URL.Path
|
||||
defer req.Body.Close()
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.body = body
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
}, nil
|
||||
}
|
||||
|
||||
54
command/plugin_runtime.go
Normal file
54
command/plugin_runtime.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*PluginRuntimeCommand)(nil)
|
||||
|
||||
type PluginRuntimeCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeCommand) Synopsis() string {
|
||||
return "Interact with Vault plugin runtimes catalog."
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin runtime <subcommand> [options] [args]
|
||||
|
||||
This command groups subcommands for interacting with Vault's plugin runtimes and the
|
||||
plugin runtime catalog. The plugin runtime catalog is divided into types. Currently,
|
||||
Vault only supports "container" plugin runtimes. A plugin runtime allows users to
|
||||
fine-tune the parameters with which a plugin is executed. For example, you can select
|
||||
a different OCI-compatible runtime, or set resource limits. A plugin runtime can
|
||||
optionally be referenced during plugin registration. A type must be specified on each call.
|
||||
Here are a few examples of the plugin runtime commands.
|
||||
|
||||
List all available plugin runtimes in the catalog of a particular type:
|
||||
|
||||
$ vault plugin runtime list -type=container
|
||||
|
||||
Register a new plugin runtime to the catalog as a particular type:
|
||||
|
||||
$ vault plugin runtime register -type=container -oci_runtime=my-oci-runtime my-custom-plugin-runtime
|
||||
|
||||
Get information about a plugin runtime in the catalog listed under a particular type:
|
||||
|
||||
$ vault plugin runtime info -type=container my-custom-plugin-runtime
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeCommand) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
124
command/plugin_runtime_deregister.go
Normal file
124
command/plugin_runtime_deregister.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*PluginRuntimeDeregisterCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*PluginRuntimeDeregisterCommand)(nil)
|
||||
)
|
||||
|
||||
type PluginRuntimeDeregisterCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagType string
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeDeregisterCommand) Synopsis() string {
|
||||
return "Deregister an existing plugin runtime in the catalog"
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeDeregisterCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin runtime deregister [options] NAME
|
||||
|
||||
Deregister an existing plugin runtime in the catalog with the given name. If
|
||||
any registered plugin references the plugin runtime, an error is returned. If
|
||||
the plugin runtime does not exist, an error is returned. The -type flag
|
||||
currently only accepts "container".
|
||||
|
||||
Deregister a plugin runtime:
|
||||
|
||||
$ vault plugin runtime deregister -type=container my-plugin-runtime
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeDeregisterCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "type",
|
||||
Target: &c.flagType,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Plugin runtime type. Vault currently only supports \"container\" runtime type.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeDeregisterCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeDeregisterCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeDeregisterCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
runtimeTyeRaw := strings.TrimSpace(c.flagType)
|
||||
if len(runtimeTyeRaw) == 0 {
|
||||
c.UI.Error("-type is required for plugin runtime deregistration")
|
||||
return 1
|
||||
}
|
||||
|
||||
runtimeType, err := api.ParsePluginRuntimeType(runtimeTyeRaw)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
var runtimeNameRaw string
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
|
||||
// This case should come after invalid cases have been checked
|
||||
case len(args) == 1:
|
||||
runtimeNameRaw = args[0]
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
runtimeName := strings.TrimSpace(runtimeNameRaw)
|
||||
if err = client.Sys().DeregisterPluginRuntime(context.Background(), &api.DeregisterPluginRuntimeInput{
|
||||
Name: runtimeName,
|
||||
Type: runtimeType,
|
||||
}); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error deregistering plugin runtime named %s: %s", runtimeName, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Output(fmt.Sprintf("Success! Deregistered plugin runtime: %s", runtimeName))
|
||||
return 0
|
||||
}
|
||||
116
command/plugin_runtime_deregister_test.go
Normal file
116
command/plugin_runtime_deregister_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testPluginRuntimeDeregisterCommand(tb testing.TB) (*cli.MockUi, *PluginRuntimeDeregisterCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginRuntimeDeregisterCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRuntimeDeregisterCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
out string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
"not_enough_args",
|
||||
[]string{"-type=container"},
|
||||
"Not enough arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"too_many_args",
|
||||
[]string{"-type=container", "foo", "baz"},
|
||||
"Too many arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"invalid_runtime_type",
|
||||
[]string{"-type=foo", "bar"},
|
||||
"\"foo\" is not a supported plugin runtime type",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"info_container_on_empty_plugin_runtime_catalog",
|
||||
[]string{"-type=container", "my-plugin-runtime"},
|
||||
"Error deregistering plugin runtime named my-plugin-runtime",
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("validations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeDeregisterCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run(tc.args)
|
||||
if code != tc.code {
|
||||
t.Errorf("expected %d to be %d", code, tc.code)
|
||||
}
|
||||
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
matcher := regexp.MustCompile(tc.out)
|
||||
if !matcher.MatchString(combined) {
|
||||
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("communication_failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServerBad(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeDeregisterCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{"-type=container", "my-plugin-runtime"})
|
||||
if exp := 2; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Error deregistering plugin runtime named my-plugin-runtime"
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no_tabs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, cmd := testPluginRuntimeDeregisterCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
||||
140
command/plugin_runtime_info.go
Normal file
140
command/plugin_runtime_info.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*PluginRuntimeInfoCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*PluginRuntimeInfoCommand)(nil)
|
||||
)
|
||||
|
||||
type PluginRuntimeInfoCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagType string
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeInfoCommand) Synopsis() string {
|
||||
return "Read information about a plugin runtime in the catalog"
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeInfoCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin runtime info [options] NAME
|
||||
|
||||
Displays information about a plugin runtime in the catalog with the given name. If
|
||||
the plugin runtime does not exist, an error is returned. The -type flag
|
||||
currently only accepts "container".
|
||||
|
||||
Get info about a plugin runtime:
|
||||
|
||||
$ vault plugin runtime info -type=container my-plugin-runtime
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeInfoCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "type",
|
||||
Target: &c.flagType,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Plugin runtime type. Vault currently only supports \"container\" runtime type.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeInfoCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeInfoCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeInfoCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
runtimeTyeRaw := strings.TrimSpace(c.flagType)
|
||||
if len(runtimeTyeRaw) == 0 {
|
||||
c.UI.Error("-type is required for plugin runtime info retrieval")
|
||||
return 1
|
||||
}
|
||||
|
||||
runtimeType, err := api.ParsePluginRuntimeType(runtimeTyeRaw)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
var runtimeNameRaw string
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
|
||||
// This case should come after invalid cases have been checked
|
||||
case len(args) == 1:
|
||||
runtimeNameRaw = args[0]
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
runtimeName := strings.TrimSpace(runtimeNameRaw)
|
||||
resp, err := client.Sys().GetPluginRuntime(context.Background(), &api.GetPluginRuntimeInput{
|
||||
Name: runtimeName,
|
||||
Type: runtimeType,
|
||||
})
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading plugin runtime named %s: %s", runtimeName, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found for plugin runtime %q", runtimeName))
|
||||
return 2
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": resp.Name,
|
||||
"type": resp.Type,
|
||||
"oci_runtime": resp.OCIRuntime,
|
||||
"cgroup_parent": resp.CgroupParent,
|
||||
"cpu_nanos": resp.CPU,
|
||||
"memory_bytes": resp.Memory,
|
||||
}
|
||||
|
||||
if c.flagField != "" {
|
||||
return PrintRawField(c.UI, data, c.flagField)
|
||||
}
|
||||
return OutputData(c.UI, data)
|
||||
}
|
||||
116
command/plugin_runtime_info_test.go
Normal file
116
command/plugin_runtime_info_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testPluginRuntimeInfoCommand(tb testing.TB) (*cli.MockUi, *PluginRuntimeInfoCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginRuntimeInfoCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRuntimeInfoCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
out string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
"not_enough_args",
|
||||
[]string{"-type=container"},
|
||||
"Not enough arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"too_many_args",
|
||||
[]string{"-type=container", "bar", "baz"},
|
||||
"Too many arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"invalid_runtime_type",
|
||||
[]string{"-type=foo", "bar"},
|
||||
"\"foo\" is not a supported plugin runtime type",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"info_container_on_empty_plugin_runtime_catalog",
|
||||
[]string{"-type=container", "my-plugin-runtime"},
|
||||
"Error reading plugin runtime named my-plugin-runtime",
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("validations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeInfoCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run(tc.args)
|
||||
if code != tc.code {
|
||||
t.Errorf("expected %d to be %d", code, tc.code)
|
||||
}
|
||||
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
matcher := regexp.MustCompile(tc.out)
|
||||
if !matcher.MatchString(combined) {
|
||||
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("communication_failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServerBad(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeInfoCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{"-type=container", "my-plugin-runtime"})
|
||||
if exp := 2; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Error reading plugin runtime named my-plugin-runtime"
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no_tabs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, cmd := testPluginRuntimeInfoCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
||||
131
command/plugin_runtime_list.go
Normal file
131
command/plugin_runtime_list.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*PluginRuntimeListCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*PluginRuntimeListCommand)(nil)
|
||||
)
|
||||
|
||||
type PluginRuntimeListCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagType string
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) Synopsis() string {
|
||||
return "Lists available plugin runtimes"
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin runtime list [options]
|
||||
|
||||
Lists available plugin runtimes registered in the catalog. This does not list whether
|
||||
plugin runtimes are in use, but rather just their availability.
|
||||
|
||||
List all available plugin runtimes in the catalog:
|
||||
|
||||
$ vault plugin runtime list
|
||||
|
||||
List all available container plugin runtimes in the catalog:
|
||||
|
||||
$ vault plugin runtime list -type=container
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "type",
|
||||
Target: &c.flagType,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Plugin runtime type. Vault currently only supports \"container\" runtime type.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(f.Args()) > 0 {
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
var input *api.ListPluginRuntimesInput
|
||||
runtimeTyeRaw := strings.TrimSpace(c.flagType)
|
||||
if len(runtimeTyeRaw) > 0 {
|
||||
runtimeType, err := api.ParsePluginRuntimeType(runtimeTyeRaw)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
input = &api.ListPluginRuntimesInput{Type: runtimeType}
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
resp, err := client.Sys().ListPluginRuntimes(context.Background(), input)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error listing available plugin runtimes: %s", err))
|
||||
return 2
|
||||
}
|
||||
if resp == nil {
|
||||
c.UI.Error("No tableResponse from server when listing plugin runtimes")
|
||||
return 2
|
||||
}
|
||||
|
||||
switch Format(c.UI) {
|
||||
case "table":
|
||||
c.UI.Output(tableOutput(c.tableResponse(resp), nil))
|
||||
return 0
|
||||
default:
|
||||
return OutputData(c.UI, resp.Runtimes)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeListCommand) tableResponse(response *api.ListPluginRuntimesResponse) []string {
|
||||
out := []string{"Name | Type | OCI Runtime | Parent Cgroup | CPU Nanos | Memory Bytes"}
|
||||
for _, runtime := range response.Runtimes {
|
||||
out = append(out, fmt.Sprintf("%s | %s | %s | %s | %d | %d",
|
||||
runtime.Name, runtime.Type, runtime.OCIRuntime, runtime.CgroupParent, runtime.CPU, runtime.Memory))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
116
command/plugin_runtime_list_test.go
Normal file
116
command/plugin_runtime_list_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testPluginRuntimeListCommand(tb testing.TB) (*cli.MockUi, *PluginRuntimeListCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginRuntimeListCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRuntimeListCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
out string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
"too_many_args",
|
||||
[]string{"foo"},
|
||||
"Too many arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"invalid_runtime_type",
|
||||
[]string{"-type=foo"},
|
||||
"\"foo\" is not a supported plugin runtime type",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"list container on empty plugin runtime catalog",
|
||||
[]string{"-type=container"},
|
||||
"Error listing available plugin runtimes:",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"list on empty plugin runtime catalog",
|
||||
nil,
|
||||
"Error listing available plugin runtimes:",
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("validations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeListCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run(tc.args)
|
||||
if code != tc.code {
|
||||
t.Errorf("expected %d to be %d", code, tc.code)
|
||||
}
|
||||
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
matcher := regexp.MustCompile(tc.out)
|
||||
if !matcher.MatchString(combined) {
|
||||
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("communication_failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServerBad(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeListCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{"-type=container"})
|
||||
if exp := 2; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Error listing available plugin runtimes: "
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no_tabs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, cmd := testPluginRuntimeListCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
||||
161
command/plugin_runtime_register.go
Normal file
161
command/plugin_runtime_register.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*PluginRuntimeRegisterCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*PluginRuntimeRegisterCommand)(nil)
|
||||
)
|
||||
|
||||
type PluginRuntimeRegisterCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagType string
|
||||
flagOCIRuntime string
|
||||
flagCgroupParent string
|
||||
flagCPUNanos int64
|
||||
flagMemoryBytes int64
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeRegisterCommand) Synopsis() string {
|
||||
return "Registers a new plugin runtime in the catalog"
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeRegisterCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin runtime register [options] NAME
|
||||
|
||||
Registers a new plugin runtime in the catalog. Currently, Vault only supports registering runtimes of type "container".
|
||||
The OCI runtime must be available on Vault's host. If no OCI runtime is specified, Vault will use "runsc", gVisor's OCI runtime.
|
||||
|
||||
Register the plugin runtime named my-custom-plugin-runtime:
|
||||
|
||||
$ vault plugin runtime register -type=container -oci_runtime=my-oci-runtime my-custom-plugin-runtime
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeRegisterCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "type",
|
||||
Target: &c.flagType,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Plugin runtime type. Vault currently only supports \"container\" runtime type.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "oci_runtime",
|
||||
Target: &c.flagOCIRuntime,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "OCI runtime. Default is \"runsc\", gVisor's OCI runtime.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "cgroup_parent",
|
||||
Target: &c.flagCgroupParent,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Parent cgroup to set for each container. This can be used to control the total resource usage for a group of plugins.",
|
||||
})
|
||||
|
||||
f.Int64Var(&Int64Var{
|
||||
Name: "cpu_nanos",
|
||||
Target: &c.flagCPUNanos,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "CPU limit to set per container in nanos. Defaults to no limit.",
|
||||
})
|
||||
|
||||
f.Int64Var(&Int64Var{
|
||||
Name: "memory_bytes",
|
||||
Target: &c.flagMemoryBytes,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Memory limit to set per container in bytes. Defaults to no limit.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeRegisterCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeRegisterCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeRegisterCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
runtimeTyeRaw := strings.TrimSpace(c.flagType)
|
||||
if len(runtimeTyeRaw) == 0 {
|
||||
c.UI.Error("-type is required for plugin runtime registration")
|
||||
return 1
|
||||
}
|
||||
|
||||
runtimeType, err := api.ParsePluginRuntimeType(runtimeTyeRaw)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
var runtimeNameRaw string
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
|
||||
// This case should come after invalid cases have been checked
|
||||
case len(args) == 1:
|
||||
runtimeNameRaw = args[0]
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
runtimeName := strings.TrimSpace(runtimeNameRaw)
|
||||
ociRuntime := strings.TrimSpace(c.flagOCIRuntime)
|
||||
cgroupParent := strings.TrimSpace(c.flagCgroupParent)
|
||||
|
||||
if err := client.Sys().RegisterPluginRuntime(context.Background(), &api.RegisterPluginRuntimeInput{
|
||||
Name: runtimeName,
|
||||
Type: runtimeType,
|
||||
OCIRuntime: ociRuntime,
|
||||
CgroupParent: cgroupParent,
|
||||
CPU: c.flagCPUNanos,
|
||||
Memory: c.flagMemoryBytes,
|
||||
}); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error registering plugin runtime %s: %s", runtimeName, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Output(fmt.Sprintf("Success! Registered plugin runtime: %s", runtimeName))
|
||||
return 0
|
||||
}
|
||||
202
command/plugin_runtime_register_test.go
Normal file
202
command/plugin_runtime_register_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testPluginRuntimeRegisterCommand(tb testing.TB) (*cli.MockUi, *PluginRuntimeRegisterCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginRuntimeRegisterCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRuntimeRegisterCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
flags []string
|
||||
args []string
|
||||
out string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
"no type specified",
|
||||
[]string{},
|
||||
[]string{"foo"},
|
||||
"-type is required for plugin runtime registration",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"invalid type",
|
||||
[]string{"-type", "foo"},
|
||||
[]string{"not"},
|
||||
"\"foo\" is not a supported plugin runtime type",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"not_enough_args",
|
||||
[]string{"-type", consts.PluginRuntimeTypeContainer.String()},
|
||||
[]string{},
|
||||
"Not enough arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"too_many_args",
|
||||
[]string{"-type", consts.PluginRuntimeTypeContainer.String()},
|
||||
[]string{"foo", "bar"},
|
||||
"Too many arguments",
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeRegisterCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
args := append(tc.flags, tc.args...)
|
||||
code := cmd.Run(args)
|
||||
if code != tc.code {
|
||||
t.Errorf("expected %d to be %d", code, tc.code)
|
||||
}
|
||||
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, tc.out) {
|
||||
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("communication_failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServerBad(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginRuntimeRegisterCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{"-type", consts.PluginRuntimeTypeContainer.String(), "my-plugin-runtime"})
|
||||
if exp := 2; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Error registering plugin runtime my-plugin-runtime"
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no_tabs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, cmd := testPluginRuntimeRegisterCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
||||
|
||||
// TestPluginRuntimeFlagParsing ensures that flags passed to vault plugin runtime register correctly
|
||||
// translate into the expected JSON body and request path.
|
||||
func TestPluginRuntimeFlagParsing(t *testing.T) {
|
||||
for name, tc := range map[string]struct {
|
||||
runtimeType api.PluginRuntimeType
|
||||
name string
|
||||
ociRuntime string
|
||||
cgroupParent string
|
||||
cpu int64
|
||||
memory int64
|
||||
args []string
|
||||
expectedPayload string
|
||||
}{
|
||||
"minimal": {
|
||||
runtimeType: api.PluginRuntimeTypeContainer,
|
||||
name: "foo",
|
||||
expectedPayload: `{"type":1,"name":"foo"}`,
|
||||
},
|
||||
"full": {
|
||||
runtimeType: api.PluginRuntimeTypeContainer,
|
||||
name: "foo",
|
||||
cgroupParent: "/cpulimit/",
|
||||
ociRuntime: "runtime",
|
||||
cpu: 5678,
|
||||
memory: 1234,
|
||||
expectedPayload: `{"type":1,"cgroup_parent":"/cpulimit/","memory_bytes":1234,"cpu_nanos":5678,"oci_runtime":"runtime"}`,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ui, cmd := testPluginRuntimeRegisterCommand(t)
|
||||
var requestLogger *recordingRoundTripper
|
||||
cmd.client, requestLogger = mockClient(t)
|
||||
|
||||
var args []string
|
||||
if tc.cgroupParent != "" {
|
||||
args = append(args, "-cgroup_parent="+tc.cgroupParent)
|
||||
}
|
||||
if tc.ociRuntime != "" {
|
||||
args = append(args, "-oci_runtime="+tc.ociRuntime)
|
||||
}
|
||||
if tc.memory != 0 {
|
||||
args = append(args, fmt.Sprintf("-memory_bytes=%d", tc.memory))
|
||||
}
|
||||
if tc.cpu != 0 {
|
||||
args = append(args, fmt.Sprintf("-cpu_nanos=%d", tc.cpu))
|
||||
}
|
||||
|
||||
if tc.runtimeType != api.PluginRuntimeTypeUnsupported {
|
||||
args = append(args, "-type="+tc.runtimeType.String())
|
||||
}
|
||||
args = append(args, tc.name)
|
||||
t.Log(args)
|
||||
|
||||
code := cmd.Run(args)
|
||||
if exp := 0; code != exp {
|
||||
t.Fatalf("expected %d to be %d\nstdout: %s\nstderr: %s", code, exp, ui.OutputWriter.String(), ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
actual := &api.RegisterPluginRuntimeInput{}
|
||||
expected := &api.RegisterPluginRuntimeInput{}
|
||||
err := json.Unmarshal(requestLogger.body, actual)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(tc.expectedPayload), expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("expected: %s\ngot: %s", tc.expectedPayload, requestLogger.body)
|
||||
}
|
||||
expectedPath := fmt.Sprintf("/v1/sys/plugins/runtimes/catalog/%s/%s", tc.runtimeType.String(), tc.name)
|
||||
|
||||
if requestLogger.path != expectedPath {
|
||||
t.Errorf("Expected path %s, got %s", expectedPath, requestLogger.path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,13 @@ package command
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/config"
|
||||
"github.com/hashicorp/vault/command/token"
|
||||
@@ -161,3 +164,40 @@ func getWriterFromUI(ui cli.Ui) io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
}
|
||||
|
||||
func mockClient(t *testing.T) (*api.Client, *recordingRoundTripper) {
|
||||
t.Helper()
|
||||
|
||||
config := api.DefaultConfig()
|
||||
httpClient := cleanhttp.DefaultClient()
|
||||
roundTripper := &recordingRoundTripper{}
|
||||
httpClient.Transport = roundTripper
|
||||
config.HttpClient = httpClient
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return client, roundTripper
|
||||
}
|
||||
|
||||
var _ http.RoundTripper = (*recordingRoundTripper)(nil)
|
||||
|
||||
type recordingRoundTripper struct {
|
||||
path string
|
||||
body []byte
|
||||
}
|
||||
|
||||
func (r *recordingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
r.path = req.URL.Path
|
||||
defer req.Body.Close()
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.body = body
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -6191,15 +6191,15 @@ This path responds to the following HTTP methods.
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_cgroup-parent": {
|
||||
"Optional parent cgroup for the container",
|
||||
"Parent cgroup to set for each container. This can be used to control the total resource usage for a group of plugins.",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_cpu-nanos": {
|
||||
"The limit of runtime CPU in nanos",
|
||||
"CPU limit to set per container in nanos. Defaults to no limit.",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_memory-bytes": {
|
||||
"The limit of runtime memory in bytes",
|
||||
"Memory limit to set per container in bytes. Defaults to no limit.",
|
||||
"",
|
||||
},
|
||||
"leases": {
|
||||
|
||||
Reference in New Issue
Block a user