mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +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(),
|
BaseCommand: getBaseCommand(),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"namespace patch": func() (cli.Command, error) {
|
||||||
|
return &NamespacePatchCommand{
|
||||||
|
BaseCommand: getBaseCommand(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"namespace delete": func() (cli.Command, error) {
|
"namespace delete": func() (cli.Command, error) {
|
||||||
return &NamespaceDeleteCommand{
|
return &NamespaceDeleteCommand{
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ Usage: vault namespace <subcommand> [options] [args]
|
|||||||
|
|
||||||
$ vault namespace create
|
$ vault namespace create
|
||||||
|
|
||||||
|
Patch an existing namespace:
|
||||||
|
|
||||||
|
$ vault namespace patch
|
||||||
|
|
||||||
Delete an existing namespace:
|
Delete an existing namespace:
|
||||||
|
|
||||||
$ vault namespace delete
|
$ vault namespace delete
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ var (
|
|||||||
|
|
||||||
type NamespaceCreateCommand struct {
|
type NamespaceCreateCommand struct {
|
||||||
*BaseCommand
|
*BaseCommand
|
||||||
|
|
||||||
|
flagCustomMetadata map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NamespaceCreateCommand) Synopsis() string {
|
func (c *NamespaceCreateCommand) Synopsis() string {
|
||||||
@@ -43,7 +45,18 @@ Usage: vault namespace create [options] PATH
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *NamespaceCreateCommand) Flags() *FlagSets {
|
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 {
|
func (c *NamespaceCreateCommand) AutocompleteArgs() complete.Predictor {
|
||||||
@@ -80,7 +93,11 @@ func (c *NamespaceCreateCommand) Run(args []string) int {
|
|||||||
return 2
|
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 {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error creating namespace: %s", err))
|
c.UI.Error(fmt.Sprintf("Error creating namespace: %s", err))
|
||||||
return 2
|
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 contextValues struct{}
|
||||||
|
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id" mapstructure:"id"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path" mapstructure:"path"`
|
||||||
CustomMetadata map[string]string `json:"custom_metadata"`
|
CustomMetadata map[string]string `json:"custom_metadata" mapstructure:"custom_metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Namespace) String() string {
|
func (n *Namespace) String() string {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package custommetadata
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||||
)
|
)
|
||||||
@@ -16,6 +18,31 @@ const (
|
|||||||
validationErrorPrefix = "custom_metadata validation failed"
|
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.
|
// Validate will perform input validation for custom metadata.
|
||||||
// CustomMetadata should be arbitrary user-provided key-value pairs meant to
|
// CustomMetadata should be arbitrary user-provided key-value pairs meant to
|
||||||
// provide supplemental information about a resource. If the key count
|
// provide supplemental information about a resource. If the key count
|
||||||
|
|||||||
Reference in New Issue
Block a user