pulumi/cmd/login.go
Chris Smith d5846d7e16 Add login and logout commands. (#437)
This PR adds `login` and `logout` commands to the `pulumi` CLI.

Rather than requiring a user name and password like before, we instead require users to login with GitHub credentials on the Pulumi Console website. (You can do this now via https://beta.moolumi.io.) Once there, the account page will show you an "access token" you can use to authenticate against the CLI.

Upon successful login, the user's credentials will be stored in `~/.pulumi/credentials.json`. This credentials file will be automatically read with the credentials added to every call to `PulumiRESTCall`.
2017-10-19 15:22:07 -07:00

93 lines
2.6 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
)
func newLoginCmd() *cobra.Command {
return &cobra.Command{
Use: "login",
Short: "Log into the Pulumi Cloud Console",
Long: "Log into the Pulumi Cloud Console. You can script by using PULUMI_ACCESS_TOKEN environment variable.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
return loginCmd()
}),
}
}
func newLogoutCmd() *cobra.Command {
return &cobra.Command{
Use: "logout",
Short: "Log out of the Pulumi CLI",
Long: "Log out of the Pulumi CLI. Deletes stored credentials on the local machine.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
return deleteStoredCredentials()
}),
}
}
// loginCmd is the implementation of the login command.
func loginCmd() error {
// Check if the the user is already logged in.
_, err := getStoredCredentials()
if err == nil {
return fmt.Errorf("already logged in")
}
// We intentionally don't accept command-line args for the user's access token. Having it in
// .bash_history is not great, and specifying it via flag isn't of much use.
//
// However, if PULUMI_ACCESS_TOKEN is available, we'll use that.
accessToken := os.Getenv("PULUMI_ACCESS_TOKEN")
if accessToken != "" {
fmt.Println("Using access token from PULUMI_ACCESS_TOKEN.")
} else {
fmt.Println("Enter Pulumi access token:")
reader := bufio.NewReader(os.Stdin)
raw, readErr := reader.ReadString('\n')
if readErr != nil {
return fmt.Errorf("reading STDIN: %v", err)
}
accessToken = strings.TrimSpace(raw)
}
// Try and use the credentials to see if they are valid.
valid, err := isValidAccessToken(accessToken)
if err != nil {
return err
}
if !valid {
return fmt.Errorf("invalid access token")
}
// Save them.
creds := accountCredentials{
AccessToken: accessToken,
}
return storeCredentials(creds)
}
// isValidAccessToken tries to use the provided Pulumi access token and returns if it is accepted
// or not. Returns error on any unexpected error.
func isValidAccessToken(accessToken string) (bool, error) {
// Make a request to get the authenticated user. If it returns a successful result, the token
// checks out.
if err := pulumiRESTCallWithAccessToken("GET", "/user", nil, nil, accessToken); err != nil {
if errResp, ok := err.(*apitype.ErrorResponse); ok && errResp.Code == 401 {
return false, nil
}
return false, fmt.Errorf("testing access token: %v", err)
}
return true, nil
}