Merge pull request #16537 from influxdata/cli_update_password

feat(cmd/influx): update password in cli
pull/16553/head
kelwang 2020-01-15 14:17:20 -05:00 committed by GitHub
commit 540e785eb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 622 additions and 141 deletions

View File

@ -44,6 +44,7 @@
1. [16509](https://github.com/influxdata/influxdb/pull/16509): Add support for applying an influx package via a public facing URL 1. [16509](https://github.com/influxdata/influxdb/pull/16509): Add support for applying an influx package via a public facing URL
1. [16511](https://github.com/influxdata/influxdb/pull/16511): Add jsonnet support for influx packages 1. [16511](https://github.com/influxdata/influxdb/pull/16511): Add jsonnet support for influx packages
1. [14782](https://github.com/influxdata/influxdb/pull/16336): Add view page for Check 1. [14782](https://github.com/influxdata/influxdb/pull/16336): Add view page for Check
1. [16537](https://github.com/influxdata/influxdb/pull/16537): Add update password for CLI
### Bug Fixes ### Bug Fixes
@ -68,6 +69,7 @@
1. [16491](https://github.com/influxdata/influxdb/pull/16491): Add missing env vals to influx cli usage and fixes precedence of flag/env var priority 1. [16491](https://github.com/influxdata/influxdb/pull/16491): Add missing env vals to influx cli usage and fixes precedence of flag/env var priority
### UI Improvements ### UI Improvements
1. [16444](https://github.com/influxdata/influxdb/pull/16444): Add honeybadger reporting to create checks 1. [16444](https://github.com/influxdata/influxdb/pull/16444): Add honeybadger reporting to create checks
## v2.0.0-alpha.21 [2019-12-13] ## v2.0.0-alpha.21 [2019-12-13]

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"reflect" "reflect"
"testing" "testing"
"io/ioutil"
"time" "time"
"github.com/influxdata/influxdb" "github.com/influxdata/influxdb"
@ -102,7 +103,7 @@ func TestCmdBucket(t *testing.T) {
return nil return nil
} }
builder := newCmdBucketBuilder(fakeSVCFn(svc), out(new(bytes.Buffer))) builder := newCmdBucketBuilder(fakeSVCFn(svc), out(ioutil.Discard))
cmd := builder.cmdCreate() cmd := builder.cmdCreate()
cmd.RunE = builder.cmdCreateRunEFn cmd.RunE = builder.cmdCreateRunEFn
return cmd return cmd
@ -151,7 +152,7 @@ func TestCmdBucket(t *testing.T) {
return nil return nil
} }
builder := newCmdBucketBuilder(fakeSVCFn(svc), out(new(bytes.Buffer))) builder := newCmdBucketBuilder(fakeSVCFn(svc), out(ioutil.Discard))
cmd := builder.cmdDelete() cmd := builder.cmdDelete()
cmd.RunE = builder.cmdDeleteRunEFn cmd.RunE = builder.cmdDeleteRunEFn
return cmd return cmd
@ -258,7 +259,7 @@ func TestCmdBucket(t *testing.T) {
return nil, 0, nil return nil, 0, nil
} }
builder := newCmdBucketBuilder(fakeSVCFn(svc), in(new(bytes.Buffer)), out(new(bytes.Buffer))) builder := newCmdBucketBuilder(fakeSVCFn(svc), in(new(bytes.Buffer)), out(ioutil.Discard))
cmd := builder.cmdFind() cmd := builder.cmdFind()
cmd.RunE = builder.cmdFindRunEFn cmd.RunE = builder.cmdFindRunEFn
return cmd, calls return cmd, calls
@ -352,7 +353,7 @@ func TestCmdBucket(t *testing.T) {
return &influxdb.Bucket{}, nil return &influxdb.Bucket{}, nil
} }
builder := newCmdBucketBuilder(fakeSVCFn(svc), out(new(bytes.Buffer))) builder := newCmdBucketBuilder(fakeSVCFn(svc), out(ioutil.Discard))
cmd := builder.cmdUpdate() cmd := builder.cmdUpdate()
cmd.RunE = builder.cmdUpdateRunEFn cmd.RunE = builder.cmdUpdateRunEFn
return cmd return cmd

View File

@ -128,7 +128,7 @@ func influxCmd(opts ...genericCLIOptFn) *cobra.Command {
cmdREPL(), cmdREPL(),
cmdSetup(), cmdSetup(),
cmdTask(), cmdTask(),
cmdUser(), cmdUser(runEWrapper),
cmdWrite(), cmdWrite(),
) )

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil"
"reflect" "reflect"
"testing" "testing"
@ -63,7 +64,7 @@ func TestCmdOrg(t *testing.T) {
return nil return nil
} }
builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(ioutil.Discard))
cmd := builder.cmdCreate() cmd := builder.cmdCreate()
return cmd return cmd
} }
@ -109,7 +110,7 @@ func TestCmdOrg(t *testing.T) {
return nil return nil
} }
builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(ioutil.Discard))
cmd := builder.cmdDelete() cmd := builder.cmdDelete()
return cmd return cmd
} }
@ -181,7 +182,7 @@ func TestCmdOrg(t *testing.T) {
return nil, 0, nil return nil, 0, nil
} }
builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(ioutil.Discard))
cmd := builder.cmdFind() cmd := builder.cmdFind()
return cmd, calls return cmd, calls
} }
@ -268,7 +269,7 @@ func TestCmdOrg(t *testing.T) {
return &influxdb.Organization{}, nil return &influxdb.Organization{}, nil
} }
builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(ioutil.Discard))
cmd := builder.cmdUpdate() cmd := builder.cmdUpdate()
return cmd return cmd
} }
@ -366,7 +367,7 @@ func TestCmdOrg(t *testing.T) {
return &influxdb.Organization{ID: 1}, nil return &influxdb.Organization{ID: 1}, nil
} }
builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(ioutil.Discard))
cmd := builder.cmdMemberList() cmd := builder.cmdMemberList()
return cmd, calls return cmd, calls
} }
@ -394,7 +395,7 @@ func TestCmdOrg(t *testing.T) {
return nil return nil
} }
builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(ioutil.Discard))
cmd := builder.cmdMemberAdd() cmd := builder.cmdMemberAdd()
return cmd, calls return cmd, calls
} }
@ -457,7 +458,7 @@ func TestCmdOrg(t *testing.T) {
return nil return nil
} }
builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(new(bytes.Buffer))) builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(ioutil.Discard))
cmd := builder.cmdMemberRemove() cmd := builder.cmdMemberRemove()
return cmd, calls return cmd, calls
} }

