influxdb/cmd/influx/organization.go

608 lines
14 KiB
Go

package main
import (
"context"
"fmt"
"io"
"github.com/influxdata/influxdb/http"
"github.com/influxdata/influxdb"
"github.com/spf13/cobra"
)
type orgSVCFn func() (influxdb.OrganizationService, influxdb.UserResourceMappingService, influxdb.UserService, error)
func cmdOrganization(f *globalFlags, opts genericCLIOpts) *cobra.Command {
builder := newCmdOrgBuilder(newOrgServices, opts)
builder.globalFlags = f
return builder.cmd()
}
type cmdOrgBuilder struct {
genericCLIOpts
*globalFlags
svcFn orgSVCFn
description string
id string
memberID string
name string
}
func newCmdOrgBuilder(svcFn orgSVCFn, opts genericCLIOpts) *cmdOrgBuilder {
return &cmdOrgBuilder{
genericCLIOpts: opts,
svcFn: svcFn,
}
}
func (b *cmdOrgBuilder) cmd() *cobra.Command {
cmd := b.newCmd("org", nil)
cmd.Aliases = []string{"organization"}
cmd.Short = "Organization management commands"
cmd.Run = seeHelp
cmd.AddCommand(
b.cmdCreate(),
b.cmdDelete(),
b.cmdFind(),
b.cmdMember(),
b.cmdUpdate(),
)
return cmd
}
func (b *cmdOrgBuilder) cmdCreate() *cobra.Command {
cmd := b.newCmd("create", b.createRunEFn)
cmd.Short = "Create organization"
cmd.Flags().StringVarP(&b.name, "name", "n", "", "The name of organization that will be created")
cmd.MarkFlagRequired("name")
cmd.Flags().StringVarP(&b.description, "description", "d", "", "The description of the organization that will be created")
return cmd
}
func (b *cmdOrgBuilder) createRunEFn(cmd *cobra.Command, args []string) error {
orgSvc, _, _, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
org := &influxdb.Organization{
Name: b.name,
Description: b.description,
}
if err := orgSvc.CreateOrganization(context.Background(), org); err != nil {
return fmt.Errorf("failed to create organization: %v", err)
}
w := b.newTabWriter()
w.WriteHeaders("ID", "Name")
w.Write(map[string]interface{}{
"ID": org.ID.String(),
"Name": org.Name,
})
w.Flush()
return nil
}
func (b *cmdOrgBuilder) cmdDelete() *cobra.Command {
cmd := b.newCmd("delete", b.deleteRunEFn)
cmd.Short = "Delete organization"
opts := flagOpts{
{
DestP: &b.id,
Flag: "id",
Short: 'i',
EnvVar: "ORG_ID",
Desc: "The organization ID",
},
}
opts.mustRegister(cmd)
return cmd
}
func (b *cmdOrgBuilder) deleteRunEFn(cmd *cobra.Command, args []string) error {
orgSvc, _, _, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
var id influxdb.ID
if err := id.DecodeFromString(b.id); err != nil {
return fmt.Errorf("failed to decode org id %s: %v", b.id, err)
}
ctx := context.TODO()
o, err := orgSvc.FindOrganizationByID(ctx, id)
if err != nil {
return fmt.Errorf("failed to find org with id %q: %v", id, err)
}
if err = orgSvc.DeleteOrganization(ctx, id); err != nil {
return fmt.Errorf("failed to delete org with id %q: %v", id, err)
}
w := b.newTabWriter()
w.WriteHeaders("ID", "Name", "Deleted")
w.Write(map[string]interface{}{
"ID": o.ID.String(),
"Name": o.Name,
"Deleted": true,
})
w.Flush()
return nil
}
func (b *cmdOrgBuilder) cmdFind() *cobra.Command {
cmd := b.newCmd("list", b.findRunEFn)
cmd.Short = "List organizations"
cmd.Aliases = []string{"find", "ls"}
opts := flagOpts{
{
DestP: &b.name,
Flag: "name",
Short: 'n',
EnvVar: "ORG",
Desc: "The organization name",
},
{
DestP: &b.id,
Flag: "id",
Short: 'i',
EnvVar: "ORG_ID",
Desc: "The organization ID",
},
}
opts.mustRegister(cmd)
return cmd
}
func (b *cmdOrgBuilder) findRunEFn(cmd *cobra.Command, args []string) error {
orgSvc, _, _, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
filter := influxdb.OrganizationFilter{}
if b.name != "" {
filter.Name = &b.name
}
if b.id != "" {
id, err := influxdb.IDFromString(b.id)
if err != nil {
return fmt.Errorf("failed to decode org id %s: %v", b.id, err)
}
filter.ID = id
}
orgs, _, err := orgSvc.FindOrganizations(context.Background(), filter)
if err != nil {
return fmt.Errorf("failed find orgs: %v", err)
}
w := b.newTabWriter()
w.WriteHeaders("ID", "Name")
for _, o := range orgs {
w.Write(map[string]interface{}{
"ID": o.ID.String(),
"Name": o.Name,
})
}
w.Flush()
return nil
}
func (b *cmdOrgBuilder) cmdUpdate() *cobra.Command {
cmd := b.newCmd("update", b.updateRunEFn)
cmd.Short = "Update organization"
opts := flagOpts{
{
DestP: &b.id,
Flag: "id",
Short: 'i',
EnvVar: "ORG_ID",
Desc: "The organization ID (required)",
Required: true,
},
{
DestP: &b.name,
Flag: "name",
Short: 'n',
EnvVar: "ORG",
Desc: "The organization name",
},
{
DestP: &b.description,
Flag: "description",
Short: 'd',
EnvVar: "ORG_DESCRIPTION",
Desc: "The organization name",
},
}
opts.mustRegister(cmd)
return cmd
}
func (b *cmdOrgBuilder) updateRunEFn(cmd *cobra.Command, args []string) error {
orgSvc, _, _, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
var id influxdb.ID
if err := id.DecodeFromString(b.id); err != nil {
return fmt.Errorf("failed to decode org id %s: %v", b.id, err)
}
update := influxdb.OrganizationUpdate{}
if b.name != "" {
update.Name = &b.name
}
if b.description != "" {
update.Description = &b.description
}
o, err := orgSvc.UpdateOrganization(context.Background(), id, update)
if err != nil {
return fmt.Errorf("failed to update org: %v", err)
}
w := b.newTabWriter()
w.WriteHeaders("ID", "Name")
w.Write(map[string]interface{}{
"ID": o.ID.String(),
"Name": o.Name,
})
w.Flush()
return nil
}
func (b *cmdOrgBuilder) cmdMember() *cobra.Command {
cmd := b.newCmd("members", nil)
cmd.Short = "Organization membership commands"
cmd.Run = seeHelp
cmd.AddCommand(
b.cmdMemberAdd(),
b.cmdMemberList(),
b.cmdMemberRemove(),
)
return cmd
}
func (b *cmdOrgBuilder) cmdMemberList() *cobra.Command {
cmd := b.newCmd("list", b.memberListRunEFn)
cmd.Short = "List organization members"
cmd.Aliases = []string{"find", "ls"}
opts := flagOpts{
{
DestP: &b.name,
Flag: "name",
Short: 'n',
EnvVar: "ORG",
Desc: "The organization name",
},
{
DestP: &b.id,
Flag: "id",
Short: 'i',
EnvVar: "ORG_ID",
Desc: "The organization ID",
},
}
opts.mustRegister(cmd)
return cmd
}
func (b *cmdOrgBuilder) memberListRunEFn(cmd *cobra.Command, args []string) error {
orgSvc, urmSVC, userSVC, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
if b.id == "" && b.name == "" {
return fmt.Errorf("must specify exactly one of id and name")
}
var filter influxdb.OrganizationFilter
if b.name != "" {
filter.Name = &b.name
}
if b.id != "" {
var fID influxdb.ID
err := fID.DecodeFromString(b.id)
if err != nil {
return fmt.Errorf("failed to decode org id %s: %v", b.id, err)
}
filter.ID = &fID
}
organization, err := orgSvc.FindOrganization(context.Background(), filter)
if err != nil {
return fmt.Errorf("failed to find org: %v", err)
}
ctx := context.Background()
return memberList(ctx, b, urmSVC, userSVC, influxdb.UserResourceMappingFilter{
ResourceType: influxdb.OrgsResourceType,
ResourceID: organization.ID,
UserType: influxdb.Member,
})
}
func (b *cmdOrgBuilder) cmdMemberAdd() *cobra.Command {
cmd := b.newCmd("add", b.memberAddRunEFn)
cmd.Short = "Add organization member"
cmd.Flags().StringVarP(&b.memberID, "member", "m", "", "The member ID")
cmd.MarkFlagRequired("member")
opts := flagOpts{
{
DestP: &b.name,
Flag: "name",
Short: 'n',
EnvVar: "ORG",
Desc: "The organization name",
},
{
DestP: &b.id,
Flag: "id",
Short: 'i',
EnvVar: "ORG_ID",
Desc: "The organization ID",
},
}
opts.mustRegister(cmd)
return cmd
}
func (b *cmdOrgBuilder) memberAddRunEFn(cmd *cobra.Command, args []string) error {
if b.id == "" && b.name == "" {
return fmt.Errorf("must specify exactly one of id and name")
}
if b.id != "" && b.name != "" {
return fmt.Errorf("must specify exactly one of id and name")
}
orgSvc, urmSVC, _, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
var filter influxdb.OrganizationFilter
if b.name != "" {
filter.Name = &b.name
}
if b.id != "" {
var fID influxdb.ID
err := fID.DecodeFromString(b.id)
if err != nil {
return fmt.Errorf("failed to decode org id %s: %v", b.id, err)
}
filter.ID = &fID
}
ctx := context.Background()
organization, err := orgSvc.FindOrganization(ctx, filter)
if err != nil {
return fmt.Errorf("failed to find org: %v", err)
}
var memberID influxdb.ID
err = memberID.DecodeFromString(b.memberID)
if err != nil {
return fmt.Errorf("failed to decode member id %s: %v", b.memberID, err)
}
return addMember(ctx, b.w, urmSVC, influxdb.UserResourceMapping{
ResourceID: organization.ID,
ResourceType: influxdb.OrgsResourceType,
MappingType: influxdb.UserMappingType,
UserID: memberID,
UserType: influxdb.Member,
})
}
func (b *cmdOrgBuilder) cmdMemberRemove() *cobra.Command {
cmd := b.newCmd("remove", b.membersRemoveRunEFn)
cmd.Short = "Remove organization member"
opts := flagOpts{
{
DestP: &b.name,
Flag: "name",
Short: 'n',
EnvVar: "ORG",
Desc: "The organization name",
},
{
DestP: &b.id,
Flag: "id",
Short: 'i',
EnvVar: "ORG_ID",
Desc: "The organization ID",
},
}
opts.mustRegister(cmd)
cmd.Flags().StringVarP(&b.memberID, "member", "m", "", "The member ID")
cmd.MarkFlagRequired("member")
return cmd
}
func (b *cmdOrgBuilder) membersRemoveRunEFn(cmd *cobra.Command, args []string) error {
if b.id == "" && b.name == "" {
return fmt.Errorf("must specify exactly one of id and name")
}
if b.id != "" && b.name != "" {
return fmt.Errorf("must specify exactly one of id and name")
}
orgSvc, urmSVC, _, err := b.svcFn()
if err != nil {
return fmt.Errorf("failed to initialize org service client: %v", err)
}
var filter influxdb.OrganizationFilter
if b.name != "" {
filter.Name = &b.name
}
if b.id != "" {
var fID influxdb.ID
err := fID.DecodeFromString(b.id)
if err != nil {
return fmt.Errorf("failed to decode org id %s: %v", b.id, err)
}
filter.ID = &fID
}
ctx := context.Background()
organization, err := orgSvc.FindOrganization(ctx, filter)
if err != nil {
return fmt.Errorf("failed to find organization: %v", err)
}
var memberID influxdb.ID
err = memberID.DecodeFromString(b.memberID)
if err != nil {
return fmt.Errorf("failed to decode member id %s: %v", b.memberID, err)
}
return removeMember(ctx, b.w, urmSVC, organization.ID, memberID)
}
func newOrgServices() (influxdb.OrganizationService, influxdb.UserResourceMappingService, influxdb.UserService, error) {
if flags.local {
svc, err := newLocalKVService()
if err != nil {
return nil, nil, nil, err
}
return svc, svc, svc, nil
}
client, err := newHTTPClient()
if err != nil {
return nil, nil, nil, err
}
orgSVC := &http.OrganizationService{Client: client}
urmSVC := &http.UserResourceMappingService{Client: client}
userSVC := &http.UserService{Client: client}
return orgSVC, urmSVC, userSVC, nil
}
func newOrganizationService() (influxdb.OrganizationService, error) {
if flags.local {
return newLocalKVService()
}
client, err := newHTTPClient()
if err != nil {
return nil, err
}
return &http.OrganizationService{
Client: client,
}, nil
}
func memberList(ctx context.Context, b *cmdOrgBuilder, urmSVC influxdb.UserResourceMappingService, userSVC influxdb.UserService, f influxdb.UserResourceMappingFilter) error {
mps, _, err := urmSVC.FindUserResourceMappings(ctx, f)
if err != nil {
return fmt.Errorf("failed to find members: %v", err)
}
urs := make([]*influxdb.User, len(mps))
ursC := make(chan struct {
User *influxdb.User
Index int
})
errC := make(chan error)
sem := make(chan struct{}, maxTCPConnections)
for k, v := range mps {
sem <- struct{}{}
go func(k int, v *influxdb.UserResourceMapping) {
defer func() { <-sem }()
usr, err := userSVC.FindUserByID(ctx, v.UserID)
if err != nil {
errC <- fmt.Errorf("failed to retrieve user details: %v", err)
return
}
ursC <- struct {
User *influxdb.User
Index int
}{
User: usr,
Index: k,
}
}(k, v)
}
for i := 0; i < len(mps); i++ {
select {
case <-ctx.Done():
return &influxdb.Error{
Msg: "Timeout retrieving user details",
}
case err := <-errC:
return err
case item := <-ursC:
urs[item.Index] = item.User
}
}
tw := b.newTabWriter()
tw.WriteHeaders("ID", "Name", "Status")
for _, m := range urs {
tw.Write(map[string]interface{}{
"ID": m.ID.String(),
"Name": m.Name,
"Status": string(m.Status),
})
}
tw.Flush()
return nil
}
func addMember(ctx context.Context, w io.Writer, urmSVC influxdb.UserResourceMappingService, urm influxdb.UserResourceMapping) error {
if err := urmSVC.CreateUserResourceMapping(ctx, &urm); err != nil {
return fmt.Errorf("failed to add member: %v", err)
}
_, err := fmt.Fprintf(w, "user %s has been added as a %s of %s: %s\n", urm.UserID, urm.UserType, urm.ResourceType, urm.ResourceID)
return err
}
func removeMember(ctx context.Context, w io.Writer, urmSVC influxdb.UserResourceMappingService, resourceID, userID influxdb.ID) error {
if err := urmSVC.DeleteUserResourceMapping(ctx, resourceID, userID); err != nil {
return fmt.Errorf("failed to remove member: %v", err)
}
_, err := fmt.Fprintf(w, "userID %s has been removed from ResourceID %s\n", userID, resourceID)
return err
}