pulumi/cmd/util.go
Joe Duffy 6dc16a5548
Make cloud authentication more intuitive (#738)
The prior behavior with cloud authentication was a bit confusing
when authenticating against anything but https://pulumi.com/.  This
change fixes a few aspects of this:

* Improve error messages to differentiate between "authentication
  failed" and "you haven't logged into the target cloud URL."

* Default to the cloud you're currently authenticated with, rather
  than unconditionally selecting https://pulumi.com/.  This ensures

      $ pulumi login -c https://api.moolumi.io
      $ pulumi stack ls

  works, versus what was currently required

      $ pulumi login -c https://api.moolumi.io
      $ pulumi stack ls -c https://api.moolumi.io

  with confusing error messages if you forgot the second -c.

* To do this, our default cloud logic changes to

    1) Prefer the explicit -c if supplied;

    2) Otherwise, pick the "currently authenticated" cloud; this is
       the last cloud to have been targeted with pulumi login, or
       otherwise the single cloud in the list if there is only one;

    3) https://pulumi.com/ otherwise.
2017-12-16 07:49:41 -08:00

147 lines
4.1 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cmd
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/pkg/errors"
git "gopkg.in/src-d/go-git.v4"
"github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/backend/cloud"
"github.com/pulumi/pulumi/pkg/backend/local"
"github.com/pulumi/pulumi/pkg/backend/state"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/util/fsutil"
)
// allBackends returns all known backends. The boolean is true if any are cloud backends.
func allBackends() ([]backend.Backend, bool) {
// Add all the known backends to the list and query them all. We always use the local backend,
// in addition to all of those cloud backends we are currently logged into.
d := cmdutil.Diag()
backends := []backend.Backend{local.New(d)}
cloudBackends, _, err := cloud.CurrentBackends(d)
if err != nil {
// Print the error, but keep going so that the local operations still occur.
_, fmterr := fmt.Fprintf(os.Stderr, "error: could not obtain current cloud backends: %v", err)
contract.IgnoreError(fmterr)
} else {
for _, be := range cloudBackends {
backends = append(backends, be)
}
}
return backends, len(cloudBackends) > 0
}
func requireStack(stackName tokens.QName) (backend.Stack, error) {
if stackName == "" {
return requireCurrentStack()
}
bes, _ := allBackends()
stack, err := state.Stack(stackName, bes)
if err != nil {
return nil, err
} else if stack == nil {
return nil, errors.Errorf("no stack named '%s' found; double check that you're logged in", stackName)
}
return stack, nil
}
func requireCurrentStack() (backend.Stack, error) {
bes, _ := allBackends()
stack, err := state.CurrentStack(bes)
if err != nil {
return nil, err
} else if stack == nil {
return nil, errors.New("no current stack detected; please use `pulumi stack` to `init` or `select` one")
}
return stack, nil
}
func detectOwnerAndName(dir string) (string, string, error) {
owner, repo, err := getGitHubProjectForOrigin(dir)
if err == nil {
return owner, repo, nil
}
user, err := user.Current()
if err != nil {
return "", "", err
}
return user.Username, filepath.Base(dir), nil
}
func getGitHubProjectForOrigin(dir string) (string, string, error) {
gitRoot, err := fsutil.WalkUp(dir, func(s string) bool { return filepath.Base(s) == ".git" }, nil)
if err != nil {
return "", "", errors.Wrap(err, "could not detect git repository")
}
if gitRoot == "" {
return "", "", errors.Errorf("could not locate git repository starting at: %s", dir)
}
repo, err := git.NewFilesystemRepository(gitRoot)
if err != nil {
return "", "", err
}
remote, err := repo.Remote("origin")
if err != nil {
return "", "", errors.Wrap(err, "could not read origin information")
}
remoteURL := remote.Config().URL
project := ""
const GitHubSSHPrefix = "git@github.com:"
const GitHubHTTPSPrefix = "https://github.com/"
const GitHubRepositorySuffix = ".git"
if strings.HasPrefix(remoteURL, GitHubSSHPrefix) {
project = trimGitRemoteURL(remoteURL, GitHubSSHPrefix, GitHubRepositorySuffix)
} else if strings.HasPrefix(remoteURL, GitHubHTTPSPrefix) {
project = trimGitRemoteURL(remoteURL, GitHubHTTPSPrefix, GitHubRepositorySuffix)
}
split := strings.Split(project, "/")
if len(split) != 2 {
return "", "", errors.Errorf("could not detect GitHub project from url: %v", remote)
}
return split[0], split[1], nil
}
func trimGitRemoteURL(url string, prefix string, suffix string) string {
return strings.TrimSuffix(strings.TrimPrefix(url, prefix), suffix)
}
func GetColor(debug bool, color string) (diag.Color, error) {
switch color {
case "auto":
if debug {
// we will use color so long as we're not spewing to debug (which is colorless).
return diag.Never, nil
}
return diag.Always, nil
case "always":
return diag.Always, nil
case "never":
return diag.Never, nil
case "raw":
return diag.Raw, nil
}
return diag.Never, fmt.Errorf("unsupported color option: '%s'. Supported values are: auto, always, never, raw", color)
}