chronograf/enterprise/meta.go

446 lines
10 KiB
Go
Raw Normal View History

package enterprise
import (
2017-02-17 16:12:45 +00:00
"bytes"
"context"
"encoding/json"
2017-02-17 16:12:45 +00:00
"errors"
"fmt"
"io"
"net/http"
"net/url"
"github.com/influxdata/chronograf"
)
// MetaClient represents a Meta node in an Influx Enterprise cluster
type MetaClient struct {
2017-02-22 00:34:53 +00:00
URL *url.URL
client interface {
Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error)
}
}
// NewMetaClient represents a meta node in an Influx Enterprise cluster
func NewMetaClient(url *url.URL) *MetaClient {
return &MetaClient{
URL: url,
client: &defaultClient{},
}
}
// ShowCluster returns the cluster configuration (not health)
2017-02-22 00:34:53 +00:00
func (m *MetaClient) ShowCluster(ctx context.Context) (*Cluster, error) {
res, err := m.Do(ctx, "GET", "/show-cluster", nil, nil)
2017-02-17 16:12:45 +00:00
if err != nil {
return nil, err
}
2017-02-17 16:12:45 +00:00
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
out := &Cluster{}
err = dec.Decode(out)
if err != nil {
return nil, err
}
2017-02-17 16:12:45 +00:00
return out, nil
}
2017-02-17 16:12:45 +00:00
// Users gets all the users. If name is not nil it filters for a single user
2017-02-22 00:34:53 +00:00
func (m *MetaClient) Users(ctx context.Context, name *string) (*Users, error) {
2017-02-17 16:12:45 +00:00
params := map[string]string{}
if name != nil {
params["name"] = *name
}
2017-02-22 00:34:53 +00:00
res, err := m.Do(ctx, "GET", "/user", params, nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
2017-02-17 16:12:45 +00:00
users := &Users{}
err = dec.Decode(users)
if err != nil {
return nil, err
}
2017-02-17 16:12:45 +00:00
return users, nil
}
// User returns a single Influx Enterprise user
2017-02-22 00:34:53 +00:00
func (m *MetaClient) User(ctx context.Context, name string) (*User, error) {
users, err := m.Users(ctx, &name)
2017-02-17 16:12:45 +00:00
if err != nil {
return nil, err
}
2017-02-22 00:34:53 +00:00
2017-02-17 16:12:45 +00:00
for _, user := range users.Users {
return &user, nil
}
return nil, fmt.Errorf("No user found")
}
// CreateUser adds a user to Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) CreateUser(ctx context.Context, name, passwd string) error {
return m.CreateUpdateUser(ctx, "create", name, passwd)
2017-02-17 16:12:45 +00:00
}
// ChangePassword updates a user's password in Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) ChangePassword(ctx context.Context, name, passwd string) error {
return m.CreateUpdateUser(ctx, "change-password", name, passwd)
2017-02-17 16:12:45 +00:00
}
// CreateUpdateUser is a helper function to POST to the /user Influx Enterprise endpoint
2017-02-22 00:34:53 +00:00
func (m *MetaClient) CreateUpdateUser(ctx context.Context, action, name, passwd string) error {
2017-02-17 16:12:45 +00:00
a := &UserAction{
Action: action,
User: &User{
Name: name,
Password: passwd,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/user", a, nil)
2017-02-17 16:12:45 +00:00
}
// DeleteUser removes a user from Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) DeleteUser(ctx context.Context, name string) error {
2017-02-17 16:12:45 +00:00
a := &UserAction{
Action: "delete",
User: &User{
Name: name,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/user", a, nil)
2017-02-17 16:12:45 +00:00
}
// RemoveAllUserPerms revokes all permissions for a user in Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) RemoveAllUserPerms(ctx context.Context, name string) error {
user, err := m.User(ctx, name)
2017-02-17 16:12:45 +00:00
if err != nil {
return err
}
// No permissions to remove
if len(user.Permissions) == 0 {
return nil
}
a := &UserAction{
Action: "remove-permissions",
User: user,
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/user", a, nil)
2017-02-17 16:12:45 +00:00
}
// SetUserPerms removes all permissions and then adds the requested perms
2017-02-22 00:34:53 +00:00
func (m *MetaClient) SetUserPerms(ctx context.Context, name string, perms Permissions) error {
err := m.RemoveAllUserPerms(ctx, name)
2017-02-17 16:12:45 +00:00
if err != nil {
return err
}
// No permissions to add, so, user is in the right state
if len(perms) == 0 {
return nil
}
a := &UserAction{
Action: "add-permissions",
User: &User{
Name: name,
Permissions: perms,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/user", a, nil)
2017-02-17 16:12:45 +00:00
}
// UserRoles returns a map of users to all of their current roles
func (m *MetaClient) UserRoles(ctx context.Context) (map[string]Roles, error) {
res, err := m.Roles(ctx, nil)
if err != nil {
return nil, err
}
userRoles := make(map[string]Roles)
for _, role := range res.Roles {
for _, u := range role.Users {
ur, ok := userRoles[u]
if !ok {
ur = Roles{}
}
ur.Roles = append(ur.Roles, role)
userRoles[u] = ur
}
}
return userRoles, nil
}
// Roles gets all the roles. If name is not nil it filters for a single role
2017-02-22 00:34:53 +00:00
func (m *MetaClient) Roles(ctx context.Context, name *string) (*Roles, error) {
2017-02-17 16:12:45 +00:00
params := map[string]string{}
if name != nil {
params["name"] = *name
}
2017-02-22 00:34:53 +00:00
res, err := m.Do(ctx, "GET", "/role", params, nil)
2017-02-17 16:12:45 +00:00
if err != nil {
return nil, err
}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
roles := &Roles{}
err = dec.Decode(roles)
if err != nil {
return nil, err
}
return roles, nil
}
// Role returns a single named role
2017-02-22 00:34:53 +00:00
func (m *MetaClient) Role(ctx context.Context, name string) (*Role, error) {
roles, err := m.Roles(ctx, &name)
2017-02-17 16:12:45 +00:00
if err != nil {
return nil, err
}
for _, role := range roles.Roles {
return &role, nil
}
return nil, fmt.Errorf("No role found")
}
// CreateRole adds a role to Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) CreateRole(ctx context.Context, name string) error {
2017-02-17 16:12:45 +00:00
a := &RoleAction{
Action: "create",
Role: &Role{
Name: name,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/role", a, nil)
2017-02-17 16:12:45 +00:00
}
// DeleteRole removes a role from Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) DeleteRole(ctx context.Context, name string) error {
2017-02-17 16:12:45 +00:00
a := &RoleAction{
Action: "delete",
Role: &Role{
Name: name,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/role", a, nil)
2017-02-17 16:12:45 +00:00
}
// RemoveAllRolePerms removes all permissions from a role
2017-02-22 00:34:53 +00:00
func (m *MetaClient) RemoveAllRolePerms(ctx context.Context, name string) error {
role, err := m.Role(ctx, name)
2017-02-17 16:12:45 +00:00
if err != nil {
return err
}
// No permissions to remove
if len(role.Permissions) == 0 {
return nil
}
a := &RoleAction{
Action: "remove-permissions",
Role: role,
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/role", a, nil)
2017-02-17 16:12:45 +00:00
}
// SetRolePerms removes all permissions and then adds the requested perms to role
2017-02-22 00:34:53 +00:00
func (m *MetaClient) SetRolePerms(ctx context.Context, name string, perms Permissions) error {
err := m.RemoveAllRolePerms(ctx, name)
2017-02-17 16:12:45 +00:00
if err != nil {
return err
}
// No permissions to add, so, role is in the right state
if len(perms) == 0 {
return nil
}
a := &RoleAction{
Action: "add-permissions",
Role: &Role{
Name: name,
Permissions: perms,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/role", a, nil)
2017-02-17 16:12:45 +00:00
}
// SetRoleUsers removes all users and then adds the requested users to role
func (m *MetaClient) SetRoleUsers(ctx context.Context, name string, users []string) error {
2017-02-22 00:34:53 +00:00
role, err := m.Role(ctx, name)
2017-02-17 16:12:45 +00:00
if err != nil {
return err
}
revoke, add := Difference(users, role.Users)
if err := m.RemoveRoleUsers(ctx, name, revoke); err != nil {
return err
}
2017-02-17 16:12:45 +00:00
return m.AddRoleUsers(ctx, name, add)
}
// Difference compares two sets and returns a set to be removed and a set to be added
func Difference(wants []string, haves []string) (revoke []string, add []string) {
for _, want := range wants {
found := false
for _, got := range haves {
if want != got {
continue
}
found = true
}
if !found {
add = append(add, want)
}
}
for _, got := range haves {
found := false
for _, want := range wants {
if want != got {
continue
}
found = true
break
}
if !found {
revoke = append(revoke, got)
}
}
return
}
// AddRoleUsers updates a role to have additional users.
func (m *MetaClient) AddRoleUsers(ctx context.Context, name string, users []string) error {
// No permissions to add, so, role is in the right state
if len(users) == 0 {
2017-02-17 16:12:45 +00:00
return nil
}
a := &RoleAction{
Action: "add-users",
Role: &Role{
Name: name,
Users: users,
},
2017-02-17 16:12:45 +00:00
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/role", a, nil)
2017-02-17 16:12:45 +00:00
}
// RemoveRoleUsers updates a role to remove some users.
func (m *MetaClient) RemoveRoleUsers(ctx context.Context, name string, users []string) error {
2017-02-17 16:12:45 +00:00
// No permissions to add, so, role is in the right state
if len(users) == 0 {
return nil
}
a := &RoleAction{
Action: "remove-users",
2017-02-17 16:12:45 +00:00
Role: &Role{
Name: name,
Users: users,
},
}
2017-02-22 00:34:53 +00:00
return m.Post(ctx, "/role", a, nil)
2017-02-17 16:12:45 +00:00
}
// Post is a helper function to POST to Influx Enterprise
2017-02-22 00:34:53 +00:00
func (m *MetaClient) Post(ctx context.Context, path string, action interface{}, params map[string]string) error {
2017-02-17 16:12:45 +00:00
b, err := json.Marshal(action)
if err != nil {
return err
}
body := bytes.NewReader(b)
2017-02-22 00:34:53 +00:00
_, err = m.Do(ctx, "POST", path, params, body)
2017-02-17 16:12:45 +00:00
if err != nil {
return err
}
return nil
}
2017-02-22 00:34:53 +00:00
type defaultClient struct{}
// Do is a helper function to interface with Influx Enterprise's Meta API
func (d *defaultClient) Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) {
2017-02-17 16:12:45 +00:00
p := url.Values{}
for k, v := range params {
p.Add(k, v)
}
2017-02-22 00:34:53 +00:00
URL.Path = path
URL.RawQuery = p.Encode()
2017-02-17 16:12:45 +00:00
req, err := http.NewRequest(method, URL.String(), body)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
// Meta servers will redirect (307) to leader. We need
// special handling to preserve authentication headers.
client := &http.Client{
CheckRedirect: AuthedCheckRedirect,
}
res, err := client.Do(req)
2017-02-17 16:12:45 +00:00
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
out := &Error{}
err = dec.Decode(out)
if err != nil {
return nil, err
}
return nil, errors.New(out.Error)
}
return res, nil
}
// AuthedCheckRedirect tries to follow the Influx Enterprise pattern of
// redirecting to the leader but preserving authentication headers.
func AuthedCheckRedirect(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("too many redirects")
} else if len(via) == 0 {
return nil
}
preserve := "Authorization"
if auth, ok := via[0].Header[preserve]; ok {
req.Header[preserve] = auth
}
return nil
}
// Do is a cancelable function to interface with Influx Enterprise's Meta API
2017-02-22 00:34:53 +00:00
func (m *MetaClient) Do(ctx context.Context, method, path string, params map[string]string, body io.Reader) (*http.Response, error) {
type result struct {
Response *http.Response
Err error
}
resps := make(chan (result))
go func() {
2017-02-22 00:34:53 +00:00
resp, err := m.client.Do(m.URL, path, method, params, body)
resps <- result{resp, err}
}()
select {
case resp := <-resps:
return resp.Response, resp.Err
case <-ctx.Done():
return nil, chronograf.ErrUpstreamTimeout
}
}