Suport workspace local configuration and use it by default
Previously, we stored configuration information in the Pulumi.yaml
file. This was a change from the old model where configuration was
stored in a special section of the checkpoint file.
While doing things this way has some upsides with being able to flow
configuration changes with your source code (e.g. fixed values for a
production stack that version with the code) it caused some friction
for the local development scinerio. In this case, setting
configuration values would pend changes to Pulumi.yaml and if you
didn't want to publish these changes, you'd have to remember to remove
them before commiting. It also was problematic for our examples, where
it was not clear if we wanted to actually include values like
`aws:config:region` in our samples. Finally, we found that for our
own pulumi service, we'd have values that would differ across each
individual dev stack, and publishing these values to a global
Pulumi.yaml file would just be adding noise to things.
We now adopt a hybrid model, where by default configuration is stored
locally, in the workspace's settings per project. A new flag `--save`
tests commands to actual operate on the configuration information
stored in Pulumi.yaml.
With the following change, we have have four "slots" configuration
values can end up in:
1. In the Pulumi.yaml file, applies to all stacks
2. In the Pulumi.yaml file, applied to a specific stack
3. In the local workspace.json file, applied to all stacks
4. In the local workspace.json file, applied to a specific stack
When computing the configuration information for a stack, we apply
configuration in the above order, overriding values as we go
along.
We also invert the default behavior of the `pulumi config` commands so
they operate on a specific stack (i.e. how they did before
e3610989
). If you want to apply configuration to all stacks, `--all`
can be passed to any configuration command.
This commit is contained in:
parent
ede8f303f9
commit
44d432a559
152
cmd/config.go
152
cmd/config.go
|
@ -61,7 +61,7 @@ func newConfigLsCmd() *cobra.Command {
|
|||
|
||||
lsCmd.PersistentFlags().StringVarP(
|
||||
&stack, "stack", "s", "",
|
||||
"Target a specific stack instead of all of this project's stacks")
|
||||
"List configuration for a different stack than the currently selected stack")
|
||||
lsCmd.PersistentFlags().BoolVar(
|
||||
&showSecrets, "show-secrets", false,
|
||||
"Show secret values when listing config instead of displaying blinded values")
|
||||
|
@ -70,6 +70,8 @@ func newConfigLsCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func newConfigRmCmd() *cobra.Command {
|
||||
var all bool
|
||||
var save bool
|
||||
var stack string
|
||||
|
||||
rmCmd := &cobra.Command{
|
||||
|
@ -77,25 +79,47 @@ func newConfigRmCmd() *cobra.Command {
|
|||
Short: "Remove configuration value",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
stackName := tokens.QName(stack)
|
||||
if all && stack != "" {
|
||||
return errors.New("if --all is specified, an explicit stack can not be provided")
|
||||
}
|
||||
|
||||
var stackName tokens.QName
|
||||
if !all {
|
||||
var err error
|
||||
if stackName, err = explicitOrCurrent(stack, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
key, err := parseConfigKey(args[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid configuration key")
|
||||
}
|
||||
|
||||
return deleteConfiguration(stackName, key)
|
||||
if save {
|
||||
return deleteProjectConfiguration(stackName, key)
|
||||
}
|
||||
|
||||
return deleteWorkspaceConfiguration(stackName, key)
|
||||
}),
|
||||
}
|
||||
|
||||
rmCmd.PersistentFlags().StringVarP(
|
||||
&stack, "stack", "s", "",
|
||||
"Target a specific stack instead of all of this project's stacks")
|
||||
"Target a specific stack instead of the default stack")
|
||||
rmCmd.PersistentFlags().BoolVar(
|
||||
&save, "save", false,
|
||||
"Remove the configuration value in the project file instead instead of a locally set value")
|
||||
rmCmd.PersistentFlags().BoolVar(
|
||||
&all, "all", false,
|
||||
"Remove a project wide configuration value that applies to all stacks")
|
||||
|
||||
return rmCmd
|
||||
}
|
||||
|
||||
func newConfigTextCmd() *cobra.Command {
|
||||
var all bool
|
||||
var save bool
|
||||
var stack string
|
||||
|
||||
textCmd := &cobra.Command{
|
||||
|
@ -103,25 +127,43 @@ func newConfigTextCmd() *cobra.Command {
|
|||
Short: "Set configuration value",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
stackName := tokens.QName(stack)
|
||||
if all && stack != "" {
|
||||
return errors.New("if --all is specified, an explicit stack can not be provided")
|
||||
}
|
||||
|
||||
var stackName tokens.QName
|
||||
if !all {
|
||||
var err error
|
||||
if stackName, err = explicitOrCurrent(stack, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
key, err := parseConfigKey(args[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid configuration key")
|
||||
}
|
||||
|
||||
return setConfiguration(stackName, key, config.NewValue(args[1]))
|
||||
return setConfiguration(stackName, key, config.NewValue(args[1]), save)
|
||||
}),
|
||||
}
|
||||
|
||||
textCmd.PersistentFlags().StringVarP(
|
||||
&stack, "stack", "s", "",
|
||||
"Target a specific stack instead of all of this project's stacks")
|
||||
"Target a specific stack instead of the default stack")
|
||||
textCmd.PersistentFlags().BoolVar(
|
||||
&save, "save", false,
|
||||
"Save the configuration value in the project file instead of locally")
|
||||
textCmd.PersistentFlags().BoolVar(
|
||||
&all, "all", false,
|
||||
"Set a configuration value for all stacks for this project")
|
||||
|
||||
return textCmd
|
||||
}
|
||||
|
||||
func newConfigSecretCmd() *cobra.Command {
|
||||
var all bool
|
||||
var save bool
|
||||
var stack string
|
||||
|
||||
secretCmd := &cobra.Command{
|
||||
|
@ -129,7 +171,17 @@ func newConfigSecretCmd() *cobra.Command {
|
|||
Short: "Set an encrypted configuration value",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
stackName := tokens.QName(stack)
|
||||
if all && stack != "" {
|
||||
return errors.New("if --all is specified, an explicit stack can not be provided")
|
||||
}
|
||||
|
||||
var stackName tokens.QName
|
||||
if !all {
|
||||
var err error
|
||||
if stackName, err = explicitOrCurrent(stack, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
key, err := parseConfigKey(args[0])
|
||||
if err != nil {
|
||||
|
@ -156,13 +208,19 @@ func newConfigSecretCmd() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
return setConfiguration(stackName, key, config.NewSecureValue(encryptedValue))
|
||||
return setConfiguration(stackName, key, config.NewSecureValue(encryptedValue), save)
|
||||
}),
|
||||
}
|
||||
|
||||
secretCmd.PersistentFlags().StringVarP(
|
||||
&stack, "stack", "s", "",
|
||||
"Target a specific stack instead of all of this project's stacks")
|
||||
"Target a specific stack instead of the default stack")
|
||||
secretCmd.PersistentFlags().BoolVar(
|
||||
&save, "save", false,
|
||||
"Save the configuration value in the project file instead of locally")
|
||||
secretCmd.PersistentFlags().BoolVar(
|
||||
&all, "all", false,
|
||||
"Set a configuration value for all stacks for this project")
|
||||
|
||||
return secretCmd
|
||||
}
|
||||
|
@ -202,6 +260,14 @@ func prettyKeyForPackage(key string, pkg *pack.Package) string {
|
|||
return s
|
||||
}
|
||||
|
||||
func setConfiguration(stackName tokens.QName, key tokens.ModuleMember, value config.Value, save bool) error {
|
||||
if save {
|
||||
return setProjectConfiguration(stackName, key, value)
|
||||
}
|
||||
|
||||
return setWorkspaceConfiguration(stackName, key, value)
|
||||
}
|
||||
|
||||
func listConfig(stackName tokens.QName, showSecrets bool) error {
|
||||
cfg, err := getConfiguration(stackName)
|
||||
if err != nil {
|
||||
|
@ -273,20 +339,35 @@ func getConfig(stackName tokens.QName, key tokens.ModuleMember) error {
|
|||
func getConfiguration(stackName tokens.QName) (map[tokens.ModuleMember]config.Value, error) {
|
||||
contract.Require(stackName != "", "stackName")
|
||||
|
||||
workspace, err := newWorkspace()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkg, err := getPackage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stackInfo, hasStackInfo := pkg.Stacks[stackName]
|
||||
if !hasStackInfo {
|
||||
return pkg.Config, nil
|
||||
configs := make([]map[tokens.ModuleMember]config.Value, 4)
|
||||
configs = append(configs, pkg.Config)
|
||||
|
||||
if stackInfo, has := pkg.Stacks[stackName]; has {
|
||||
configs = append(configs, stackInfo.Config)
|
||||
}
|
||||
|
||||
return mergeConfigs(pkg.Config, stackInfo.Config), nil
|
||||
if localAllStackConfig, has := workspace.Settings().Config[""]; has {
|
||||
configs = append(configs, localAllStackConfig)
|
||||
}
|
||||
|
||||
if localStackConfig, has := workspace.Settings().Config[stackName]; has {
|
||||
configs = append(configs, localStackConfig)
|
||||
}
|
||||
|
||||
return mergeConfigs(configs...), nil
|
||||
}
|
||||
|
||||
func deleteConfiguration(stackName tokens.QName, key tokens.ModuleMember) error {
|
||||
func deleteProjectConfiguration(stackName tokens.QName, key tokens.ModuleMember) error {
|
||||
pkg, err := getPackage()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -305,7 +386,20 @@ func deleteConfiguration(stackName tokens.QName, key tokens.ModuleMember) error
|
|||
return savePackage(pkg)
|
||||
}
|
||||
|
||||
func setConfiguration(stackName tokens.QName, key tokens.ModuleMember, value config.Value) error {
|
||||
func deleteWorkspaceConfiguration(stackName tokens.QName, key tokens.ModuleMember) error {
|
||||
workspace, err := newWorkspace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config, has := workspace.Settings().Config[stackName]; has {
|
||||
delete(config, key)
|
||||
}
|
||||
|
||||
return workspace.Save()
|
||||
}
|
||||
|
||||
func setProjectConfiguration(stackName tokens.QName, key tokens.ModuleMember, value config.Value) error {
|
||||
pkg, err := getPackage()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -334,22 +428,28 @@ func setConfiguration(stackName tokens.QName, key tokens.ModuleMember, value con
|
|||
return savePackage(pkg)
|
||||
}
|
||||
|
||||
func mergeConfigs(global, stack map[tokens.ModuleMember]config.Value) map[tokens.ModuleMember]config.Value {
|
||||
if stack == nil {
|
||||
return global
|
||||
func setWorkspaceConfiguration(stackName tokens.QName, key tokens.ModuleMember, value config.Value) error {
|
||||
workspace, err := newWorkspace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if global == nil {
|
||||
return stack
|
||||
if _, has := workspace.Settings().Config[stackName]; !has {
|
||||
workspace.Settings().Config[stackName] = make(map[tokens.ModuleMember]config.Value)
|
||||
}
|
||||
|
||||
workspace.Settings().Config[stackName][key] = value
|
||||
|
||||
return workspace.Save()
|
||||
}
|
||||
|
||||
func mergeConfigs(configs ...map[tokens.ModuleMember]config.Value) map[tokens.ModuleMember]config.Value {
|
||||
merged := make(map[tokens.ModuleMember]config.Value)
|
||||
for key, value := range global {
|
||||
merged[key] = value
|
||||
}
|
||||
|
||||
for key, value := range stack {
|
||||
merged[key] = value
|
||||
for _, config := range configs {
|
||||
for key, value := range config {
|
||||
merged[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
|
|
|
@ -18,6 +18,7 @@ const BookkeepingDir = ".pulumi" // the name of our bookeeping folder, we
|
|||
const StackDir = "stacks" // the name of the directory that holds stack information for projects.
|
||||
const WorkspaceDir = "workspaces" // the name of the directory that holds workspace information for projects.
|
||||
const RepoFile = "settings.json" // the name of the file that holds information specific to the entire repository.
|
||||
const ConfigDir = "config" // the name of the folder that holds local configuration information.
|
||||
const WorkspaceFile = "workspace.json" // the name of the file that holds workspace information.
|
||||
|
||||
// DetectPackage locates the closest package from the given path, searching "upwards" in the directory hierarchy. If no
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi/pkg/resource/config"
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
)
|
||||
|
||||
// Settings defines workspace settings shared amongst many related projects.
|
||||
// nolint: lll
|
||||
type Settings struct {
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` // an optional namespace.
|
||||
Stack tokens.QName `json:"env,omitempty" yaml:"env,omitempty"` // an optional default stack to use.
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` // an optional namespace.
|
||||
Stack tokens.QName `json:"env,omitempty" yaml:"env,omitempty"` // an optional default stack to use.
|
||||
Config map[tokens.QName]map[tokens.ModuleMember]config.Value `json:"config,omitempty" yaml:"config,omitempty"` // optional workspace local configuration (overrides values in a project)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pulumi/pulumi/pkg/pack"
|
||||
"github.com/pulumi/pulumi/pkg/resource/config"
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
)
|
||||
|
||||
|
@ -60,6 +61,10 @@ func NewProjectWorkspace(dir string) (W, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if w.settings.Config == nil {
|
||||
w.settings.Config = make(map[tokens.QName]map[tokens.ModuleMember]config.Value)
|
||||
}
|
||||
|
||||
return &w, nil
|
||||
}
|
||||
|
||||
|
@ -76,6 +81,13 @@ func (pw *projectWorkspace) DetectPackage() (string, error) {
|
|||
}
|
||||
|
||||
func (pw *projectWorkspace) Save() error {
|
||||
// let's remove all the empty entries from the config array
|
||||
for k, v := range pw.settings.Config {
|
||||
if len(v) == 0 {
|
||||
delete(pw.settings.Config, k)
|
||||
}
|
||||
}
|
||||
|
||||
settingsFile := pw.settingsPath()
|
||||
|
||||
// ensure the path exists
|
||||
|
|
Loading…
Reference in a new issue