Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hierarchical and structured config implementation: the initial pass #10832

Merged
merged 36 commits into from Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6c6b5ce
Initial work on project-level config (MVP)
Zaid-Ajaj Sep 20, 2022
683f7e1
Implement stack config validation and inheritance
Zaid-Ajaj Sep 20, 2022
f659376
Add description to the config type declaration
Zaid-Ajaj Sep 20, 2022
966e96f
Apply project config to stack config when appropriate
Zaid-Ajaj Sep 21, 2022
aa648fd
Implement short-hand configuration value support
Zaid-Ajaj Sep 21, 2022
dff4079
support string, int and bool in shorthand config definitions
Zaid-Ajaj Sep 21, 2022
6531b66
test config schemas that use short hand version
Zaid-Ajaj Sep 22, 2022
d419605
Moaar unit tests for config validation
Zaid-Ajaj Sep 22, 2022
02bdbd0
changelog entry
Zaid-Ajaj Sep 22, 2022
7975498
lint
Zaid-Ajaj Sep 22, 2022
78ecb2b
Merge branch 'master' into minimal-config
Zaid-Ajaj Sep 24, 2022
969e0e6
project.json should validate short-hand config syntax before rewritin…
Zaid-Ajaj Sep 24, 2022
0498be1
Rewrite config to stackConfigDir when provided as string
Zaid-Ajaj Sep 24, 2022
65cfee4
pulumi import uses hierarchical config
Zaid-Ajaj Sep 24, 2022
5bd9b80
namespaced config values don't need the project as root namespace
Zaid-Ajaj Sep 24, 2022
fbc68cb
Project namespace is now optional when defining stack config
Zaid-Ajaj Sep 26, 2022
77c7880
Fix reading empty project stack file
Zaid-Ajaj Sep 26, 2022
ae03899
Fix TestSecretsProviderOverride which requires both stack and project…
Zaid-Ajaj Sep 26, 2022
46be886
lint
Zaid-Ajaj Sep 26, 2022
2755638
Fix TestDestroyStackRef: don't change CWD and fix TestStackInitValida…
Zaid-Ajaj Sep 27, 2022
afeac6e
simplify test assert even more (attempt #3)
Zaid-Ajaj Sep 27, 2022
9a9a39b
Make sure config defined by a stack are also defined by the project +…
Zaid-Ajaj Oct 4, 2022
c739adb
Fix tests that have incomplete project config. Correct docs for Creat…
Zaid-Ajaj Oct 4, 2022
4cb2c19
Use "omit empty" for config types, add another integration test, chec…
Zaid-Ajaj Oct 5, 2022
37730d4
Merge branch 'master' into minimal-config
Zaid-Ajaj Oct 5, 2022
00f4ba3
Merge branch 'master' into minimal-config
Zaid-Ajaj Oct 6, 2022
813ac76
Add array config to test that the array is marshalled/unmarshalled co…
Zaid-Ajaj Oct 6, 2022
7c509d2
Assume configFile is non-empty
Zaid-Ajaj Oct 7, 2022
0766b86
Merge branch 'master' into minimal-config
Zaid-Ajaj Oct 12, 2022
12f3992
Revert changing the temp dir TestDestroyStackRef
Zaid-Ajaj Oct 12, 2022
c606757
fix TestProjectLoadYAML expected error messages
Zaid-Ajaj Oct 12, 2022
42a51fe
Allow project to be nil when namespacing project stack config
Zaid-Ajaj Oct 12, 2022
51b1329
remove readProject from getStackConfiguration and pass project from o…
Zaid-Ajaj Oct 12, 2022
fb11347
No need to rewrite project when validating project schema and refacto…
Zaid-Ajaj Oct 13, 2022
58de26d
Don't know why the change in the project schema was reverted but here…
Zaid-Ajaj Oct 13, 2022
f2ac21f
Update changelog/pending/20220922--cli-initial-mvp-config.yaml
Frassle Oct 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -15,7 +15,7 @@ venv/

**/.idea/
*.iml

.yarn
# VSCode creates this binary when running tests in the debugger
**/debug.test

Expand Down
4 changes: 4 additions & 0 deletions changelog/pending/20220922--cli-initial-mvp-config.yaml
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: cli
description: Implement initial MVP for hierarchical and structured project configuration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: Implement initial MVP for hierarchical and structured project configuration
description: Implement initial MVP for hierarchical and structured project configuration.

Frassle marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 7 additions & 6 deletions pkg/backend/filestate/crypto.go
Expand Up @@ -26,15 +26,16 @@ func NewPassphraseSecretsManager(stackName tokens.Name, configFile string,
rotatePassphraseSecretsProvider bool) (secrets.Manager, error) {
contract.Assertf(stackName != "", "stackName %s", "!= \"\"")

project, path, err := workspace.DetectProjectStackPath(stackName.Q())
if err != nil {
return nil, err
}

if configFile == "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm we might need to clean up this configFile global before merging this, it's a global injection used by tests so they don't actually have to have a Pulumi..yaml setup, but given we now need Pulumi.yaml and Pulumi..yaml it might be simpler to just clean up the tests rather than rely on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right I've cleaned up configFile now on master, so it should always set here.

f, err := workspace.DetectProjectStackPath(stackName.Q())
if err != nil {
return nil, err
}
configFile = f
configFile = path
}

info, err := workspace.LoadProjectStack(configFile)
info, err := workspace.LoadProjectStack(project, configFile)
if err != nil {
return nil, err
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/backend/httpstate/crypto.go
Expand Up @@ -25,15 +25,16 @@ import (
func NewServiceSecretsManager(s Stack, stackName tokens.Name, configFile string) (secrets.Manager, error) {
contract.Assertf(stackName != "", "stackName %s", "!= \"\"")

project, path, err := workspace.DetectProjectStackPath(stackName.Q())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, configFile should always be set here.

if err != nil {
return nil, err
}

if configFile == "" {
f, err := workspace.DetectProjectStackPath(stackName.Q())
if err != nil {
return nil, err
}
configFile = f
configFile = path
}

info, err := workspace.LoadProjectStack(configFile)
info, err := workspace.LoadProjectStack(project, configFile)
if err != nil {
return nil, err
}
Expand Down
131 changes: 106 additions & 25 deletions pkg/cmd/pulumi/config.go
Expand Up @@ -39,6 +39,10 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
)

type StackConfigOptions struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just get rid of this, I don't forsee any options here and applyProjectConfig should always be true for now.

applyProjectConfig bool
}

func newConfigCmd() *cobra.Command {
var stack string
var showSecrets bool
Expand All @@ -57,12 +61,18 @@ func newConfigCmd() *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

project, _, err := readProject()

if err != nil {
return err
}

stack, err := requireStack(ctx, stack, true, opts, true /*setCurrent*/)
if err != nil {
return err
}

return listConfig(ctx, stack, showSecrets, jsonOut)
return listConfig(ctx, project, stack, showSecrets, jsonOut)
}),
}

