-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[import] Handle importing resource properties of type Union #10995
Conversation
Changelog[uncommitted] (2022-10-13)Bug Fixes
|
Adding Ian and Justin to review as well, I don't fully get all the PCL codebase yet. |
pkg/codegen/importer/hcl2.go
Outdated
selectElementType := func() (*schema.ObjectType, bool) { | ||
discriminatorValue, ok := obj[resource.PropertyKey(discriminatorPropertyName)] | ||
if !ok { | ||
// discriminator property is not present, so we can't select a type |
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.
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 think it would be simpler to handle this in a more general case. After we set typ = codegen.UnwrapType(type)
, we could put
if union, ok := typ.(*schema.UnionType); ok {
typ = reduceUnion(union, value)
}
Reduce union would look like
// reduceUnion returns the element type in union that bests fits value.
// If more than one type fits, reduceUnion will return the union.DefaultType if it is non-nil, otherwise it will return the first type that fits.
// If no type fits, nil is returned.
func reduceUnion(union *schema.UnionType, value resource.PropertyValue) schema.Type
UnionType.Discriminator
is optional, so we will need to reduce based on the shape of the value, not just on the discriminator.
pkg/codegen/importer/hcl2.go
Outdated
@@ -410,7 +410,8 @@ func generateValue(typ schema.Type, value resource.PropertyValue) (model.Express | |||
obj := value.ObjectValue() | |||
items := make([]model.ObjectConsItem, 0, len(obj)) | |||
|
|||
if objectType, ok := typ.(*schema.ObjectType); ok { | |||
switch objectType := typ.(type) { |
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 idiomatic to shadow here. Regardless, we shouldn't call this objectType
when it's not of type object.
switch objectType := typ.(type) { | |
switch typ := typ.(type) { |
} | ||
} | ||
} | ||
|
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 think we need to do something for !ok
. We can fall back to what we were doing before.
pkg/codegen/importer/hcl2.go
Outdated
} else { | ||
case *schema.UnionType: | ||
discriminatorPropertyName := objectType.Discriminator | ||
selectElementType := func() (*schema.ObjectType, 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.
Why do we create a lambda here, only to invoke it on the next line? We should either separate out selectElementType
into its own function or inline it.
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.
Just a stylistic choice to make to more readable. I'll rewrite it.
pkg/codegen/importer/hcl2.go
Outdated
// match it against the element type which should be an object | ||
elementTypeObject, ok := codegen.UnwrapType(elementType).(*schema.ObjectType) | ||
if ok { | ||
elementTypeParts := strings.Split(elementTypeObject.Token, ":") |
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.
Can we replace this by casting to tokens.Type
and then calling the appropriate methods?
@iwahbe you mean here for each property in the object value, go through each union element type and do a best-effect match to see one object fits? It doesn't seem deterministic imo, maybe in this case we fall back to the |
I mean for each Element type in the union, check if I think we should fall back to the |
@iwahbe I've addressed your comments, can you have another look? 🙏 |
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 left a comment on union resolution. If you want, we can return nil
for non-discriminated unions and open a new issue to add the correct checking.
We will definitely want a test here.
pkg/codegen/importer/hcl2.go
Outdated
case value.IsObject(): | ||
// If the value is an object, use the discriminator to choose the element type. | ||
if schemaUnion.Discriminator == "" { | ||
// If there is no discriminator, return first type which is an 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.
I don't agree with this. If there is no discriminator, we need to return the first type it is legal to assign value
to. This is a structural type check.
For example, given the value { foo: 1 }
and Union[{ foo: string }, { bar: Union[none, string], foo: number }
we should resolve to the second element, not the first.
An example of where this would be necessary is
https://github.com/pulumi/pulumi-aws-native/blob/edc3d04caddc8996061d1ce2a8b80fbe5236b313/provider/cmd/pulumi-resource-aws-native/schema.json#L13430-L13451.
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've implemented the structural type check to find the best fit + tests ✅
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 left a last set of comments, but I'm out of corner cases. I appreciate the tests and working with me to add what is essentially a structural type checker to package importer
.
LGTM!
pkg/codegen/importer/hcl2.go
Outdated
case value.IsString(): | ||
// check to see if one of the element types in the union | ||
// actually allows a string type and return it | ||
for _, t := range schemaUnion.ElementTypes { | ||
if t == schema.StringType { | ||
return t | ||
} | ||
} | ||
|
||
case value.IsBool(): | ||
// check to see if one of the element types in the union | ||
// actually allows a bool type and return it | ||
for _, t := range schemaUnion.ElementTypes { | ||
if t == schema.BoolType { | ||
return t | ||
} | ||
} | ||
|
||
case value.IsNumber(): | ||
// check to see if one of the element types in the union | ||
// actually allows a number type and return it | ||
for _, t := range schemaUnion.ElementTypes { | ||
if t == schema.NumberType { | ||
return t | ||
} | ||
} | ||
} |
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.
Unions come in two types: discriminated objects and structural. We can simplify this (and handle the missing case value.IsArray()
) by invoking valueStructurallyTypedAs
:
case value.IsString(): | |
// check to see if one of the element types in the union | |
// actually allows a string type and return it | |
for _, t := range schemaUnion.ElementTypes { | |
if t == schema.StringType { | |
return t | |
} | |
} | |
case value.IsBool(): | |
// check to see if one of the element types in the union | |
// actually allows a bool type and return it | |
for _, t := range schemaUnion.ElementTypes { | |
if t == schema.BoolType { | |
return t | |
} | |
} | |
case value.IsNumber(): | |
// check to see if one of the element types in the union | |
// actually allows a number type and return it | |
for _, t := range schemaUnion.ElementTypes { | |
if t == schema.NumberType { | |
return t | |
} | |
} | |
} | |
default: | |
for _, t := range schemaUnion.ElementTypes { | |
if valueStructurallyTypedAs(value, t) { | |
return t | |
} | |
} | |
} |
@@ -365,11 +366,222 @@ func generatePropertyValue(property *schema.Property, value resource.PropertyVal | |||
return generateValue(property.Type, value) | |||
} | |||
|
|||
// valueStructurallyTypedAs returns true if the given value is structurally typed as the given schema type. | |||
func valueStructurallyTypedAs(value resource.PropertyValue, schemaType schema.Type) bool { | |||
switch { |
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 can hoist the union check here, since we need to repeat it for every value anyway:
switch { | |
if union, ok := schemaType.(*schema.Union); ok { | |
schemaType = reduceUnion(union, value) | |
} | |
switch |
The reduction is mutually recursive since we want to handle nested discriminated unions.
This lets us remove the case *schema.UnionType
checks in each branch of the case value.Is*()
.
switch arg := schemaType.(type) { | ||
case *schema.ObjectType: | ||
schemaProperties := make(map[string]schema.Type) | ||
for _, schemaProperty := range arg.Properties { |
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 map is built into schema.ObjectType
. The API is here.
case *schema.ObjectType: | ||
schemaProperties := make(map[string]schema.Type) | ||
for _, schemaProperty := range arg.Properties { | ||
schemaProperties[schemaProperty.Name] = schemaProperty.Type |
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.
schemaProperties[schemaProperty.Name] = schemaProperty.Type | |
// If `value.ObjectValue()` is missing a property | |
if _, ok := value.ObjectValue()[schemaProperty.Name]; !ok | |
// And that property is not optional | |
if _, ok := schemaProperty.Type.(*schema.OptionalType); !ok { | |
// We are missing a required property, so the type does not match | |
return false | |
} | |
} |
case value.IsObject(): | ||
switch arg := schemaType.(type) { | ||
case *schema.ObjectType: | ||
schemaProperties := make(map[string]schema.Type) |
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 map is built into schema.ObjectType
. The API is here.
11de0a1
to
d1843cc
Compare
bors merge |
10995: [import] Handle importing resource properties of type Union r=Zaid-Ajaj a=Zaid-Ajaj <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #10934 ## Checklist <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Service, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. --> Co-authored-by: Zaid Ajaj <zaid.naom@gmail.com>
bors cancel |
Canceled. |
bors retry |
bors merge |
10995: [import] Handle importing resource properties of type Union r=AaronFriel a=Zaid-Ajaj <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #10934 ## Checklist <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Service, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. --> 11011: Add PCL as a target language for pulumi convert r=Zaid-Ajaj a=Zaid-Ajaj <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Every time I need to debug `pulumi convert` I have to hack YAML program-gen and make it output the intermediate PCL program text to stdout and work from there. Here, I've added PCL as a proper target language for `pulumi convert` (without needing to parse the program text and generate `pcl.Program` from it) I tried a simpler approach: ```go projectGenerator = func(directory string, project workspace.Project, p *pcl.Program) error { programText := fmt.Sprintf("%v", p) outputFile := path.Join(directory, "main.pp") err := ioutil.WriteFile(outputFile, []byte(programText), 0600) if err != nil { return fmt.Errorf("could not write output program: %w", err) } return nil } ``` but the default formatter for `pcl.Program` writes out invalid PCL (including non-formatted type metadata) so I went with this approach instead ## Checklist <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Service, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. --> Co-authored-by: Zaid Ajaj <zaid.naom@gmail.com>
I canceled & requeued as one of the build jobs hanged: https://github.com/pulumi/pulumi/actions/runs/3246406652/jobs/5325125965 |
bors cancel |
Canceled. |
bors merge |
10995: [import] Handle importing resource properties of type Union r=AaronFriel a=Zaid-Ajaj <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #10934 ## Checklist <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Service, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. --> 11011: Add PCL as a target language for pulumi convert r=AaronFriel a=Zaid-Ajaj <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Every time I need to debug `pulumi convert` I have to hack YAML program-gen and make it output the intermediate PCL program text to stdout and work from there. Here, I've added PCL as a proper target language for `pulumi convert` (without needing to parse the program text and generate `pcl.Program` from it) I tried a simpler approach: ```go projectGenerator = func(directory string, project workspace.Project, p *pcl.Program) error { programText := fmt.Sprintf("%v", p) outputFile := path.Join(directory, "main.pp") err := ioutil.WriteFile(outputFile, []byte(programText), 0600) if err != nil { return fmt.Errorf("could not write output program: %w", err) } return nil } ``` but the default formatter for `pcl.Program` writes out invalid PCL (including non-formatted type metadata) so I went with this approach instead ## Checklist <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Service, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. --> Co-authored-by: Zaid Ajaj <zaid.naom@gmail.com>
I would have liked to get this in, but it failed 3 times in CI. https://github.com/pulumi/pulumi/actions/runs/3246806849/jobs/5326028122 |
bors cancel |
Canceled. |
@AaronFriel can't seem to decipher why CI is failing or why it would fail in regard to this change, will just retry before digging further into it |
bors merge |
10995: [import] Handle importing resource properties of type Union r=Zaid-Ajaj a=Zaid-Ajaj <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes #10934 ## Checklist <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Service, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. --> Co-authored-by: Zaid Ajaj <zaid.naom@gmail.com>
Build failed: |
bors retry |
Build succeeded: |
Description
Fixes #10934
Checklist
make changelog
and committed thechangelog/pending/<file>
documenting my change