mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
VAULT-7256: Add custom_metadata to namespaces (#16640)
* add mapstructure tags to Namespace struct * add custom metadata Parse helper * add ns custom metadata and patch
This commit is contained in:
@@ -347,6 +347,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"namespace patch": func() (cli.Command, error) {
|
||||
return &NamespacePatchCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"namespace delete": func() (cli.Command, error) {
|
||||
return &NamespaceDeleteCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
|
||||
@@ -36,6 +36,10 @@ Usage: vault namespace <subcommand> [options] [args]
|
||||
|
||||
$ vault namespace create
|
||||
|
||||
Patch an existing namespace:
|
||||
|
||||
$ vault namespace patch
|
||||
|
||||
Delete an existing namespace:
|
||||
|
||||
$ vault namespace delete
|
||||
|
||||
@@ -15,6 +15,8 @@ var (
|
||||
|
||||
type NamespaceCreateCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagCustomMetadata map[string]string
|
||||
}
|
||||
|
||||
func (c *NamespaceCreateCommand) Synopsis() string {
|
||||
@@ -43,7 +45,18 @@ Usage: vault namespace create [options] PATH
|
||||
}
|
||||
|
||||
func (c *NamespaceCreateCommand) Flags() *FlagSets {
|
||||
return c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
f.StringMapVar(&StringMapVar{
|
||||
Name: "custom-metadata",
|
||||
Target: &c.flagCustomMetadata,
|
||||
Default: map[string]string{},
|
||||
Usage: "Specifies arbitrary key=value metadata meant to describe a namespace." +
|
||||
"This can be specified multiple times to add multiple pieces of metadata.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *NamespaceCreateCommand) AutocompleteArgs() complete.Predictor {
|
||||
@@ -80,7 +93,11 @@ func (c *NamespaceCreateCommand) Run(args []string) int {
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write("sys/namespaces/"+namespacePath, nil)
|
||||
data := map[string]interface{}{
|
||||
"custom_metadata": c.flagCustomMetadata,
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write("sys/namespaces/"+namespacePath, data)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error creating namespace: %s", err))
|
||||
return 2
|
||||
|
||||
137
command/namespace_patch.go
Normal file
137
command/namespace_patch.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/posener/complete"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*NamespacePatchCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*NamespacePatchCommand)(nil)
|
||||
)
|
||||
|
||||
type NamespacePatchCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagCustomMetadata map[string]string
|
||||
flagRemoveCustomMetadata []string
|
||||
}
|
||||
|
||||
func (c *NamespacePatchCommand) Synopsis() string {
|
||||
return "Patch an existing namespace"
|
||||
}
|
||||
|
||||
func (c *NamespacePatchCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault namespace patch [options] PATH
|
||||
|
||||
Patch an existing namespace. The namespace patched will be relative to the
|
||||
namespace provided in either the VAULT_NAMESPACE environment variable or
|
||||
-namespace CLI flag.
|
||||
|
||||
Patch an existing child namespace by adding and removing custom-metadata (e.g. ns1/):
|
||||
|
||||
$ vault namespace patch ns1 -custom-metadata=foo=abc -remove-custom-metadata=bar
|
||||
|
||||
Patch an existing child namespace from a parent namespace (e.g. ns1/ns2/):
|
||||
|
||||
$ vault namespace patch -namespace=ns1 ns2 -custom-metadata=foo=abc
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *NamespacePatchCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
f.StringMapVar(&StringMapVar{
|
||||
Name: "custom-metadata",
|
||||
Target: &c.flagCustomMetadata,
|
||||
Default: map[string]string{},
|
||||
Usage: "Specifies arbitrary key=value metadata meant to describe a namespace." +
|
||||
"This can be specified multiple times to add multiple pieces of metadata.",
|
||||
})
|
||||
|
||||
f.StringSliceVar(&StringSliceVar{
|
||||
Name: "remove-custom-metadata",
|
||||
Target: &c.flagRemoveCustomMetadata,
|
||||
Default: []string{},
|
||||
Usage: "Key to remove from custom metadata. To specify multiple values, specify this flag multiple times.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *NamespacePatchCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
func (c *NamespacePatchCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *NamespacePatchCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
namespacePath := strings.TrimSpace(args[0])
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
customMetadata := make(map[string]interface{})
|
||||
|
||||
for key, value := range c.flagCustomMetadata {
|
||||
customMetadata[key] = value
|
||||
}
|
||||
|
||||
for _, key := range c.flagRemoveCustomMetadata {
|
||||
// A null in a JSON merge patch payload will remove the associated key
|
||||
customMetadata[key] = nil
|
||||
}
|
||||
|
||||
data["custom_metadata"] = customMetadata
|
||||
|
||||
secret, err := client.Logical().JSONMergePatch(context.Background(), "sys/namespaces/"+namespacePath, data)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error patching namespace: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
c.UI.Error(fmt.Sprintf("No namespace found: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Handle single field output
|
||||
if c.flagField != "" {
|
||||
return PrintRawField(c.UI, secret, c.flagField)
|
||||
}
|
||||
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
type contextValues struct{}
|
||||
|
||||
type Namespace struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
CustomMetadata map[string]string `json:"custom_metadata"`
|
||||
ID string `json:"id" mapstructure:"id"`
|
||||
Path string `json:"path" mapstructure:"path"`
|
||||
CustomMetadata map[string]string `json:"custom_metadata" mapstructure:"custom_metadata"`
|
||||
}
|
||||
|
||||
func (n *Namespace) String() string {
|
||||
|
||||
@@ -3,6 +3,8 @@ package custommetadata
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
)
|
||||
@@ -16,6 +18,31 @@ const (
|
||||
validationErrorPrefix = "custom_metadata validation failed"
|
||||
)
|
||||
|
||||
// Parse is used to effectively convert the TypeMap
|
||||
// (map[string]interface{}) into a TypeKVPairs (map[string]string)
|
||||
// which is how custom_metadata is stored. Defining custom_metadata
|
||||
// as a TypeKVPairs will convert nulls into empty strings. A null,
|
||||
// however, is essential for a PATCH operation in that it signals
|
||||
// the handler to remove the field. The filterNils flag should
|
||||
// only be used during a patch operation.
|
||||
func Parse(raw map[string]interface{}, filterNils bool) (map[string]string, error) {
|
||||
customMetadata := map[string]string{}
|
||||
for k, v := range raw {
|
||||
if filterNils && v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var s string
|
||||
if err := mapstructure.WeakDecode(v, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customMetadata[k] = s
|
||||
}
|
||||
|
||||
return customMetadata, nil
|
||||
}
|
||||
|
||||
// Validate will perform input validation for custom metadata.
|
||||
// CustomMetadata should be arbitrary user-provided key-value pairs meant to
|
||||
// provide supplemental information about a resource. If the key count
|
||||
|
||||
Reference in New Issue
Block a user