229 lines
6.3 KiB
Go
229 lines
6.3 KiB
Go
package setup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/influxdata/influx-cli/v2/api"
|
|
"github.com/influxdata/influx-cli/v2/clients"
|
|
"github.com/influxdata/influx-cli/v2/clients/bucket"
|
|
"github.com/influxdata/influx-cli/v2/config"
|
|
"github.com/influxdata/influx-cli/v2/pkg/duration"
|
|
"github.com/influxdata/influx-cli/v2/pkg/stdio"
|
|
)
|
|
|
|
var (
|
|
ErrAlreadySetUp = errors.New("instance has already been set up")
|
|
ErrConfigNameRequired = errors.New("config name is required if you already have existing configs")
|
|
ErrSetupCanceled = errors.New("setup was canceled")
|
|
)
|
|
|
|
type Client struct {
|
|
clients.CLI
|
|
api.SetupApi
|
|
}
|
|
|
|
type Params struct {
|
|
Username string
|
|
Password string
|
|
AuthToken string
|
|
Org string
|
|
Bucket string
|
|
Retention string
|
|
Force bool
|
|
ConfigName string
|
|
Host string
|
|
}
|
|
|
|
func (c Client) Setup(ctx context.Context, params *Params) error {
|
|
// Check if setup is even allowed.
|
|
checkResp, err := c.GetSetup(ctx).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check if already set up: %w", err)
|
|
}
|
|
if checkResp.Allowed == nil || !*checkResp.Allowed {
|
|
return ErrAlreadySetUp
|
|
}
|
|
|
|
// Ensure we'll be able to write onboarding results to local config.
|
|
// Do this so we catch any problems before modifying state on the server side.
|
|
if err := c.validateNoNameCollision(params.ConfigName); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Initialize the server.
|
|
setupBody, err := c.OnboardingRequest(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := c.PostSetup(ctx).OnboardingRequest(setupBody).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to setup instance: %w", err)
|
|
}
|
|
|
|
cfg := config.Config{
|
|
Name: config.DefaultConfig.Name,
|
|
Host: config.DefaultConfig.Host,
|
|
Token: *resp.Auth.Token,
|
|
Org: resp.Org.Name,
|
|
Active: true,
|
|
}
|
|
if params.ConfigName != "" {
|
|
cfg.Name = params.ConfigName
|
|
}
|
|
if params.Host != "" {
|
|
cfg.Host = params.Host
|
|
}
|
|
|
|
if _, err := c.ConfigService.CreateConfig(cfg); err != nil {
|
|
return fmt.Errorf("setup succeeded, but failed to write new config to local path: %w", err)
|
|
}
|
|
|
|
if c.PrintAsJSON {
|
|
return c.PrintJSON(map[string]interface{}{
|
|
"user": resp.User.Name,
|
|
"organization": resp.Org.Name,
|
|
"bucket": resp.Bucket.Name,
|
|
})
|
|
}
|
|
|
|
return c.PrintTable([]string{"User", "Organization", "Bucket"}, map[string]interface{}{
|
|
"User": resp.User.Name,
|
|
"Organization": resp.Org.Name,
|
|
"Bucket": resp.Bucket.Name,
|
|
})
|
|
}
|
|
|
|
// validateNoNameCollision checks that we will be able to write onboarding results to local config:
|
|
// - If a custom name was given, check that it doesn't collide with existing config
|
|
// - If no custom name was given, check that we don't already have configs
|
|
func (c Client) validateNoNameCollision(configName string) error {
|
|
existingConfigs, err := c.ConfigService.ListConfigs()
|
|
if err != nil {
|
|
return fmt.Errorf("error checking existing configs: %w", err)
|
|
}
|
|
if len(existingConfigs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// If there are existing configs then require that a name be
|
|
// specified in order to distinguish this new config from what's
|
|
// there already.
|
|
if configName == "" {
|
|
return ErrConfigNameRequired
|
|
}
|
|
if _, ok := existingConfigs[configName]; ok {
|
|
return fmt.Errorf("config name %q already exists", configName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// OnboardingRequest constructs a request body for the onboarding API.
|
|
// Unless the 'force' parameter is set, the user will be prompted to enter any missing information
|
|
// and to confirm the final request parameters.
|
|
func (c Client) OnboardingRequest(params *Params) (req api.OnboardingRequest, err error) {
|
|
if (params.Force || params.Password != "") && len(params.Password) < stdio.MinPasswordLen {
|
|
return req, clients.ErrPasswordIsTooShort
|
|
}
|
|
|
|
// Populate the request with CLI args.
|
|
req.Username = params.Username
|
|
req.Org = params.Org
|
|
req.Bucket = params.Bucket
|
|
if params.Password != "" {
|
|
req.Password = ¶ms.Password
|
|
}
|
|
if params.AuthToken != "" {
|
|
req.Token = ¶ms.AuthToken
|
|
}
|
|
rpSecs := int64(bucket.InfiniteRetention)
|
|
if params.Retention != "" {
|
|
dur, err := duration.RawDurationToTimeDuration(params.Retention)
|
|
if err != nil {
|
|
return req, fmt.Errorf("failed to parse %q: %w", params.Retention, err)
|
|
}
|
|
secs, nanos := math.Modf(dur.Seconds())
|
|
if nanos > 0 {
|
|
return req, fmt.Errorf("retention policy %q is too precise, must be divisible by 1s", params.Retention)
|
|
}
|
|
rpSecs = int64(secs)
|
|
}
|
|
req.RetentionPeriodSeconds = &rpSecs
|
|
|
|
if params.Force {
|
|
return req, nil
|
|
}
|
|
|
|
// Ask the user for any missing information.
|
|
if err := c.StdIO.Banner("Welcome to InfluxDB 2.0!"); err != nil {
|
|
return req, err
|
|
}
|
|
if params.Username == "" {
|
|
req.Username, err = c.StdIO.GetStringInput("Please type your primary username", "")
|
|
if err != nil {
|
|
return req, err
|
|
}
|
|
}
|
|
if params.Password == "" {
|
|
pass, err := c.StdIO.GetPassword("Please type your password")
|
|
if err != nil {
|
|
return req, err
|
|
}
|
|
req.Password = &pass
|
|
}
|
|
if params.Org == "" {
|
|
req.Org, err = c.StdIO.GetStringInput("Please type your primary organization name", "")
|
|
if err != nil {
|
|
return req, err
|
|
}
|
|
}
|
|
if params.Bucket == "" {
|
|
req.Bucket, err = c.StdIO.GetStringInput("Please type your primary bucket name", "")
|
|
if err != nil {
|
|
return req, err
|
|
}
|
|
}
|
|
if params.Retention == "" {
|
|
infiniteStr := strconv.Itoa(bucket.InfiniteRetention)
|
|
for {
|
|
rpStr, err := c.StdIO.GetStringInput("Please type your retention period in hours, or 0 for infinite", infiniteStr)
|
|
if err != nil {
|
|
return req, err
|
|
}
|
|
rp, err := strconv.Atoi(rpStr)
|
|
if err != nil {
|
|
return req, err
|
|
}
|
|
if rp >= 0 {
|
|
rpSeconds := int64((time.Duration(rp) * time.Hour).Seconds())
|
|
req.RetentionPeriodSeconds = &rpSeconds
|
|
break
|
|
} else if err := c.StdIO.Error("Retention period cannot be negative"); err != nil {
|
|
return req, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if confirmed := c.StdIO.GetConfirm(func() string {
|
|
rp := "infinite"
|
|
if req.RetentionPeriodSeconds != nil && *req.RetentionPeriodSeconds > 0 {
|
|
rp = (time.Duration(*req.RetentionPeriodSeconds) * time.Second).String()
|
|
}
|
|
return fmt.Sprintf(`Setup with these parameters?
|
|
Username: %s
|
|
Organization: %s
|
|
Bucket: %s
|
|
Retention Period: %s
|
|
`, req.Username, req.Org, req.Bucket, rp)
|
|
}()); !confirmed {
|
|
return api.OnboardingRequest{}, ErrSetupCanceled
|
|
}
|
|
|
|
return req, nil
|
|
}
|