View File

@ -88,7 +88,7 @@ func setupF(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to write token to path %q: %v", dPath, err) return fmt.Errorf("failed to write token to path %q: %v", dPath, err)
} }
fmt.Println(promptWithColor("Your token has been stored in "+dPath+".", colorCyan)) fmt.Println(string(promptWithColor("Your token has been stored in "+dPath+".", colorCyan)))
w := internal.NewTabWriter(os.Stdout) w := internal.NewTabWriter(os.Stdout)
w.WriteHeaders( w.WriteHeaders(
@ -144,7 +144,7 @@ func interactive() (req *platform.OnboardingRequest, err error) {
Reader: os.Stdin, Reader: os.Stdin,
} }
req = new(platform.OnboardingRequest) req = new(platform.OnboardingRequest)
fmt.Println(promptWithColor(`Welcome to InfluxDB 2.0!`, colorYellow)) fmt.Println(string(promptWithColor(`Welcome to InfluxDB 2.0!`, colorYellow)))
if setupFlags.username != "" { if setupFlags.username != "" {
req.User = setupFlags.username req.User = setupFlags.username
} else { } else {
@ -153,7 +153,7 @@ func interactive() (req *platform.OnboardingRequest, err error) {
if setupFlags.password != "" { if setupFlags.password != "" {
req.Password = setupFlags.password req.Password = setupFlags.password
} else { } else {
req.Password = getPassword(ui) req.Password = getPassword(ui, false)
} }
if setupFlags.token != "" { if setupFlags.token != "" {
req.Token = setupFlags.token req.Token = setupFlags.token
@ -200,8 +200,9 @@ var (
keyReset = []byte{keyEscape, '[', '0', 'm'} keyReset = []byte{keyEscape, '[', '0', 'm'}
) )
func promptWithColor(s string, color []byte) string { func promptWithColor(s string, color []byte) []byte {
return string(color) + s + string(keyReset) bb := append(color, []byte(s)...)
return append(bb, keyReset...)
} }
func getConfirm(ui *input.UI, or *platform.OnboardingRequest) bool { func getConfirm(ui *input.UI, or *platform.OnboardingRequest) bool {
@ -211,14 +212,14 @@ func getConfirm(ui *input.UI, or *platform.OnboardingRequest) bool {
if or.RetentionPeriod > 0 { if or.RetentionPeriod > 0 {
rp = fmt.Sprintf("%d hrs", or.RetentionPeriod) rp = fmt.Sprintf("%d hrs", or.RetentionPeriod)
} }
fmt.Print(promptWithColor(fmt.Sprintf(` ui.Writer.Write(promptWithColor(fmt.Sprintf(`
You have entered: You have entered:
Username: %s Username: %s
Organization: %s Organization: %s
Bucket: %s Bucket: %s
Retention Period: %s Retention Period: %s
`, or.User, or.Org, or.Bucket, rp), colorCyan)) `, or.User, or.Org, or.Bucket, rp), colorCyan))
result, err := ui.Ask(prompt, &input.Options{ result, err := ui.Ask(string(prompt), &input.Options{
HideOrder: true, HideOrder: true,
}) })
if err != nil { if err != nil {
@ -240,15 +241,20 @@ var (
errPasswordIsTooShort = fmt.Errorf("passwords is too short") errPasswordIsTooShort = fmt.Errorf("passwords is too short")
) )
func getPassword(ui *input.UI) (password string) { func getPassword(ui *input.UI, showNew bool) (password string) {
newStr := ""
if showNew {
newStr = " new"
}
var err error var err error
enterPasswd: enterPasswd:
query := promptWithColor("Please type your password", colorCyan) query := string(promptWithColor("Please type your"+newStr+" password", colorCyan))
for { for {
password, err = ui.Ask(query, &input.Options{ password, err = ui.Ask(query, &input.Options{
Required: true, Required: true,
HideOrder: true, HideOrder: true,
Hide: true, Hide: true,
Mask: false,
ValidateFunc: func(s string) error { ValidateFunc: func(s string) error {
if len(s) < 8 { if len(s) < 8 {
return errPasswordIsTooShort return errPasswordIsTooShort
@ -260,7 +266,7 @@ enterPasswd:
case input.ErrInterrupted: case input.ErrInterrupted:
os.Exit(1) os.Exit(1)
case errPasswordIsTooShort: case errPasswordIsTooShort:
fmt.Println(promptWithColor("Password too short - minimum length is 8 characters!", colorRed)) ui.Writer.Write(promptWithColor("Password too short - minimum length is 8 characters!", colorRed))
goto enterPasswd goto enterPasswd
default: default:
if password = strings.TrimSpace(password); password == "" { if password = strings.TrimSpace(password); password == "" {
@ -269,7 +275,7 @@ enterPasswd:
} }
break break
} }
query = promptWithColor("Please type your password again", colorCyan) query = string(promptWithColor("Please type your"+newStr+" password again", colorCyan))
for { for {
_, err = ui.Ask(query, &input.Options{ _, err = ui.Ask(query, &input.Options{
Required: true, Required: true,
@ -288,7 +294,7 @@ enterPasswd:
case nil: case nil:
// Nothing. // Nothing.
default: default:
fmt.Println(promptWithColor("Passwords do not match!", colorRed)) ui.Writer.Write(promptWithColor("Passwords do not match!\n", colorRed))
goto enterPasswd goto enterPasswd
} }
break break
@ -305,7 +311,7 @@ func getInput(ui *input.UI, prompt, defaultValue string) string {
option.Default = defaultValue option.Default = defaultValue
option.HideDefault = true option.HideDefault = true
} }
prompt = promptWithColor(prompt, colorCyan) prompt = string(promptWithColor(prompt, colorCyan))
for { for {
line, err := ui.Ask(prompt, option) line, err := ui.Ask(prompt, option)
switch err { switch err {

View File

@ -3,50 +3,96 @@ package main
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"os" "os"
platform "github.com/influxdata/influxdb" "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/cmd/influx/internal" "github.com/influxdata/influxdb/cmd/influx/internal"
"github.com/influxdata/influxdb/http" "github.com/influxdata/influxdb/http"
"github.com/spf13/cobra" "github.com/spf13/cobra"
input "github.com/tcnksm/go-input"
) )
func cmdUser() *cobra.Command { type userSVCsFn func() (
cmd := &cobra.Command{ cmdUserDeps,
Use: "user", error,
Short: "User management commands", )
Run: seeHelp,
type cmdUserDeps struct {
userSVC influxdb.UserService
orgSvc influxdb.OrganizationService
passSVC influxdb.PasswordsService
urmSVC influxdb.UserResourceMappingService
getPassFn func(*input.UI, bool) string
}
func cmdUser(opts ...genericCLIOptFn) *cobra.Command {
return newCmdUserBuilder(newUserSVC, opts...).cmd()
}
type cmdUserBuilder struct {
genericCLIOpts
svcFn userSVCsFn
id string
name string
password string
org organization
}
func newCmdUserBuilder(svcsFn userSVCsFn, opts ...genericCLIOptFn) *cmdUserBuilder {
opt := genericCLIOpts{
in: os.Stdin,
w: os.Stdout,
} }
for _, o := range opts {
o(&opt)
}
return &cmdUserBuilder{
genericCLIOpts: opt,
svcFn: svcsFn,
}
}
func (b *cmdUserBuilder) cmd() *cobra.Command {
cmd := b.newCmd("user", nil)
cmd.Short = "User management commands"
cmd.Run = seeHelp
cmd.AddCommand( cmd.AddCommand(
userCreateCmd(), b.cmdCreate(),
userDeleteCmd(), b.cmdDelete(),
userFindCmd(), b.cmdFind(),
userUpdateCmd(), b.cmdUpdate(),
b.cmdPassword(),
) )
return cmd return cmd
} }
var userUpdateFlags struct { func (b *cmdUserBuilder) cmdPassword() *cobra.Command {
id string cmd := b.newCmd("password", b.cmdPasswordRunEFn)
name string cmd.Short = "Update user password"
cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID")
cmd.Flags().StringVarP(&b.name, "name", "n", "", "The user name")
return cmd
} }
func userUpdateCmd() *cobra.Command { func (b *cmdUserBuilder) cmdUpdate() *cobra.Command {
cmd := &cobra.Command{ cmd := b.newCmd("update", b.cmdUpdateRunEFn)
Use: "update", cmd.Short = "Update user"
Short: "Update user",
RunE: wrapCheckSetup(userUpdateF),
}
cmd.Flags().StringVarP(&userUpdateFlags.id, "id", "i", "", "The user ID (required)") cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID (required)")
cmd.Flags().StringVarP(&userUpdateFlags.name, "name", "n", "", "The user name") cmd.Flags().StringVarP(&b.name, "name", "n", "", "The user name")
cmd.MarkFlagRequired("id") cmd.MarkFlagRequired("id")
return cmd return cmd
} }
func newUserService() (platform.UserService, error) { func newUserService() (influxdb.UserService, error) {
if flags.local { if flags.local {
return newLocalKVService() return newLocalKVService()
} }
@ -60,28 +106,85 @@ func newUserService() (platform.UserService, error) {
}, nil }, nil
} }
func userUpdateF(cmd *cobra.Command, args []string) error { func newUserSVC() (
s, err := newUserService() cmdUserDeps,
error) {
httpClient, err := newHTTPClient()
if err != nil {
return cmdUserDeps{}, err
}
userSvc := &http.UserService{Client: httpClient}
orgSvc := &http.OrganizationService{Client: httpClient}
passSvc := &http.PasswordService{Client: httpClient}
urmSvc := &http.UserResourceMappingService{Client: httpClient}
getPassFn := getPassword
return cmdUserDeps{
userSVC: userSvc,
orgSvc: orgSvc,
passSVC: passSvc,
urmSVC: urmSvc,
getPassFn: getPassFn,
}, nil
}
func (b *cmdUserBuilder) cmdPasswordRunEFn(cmd *cobra.Command, args []string) error {
ctx := context.Background()
dep, err := b.svcFn()
if err != nil { if err != nil {
return err return err
} }
var id platform.ID filter := influxdb.UserFilter{}
if err := id.DecodeFromString(userUpdateFlags.id); err != nil { if b.name != "" {
filter.Name = &b.name
}
if b.id != "" {
id, err := influxdb.IDFromString(b.id)
if err != nil {
return err return err
} }
filter.ID = id
update := platform.UserUpdate{}
if userUpdateFlags.name != "" {
update.Name = &userUpdateFlags.name
} }
u, err := dep.userSVC.FindUser(ctx, filter)
if err != nil {
return err
}
ui := &input.UI{
Writer: b.genericCLIOpts.w,
Reader: b.genericCLIOpts.in,
}
password := dep.getPassFn(ui, true)
user, err := s.UpdateUser(context.Background(), id, update) if err = dep.passSVC.SetPassword(ctx, u.ID, password); err != nil {
return err
}
fmt.Fprintln(b.w, "Your password has been successfully updated.")
return nil
}
func (b *cmdUserBuilder) cmdUpdateRunEFn(cmd *cobra.Command, args []string) error {
dep, err := b.svcFn()
if err != nil { if err != nil {
return err return err
} }
w := internal.NewTabWriter(os.Stdout) var id influxdb.ID
if err := id.DecodeFromString(b.id); err != nil {
return err
}
update := influxdb.UserUpdate{}
if b.name != "" {
update.Name = &b.name
}
user, err := dep.userSVC.UpdateUser(context.Background(), id, update)
if err != nil {
return err
}
w := internal.NewTabWriter(b.w)
w.WriteHeaders( w.WriteHeaders(
"ID", "ID",
"Name", "Name",
@ -95,42 +198,43 @@ func userUpdateF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
var userCreateFlags struct { func (b *cmdUserBuilder) cmdCreate() *cobra.Command {
name string cmd := b.newCmd("create", b.cmdCreateRunEFn)
password string cmd.Short = "Create user"
org organization
}
func userCreateCmd() *cobra.Command { opts := flagOpts{
cmd := &cobra.Command{ {
Use: "create", DestP: &b.name,
Short: "Create user", Flag: "name",
RunE: wrapCheckSetup(userCreateF), Short: 'n',
Desc: "The user name (required)",
Required: true,
},
} }
opts.mustRegister(cmd)
userCreateFlags.org.register(cmd, false) cmd.Flags().StringVarP(&b.password, "password", "p", "", "The user password")
cmd.Flags().StringVarP(&userCreateFlags.name, "name", "n", "", "The user name (required)") b.org.register(cmd, false)
cmd.MarkFlagRequired("name")
cmd.Flags().StringVarP(&userCreateFlags.password, "password", "p", "", "The user password")
return cmd return cmd
} }
func userCreateF(cmd *cobra.Command, args []string) error { func (b *cmdUserBuilder) cmdCreateRunEFn(*cobra.Command, []string) error {
if err := userCreateFlags.org.validOrgFlags(); err != nil { ctx := context.Background()
if err := b.org.validOrgFlags(); err != nil {
return err return err
} }
s, err := newUserService() dep, err := b.svcFn()
if err != nil { if err != nil {
return err return err
} }
user := &platform.User{ user := &influxdb.User{
Name: userCreateFlags.name, Name: b.name,
} }
if err := s.CreateUser(context.Background(), user); err != nil { if err := dep.userSVC.CreateUser(ctx, user); err != nil {
return err return err
} }
@ -143,7 +247,7 @@ func userCreateF(cmd *cobra.Command, args []string) error {
for i, h := range headers { for i, h := range headers {
m[h] = vals[i] m[h] = vals[i]
} }
w := internal.NewTabWriter(os.Stdout) w := internal.NewTabWriter(b.w)
w.WriteHeaders(headers...) w.WriteHeaders(headers...)
w.Write(m) w.Write(m)
w.Flush() w.Flush()
@ -151,17 +255,12 @@ func userCreateF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
orgSVC, err := newOrganizationService() orgID, err := b.org.getID(dep.orgSvc)
if err != nil { if err != nil {
return err return err
} }
orgID, err := userCreateFlags.org.getID(orgSVC) pass := b.password
if err != nil {
return err
}
pass := userCreateFlags.password
if orgID == 0 && pass == "" { if orgID == 0 && pass == "" {
return writeOutput([]string{"ID", "Name"}, user.ID.String(), user.Name) return writeOutput([]string{"ID", "Name"}, user.ID.String(), user.Name)
} }
@ -170,77 +269,57 @@ func userCreateF(cmd *cobra.Command, args []string) error {
return errors.New("an org id is required when providing a user password") return errors.New("an org id is required when providing a user password")
} }
c, err := newHTTPClient() err = dep.urmSVC.CreateUserResourceMapping(context.Background(), &influxdb.UserResourceMapping{
if err != nil {
return err
}
userResMapSVC := &http.UserResourceMappingService{
Client: c,
}
err = userResMapSVC.CreateUserResourceMapping(context.Background(), &platform.UserResourceMapping{
UserID: user.ID, UserID: user.ID,
UserType: platform.Member, UserType: influxdb.Member,
ResourceType: platform.OrgsResourceType, ResourceType: influxdb.OrgsResourceType,
ResourceID: orgID, ResourceID: orgID,
}) })
if err != nil { if err != nil {
return err return err
} }
passSVC := &http.PasswordService{Client: c} if err := dep.passSVC.SetPassword(ctx, user.ID, pass); err != nil {
ctx := context.Background()
if err := passSVC.SetPassword(ctx, user.ID, pass); err != nil {
return err return err
} }
return writeOutput([]string{"ID", "Name", "Organization ID"}, user.ID.String(), user.Name, orgID.String()) return writeOutput([]string{"ID", "Name", "Organization ID"}, user.ID.String(), user.Name, orgID.String())
} }
var userFindFlags struct { func (b *cmdUserBuilder) cmdFind() *cobra.Command {
id string cmd := b.newCmd("find", b.cmdFindRunEFn)
name string cmd.Short = "Find user"
}
func userFindCmd() *cobra.Command { cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID")
cmd := &cobra.Command{ cmd.Flags().StringVarP(&b.name, "name", "n", "", "The user name")
Use: "find",
Short: "Find user",
RunE: wrapCheckSetup(userFindF),
}
cmd.Flags().StringVarP(&userFindFlags.id, "id", "i", "", "The user ID")
cmd.Flags().StringVarP(&userFindFlags.name, "name", "n", "", "The user name")
return cmd return cmd
} }
func userFindF(cmd *cobra.Command, args []string) error { func (b *cmdUserBuilder) cmdFindRunEFn(*cobra.Command, []string) error {
s, err := newUserService() dep, err := b.svcFn()
if err != nil { if err != nil {
return err return err
} }
filter := platform.UserFilter{} filter := influxdb.UserFilter{}
if userFindFlags.name != "" { if b.name != "" {
filter.Name = &userFindFlags.name filter.Name = &b.name
} }
if userFindFlags.id != "" { if b.id != "" {
id, err := platform.IDFromString(userFindFlags.id) id, err := influxdb.IDFromString(b.id)
if err != nil { if err != nil {
return err return err
} }
filter.ID = id filter.ID = id
} }
users, _, err := s.FindUsers(context.Background(), filter) users, _, err := dep.userSVC.FindUsers(context.Background(), filter)
if err != nil { if err != nil {
return err return err
} }
w := internal.NewTabWriter(os.Stdout) w := internal.NewTabWriter(b.w)
w.WriteHeaders( w.WriteHeaders(
"ID", "ID",
"Name", "Name",
@ -256,45 +335,38 @@ func userFindF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
var userDeleteFlags struct { func (b *cmdUserBuilder) cmdDelete() *cobra.Command {
id string cmd := b.newCmd("delete", b.cmdDeleteRunEFn)
} cmd.Short = "Delete user"
func userDeleteCmd() *cobra.Command { cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID (required)")
cmd := &cobra.Command{
Use: "delete",
Short: "Delete user",
RunE: wrapCheckSetup(userDeleteF),
}
cmd.Flags().StringVarP(&userDeleteFlags.id, "id", "i", "", "The user ID (required)")
cmd.MarkFlagRequired("id") cmd.MarkFlagRequired("id")
return cmd return cmd
} }
func userDeleteF(cmd *cobra.Command, args []string) error { func (b *cmdUserBuilder) cmdDeleteRunEFn(cmd *cobra.Command, args []string) error {
s, err := newUserService() dep, err := b.svcFn()
if err != nil { if err != nil {
return err return err
} }
var id platform.ID var id influxdb.ID
if err := id.DecodeFromString(userDeleteFlags.id); err != nil { if err := id.DecodeFromString(b.id); err != nil {
return err return err
} }
ctx := context.Background() ctx := context.Background()
u, err := s.FindUserByID(ctx, id) u, err := dep.userSVC.FindUserByID(ctx, id)
if err != nil { if err != nil {
return err return err
} }
if err := s.DeleteUser(ctx, id); err != nil { if err := dep.userSVC.DeleteUser(ctx, id); err != nil {
return err return err
} }
w := internal.NewTabWriter(os.Stdout) w := internal.NewTabWriter(b.w)
w.WriteHeaders( w.WriteHeaders(
"ID", "ID",
"Name", "Name",

399
cmd/influx/user_test.go Normal file
View File

@ -0,0 +1,399 @@
package main
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"reflect"
"testing"
"github.com/influxdata/influxdb"
platform "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/mock"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
input "github.com/tcnksm/go-input"
)
func newCMDUserDeps(
userSVC influxdb.UserService,
passSVC influxdb.PasswordsService,
getPassFn func(*input.UI, bool) string,
) cmdUserDeps {
return cmdUserDeps{
userSVC: userSVC,
orgSvc: &mock.OrganizationService{
FindOrganizationF: func(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) {
return &influxdb.Organization{ID: influxdb.ID(9000), Name: "influxdata"}, nil
},
},
passSVC: passSVC,
urmSVC: &mock.UserResourceMappingService{
CreateMappingFn: func(context.Context, *platform.UserResourceMapping) error {
return nil
},
},
getPassFn: getPassFn,
}
}
func TestCmdUser(t *testing.T) {
setViperOptions()
type userResult struct {
user influxdb.User
password string
}
fakeSVCFn := func(dep cmdUserDeps) userSVCsFn {
return func() (
cmdUserDeps,
error) {
return dep, nil
}
}
t.Run("create", func(t *testing.T) {
tests := []struct {
name string
expected userResult
flags []string
envVars map[string]string
}{
{
name: "basic just name",
flags: []string{"--name=new name", "--org=org name"},
expected: userResult{
user: influxdb.User{
Name: "new name",
},
},
},
{
name: "with password",
flags: []string{
"--name=new name",
"--password=pass1",
"--org=org name",
},
expected: userResult{
user: influxdb.User{
Name: "new name",
},
password: "pass1",
},
},
{
name: "with password and env",
flags: []string{
"--name=new name",
"--password=pass1",
},
envVars: map[string]string{
"INFLUX_ORG_ID": influxdb.ID(1).String(),
},
expected: userResult{
user: influxdb.User{
Name: "new name",
},
password: "pass1",
},
},
{
name: "shorts",
flags: []string{
"-n=new name",
"-o=org name",
},
expected: userResult{
user: influxdb.User{
Name: "new name",
},
},
},
}
cmdFn := func(expected userResult) *cobra.Command {
svc := mock.NewUserService()
svc.CreateUserFn = func(ctx context.Context, User *influxdb.User) error {
if expected.user != *User {
return fmt.Errorf("unexpected User;\n\twant= %+v\n\tgot= %+v", expected, *User)
}
return nil
}
passSVC := mock.NewPasswordsService()
passSVC.SetPasswordFn = func(ctx context.Context, id influxdb.ID, password string) error {
if expected.password != password {
return fmt.Errorf("unexpected password;\n\twant= %+v\n\tgot= %+v", expected.password, password)
}
return nil
}
builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, passSVC, nil)), out(ioutil.Discard))
cmd := builder.cmdCreate()
cmd.RunE = builder.cmdCreateRunEFn
return cmd
}
for _, tt := range tests {
fn := func(t *testing.T) {
defer addEnvVars(t, tt.envVars)()
cmd := cmdFn(tt.expected)
cmd.LocalFlags().Parse(tt.flags)
err := cmd.Execute()
require.NoError(t, err)
}
t.Run(tt.name, fn)
}
})
t.Run("delete", func(t *testing.T) {
tests := []struct {
name string
expectedID influxdb.ID
flag string
}{
{
name: "long id",
expectedID: influxdb.ID(1),
flag: "--id=",
},
{
name: "shorts",
expectedID: influxdb.ID(1),
flag: "-i=",
},
}
cmdFn := func(expectedID influxdb.ID) *cobra.Command {
svc := mock.NewUserService()
svc.FindUserByIDFn = func(ctx context.Context, id influxdb.ID) (*influxdb.User, error) {
return &influxdb.User{ID: id}, nil
}
svc.DeleteUserFn = func(ctx context.Context, id influxdb.ID) error {
if expectedID != id {
return fmt.Errorf("unexpected id:\n\twant= %s\n\tgot= %s", expectedID, id)
}
return nil
}
builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, nil, nil)), out(ioutil.Discard))
cmd := builder.cmdDelete()
cmd.RunE = builder.cmdDeleteRunEFn
return cmd
}
for _, tt := range tests {
fn := func(t *testing.T) {
cmd := cmdFn(tt.expectedID)
idFlag := tt.flag + tt.expectedID.String()
cmd.LocalFlags().Parse([]string{idFlag})
require.NoError(t, cmd.Execute())
}
t.Run(tt.name, fn)
}
})
t.Run("find", func(t *testing.T) {
type called struct {
name string
id influxdb.ID
}
tests := []struct {
name string
expected called
flags []string
}{
{
name: "id",
flags: []string{
"--id=" + influxdb.ID(2).String(),
},
expected: called{
id: 2,
},
},
{
name: "name",
flags: []string{"--name=name1"},
expected: called{name: "name1"},
},
{
name: "shorts",
flags: []string{
"-n=name1",
"-i=" + influxdb.ID(1).String(),
},
expected: called{name: "name1", id: 1},
},
}
cmdFn := func() (*cobra.Command, *called) {
calls := new(called)
svc := mock.NewUserService()
svc.FindUsersFn = func(ctx context.Context, f influxdb.UserFilter, opt ...influxdb.FindOptions) ([]*influxdb.User, int, error) {
if f.ID != nil {
calls.id = *f.ID
}
if f.Name != nil {
calls.name = *f.Name
}
return nil, 0, nil
}
builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, nil, nil)), in(new(bytes.Buffer)), out(ioutil.Discard))
cmd := builder.cmdFind()
cmd.RunE = builder.cmdFindRunEFn
return cmd, calls
}
for _, tt := range tests {
fn := func(t *testing.T) {
cmd, calls := cmdFn()
cmd.LocalFlags().Parse(tt.flags)
require.NoError(t, cmd.Execute())
assert.Equal(t, tt.expected, *calls)
}
t.Run(tt.name, fn)
}
})
t.Run("update", func(t *testing.T) {
tests := []struct {
name string
expected influxdb.UserUpdate
flags []string
}{
{
name: "basic just name",
flags: []string{
"--id=" + influxdb.ID(3).String(),
"--name=new name",
},
expected: influxdb.UserUpdate{
Name: strPtr("new name"),
},
},
{
name: "with all fields",
flags: []string{
"--id=" + influxdb.ID(3).String(),
"--name=new name",
},
expected: influxdb.UserUpdate{
Name: strPtr("new name"),
},
},
{
name: "shorts",
flags: []string{
"-i=" + influxdb.ID(3).String(),
"-n=new name",
},
expected: influxdb.UserUpdate{
Name: strPtr("new name"),
},
},
}
cmdFn := func(expectedUpdate influxdb.UserUpdate) *cobra.Command {
svc := mock.NewUserService()
svc.UpdateUserFn = func(ctx context.Context, id influxdb.ID, upd influxdb.UserUpdate) (*influxdb.User, error) {
if id != 3 {
return nil, fmt.Errorf("unexpecte id:\n\twant= %s\n\tgot= %s", influxdb.ID(3), id)
}
if !reflect.DeepEqual(expectedUpdate, upd) {
return nil, fmt.Errorf("unexpected User update;\n\twant= %+v\n\tgot= %+v", expectedUpdate, upd)
}
return &influxdb.User{}, nil
}
builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, nil, nil)), out(ioutil.Discard))
cmd := builder.cmdUpdate()
cmd.RunE = builder.cmdUpdateRunEFn
return cmd
}
for _, tt := range tests {
fn := func(t *testing.T) {
cmd := cmdFn(tt.expected)
cmd.LocalFlags().Parse(tt.flags)
require.NoError(t, cmd.Execute())
}
t.Run(tt.name, fn)
}
})
t.Run("password", func(t *testing.T) {
tests := []struct {
name string
expected string
flags []string
}{
{
name: "basic id",
flags: []string{
"--id=" + influxdb.ID(3).String(),
},
expected: "pass1",
},
{
name: "shorts",
flags: []string{
"-i=" + influxdb.ID(3).String(),
"-n=new name",
},
expected: "pass1",
},
}
cmdFn := func(expected string) *cobra.Command {
svc := mock.NewUserService()
svc.FindUserFn = func(ctx context.Context, f influxdb.UserFilter) (*influxdb.User, error) {
usr := new(influxdb.User)
if id := f.ID; id != nil {
usr.ID = *id
}
if name := f.Name; name != nil {
usr.Name = *name
}
return usr, nil
}
passSVC := mock.NewPasswordsService()
passSVC.SetPasswordFn = func(ctx context.Context, id influxdb.ID, pass string) error {
if pass != expected {
return fmt.Errorf("unexpecte id:\n\twant= %s\n\tgot= %s", pass, expected)
}
return nil
}
getPassFn := func(*input.UI, bool) string {
return expected
}
builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, passSVC, getPassFn)),
out(ioutil.Discard))
cmd := builder.cmdPassword()
cmd.RunE = builder.cmdPasswordRunEFn
return cmd
}
for _, tt := range tests {
fn := func(t *testing.T) {
cmd := cmdFn(tt.expected)
cmd.LocalFlags().Parse(tt.flags)
require.NoError(t, cmd.Execute())
}
t.Run(tt.name, fn)
}
})
}