From 3addadc12aa4c6ef24bb805c564739959491eac0 Mon Sep 17 00:00:00 2001 From: Kelvin Wang Date: Tue, 2 Oct 2018 13:21:36 -0400 Subject: [PATCH] add setup service --- cmd/influx/main.go | 1 + cmd/influx/setup.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ http/onboarding.go | 99 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 cmd/influx/setup.go diff --git a/cmd/influx/main.go b/cmd/influx/main.go index b7de557973..71a9953d2b 100644 --- a/cmd/influx/main.go +++ b/cmd/influx/main.go @@ -25,6 +25,7 @@ func init() { influxCmd.AddCommand(queryCmd) influxCmd.AddCommand(organizationCmd) influxCmd.AddCommand(userCmd) + influxCmd.AddCommand(setupCmd) } // Flags contains all the CLI flag values for influx. diff --git a/cmd/influx/setup.go b/cmd/influx/setup.go new file mode 100644 index 0000000000..a274894f06 --- /dev/null +++ b/cmd/influx/setup.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/influxdata/platform" + "github.com/influxdata/platform/cmd/influx/internal" + "github.com/influxdata/platform/http" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" +) + +// setup Command +var setupCmd = &cobra.Command{ + Use: "setup", + Short: "Create default username, password, org, bucket...", + Run: setupF, +} + +func setupF(cmd *cobra.Command, args []string) { + // check if setup is allowed + s := &http.SetupService{ + Addr: flags.host, + } + + allowed, err := s.IsOnboarding(context.Background()) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if !allowed { + fmt.Println("Initialization has been already completed") + os.Exit(1) + } + + or := new(platform.OnboardingRequest) + + fmt.Println("Welcome to influxdata platform!") + or.User = getInput("Please type your primary username.\r\nOr ENTER to use \"admin\":", "admin", false) + or.Password = getInput("Please type your password:", "", true) + or.Org = getInput("Please type your primary organization name.\r\nOr ENTER to use \"default\":", "default", false) + or.Bucket = getInput("Please type your primary bucket name.\r\nOr ENTER to use \"default\":", "default", false) + fmt.Println(or) + + result, err := s.Generate(context.Background(), or) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + w := internal.NewTabWriter(os.Stdout) + w.WriteHeaders( + "UserID", + "UserName", + "Organization", + "Bucket", + "Token", + ) + w.Write(map[string]interface{}{ + "UserID": result.User.ID.String(), + "UserName": result.User.Name, + "Organization": result.Org.Name, + "Bucket": result.Bucket.Name, + "Token": result.Auth.Token, + }) + w.Flush() +} + +func getInput(prompt, defaultValue string, isPassword bool) string { + var line string + oldState, err := terminal.MakeRaw(0) + if err != nil { + return "" + } + defer terminal.Restore(0, oldState) + term := terminal.NewTerminal(struct { + io.Reader + io.Writer + }{os.Stdin, os.Stdout}, "") + prompt = string(term.Escape.Cyan) + prompt + " " + string(term.Escape.Reset) + if isPassword { + for { + line, _ = term.ReadPassword(prompt) + if line == "" { + continue + } + return line + } + } + term.SetPrompt(prompt) + if line, _ = term.ReadLine(); line == "" { + line = defaultValue + } + return line +} diff --git a/http/onboarding.go b/http/onboarding.go index f8736ae889..2fb5071a14 100644 --- a/http/onboarding.go +++ b/http/onboarding.go @@ -1,9 +1,9 @@ package http import ( + "bytes" "context" "encoding/json" - "fmt" "net/http" "github.com/influxdata/platform" @@ -17,18 +17,25 @@ type SetupHandler struct { OnboardingService platform.OnboardingService } +const ( + setupPath = "/api/v2/setup" +) + // NewSetupHandler returns a new instance of SetupHandler. func NewSetupHandler() *SetupHandler { h := &SetupHandler{ Router: httprouter.New(), } - h.HandlerFunc("POST", "/api/v2/setup", h.handlePostSetup) - h.HandlerFunc("GET", "/api/v2/setup", h.isOnboarding) + h.HandlerFunc("POST", setupPath, h.handlePostSetup) + h.HandlerFunc("GET", setupPath, h.isOnboarding) return h } -// isOnboarding is the HTTP handler for the GET /setup route. -// returns true/false +type isOnboardingResponse struct { + Allowed bool `json:"allowed"` +} + +// isOnboarding is the HTTP handler for the GET /api/v2/setup route. func (h *SetupHandler) isOnboarding(w http.ResponseWriter, r *http.Request) { ctx := r.Context() result, err := h.OnboardingService.IsOnboarding(ctx) @@ -36,10 +43,13 @@ func (h *SetupHandler) isOnboarding(w http.ResponseWriter, r *http.Request) { EncodeError(ctx, err, w) return } - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{"allowed": %v}`, result) + if err := encodeResponse(ctx, w, http.StatusOK, isOnboardingResponse{result}); err != nil { + EncodeError(ctx, err, w) + return + } } +// isOnboarding is the HTTP handler for the POST /api/v2/setup route. func (h *SetupHandler) handlePostSetup(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -83,3 +93,78 @@ func decodePostSetupRequest(ctx context.Context, r *http.Request) (*platform.Onb return req, nil } + +// SetupService connects to Influx via HTTP to perform onboarding operations +type SetupService struct { + Addr string + InsecureSkipVerify bool +} + +// IsOnboarding determine if onboarding request is allowed. +func (s *SetupService) IsOnboarding(ctx context.Context) (bool, error) { + u, err := newURL(s.Addr, setupPath) + if err != nil { + return false, err + } + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return false, err + } + hc := newClient(u.Scheme, s.InsecureSkipVerify) + resp, err := hc.Do(req) + if err != nil { + return false, err + } + if err := CheckError(resp); err != nil { + return false, err + } + defer resp.Body.Close() + var ir isOnboardingResponse + if err := json.NewDecoder(resp.Body).Decode(&ir); err != nil { + return false, err + } + return ir.Allowed, nil +} + +// Generate OnboardingResults. +func (s *SetupService) Generate(ctx context.Context, or *platform.OnboardingRequest) (*platform.OnboardingResults, error) { + u, err := newURL(s.Addr, setupPath) + if err != nil { + return nil, err + } + octets, err := json.Marshal(or) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets)) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + hc := newClient(u.Scheme, s.InsecureSkipVerify) + + resp, err := hc.Do(req) + if err != nil { + return nil, err + } + // TODO(jsternberg): Should this check for a 201 explicitly? + if err := CheckError(resp); err != nil { + return nil, err + } + + var oResp onboardingResponse + if err := json.NewDecoder(resp.Body).Decode(&oResp); err != nil { + return nil, err + } + + return &platform.OnboardingResults{ + User: &oResp.User.User, + Auth: &oResp.Auth.Authorization, + Org: &oResp.Organization.Organization, + Bucket: &platform.Bucket{ + ID: oResp.Bucket.ID, + Name: oResp.Bucket.Name, + }, + }, nil +}