Port some ns stuff over
This commit is contained in:
parent
343d779434
commit
fb3c7eb449
|
@ -0,0 +1,97 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ListNamespacesResponse is the response from the ListNamespaces call.
|
||||
type ListNamespacesResponse struct {
|
||||
// NamespacePaths is the list of child namespace paths
|
||||
NamespacePaths []string `json:"namespace_paths"`
|
||||
}
|
||||
|
||||
type GetNamespaceResponse struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// ListNamespaces lists any existing namespace relative to the namespace
|
||||
// provided in the client's namespace header.
|
||||
func (c *Sys) ListNamespaces() (*ListNamespacesResponse, error) {
|
||||
r := c.c.NewRequest("LIST", "/v1/sys/namespaces")
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Data struct {
|
||||
Keys []string `json:"keys"`
|
||||
} `json:"data"`
|
||||
}
|
||||
err = resp.DecodeJSON(&result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ListNamespacesResponse{NamespacePaths: result.Data.Keys}, nil
|
||||
}
|
||||
|
||||
// GetNamespace returns namespace information
|
||||
func (c *Sys) GetNamespace(path string) (*GetNamespaceResponse, error) {
|
||||
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/namespaces/%s", path))
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ret := &GetNamespaceResponse{}
|
||||
result := map[string]interface{}{
|
||||
"data": map[string]interface{}{},
|
||||
}
|
||||
if err := resp.DecodeJSON(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data, ok := result["data"]; ok {
|
||||
if pathOk, ok := data.(map[string]interface{})["path"]; ok {
|
||||
if pathRaw, ok := pathOk.(string); ok {
|
||||
ret.Path = pathRaw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// CreateNamespace creates a new namespace relative to the namespace provided
|
||||
// in the client's namespace header.
|
||||
func (c *Sys) CreateNamespace(path string) error {
|
||||
r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/namespaces/%s", path))
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteNamespace delete an existing namespace relative to the namespace
|
||||
// provided in the client's namespace header.
|
||||
func (c *Sys) DeleteNamespace(path string) error {
|
||||
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/namespaces/%s", path))
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/token"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/posener/complete"
|
||||
|
@ -37,6 +38,7 @@ type BaseCommand struct {
|
|||
flagCAPath string
|
||||
flagClientCert string
|
||||
flagClientKey string
|
||||
flagNamespace string
|
||||
flagTLSServerName string
|
||||
flagTLSSkipVerify bool
|
||||
flagWrapTTL time.Duration
|
||||
|
@ -118,6 +120,7 @@ func (c *BaseCommand) Client() (*api.Client, error) {
|
|||
}
|
||||
|
||||
client.SetMFACreds(c.flagMFA)
|
||||
client.SetNamespace(namespace.Canonicalize(c.flagNamespace))
|
||||
|
||||
c.client = client
|
||||
|
||||
|
@ -236,6 +239,16 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
|
|||
"matching the client certificate from -client-cert.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "namespace",
|
||||
Target: &c.flagNamespace,
|
||||
Default: "",
|
||||
EnvVar: "VAULT_NAMESPACE",
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "The namespace to use for the command. Setting this is not " +
|
||||
"necessary but allows using relative paths.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "tls-server-name",
|
||||
Target: &c.flagTLSServerName,
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package namespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type nsContext struct {
|
||||
context.Context
|
||||
// Note: this is currently not locked because we think all uses will take
|
||||
// place within a single goroutine. If that isn't the case, this should be
|
||||
// protected by an atomic.Value.
|
||||
cachedNS *Namespace
|
||||
}
|
||||
|
||||
type contextValues struct{}
|
||||
|
||||
const (
|
||||
RootNamespaceID = "root"
|
||||
)
|
||||
|
||||
var (
|
||||
contextNamespace contextValues = struct{}{}
|
||||
ErrNoNamespace error = errors.New("no namespace")
|
||||
)
|
||||
|
||||
type Namespace struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func New(id, path string) *Namespace {
|
||||
return &Namespace{
|
||||
ID: id,
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Namespace) HasParent(possibleParent *Namespace) bool {
|
||||
switch {
|
||||
case n.Path == "":
|
||||
return false
|
||||
case possibleParent.Path == "":
|
||||
return true
|
||||
default:
|
||||
return strings.HasPrefix(n.Path, possibleParent.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Namespace) TrimmedPath(path string) string {
|
||||
return strings.TrimPrefix(path, n.Path)
|
||||
}
|
||||
|
||||
func ContextWithNamespace(ctx context.Context, ns *Namespace) context.Context {
|
||||
nsCtx := context.WithValue(ctx, contextNamespace, ns)
|
||||
return &nsContext{
|
||||
Context: nsCtx,
|
||||
cachedNS: ns,
|
||||
}
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) (*Namespace, error) {
|
||||
if ctx == nil {
|
||||
return nil, errors.New("context was nil")
|
||||
}
|
||||
|
||||
nsCtx, ok := ctx.(*nsContext)
|
||||
if ok {
|
||||
if nsCtx.cachedNS != nil {
|
||||
return nsCtx.cachedNS, nil
|
||||
}
|
||||
}
|
||||
|
||||
ns := ctx.Value(contextNamespace)
|
||||
if ns == nil {
|
||||
return nil, ErrNoNamespace
|
||||
}
|
||||
|
||||
if ok {
|
||||
nsCtx.cachedNS = ns.(*Namespace)
|
||||
}
|
||||
|
||||
return ns.(*Namespace), nil
|
||||
}
|
||||
|
||||
func TestContext() context.Context {
|
||||
return ContextWithNamespace(context.Background(), New(RootNamespaceID, ""))
|
||||
}
|
||||
|
||||
// Canonicalize trims any prefix '/' and adds a trailing '/' to the
|
||||
// provided string
|
||||
func Canonicalize(nsPath string) string {
|
||||
if nsPath == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Canonicalize the path to not have a '/' prefix
|
||||
nsPath = strings.TrimPrefix(nsPath, "/")
|
||||
|
||||
// Canonicalize the path to always having a '/' suffix
|
||||
if !strings.HasSuffix(nsPath, "/") {
|
||||
nsPath += "/"
|
||||
}
|
||||
|
||||
return nsPath
|
||||
}
|
Loading…
Reference in New Issue