influxdb/cmd/influx/organization.go

636 lines
14 KiB
Go

package main
import (
"context"
"fmt"
"io"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/tenant"
"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, f, opts)
return builder.cmd()
}
type cmdOrgBuilder struct {
genericCLIOpts
*globalFlags
svcFn orgSVCFn
json bool
hideHeaders bool
description string
id string
memberID string
name string
}
func newCmdOrgBuilder(svcFn orgSVCFn, f *globalFlags, opts genericCLIOpts) *cmdOrgBuilder {
return &cmdOrgBuilder{
genericCLIOpts: opts,
globalFlags: f,
svcFn: svcFn,
}
}
func (b *cmdOrgBuilder) cmd() *cobra.Command {
cmd := b.genericCLIOpts.newCmd("org", nil, false)
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"
b.registerPrintFlags(cmd)
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)
}
return b.printOrg(orgPrintOpt{org: org})
}
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(b.viper, cmd)
b.registerPrintFlags(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)
}
return b.printOrg(orgPrintOpt{
deleted: true,
org: o,
})
}
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(b.viper, cmd)
b.registerPrintFlags(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)
}
return b.printOrg(orgPrintOpt{orgs: orgs})
}
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(b.viper, cmd)
b.registerPrintFlags(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)
}
return b.printOrg(orgPrintOpt{org: o})
}
func (b *cmdOrgBuilder) printOrg(opts orgPrintOpt) error {
if b.json {
var v interface{} = opts.orgs
if opts.org != nil {
v = opts.org
}
return b.writeJSON(v)
}
w := b.newTabWriter()
defer w.Flush()
w.HideHeaders(b.hideHeaders)
headers := []string{"ID", "Name"}
if opts.deleted {
headers = append(headers, "Deleted")
}
w.WriteHeaders(headers...)
if opts.org != nil {
opts.orgs = append(opts.orgs, opts.org)
}
for _, o := range opts.orgs {
m := map[string]interface{}{
"ID": o.ID.String(),
"Name": o.Name,
}
if opts.deleted {
m["Deleted"] = true
}
w.Write(m)
}
return nil
}
func (b *cmdOrgBuilder) cmdMember() *cobra.Command {
cmd := b.genericCLIOpts.newCmd("members", nil, false)
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(b.viper, cmd)
b.registerPrintFlags(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 b.memberList(ctx, 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(b.viper, 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(b.viper, 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 (b *cmdOrgBuilder) newCmd(use string, runE func(*cobra.Command, []string) error) *cobra.Command {
cmd := b.genericCLIOpts.newCmd(use, runE, true)
b.globalFlags.registerFlags(b.viper, cmd)
return cmd
}
func (b *cmdOrgBuilder) registerPrintFlags(cmd *cobra.Command) {
registerPrintOptions(b.viper, cmd, &b.hideHeaders, &b.json)
}
func newOrgServices() (influxdb.OrganizationService, influxdb.UserResourceMappingService, influxdb.UserService, error) {
client, err := newHTTPClient()
if err != nil {
return nil, nil, nil, err
}
orgSVC := &tenant.OrgClientService{Client: client}
urmSVC := &tenant.UserResourceMappingClient{Client: client}
userSVC := &tenant.UserClientService{Client: client}
return orgSVC, urmSVC, userSVC, nil
}
func newOrganizationService() (influxdb.OrganizationService, error) {
client, err := newHTTPClient()
if err != nil {
return nil, err
}
return &tenant.OrgClientService{
Client: client,
}, nil
}
func (b *cmdOrgBuilder) memberList(ctx context.Context, urmSVC influxdb.UserResourceMappingService, userSVC influxdb.UserService, f influxdb.UserResourceMappingFilter) error {
mappings, _, err := urmSVC.FindUserResourceMappings(ctx, f)
if err != nil {
return fmt.Errorf("failed to find members: %v", err)
}
var (
ursC = make(chan struct {
User *influxdb.User
Index int
})
errC = make(chan error)
sem = make(chan struct{}, maxTCPConnections)
)
for k, v := range mappings {
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)
}
users := make([]*influxdb.User, len(mappings))
for i := 0; i < len(mappings); i++ {
select {
case <-ctx.Done():
return &influxdb.Error{
Msg: "Timeout retrieving user details",
}
case err := <-errC:
return err
case item := <-ursC:
users[item.Index] = item.User
}
}
if b.json {
return b.writeJSON(users)
}
tw := b.newTabWriter()
defer tw.Flush()
tw.HideHeaders(b.hideHeaders)
tw.WriteHeaders("ID", "Name", "User Type", "Status")
for idx, m := range users {
tw.Write(map[string]interface{}{
"ID": m.ID.String(),
"User Name": m.Name,
"User Type": string(mappings[idx].UserType),
"Status": string(m.Status),
})
}
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
}
type orgPrintOpt struct {
deleted bool
org *influxdb.Organization
orgs []*influxdb.Organization
}