influxdb/cmd/influx/setup.go

262 lines
6.2 KiB
Go

package main
import (
"context"
"fmt"
"os"
"strconv"
"strings"
platform "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/cmd/influx/internal"
"github.com/influxdata/influxdb/http"
"github.com/spf13/cobra"
"github.com/tcnksm/go-input"
)
// setup Command
var setupCmd = &cobra.Command{
Use: "setup",
Short: "Setup instance with initial user, org, bucket",
RunE: wrapErrorFmt(setupF),
}
type SetupFlags struct {
username string
password string
token string
org string
bucket string
retention int
force bool
}
var setupFlags SetupFlags
func init() {
setupCmd.Flags().StringVarP(&setupFlags.username, "username", "u", "", "primary username")
setupCmd.Flags().StringVarP(&setupFlags.password, "password", "p", "", "password for username")
setupCmd.Flags().StringVarP(&setupFlags.token, "token", "t", "", "token for username, else auto-generated")
setupCmd.Flags().StringVarP(&setupFlags.org, "org", "o", "", "primary organization name")
setupCmd.Flags().StringVarP(&setupFlags.bucket, "bucket", "b", "", "primary bucket name")
setupCmd.Flags().IntVarP(&setupFlags.retention, "retention", "r", -1, "retention period in hours, else infinite")
setupCmd.Flags().BoolVarP(&setupFlags.force, "force", "f", false, "skip confirmation prompt")
}
func setupF(cmd *cobra.Command, args []string) error {
if flags.local {
return fmt.Errorf("local flag not supported for setup command")
}
// check if setup is allowed
s := &http.SetupService{
Addr: flags.host,
}
allowed, err := s.IsOnboarding(context.Background())
if err != nil {
return fmt.Errorf("failed to determine if instance has been configured: %v", err)
}
if !allowed {
return fmt.Errorf("instance at %q has already been setup", flags.host)
}
req, err := getOnboardingRequest()
if err != nil {
return fmt.Errorf("failed to retrieve data to setup instance: %v", err)
}
result, err := s.Generate(context.Background(), req)
if err != nil {
return fmt.Errorf("failed to setup instance: %v", err)
}
writeTokenToPath(result.Auth.Token, defaultTokenPath())
fmt.Println(promptWithColor("Your token has been stored in "+defaultTokenPath()+".", colorCyan))
w := internal.NewTabWriter(os.Stdout)
w.WriteHeaders(
"User",
"Organization",
"Bucket",
)
w.Write(map[string]interface{}{
"User": result.User.Name,
"Organization": result.Org.Name,
"Bucket": result.Bucket.Name,
})
w.Flush()
return nil
}
func getOnboardingRequest() (req *platform.OnboardingRequest, err error) {
ui := &input.UI{
Writer: os.Stdout,
Reader: os.Stdin,
}
req = new(platform.OnboardingRequest)
fmt.Println(promptWithColor(`Welcome to InfluxDB 2.0!`, colorYellow))
if setupFlags.username != "" {
req.User = setupFlags.username
} else {
req.User = getInput(ui, "Please type your primary username", "")
}
if setupFlags.password != "" {
req.Password = setupFlags.password
} else {
req.Password = getPassword(ui)
}
if setupFlags.token != "" {
req.Token = setupFlags.token
// else auto-generated by service
}
if setupFlags.org != "" {
req.Org = setupFlags.org
} else {
req.Org = getInput(ui, "Please type your primary organization name", "")
}
if setupFlags.bucket != "" {
req.Bucket = setupFlags.bucket
} else {
req.Bucket = getInput(ui, "Please type your primary bucket name", "")
}
if setupFlags.retention >= 0 {
req.RetentionPeriod = uint(setupFlags.retention)
} else {
for {
rpStr := getInput(ui, "Please type your retention period in hours.\r\nOr press ENTER for infinite.", strconv.Itoa(platform.InfiniteRetention))
rp, err := strconv.Atoi(rpStr)
if rp >= 0 && err == nil {
req.RetentionPeriod = uint(rp)
break
}
}
}
if !setupFlags.force {
if confirmed := getConfirm(ui, req); !confirmed {
return nil, fmt.Errorf("setup was canceled")
}
}
return req, nil
}
// vt100EscapeCodes
var (
keyEscape = byte(27)
colorRed = []byte{keyEscape, '[', '3', '1', 'm'}
colorYellow = []byte{keyEscape, '[', '3', '3', 'm'}
colorCyan = []byte{keyEscape, '[', '3', '6', 'm'}
keyReset = []byte{keyEscape, '[', '0', 'm'}
)
func promptWithColor(s string, color []byte) string {
return string(color) + s + string(keyReset)
}
func getConfirm(ui *input.UI, or *platform.OnboardingRequest) bool {
prompt := promptWithColor("Confirm? (y/n)", colorRed)
for {
rp := "infinite"
if or.RetentionPeriod > 0 {
rp = fmt.Sprintf("%d hrs", or.RetentionPeriod)
}
fmt.Print(promptWithColor(fmt.Sprintf(`
You have entered:
Username: %s
Organization: %s
Bucket: %s
Retention Period: %s
`, or.User, or.Org, or.Bucket, rp), colorCyan))
result, err := ui.Ask(prompt, &input.Options{
HideOrder: true,
})
if err != nil {
return false
}
switch result {
case "y":
return true
case "n":
return false
default:
continue
}
}
}
var errPasswordIsNotMatch = fmt.Errorf("passwords do not match")
func getPassword(ui *input.UI) (password string) {
var err error
enterPasswd:
query := promptWithColor("Please type your password", colorCyan)
for {
password, err = ui.Ask(query, &input.Options{
Required: true,
HideOrder: true,
Hide: true,
})
switch err {
case input.ErrInterrupted:
os.Exit(1)
default:
if password = strings.TrimSpace(password); password == "" {
continue
}
}
break
}
query = promptWithColor("Please type your password again", colorCyan)
for {
_, err = ui.Ask(query, &input.Options{
Required: true,
HideOrder: true,
Hide: true,
ValidateFunc: func(s string) error {
if s != password {
return errPasswordIsNotMatch
}
return nil
},
})
switch err {
case input.ErrInterrupted:
os.Exit(1)
case nil:
// Nothing.
default:
fmt.Println(promptWithColor("Passwords do not match!", colorRed))
goto enterPasswd
}
break
}
return password
}
func getInput(ui *input.UI, prompt, defaultValue string) string {
option := &input.Options{
Required: true,
HideOrder: true,
}
if defaultValue != "" {
option.Default = defaultValue
option.HideDefault = true
}
prompt = promptWithColor(prompt, colorCyan)
for {
line, err := ui.Ask(prompt, option)
switch err {
case input.ErrInterrupted:
os.Exit(1)
default:
if line = strings.TrimSpace(line); line == "" {
continue
}
return line
}
}
}