Skip to content

Commit

Permalink
Allow to predefine login actions
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasmik committed Jul 28, 2023
1 parent d3eec79 commit 70bb742
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 52 deletions.
46 changes: 45 additions & 1 deletion internal/cmd/profile/flags.go
Original file line number Diff line number Diff line change
@@ -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{
Expand All @@ -21,3 +27,41 @@ var flagBindPort = &cli.IntFlag{
Destination: &bindPort,
EnvVars: []string{"SPACECTL_BIND_PORT"},
}

const (
methodBrowser = "browser"
methodAPI = "api"
methodGithub = "github"
)

var methodToCrendentialsType = 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"},
}
2 changes: 1 addition & 1 deletion internal/cmd/profile/get_alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var (
)

func getAlias(cliCtx *cli.Context) error {
if nArgs := cliCtx.NArg(); nArgs != 1 {
if nArgs := cliCtx.NArg(); nArgs == 0 {
return fmt.Errorf("expecting profile alias as the only argument, got %d instead", nArgs)
}

Expand Down
116 changes: 66 additions & 50 deletions internal/cmd/profile/login_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -38,8 +38,10 @@ func loginCommand() *cli.Command {
ArgsUsage: "<account-alias>",
Action: loginAction,
Flags: []cli.Flag{
flagMethod,
flagBindHost,
flagBindPort,
flagEndpoint,
},
}
}
Expand All @@ -58,75 +60,89 @@ 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 := methodToCrendentialsType[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,
HideHelp: true,
}
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 {
Expand Down

0 comments on commit 70bb742

Please sign in to comment.