Port some ns stuff over

This commit is contained in:
Jeff Mitchell 2018-08-10 12:13:06 -04:00
parent 343d779434
commit fb3c7eb449
3 changed files with 217 additions and 0 deletions

97
api/sys_namespaces.go Normal file
View File

@ -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
}

View File

@ -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,

View File

@ -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
}