mirror of https://github.com/status-im/consul.git
Add support for the Namespace HTTP API in the API Client (#6581)
This commit is contained in:
parent
53f3962cab
commit
4a5ebb57b0
14
api/acl.go
14
api/acl.go
|
@ -18,15 +18,14 @@ const (
|
|||
ACLManagementType = "management"
|
||||
)
|
||||
|
||||
type ACLTokenPolicyLink struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
type ACLTokenRoleLink struct {
|
||||
type ACLLink struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
type ACLTokenPolicyLink = ACLLink
|
||||
type ACLTokenRoleLink = ACLLink
|
||||
|
||||
// ACLToken represents an ACL Token
|
||||
type ACLToken struct {
|
||||
CreateIndex uint64
|
||||
|
@ -117,10 +116,7 @@ type ACLPolicyListEntry struct {
|
|||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
type ACLRolePolicyLink struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
type ACLRolePolicyLink = ACLLink
|
||||
|
||||
// ACLRole represents an ACL Role.
|
||||
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