Have the CLI keep track of the current environment

Previously, the engine was concered with maintaing information about
the currently active environment. Now, the CLI is in charge of
this. As part of this change, the engine can now assume that every
environment has a non empty name (and I've added asserts on the
entrypoints of the engine API to ensure that any consumer of the
engine passes a non empty environment name)
This commit is contained in:
Matt Ellis 2017-10-02 16:37:12 -07:00
parent ae8f2a84c2
commit 93ab134bbb
22 changed files with 161 additions and 135 deletions

View file

@ -20,8 +20,13 @@ func newConfigCmd() *cobra.Command {
Use: "config [<key> [value]]",
Short: "Query, set, replace, or unset configuration values",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
envName, err := explicitOrCurrent(env)
if err != nil {
return err
}
if len(args) == 0 {
return listConfig(env)
return listConfig(envName)
}
key, err := tokens.ParseModuleMember(args[0])
@ -31,12 +36,12 @@ func newConfigCmd() *cobra.Command {
if len(args) == 1 {
if !unset {
return getConfig(env, key)
return getConfig(envName, key)
}
return lumiEngine.DeleteConfig(tokens.QName(env), key)
return lumiEngine.DeleteConfig(envName, key)
}
return lumiEngine.SetConfig(tokens.QName(env), key, args[1])
return lumiEngine.SetConfig(envName, key, args[1])
}),
}
@ -50,8 +55,8 @@ func newConfigCmd() *cobra.Command {
return cmd
}
func listConfig(env string) error {
config, err := lumiEngine.GetConfiguration(tokens.QName(env))
func listConfig(envName tokens.QName) error {
config, err := lumiEngine.GetConfiguration(envName)
if err != nil {
return err
}
@ -71,8 +76,8 @@ func listConfig(env string) error {
return nil
}
func getConfig(env string, key tokens.ModuleMember) error {
config, err := lumiEngine.GetConfiguration(tokens.QName(env))
func getConfig(envName tokens.QName, key tokens.ModuleMember) error {
config, err := lumiEngine.GetConfiguration(envName)
if err != nil {
return err
}
@ -84,6 +89,6 @@ func getConfig(env string, key tokens.ModuleMember) error {
}
}
return errors.Errorf("configuration key '%v' not found for environment '%v'", key, env)
return errors.Errorf("configuration key '%v' not found for environment '%v'", key, envName)
}

View file

@ -4,7 +4,6 @@ package cmd
import (
"github.com/pulumi/pulumi/pkg/engine"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
@ -29,13 +28,14 @@ func newDestroyCmd() *cobra.Command {
"Warning: although old snapshots can be used to recreate an environment, this command\n" +
"is generally irreversable and should be used with great care.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
if env == "" {
env = lumiEngine.GetCurrentEnvName().String()
envName, err := explicitOrCurrent(env)
if err != nil {
return err
}
if preview || yes ||
confirmPrompt("This will permanently destroy all resources in the '%v' environment!", env) {
return lumiEngine.Destroy(tokens.QName(env), engine.DestroyOptions{
confirmPrompt("This will permanently destroy all resources in the '%v' environment!", envName.String()) {
return lumiEngine.Destroy(envName, engine.DestroyOptions{
Package: pkgargFromArgs(args),
DryRun: preview,
Debug: debug,

View file

@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
@ -24,11 +23,16 @@ func newEnvCmd() *cobra.Command {
"Each environment has a configuration and update history associated with it, stored in\n" +
"the workspace, in addition to a full checkpoint of the last known good update.\n",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
envInfo, err := lumiEngine.GetEnvironmentInfo(tokens.QName(""))
envName, err := getCurrentEnv()
if err != nil {
return err
}
config, err := lumiEngine.GetConfiguration(tokens.QName(""))
envInfo, err := lumiEngine.GetEnvironmentInfo(envName)
if err != nil {
return err
}
config, err := lumiEngine.GetConfiguration(envName)
if err != nil {
return err
}

View file

@ -3,12 +3,11 @@
package cmd
import (
"errors"
"fmt"
"os"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
"github.com/spf13/cobra"
@ -29,11 +28,19 @@ func newEnvInitCmd() *cobra.Command {
return errors.New("missing required environment name")
}
if _, staterr := os.Stat(workspace.EnvPath(tokens.QName(args[0]))); staterr == nil {
return fmt.Errorf("environment '%v' already exists", args[0])
envName := tokens.QName(args[0])
if _, err := lumiEngine.GetEnvironmentInfo(envName); err == nil {
return fmt.Errorf("environment '%v' already exists", envName)
}
return lumiEngine.InitEnv(tokens.QName(args[0]))
err := lumiEngine.InitEnv(envName)
if err != nil {
return err
}
return setCurrentEnv(envName, false)
}),
}
}

View file

@ -6,6 +6,8 @@ import (
"fmt"
"strconv"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
@ -16,6 +18,13 @@ func newEnvLsCmd() *cobra.Command {
Use: "ls",
Short: "List all known environments",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
currentEnv, err := getCurrentEnv()
if err != nil {
// If we couldn't figure out the current environment, just don't print the '*' later
// on instead of failing.
currentEnv = tokens.QName("")
}
envs, err := lumiEngine.GetEnvironments()
if err != nil {
return err
@ -33,7 +42,7 @@ func newEnvLsCmd() *cobra.Command {
resourceCount = strconv.Itoa(len(env.Snapshot.Resources))
}
display := env.Name
if env.IsCurrent {
if env.Name == currentEnv {
display += "*" // fancify the current environment.
}
fmt.Printf("%-20s %-48s %-12s\n", display, lastDeploy, resourceCount)

View file

@ -28,12 +28,12 @@ func newEnvRmCmd() *cobra.Command {
return errors.Errorf("missing required environment name")
}
envName := args[0]
envName := tokens.QName(args[0])
// Ensure the user really wants to do this.
if yes ||
confirmPrompt("This will permanently remove the '%v' environment!", envName) {
return lumiEngine.RemoveEnv(tokens.QName(envName), force)
confirmPrompt("This will permanently remove the '%v' environment!", envName.String()) {
return lumiEngine.RemoveEnv(envName, force)
}
return nil

View file

@ -3,6 +3,8 @@
package cmd
import (
"fmt"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
@ -21,10 +23,15 @@ func newEnvSelectCmd() *cobra.Command {
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
// Read in the name of the environment to switch to.
if len(args) == 0 {
return lumiEngine.GetCurrentEnv()
name, err := getCurrentEnv()
if err != nil {
return err
}
fmt.Printf("%v\n", name)
}
return lumiEngine.SelectEnv(tokens.QName(args[0]))
return setCurrentEnv(tokens.QName(args[0]), true)
}),
}
}

View file

@ -3,7 +3,6 @@
package cmd
import (
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/engine"
@ -34,7 +33,12 @@ func newPreviewCmd() *cobra.Command {
"By default, the package to execute is loaded from the current directory. Optionally, an\n" +
"explicit path can be provided using the [package] argument.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
return lumiEngine.Preview(tokens.QName(env), engine.PreviewOptions{
envName, err := explicitOrCurrent(env)
if err != nil {
return err
}
return lumiEngine.Preview(envName, engine.PreviewOptions{
Package: pkgargFromArgs(args),
Debug: debug,
Analyzers: analyzers,

View file

@ -3,7 +3,6 @@
package cmd
import (
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/engine"
@ -35,7 +34,12 @@ func newUpdateCmd() *cobra.Command {
"By default, the package to execute is loaded from the current directory. Optionally, an\n" +
"explicit path can be provided using the [package] argument.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
return lumiEngine.Deploy(tokens.QName(env), engine.DeployOptions{
envName, err := explicitOrCurrent(env)
if err != nil {
return err
}
return lumiEngine.Deploy(envName, engine.DeployOptions{
Package: pkgargFromArgs(args),
Debug: debug,
DryRun: dryRun,

56
cmd/util.go Normal file
View file

@ -0,0 +1,56 @@
package cmd
import (
"os"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
)
// newWorkspace creates a new workspace using the current working directory.
func newWorkspace() (workspace.W, error) {
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
return workspace.New(pwd)
}
// explicitOrCurrent returns an environment name after ensuring the environment exists. When a empty
// environment name is passed, the "current" ambient environment is returned
func explicitOrCurrent(name string) (tokens.QName, error) {
if name == "" {
return getCurrentEnv()
}
_, _, _, err := lumiEngine.Environment.GetEnvironment(tokens.QName(name))
return tokens.QName(name), err
}
// getCurrentEnv reads the current environment.
func getCurrentEnv() (tokens.QName, error) {
w, err := newWorkspace()
if err != nil {
return tokens.QName(""), err
}
return w.Settings().Env, nil
}
// setCurrentEnv changes the current environment to the given environment name, issuing an error if it doesn't exist.
func setCurrentEnv(name tokens.QName, verify bool) error {
if verify {
if _, _, _, err := lumiEngine.Environment.GetEnvironment(name); err != nil {
return err
}
}
// Switch the current workspace to that environment.
w, err := newWorkspace()
if err != nil {
return err
}
w.Settings().Env = name
return w.Save()
}

View file

@ -5,9 +5,12 @@ package engine
import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func (eng *Engine) DeleteConfig(envName tokens.QName, key tokens.ModuleMember) error {
contract.Require(envName != tokens.QName(""), "envName")
info, err := eng.initEnvCmdName(envName, "")
if err != nil {
return err

View file

@ -4,9 +4,12 @@ package engine
import (
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func (eng *Engine) GetConfiguration(environment tokens.QName) (map[tokens.ModuleMember]string, error) {
contract.Require(environment != tokens.QName(""), "environment")
info, err := eng.initEnvCmdName(environment, "")
if err != nil {
return nil, err

View file

@ -5,9 +5,12 @@ package engine
import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func (eng *Engine) SetConfig(envName tokens.QName, key tokens.ModuleMember, value string) error {
contract.Require(envName != tokens.QName(""), "envName")
info, err := eng.initEnvCmdName(envName, "")
if err != nil {
return err

View file

@ -31,6 +31,8 @@ type DeployOptions struct {
}
func (eng *Engine) Deploy(environment tokens.QName, opts DeployOptions) error {
contract.Require(environment != tokens.QName(""), "environment")
// Initialize the diagnostics logger with the right stuff.
eng.InitDiag(diag.FormatOptions{
Colors: true,

View file

@ -4,6 +4,7 @@ package engine
import (
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
type DestroyOptions struct {
@ -15,6 +16,8 @@ type DestroyOptions struct {
}
func (eng *Engine) Destroy(environment tokens.QName, opts DestroyOptions) error {
contract.Require(environment != tokens.QName(""), "environment")
info, err := eng.initEnvCmdName(environment, opts.Package)
if err != nil {
return err

View file

@ -4,27 +4,18 @@ package engine
import (
"fmt"
"os"
goerr "github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/compiler/errors"
"github.com/pulumi/pulumi/pkg/diag/colors"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/resource/environment"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/workspace"
)
func (eng *Engine) initEnvCmdName(name tokens.QName, pkgarg string) (*envCmdInfo, error) {
// If the name is blank, use the default.
if name == "" {
name = eng.getCurrentEnv()
}
if name == "" {
return nil, goerr.Errorf("missing environment name (and no default found)")
}
contract.Require(name != tokens.QName(""), "name")
// Read in the deployment information, bailing if an IO error occurs.
target, snapshot, checkpoint, err := eng.Environment.GetEnvironment(name)
@ -50,53 +41,11 @@ type envCmdInfo struct {
}
// createEnv just creates a new empty environment without deploying anything into it.
func (eng *Engine) createEnv(name tokens.QName) {
func (eng *Engine) createEnv(name tokens.QName) error {
contract.Require(name != tokens.QName(""), "name")
env := &deploy.Target{Name: name}
if err := eng.Environment.SaveEnvironment(env, nil); err == nil {
fmt.Fprintf(eng.Stdout, "Environment '%v' initialized; see `pulumi update` to deploy into it\n", name)
eng.setCurrentEnv(name, false)
}
}
// newWorkspace creates a new workspace using the current working directory.
func (eng *Engine) newWorkspace() (workspace.W, error) {
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
return workspace.New(pwd)
}
// getCurrentEnv reads the current environment.
func (eng *Engine) getCurrentEnv() tokens.QName {
var name tokens.QName
w, err := eng.newWorkspace()
if err == nil {
name = w.Settings().Env
}
if err != nil {
eng.Diag().Errorf(errors.ErrorIO, err)
}
return name
}
// setCurrentEnv changes the current environment to the given environment name, issuing an error if it doesn't exist.
func (eng *Engine) setCurrentEnv(name tokens.QName, verify bool) {
if verify {
if _, _, _, err := eng.Environment.GetEnvironment(name); err != nil {
return // no environment by this name exists, bail out.
}
}
// Switch the current workspace to that environment.
w, err := eng.newWorkspace()
if err == nil {
w.Settings().Env = name
err = w.Save()
}
if err != nil {
eng.Diag().Errorf(errors.ErrorIO, err)
}
return eng.Environment.SaveEnvironment(env, nil)
}
// removeTarget permanently deletes the environment's information from the local workstation.

View file

@ -1,14 +0,0 @@
// Copyright 2017, Pulumi Corporation. All rights reserved.
package engine
import (
"fmt"
)
func (eng *Engine) GetCurrentEnv() error {
if name := eng.getCurrentEnv(); name != "" {
fmt.Println(name)
}
return nil
}

View file

@ -4,31 +4,19 @@ package engine
import (
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func (eng *Engine) GetCurrentEnvName() tokens.QName {
return eng.getCurrentEnv()
}
func (eng *Engine) GetEnvironmentInfo(envName tokens.QName) (EnvironmentInfo, error) {
curr := envName
if curr == "" {
curr = eng.getCurrentEnv()
}
if curr == "" {
return EnvironmentInfo{}, errors.New("no current environment; either `pulumi env init` or `pulumi env select` one")
}
contract.Require(envName != tokens.QName(""), "envName")
target, snapshot, checkpoint, err := eng.Environment.GetEnvironment(curr)
_, snapshot, checkpoint, err := eng.Environment.GetEnvironment(envName)
if err != nil {
return EnvironmentInfo{}, err
}
return EnvironmentInfo{Name: curr,
return EnvironmentInfo{Name: envName,
Snapshot: snapshot,
Checkpoint: checkpoint,
IsCurrent: target.Name == curr,
}, nil
}

View file

@ -2,9 +2,13 @@
package engine
import "github.com/pulumi/pulumi/pkg/tokens"
import (
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func (eng *Engine) InitEnv(name tokens.QName) error {
eng.createEnv(name)
return nil
contract.Require(name != tokens.QName(""), "name")
return eng.createEnv(name)
}

View file

@ -19,7 +19,6 @@ type EnvironmentInfo struct {
Name tokens.QName
Snapshot *deploy.Snapshot
Checkpoint *environment.Checkpoint
IsCurrent bool
}
func (eng *Engine) GetEnvironments() ([]EnvironmentInfo, error) {
@ -32,7 +31,6 @@ func (eng *Engine) GetEnvironments() ([]EnvironmentInfo, error) {
return nil, errors.Errorf("could not read environments: %v", err)
}
curr := eng.getCurrentEnv()
for _, file := range files {
// Ignore directories.
if file.IsDir() {
@ -56,7 +54,6 @@ func (eng *Engine) GetEnvironments() ([]EnvironmentInfo, error) {
envs = append(envs, EnvironmentInfo{Name: target.Name,
Snapshot: snapshot,
Checkpoint: checkpoint,
IsCurrent: target.Name == curr,
})
}

View file

@ -1,10 +0,0 @@
// Copyright 2017, Pulumi Corporation. All rights reserved.
package engine
import "github.com/pulumi/pulumi/pkg/tokens"
func (eng *Engine) SelectEnv(envName tokens.QName) error {
eng.setCurrentEnv(envName, true)
return nil
}

View file

@ -21,6 +21,8 @@ type PreviewOptions struct {
}
func (eng *Engine) Preview(environment tokens.QName, opts PreviewOptions) error {
contract.Require(environment != tokens.QName(""), "environment")
// Initialize the diagnostics logger with the right stuff.
eng.InitDiag(diag.FormatOptions{
Colors: true,