mirror of https://github.com/pulumi/pulumi.git
193 lines
5.4 KiB
Go
193 lines
5.4 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/colors"
|
|
"github.com/pulumi/pulumi/pkg/pack"
|
|
"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"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// readPackage attempts to detect and read the package for the current workspace. If an error occurs, it will be
|
|
// printed to Stderr, and the returned value will be nil. If the package is successfully detected and read, it
|
|
// is returned along with the path to its containing directory, which will be used as the root of the package's
|
|
// Pulumi program.
|
|
func readPackage() (*pack.Package, string, error) {
|
|
pwd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
// Now that we got here, we have a path, so we will try to load it.
|
|
pkgpath, err := workspace.DetectPackageFrom(pwd)
|
|
if err != nil {
|
|
return nil, "", errors.Errorf("could not locate a package to load: %v", err)
|
|
} else if pkgpath == "" {
|
|
return nil, "", errors.Errorf("could not find Pulumi.yaml (searching upwards from %s)", pwd)
|
|
}
|
|
pkg, err := pack.Load(pkgpath)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return pkg, filepath.Dir(pkgpath), nil
|
|
}
|
|
|
|
type colorFlag struct {
|
|
value colors.Colorization
|
|
}
|
|
|
|
func (cf *colorFlag) String() string {
|
|
return string(cf.Colorization())
|
|
}
|
|
|
|
func (cf *colorFlag) Set(value string) error {
|
|
switch value {
|
|
case "always":
|
|
cf.value = colors.Always
|
|
case "never":
|
|
cf.value = colors.Never
|
|
case "raw":
|
|
cf.value = colors.Raw
|
|
// Backwards compat for old flag values.
|
|
case "auto":
|
|
cf.value = colors.Always
|
|
default:
|
|
return errors.Errorf("unsupported color option: '%s'. Supported values are: always, never, raw", value)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cf *colorFlag) Type() string {
|
|
return "colors.Colorization"
|
|
}
|
|
|
|
func (cf *colorFlag) Colorization() colors.Colorization {
|
|
if cf.value == "" {
|
|
return colors.Always
|
|
}
|
|
|
|
return cf.value
|
|
}
|