From 6d393ab95c450be2263c953f37d6f918f13558c9 Mon Sep 17 00:00:00 2001 From: Tomas <40318863+tomasmik@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:05:43 +0300 Subject: [PATCH] Allow to predefine login actions (#165) --- internal/cmd/profile/flags.go | 46 ++++++++++- internal/cmd/profile/login_command.go | 115 +++++++++++++++----------- 2 files changed, 110 insertions(+), 51 deletions(-) diff --git a/internal/cmd/profile/flags.go b/internal/cmd/profile/flags.go index cc0b992..e2409f9 100644 --- a/internal/cmd/profile/flags.go +++ b/internal/cmd/profile/flags.go @@ -1,6 +1,12 @@ package profile -import "github.com/urfave/cli/v2" +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/spacelift-io/spacectl/client/session" +) var bindHost string var flagBindHost = &cli.StringFlag{ @@ -21,3 +27,41 @@ var flagBindPort = &cli.IntFlag{ Destination: &bindPort, EnvVars: []string{"SPACECTL_BIND_PORT"}, } + +const ( + methodBrowser = "browser" + methodAPI = "api" + methodGithub = "github" +) + +var methodToCredentialsType = map[string]session.CredentialsType{ + methodGithub: session.CredentialsTypeGitHubToken, + methodAPI: session.CredentialsTypeAPIKey, + methodBrowser: session.CredentialsTypeAPIToken, +} + +var flagMethod = &cli.StringFlag{ + Name: "method", + Usage: "[Optional] the method to use for logging in to Spacelift", + Required: false, + EnvVars: []string{"SPACECTL_LOGIN_METHOD"}, + Action: func(ctx *cli.Context, v string) error { + if v == "" { + return nil + } + + switch v { + case methodBrowser, methodAPI, methodGithub: + return nil + default: + return fmt.Errorf("flag 'method' was provided an invalid value, possible values: %s, %s %s", methodBrowser, methodAPI, methodGithub) + } + }, +} + +var flagEndpoint = &cli.StringFlag{ + Name: "endpoint", + Usage: "[Optional] the endpoint to use for logging in to Spacelift", + Required: false, + EnvVars: []string{"SPACECTL_LOGIN_ENDPOINT"}, +} diff --git a/internal/cmd/profile/login_command.go b/internal/cmd/profile/login_command.go index 93b0241..945098a 100644 --- a/internal/cmd/profile/login_command.go +++ b/internal/cmd/profile/login_command.go @@ -10,11 +10,11 @@ import ( "net/http" "net/url" "os" - "strconv" "strings" "syscall" "time" + "github.com/manifoldco/promptui" "github.com/pkg/browser" "github.com/pkg/errors" "github.com/urfave/cli/v2" @@ -38,8 +38,10 @@ func loginCommand() *cli.Command { ArgsUsage: "", Action: loginAction, Flags: []cli.Flag{ + flagMethod, flagBindHost, flagBindPort, + flagEndpoint, }, } } @@ -58,75 +60,88 @@ func loginAction(ctx *cli.Context) error { reader := bufio.NewReader(os.Stdin) - fmt.Print("Enter Spacelift endpoint (eg. https://unicorn.app.spacelift.io/): ") + endpoint, err := readEndpoint(ctx, reader) + if err != nil { + return err + } + storedCredentials.Endpoint = endpoint - endpoint, err := reader.ReadString('\n') + credentialsType, err := getCredentialsType(ctx) if err != nil { - return fmt.Errorf("could not read Spacelift endpoint: %w", err) + return err } - endpoint = strings.TrimSpace(endpoint) + storedCredentials.Type = credentialsType - if endpoint == "" { - return errors.New("Spacelift endpoint cannot be empty") + switch storedCredentials.Type { + case session.CredentialsTypeAPIKey: + if err := loginUsingAPIKey(reader, &storedCredentials); err != nil { + return err + } + case session.CredentialsTypeGitHubToken: + if err := loginUsingGitHubAccessToken(&storedCredentials); err != nil { + return err + } + case session.CredentialsTypeAPIToken: + return loginUsingWebBrowser(ctx, &storedCredentials) + default: + return fmt.Errorf("invalid selection (%s), please try again", storedCredentials.Type) } - url, err := url.ParseRequestURI(endpoint) - if err != nil { - return fmt.Errorf("invalid Spacelift endpoint: %w", err) + // Check if the credentials are valid before we try persisting them. + if _, err := storedCredentials.Session(session.Defaults()); err != nil { + return fmt.Errorf("credentials look invalid: %w", err) } - if url.Scheme == "" || url.Host == "" { - return fmt.Errorf("scheme and host must be valid: parsed scheme %q and host %q", url.Scheme, url.Host) + + return persistAccessCredentials(&storedCredentials) +} + +func getCredentialsType(ctx *cli.Context) (session.CredentialsType, error) { + if ctx.IsSet(flagMethod.Name) { + got := methodToCredentialsType[ctx.String(flagMethod.Name)] + return session.CredentialsType(got), nil } - storedCredentials.Endpoint = endpoint + prompt := promptui.Select{ + Label: "Select authentication flow:", + Items: []string{"API key", "GitHub access token", "Login with a web browser"}, + Size: 3, + } + result, _, err := prompt.Run() + if err != nil { + return 0, err + } -Loop: - for { - fmt.Printf( - "Select authentication flow: \n %d) for API key,\n %d) for GitHub access token,\n %d) for login with a web browser\nOption: ", - session.CredentialsTypeAPIKey, - session.CredentialsTypeGitHubToken, - session.CredentialsTypeAPIToken, - ) + return session.CredentialsType(result + 1), nil +} - credentialsType, err := reader.ReadString('\n') - if err != nil { - return fmt.Errorf("could not read Spacelift credentials type: %w", err) - } +func readEndpoint(ctx *cli.Context, reader *bufio.Reader) (string, error) { + var endpoint string + if ctx.IsSet(flagEndpoint.Name) { + endpoint = ctx.String(flagEndpoint.Name) + } else { + fmt.Print("Enter Spacelift endpoint (eg. https://unicorn.app.spacelift.io/): ") - typeNum, err := strconv.Atoi(strings.TrimSpace(credentialsType)) + var err error + endpoint, err = reader.ReadString('\n') if err != nil { - fmt.Printf("Invalid selection (%s), please try again", credentialsType) - continue + return "", fmt.Errorf("could not read Spacelift endpoint: %w", err) } - storedCredentials.Type = session.CredentialsType(typeNum) - - switch storedCredentials.Type { - case session.CredentialsTypeAPIKey: - if err := loginUsingAPIKey(reader, &storedCredentials); err != nil { - return err - } - break Loop - case session.CredentialsTypeGitHubToken: - if err := loginUsingGitHubAccessToken(&storedCredentials); err != nil { - return err - } - break Loop - case session.CredentialsTypeAPIToken: - return loginUsingWebBrowser(ctx, &storedCredentials) - default: - fmt.Printf("Invalid selection (%s), please try again", credentialsType) - continue + endpoint = strings.TrimSpace(endpoint) + if endpoint == "" { + return "", errors.New("Spacelift endpoint cannot be empty") } } - // Check if the credentials are valid before we try persisting them. - if _, err := storedCredentials.Session(session.Defaults()); err != nil { - return fmt.Errorf("credentials look invalid: %w", err) + url, err := url.ParseRequestURI(endpoint) + if err != nil { + return "", fmt.Errorf("invalid Spacelift endpoint: %w", err) + } + if url.Scheme == "" || url.Host == "" { + return "", fmt.Errorf("scheme and host must be valid: parsed scheme %q and host %q", url.Scheme, url.Host) } - return persistAccessCredentials(&storedCredentials) + return endpoint, nil } func loginUsingAPIKey(reader *bufio.Reader, creds *session.StoredCredentials) error {