Expand Down Expand Up @@ -106,6 +116,12 @@ func newConfigCopyCmd(stack *string) *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

project, _, err := readProject()

if err != nil {
return err
}

// Get current stack and ensure that it is a different stack to the destination stack
currentStack, err := requireStack(ctx, *stack, false, opts, true /*setCurrent*/)
if err != nil {
Expand All @@ -114,7 +130,7 @@ func newConfigCopyCmd(stack *string) *cobra.Command {
if currentStack.Ref().Name().String() == destinationStackName {
return errors.New("current stack and destination stack are the same")
}
currentProjectStack, err := loadProjectStack(currentStack)
currentProjectStack, err := loadProjectStack(project, currentStack)
if err != nil {
return err
}
Expand All @@ -124,7 +140,7 @@ func newConfigCopyCmd(stack *string) *cobra.Command {
if err != nil {
return err
}
destinationProjectStack, err := loadProjectStack(destinationStack)
destinationProjectStack, err := loadProjectStack(project, destinationStack)
if err != nil {
return err
}
Expand Down Expand Up @@ -302,7 +318,12 @@ func newConfigRmCmd(stack *string) *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

s, err := requireStack(ctx, *stack, true, opts, true /*setCurrent*/)
project, _, err := readProject()
if err != nil {
return err
}

stack, err := requireStack(ctx, *stack, true, opts, true /*setCurrent*/)
if err != nil {
return err
}
Expand All @@ -312,7 +333,7 @@ func newConfigRmCmd(stack *string) *cobra.Command {
return fmt.Errorf("invalid configuration key: %w", err)
}

ps, err := loadProjectStack(s)
ps, err := loadProjectStack(project, stack)
if err != nil {
return err
}
Expand All @@ -322,7 +343,7 @@ func newConfigRmCmd(stack *string) *cobra.Command {
return err
}

return saveProjectStack(s, ps)
return saveProjectStack(stack, ps)
}),
}
rmCmd.PersistentFlags().BoolVar(
Expand Down Expand Up @@ -351,12 +372,17 @@ func newConfigRmAllCmd(stack *string) *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

s, err := requireStack(ctx, *stack, true, opts, false /*setCurrent*/)
project, _, err := readProject()
if err != nil {
return err
}

ps, err := loadProjectStack(s)
stack, err := requireStack(ctx, *stack, true, opts, false /*setCurrent*/)
if err != nil {
return err
}

ps, err := loadProjectStack(project, stack)
if err != nil {
return err
}
Expand All @@ -373,7 +399,7 @@ func newConfigRmAllCmd(stack *string) *cobra.Command {
}
}

return saveProjectStack(s, ps)
return saveProjectStack(stack, ps)
}),
}
rmAllCmd.PersistentFlags().BoolVar(
Expand All @@ -395,6 +421,11 @@ func newConfigRefreshCmd(stack *string) *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

project, _, err := readProject()
if err != nil {
return err
}

// Ensure the stack exists.
s, err := requireStack(ctx, *stack, false, opts, false /*setCurrent*/)
if err != nil {
Expand All @@ -411,7 +442,7 @@ func newConfigRefreshCmd(stack *string) *cobra.Command {
return err
}

ps, err := workspace.LoadProjectStack(configPath)
ps, err := workspace.LoadProjectStack(project, configPath)
if err != nil {
return err
}
Expand Down Expand Up @@ -480,6 +511,12 @@ func newConfigSetCmd(stack *string) *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

project, _, err := readProject()

if err != nil {
return err
}

// Ensure the stack exists.
s, err := requireStack(ctx, *stack, true, opts, true /*setCurrent*/)
if err != nil {
Expand Down Expand Up @@ -538,7 +575,7 @@ func newConfigSetCmd(stack *string) *cobra.Command {
}
}

ps, err := loadProjectStack(s)
ps, err := loadProjectStack(project, s)
if err != nil {
return err
}
Expand Down Expand Up @@ -591,13 +628,18 @@ func newConfigSetAllCmd(stack *string) *cobra.Command {
Color: cmdutil.GetGlobalColorization(),
}

project, _, err := readProject()
if err != nil {
return err
}

// Ensure the stack exists.
s, err := requireStack(ctx, *stack, true, opts, false /*setCurrent*/)
stack, err := requireStack(ctx, *stack, true, opts, false /*setCurrent*/)
if err != nil {
return err
}

ps, err := loadProjectStack(s)
ps, err := loadProjectStack(project, stack)
if err != nil {
return err
}
Expand All @@ -620,7 +662,7 @@ func newConfigSetAllCmd(stack *string) *cobra.Command {
if err != nil {
return err
}
c, cerr := getStackEncrypter(s)
c, cerr := getStackEncrypter(stack)
if cerr != nil {
return cerr
}
Expand All @@ -636,7 +678,7 @@ func newConfigSetAllCmd(stack *string) *cobra.Command {
}
}

return saveProjectStack(s, ps)
return saveProjectStack(stack, ps)
}),
}

Expand Down Expand Up @@ -680,16 +722,17 @@ var stackConfigFile string

func getProjectStackPath(stack backend.Stack) (string, error) {
if stackConfigFile == "" {
return workspace.DetectProjectStackPath(stack.Ref().Name().Q())
_, path, err := workspace.DetectProjectStackPath(stack.Ref().Name().Q())
return path, err
}
return stackConfigFile, nil
}

