Add support for the Namespace HTTP API in the API Client (#6581)
This commit is contained in:
parent
825feaadd5
commit
0e0a4cef26
14
api/acl.go
14
api/acl.go
|
@ -18,15 +18,14 @@ const (
|
||||||
ACLManagementType = "management"
|
ACLManagementType = "management"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ACLTokenPolicyLink struct {
|
type ACLLink struct {
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
type ACLTokenRoleLink struct {
|
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ACLTokenPolicyLink = ACLLink
|
||||||
|
type ACLTokenRoleLink = ACLLink
|
||||||
|
|
||||||
// ACLToken represents an ACL Token
|
// ACLToken represents an ACL Token
|
||||||
type ACLToken struct {
|
type ACLToken struct {
|
||||||
CreateIndex uint64
|
CreateIndex uint64
|
||||||
|
@ -117,10 +116,7 @@ type ACLPolicyListEntry struct {
|
||||||
ModifyIndex uint64
|
ModifyIndex uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ACLRolePolicyLink struct {
|
type ACLRolePolicyLink = ACLLink
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACLRole represents an ACL Role.
|
// ACLRole represents an ACL Role.
|
||||||
type ACLRole struct {
|
type ACLRole struct {
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Namespace is the configuration of a single namespace. Namespacing is a Consul Enterprise feature.
|
||||||
|
type Namespace struct {
|
||||||
|
// Name is the name of the Namespace. It must be unique and
|
||||||
|
// must be a DNS hostname. There are also other reserved names
|
||||||
|
// that may not be used.
|
||||||
|
Name string `json:"Name"`
|
||||||
|
|
||||||
|
// Description is where the user puts any information they want
|
||||||
|
// about the namespace. It is not used internally.
|
||||||
|
Description string `json:"Description,omitempty"`
|
||||||
|
|
||||||
|
// ACLs is the configuration of ACLs for this namespace. It has its
|
||||||
|
// own struct so that we can add more to it in the future.
|
||||||
|
// This is nullable so that we can omit if empty when encoding in JSON
|
||||||
|
ACLs *NamespaceACLConfig `json:"ACLs,omitempty"`
|
||||||
|
|
||||||
|
// DeletedAt is the time when the Namespace was marked for deletion
|
||||||
|
// This is nullable so that we can omit if empty when encoding in JSON
|
||||||
|
DeletedAt *time.Time `json:"DeletedAt,omitempty"`
|
||||||
|
|
||||||
|
// CreateIndex is the Raft index at which the Namespace was created
|
||||||
|
CreateIndex uint64 `json:"CreateIndex,omitempty"`
|
||||||
|
|
||||||
|
// ModifyIndex is the latest Raft index at which the Namespace was modified.
|
||||||
|
ModifyIndex uint64 `json:"ModifyIndex,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceACLConfig is the Namespace specific ACL configuration container
|
||||||
|
type NamespaceACLConfig struct {
|
||||||
|
// PolicyDefaults is the list of policies that should be used for the parent authorizer
|
||||||
|
// of all tokens in the associated namespace.
|
||||||
|
PolicyDefaults []ACLLink `json:"PolicyDefaults"`
|
||||||
|
// RoleDefaults is the list of roles that should be used for the parent authorizer
|
||||||
|
// of all tokens in the associated namespace.
|
||||||
|
RoleDefaults []ACLLink `json:"RoleDefaults"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespaces can be used to manage Namespaces in Consul Enterprise..
|
||||||
|
type Namespaces struct {
|
||||||
|
c *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operator returns a handle to the operator endpoints.
|
||||||
|
func (c *Client) Namespaces() *Namespaces {
|
||||||
|
return &Namespaces{c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespaces) Create(ns *Namespace, q *WriteOptions) (*Namespace, *WriteMeta, error) {
|
||||||
|
if ns.Name == "" {
|
||||||
|
return nil, nil, fmt.Errorf("Must specify a Name for Namespace creation")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := n.c.newRequest("PUT", "/v1/namespace")
|
||||||
|
r.setWriteOptions(q)
|
||||||
|
r.obj = ns
|
||||||
|
rtt, resp, err := requireOK(n.c.doRequest(r))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
wm := &WriteMeta{RequestTime: rtt}
|
||||||
|
var out Namespace
|
||||||
|
if err := decodeBody(resp, &out); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &out, wm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespaces) Update(ns *Namespace, q *WriteOptions) (*Namespace, *WriteMeta, error) {
|
||||||
|
if ns.Name == "" {
|
||||||
|
return nil, nil, fmt.Errorf("Must specify a Name for Namespace updating")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := n.c.newRequest("PUT", "/v1/namespace/"+ns.Name)
|
||||||
|
r.setWriteOptions(q)
|
||||||
|
r.obj = ns
|
||||||
|
rtt, resp, err := requireOK(n.c.doRequest(r))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
wm := &WriteMeta{RequestTime: rtt}
|
||||||
|
var out Namespace
|
||||||
|
if err := decodeBody(resp, &out); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &out, wm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespaces) Read(name string, q *QueryOptions) (*Namespace, *QueryMeta, error) {
|
||||||
|
var out Namespace
|
||||||
|
qm, err := n.c.query("/v1/namespace/"+name, &out, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &out, qm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespaces) Delete(name string, q *WriteOptions) (*WriteMeta, error) {
|
||||||
|
r := n.c.newRequest("DELETE", "/v1/namespace/"+name)
|
||||||
|
r.setWriteOptions(q)
|
||||||
|
rtt, resp, err := requireOK(n.c.doRequest(r))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
wm := &WriteMeta{RequestTime: rtt}
|
||||||
|
return wm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespaces) List(q *QueryOptions) ([]*Namespace, *QueryMeta, error) {
|
||||||
|
var out []*Namespace
|
||||||
|
qm, err := n.c.query("/v1/namespaces", &out, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, qm, nil
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
// +build consulent
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPI_Namespaces(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c, s := makeACLClient(t)
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
namespaces := c.Namespaces()
|
||||||
|
acl := c.ACL()
|
||||||
|
|
||||||
|
nsPolicy, _, err := acl.PolicyCreate(&ACLPolicy{
|
||||||
|
Name: "ns-policy",
|
||||||
|
Rules: `operator = "write"`,
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nsRole, _, err := acl.RoleCreate(&ACLRole{
|
||||||
|
Name: "ns-role",
|
||||||
|
Policies: []*ACLRolePolicyLink{
|
||||||
|
&ACLRolePolicyLink{
|
||||||
|
ID: nsPolicy.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("Create Nameless", func(t *testing.T) {
|
||||||
|
ns := Namespace{
|
||||||
|
Description: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := namespaces.Create(&ns, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "Must specify a Name for Namespace creation")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Create", func(t *testing.T) {
|
||||||
|
ns, _, err := namespaces.Create(&Namespace{
|
||||||
|
Name: "foo",
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ns)
|
||||||
|
require.Equal(t, "foo", ns.Name)
|
||||||
|
require.Nil(t, ns.ACLs)
|
||||||
|
|
||||||
|
ns, _, err = namespaces.Create(&Namespace{
|
||||||
|
Name: "acls",
|
||||||
|
Description: "This namespace has ACL config attached",
|
||||||
|
ACLs: &NamespaceACLConfig{
|
||||||
|
PolicyDefaults: []ACLLink{
|
||||||
|
ACLLink{ID: nsPolicy.ID},
|
||||||
|
},
|
||||||
|
RoleDefaults: []ACLLink{
|
||||||
|
ACLLink{ID: nsRole.ID},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ns)
|
||||||
|
require.NotNil(t, ns.ACLs)
|
||||||
|
require.Nil(t, ns.DeletedAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update Nameless", func(t *testing.T) {
|
||||||
|
ns := Namespace{
|
||||||
|
Description: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := namespaces.Update(&ns, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "Must specify a Name for Namespace updating")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update", func(t *testing.T) {
|
||||||
|
ns, _, err := namespaces.Update(&Namespace{
|
||||||
|
Name: "foo",
|
||||||
|
Description: "updated description",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ns)
|
||||||
|
require.Equal(t, "updated description", ns.Description)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("List", func(t *testing.T) {
|
||||||
|
nsList, _, err := namespaces.List(nil)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, nsList, 3)
|
||||||
|
|
||||||
|
found := make(map[string]struct{})
|
||||||
|
for _, ns := range nsList {
|
||||||
|
found[ns.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Contains(t, found, "default")
|
||||||
|
require.Contains(t, found, "foo")
|
||||||
|
require.Contains(t, found, "acls")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete", func(t *testing.T) {
|
||||||
|
_, err := namespaces.Delete("foo", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// due to deferred deletion the namespace might still exist
|
||||||
|
// this checks that either it is in fact gone and we get a 404 or
|
||||||
|
// that the namespace is still there but marked for deletion
|
||||||
|
ns, _, err := namespaces.Read("foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
require.Contains(t, err.Error(), "Unexpected response code: 404")
|
||||||
|
require.Nil(t, ns)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, ns)
|
||||||
|
require.NotNil(t, ns.DeletedAt)
|
||||||
|
require.False(t, ns.DeletedAt.IsZero())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue