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
Conversation
changes: | ||
- type: feat | ||
scope: cli | ||
description: Implement initial MVP for hierarchical and structured project configuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description: Implement initial MVP for hierarchical and structured project configuration | |
description: Implement initial MVP for hierarchical and structured project configuration. |
pkg/cmd/pulumi/import.go
Outdated
@@ -492,7 +492,11 @@ func newImportCmd() *cobra.Command { | |||
return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) | |||
} | |||
|
|||
cfg, err := getStackConfiguration(ctx, s, sm) | |||
cfg, err := getStackConfiguration(ctx, s, sm, StackConfigOptions{ | |||
// we don't need project config here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? Maybe for this first mvp, but as soon as we allow things like "aws:region" at project level even import is going to need it. I'd think it simpler to just plumb projects through everywhere and not bother introducing this StackConfigOptions type which we'll probably remove later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure in this case, so I changed this to inherit project-level config too ✅
sdk/go/common/workspace/project.go
Outdated
return errors.New("project is missing a 'runtime' attribute") | ||
} | ||
|
||
project = RewriteShorthandConfigValues(project) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really the JSON schema ought to be written such that the short-hand config values are validated ok. People could use that schema directly in vscode for example. So I feel this rewrite ought to happen after schema validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The project schema now validates short-hand config syntax ✅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great but the rewrite here should still happen after the Validate call below.
Validate the users input as is, then rewrite to the internal domain model.
} | ||
|
||
fileName := fmt.Sprintf("%s.%s%s", ProjectFile, qnameFileName(stackName), filepath.Ext(projPath)) | ||
|
||
// Back compat: StackConfigDir used to be called Config. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to support this. People could still have projects using "config: some_path". Ripping this out is another 4.0 change (maybe we should start leaving "TODO(4.0)" comments around for stuff like this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brought back support for defining config: some_path
by rewriting it to stackConfigDir
when it is a string. Erroring out when both are defined ✅
sdk/go/common/workspace/project.json
Outdated
}, | ||
"config": { | ||
"description": "A map of configuration keys to their types", | ||
"type": "object", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"type": "object", | |
"type": ["object", "string", "null"] |
Still need to support the old string path here, and it's optional so a null is also valid.
sdk/go/common/workspace/project.json
Outdated
] | ||
}, | ||
"config": { | ||
"description": "A map of configuration keys to their types", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a bit to the description about how the "Config directory location relative to the location of Pulumi.yaml." is a deprecated use of this key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added that to the description
sdk/go/common/workspace/project.json
Outdated
"$ref": "#/$defs/configItemsType" | ||
} | ||
}, | ||
"if": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if a "oneOf" results in better errors here than "if" 🤔
Not needed for this first pass but I might have a play with that idea later, so which is nicer UX.
The reason We could remove the |
…tion (less rigid contains check)
@Frassle As discussed, because of
@Frassle Done ✅
@iwahbe Nice catch! Done ✅ I've addressed your comments in the latest update and added moaar tests, please have a second look when you have time 🙏 @Frassle @iwahbe |
sdk/go/common/workspace/project.go
Outdated
type ProjectConfigItemsType struct { | ||
Type string `json:"type" yaml:"type"` | ||
Items *ProjectConfigItemsType `json:"items" yaml:"items"` | ||
} | ||
|
||
type ProjectConfigType struct { | ||
Type string `json:"type" yaml:"type"` | ||
Description string `json:"description" yaml:"description"` | ||
Items *ProjectConfigItemsType `json:"items" yaml:"items"` | ||
Default interface{} `json:"default" yaml:"default"` | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want some omitempty
s here.
sdk/go/common/workspace/project.go
Outdated
type ProjectConfigItemsType struct { | ||
Type string `json:"type" yaml:"type"` | ||
Items *ProjectConfigItemsType `json:"items" yaml:"items"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to express this as a schema.Type
? We already have a lot of logic to work on these, and we will need to translate them into schema types anyways for entry into the Pulumi YAML type checker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible to write a function to translate a config type into schema.Type
but I wouldn't want to start with this right off the bat and only support what is needed because it would seem that a config type can be any schema.Type
but that is not the case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with Zaid here, config types != schema types, it will probably end up being a subset but possibly a non-intersecting set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will definitely need the config type -> schema type
function when we incorporate this change into Pulumi YAML. We can wait for this change to settle before we unify.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point that this will probably have to be a subset otherwise there would be config values that YAML wouldn't be able to use, but I think given the subset nature of it I'm going to continue err'ing towards us not making it a schema.Type for now but it's own thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually see above, I think it might end up being non-intersecting still if we end up with config types that YAML doesn't support.
return typeName | ||
} | ||
|
||
func ValidateConfigValue(typeName string, itemsType *ProjectConfigItemsType, value interface{}) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would appreciate a doc comment explaining what constitutes a valid config value.
…ks that pulumi config goes through project config
pkg/backend/filestate/crypto.go
Outdated
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if configFile == "" { |
There was a problem hiding this comment.
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.
pkg/backend/httpstate/crypto.go
Outdated
@@ -25,7 +25,16 @@ import ( | |||
func NewServiceSecretsManager(s Stack, stackName tokens.Name, configFile string) (secrets.Manager, error) { | |||
contract.Assertf(stackName != "", "stackName %s", "!= \"\"") | |||
|
|||
info, err := workspace.LoadProjectStack(configFile) | |||
project, path, err := workspace.DetectProjectStackPath(stackName.Q()) |
There was a problem hiding this comment.
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.
@@ -986,7 +986,6 @@ func TestDestroyStackRef(t *testing.T) { | |||
|
|||
e.RunCommand("pulumi", "up", "--skip-preview", "--yes") | |||
|
|||
e.CWD = os.TempDir() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh this was by design so that destroy didn't need a Pulumi.yaml file. We should see if we can maintain that support for now.
@Frassle I've fixed the issue about |
@@ -1,180 +1,307 @@ | |||
{ | |||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indent change in this file makes diff compare very hard :( can we revert to 4 space tab?
sdk/go/common/workspace/project.go
Outdated
return errors.New("project is missing a 'runtime' attribute") | ||
} | ||
|
||
project = RewriteShorthandConfigValues(project) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great but the rewrite here should still happen after the Validate call below.
Validate the users input as is, then rewrite to the internal domain model.
…r test to assert full stackConfigDir error message
… is it: allow config to be string for validation
return nil, err | ||
} | ||
|
||
if projectStackRaw == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am very suspect of this. I assume this was to fix the "destory works without a Pulumi.yaml" but fixing it at this level means that any command using projects will try to use an empty project rather than failing validation, that feels wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was not the fix for "destory works without a Pulumi.yaml", the fix was in removing readProject()
from getStackConfiguration
and refactor getStackConfiguration
to accept a project from the outer scope (which in case of destroy, accounts for non-existing Pulumi.yaml)
This however, I believe is a check for when there is a stack file Pulumi.<stack>.yaml
but it is empty so the unmarshalled projectStackRaw
becomes nil. In this case, I treat it the same way we do when the file does not exist by returning a default project stack
@@ -105,8 +106,27 @@ func (singleton *projectLoader) load(path string) (*Project, error) { | |||
return nil, fmt.Errorf("could not validate '%s': %w", path, err) | |||
} | |||
|
|||
// just before marshalling, we will rewrite the config values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this rewrite should happen after validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the rewriting from inside the validation code but didn't realize it was duplicated in the loader code. I will adjust it
bors merge |
Build succeeded: |
Description
This implements the initial pass of hierarchical and structured config which fixes #10602.
This changes the CLI such that configuration can now be defined at the project level using a
config
block. The configuration values defined here are inherited by all the stacks and made available to the Pulumi program without having to duplicate values in every stack (hence hierarchical) and the values are also typed / structured.Example Project.yaml syntax:
This can also be rewritten using short-hand syntax and will be equivalent to the above
The complex types allowed for now are only arrays and nested arrays:
type: string | integer | boolean | array
is expected. Whentype: array
then a config block must also have propertyitems
which defines the type of array elements (can be nested)pulumi config
will list the configuration values from the selected stack AND the values inherited from the projectpulumi up
run using hierarchical config from the project,pulumi config refresh
will write ALL the used config back to the refreshed stackpulumi config set/rm
only applies to the selected stackChecklist
make changelog
and committed thechangelog/pending/<file>
documenting my change