func loadProjectStack(stack backend.Stack) (*workspace.ProjectStack, error) {
func loadProjectStack(project *workspace.Project, stack backend.Stack) (*workspace.ProjectStack, error) {
if stackConfigFile == "" {
return workspace.DetectProjectStack(stack.Ref().Name().Q())
}
return workspace.LoadProjectStack(stackConfigFile)
return workspace.LoadProjectStack(project, stackConfigFile)
}

func saveProjectStack(stack backend.Stack, ps *workspace.ProjectStack) error {
Expand Down Expand Up @@ -741,12 +784,25 @@ type configValueJSON struct {
Secret bool `json:"secret"`
}

func listConfig(ctx context.Context, stack backend.Stack, showSecrets bool, jsonOut bool) error {
ps, err := loadProjectStack(stack)
func listConfig(ctx context.Context,
project *workspace.Project,
stack backend.Stack,
showSecrets bool,
jsonOut bool) error {

ps, err := loadProjectStack(project, stack)
if err != nil {
return err
}

stackName := stack.Ref().Name().String()
// when listing configuration values
// also show values coming from the project
configError := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, project, ps.Config)
Frassle marked this conversation as resolved.
Show resolved Hide resolved
if configError != nil {
return configError
}

cfg := ps.Config

// By default, we will use a blinding decrypter to show "[secret]". If requested, display secrets in plaintext.
Expand Down Expand Up @@ -827,10 +883,19 @@ func listConfig(ctx context.Context, stack backend.Stack, showSecrets bool, json
}

func getConfig(ctx context.Context, stack backend.Stack, key config.Key, path, jsonOut bool) error {
ps, err := loadProjectStack(stack)
project, _, err := readProject()
if err != nil {
return err
}
ps, err := loadProjectStack(project, stack)
if err != nil {
return err
}
stackName := stack.Ref().Name().String()
configError := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, project, ps.Config)
if configError != nil {
return configError
}

cfg := ps.Config

Expand Down Expand Up @@ -922,17 +987,32 @@ func looksLikeSecret(k config.Key, v string) bool {
// it is uses instead of the default configuration file for the stack
func getStackConfiguration(
ctx context.Context, stack backend.Stack,
sm secrets.Manager) (backend.StackConfiguration, error) {
sm secrets.Manager,
options StackConfigOptions) (backend.StackConfiguration, error) {
var cfg config.Map
workspaceStack, err := loadProjectStack(stack)
project, _, err := readProject()
defaultStackConfig := backend.StackConfiguration{}
if err != nil {
return defaultStackConfig, err
}

workspaceStack, err := loadProjectStack(project, stack)
if err != nil || workspaceStack == nil {
// On first run or the latest configuration is unavailable, fallback to check the project's configuration
cfg, err = backend.GetLatestConfiguration(ctx, stack)
if err != nil {
return backend.StackConfiguration{}, fmt.Errorf(
return defaultStackConfig, fmt.Errorf(
"stack configuration could not be loaded from either Pulumi.yaml or the backend: %w", err)
}
} else {
if options.applyProjectConfig {
stackName := stack.Ref().Name().String()
configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, project, workspaceStack.Config)
if configErr != nil {
return defaultStackConfig, configErr
}
}

cfg = workspaceStack.Config
}

Expand All @@ -948,8 +1028,9 @@ func getStackConfiguration(

crypter, err := sm.Decrypter()
if err != nil {
return backend.StackConfiguration{}, fmt.Errorf("getting configuration decrypter: %w", err)
return defaultStackConfig, fmt.Errorf("getting configuration decrypter: %w", err)
}

return backend.StackConfiguration{
Config: cfg,
Decrypter: crypter,
Expand Down
8 changes: 7 additions & 1 deletion pkg/cmd/pulumi/crypto.go
Expand Up @@ -45,7 +45,13 @@ func getStackDecrypter(s backend.Stack) (config.Decrypter, error) {
}

func getStackSecretsManager(s backend.Stack) (secrets.Manager, error) {
ps, err := loadProjectStack(s)
project, _, err := readProject()

if err != nil {
return nil, err
}

ps, err := loadProjectStack(project, s)
if err != nil {
return nil, err
}
Expand Down