2020-03-17 03:04:32 +00:00
|
|
|
// Copyright 2016-2020, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2021-09-30 03:11:56 +00:00
|
|
|
package pcl
|
2020-03-17 03:04:32 +00:00
|
|
|
|
|
|
|
import (
|
2023-12-20 01:20:42 +00:00
|
|
|
"fmt"
|
|
|
|
|
[go/program-gen] Fix union type type resolution in Go program generation (#16297)
# Description
Fixes https://github.com/pulumi/pulumi-azure-native/issues/1554
### Context
The problem here is that when we compute `InputType: model.Type` in
`pcl.Resource`, we map the types of input properties of resources from
`schema.Type` into `model.Type`. When one of these properties is a
`schema.UnionType` (union of objects to be exact), we map that _as is_
to `model.UnionType` which trips up Go program-gen as it doesn't know
how to reduce the type to the actual _selected_ object type based on the
resource inputs.
### Resolution
The way to fix this is not in Go program-gen, instead we _reduce_ the
computed union types during the binding phase into the actual object
types based on the resource inputs so that all program generators only
work against explicit objects rather than having to deal with unions of
objects
### Example:
```pcl
resource "example" "azure-native:eventgrid:EventSubscription" {
destination = {
endpointType = "EventHub"
resourceId = "example"
}
expirationTimeUtc = "example"
scope = "example"
}
```
Before:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: eventgrid.EventHubEventSubscriptionDestination{
EndpointType: "EventHub",
ResourceId: "example",
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
After:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: &eventgrid.EventHubEventSubscriptionDestinationArgs{
EndpointType: pulumi.String("EventHub"),
ResourceId: pulumi.String("example"),
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- 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 Cloud,
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
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-06-06 19:13:19 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2020-04-07 02:43:16 +00:00
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
2020-05-04 22:04:35 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2020-03-17 03:04:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func getResourceToken(node *Resource) (string, hcl.Range) {
|
2020-04-21 17:24:42 +00:00
|
|
|
return node.syntax.Labels[1], node.syntax.LabelRanges[1]
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) bindResource(node *Resource) hcl.Diagnostics {
|
2020-04-16 23:44:34 +00:00
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
|
|
|
typeDiags := b.bindResourceTypes(node)
|
|
|
|
diagnostics = append(diagnostics, typeDiags...)
|
|
|
|
|
|
|
|
bodyDiags := b.bindResourceBody(node)
|
|
|
|
diagnostics = append(diagnostics, bodyDiags...)
|
|
|
|
|
|
|
|
return diagnostics
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
|
|
|
|
2022-10-13 09:47:01 +00:00
|
|
|
func annotateAttributeValue(expr model.Expression, attributeType schema.Type) model.Expression {
|
|
|
|
if optionalType, ok := attributeType.(*schema.OptionalType); ok {
|
|
|
|
return annotateAttributeValue(expr, optionalType.ElementType)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch attrValue := expr.(type) {
|
|
|
|
case *model.ObjectConsExpression:
|
|
|
|
if schemaObjectType, ok := attributeType.(*schema.ObjectType); ok {
|
|
|
|
schemaProperties := make(map[string]schema.Type)
|
|
|
|
for _, schemaProperty := range schemaObjectType.Properties {
|
|
|
|
schemaProperties[schemaProperty.Name] = schemaProperty.Type
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range attrValue.Items {
|
2023-12-27 12:42:52 +00:00
|
|
|
// annotate the nested object properties
|
|
|
|
// here when the key is a literal such as { key = <inner value> }
|
2022-10-13 09:47:01 +00:00
|
|
|
keyLiteral, isLit := item.Key.(*model.LiteralValueExpression)
|
|
|
|
if isLit {
|
|
|
|
correspondingSchemaType, ok := schemaProperties[keyLiteral.Value.AsString()]
|
|
|
|
if ok {
|
|
|
|
item.Value = annotateAttributeValue(item.Value, correspondingSchemaType)
|
|
|
|
}
|
|
|
|
}
|
2023-12-27 12:42:52 +00:00
|
|
|
|
|
|
|
// here when the key is a quoted literal such as { "key" = <inner value> }
|
|
|
|
if templateExpression, ok := item.Key.(*model.TemplateExpression); ok && len(templateExpression.Parts) == 1 {
|
|
|
|
if literalValue, ok := templateExpression.Parts[0].(*model.LiteralValueExpression); ok {
|
|
|
|
if correspondingSchemaType, ok := schemaProperties[literalValue.Value.AsString()]; ok {
|
|
|
|
item.Value = annotateAttributeValue(item.Value, correspondingSchemaType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-13 09:47:01 +00:00
|
|
|
}
|
|
|
|
return attrValue.WithType(func(attrValueType model.Type) *model.ObjectConsExpression {
|
|
|
|
annotateObjectProperties(attrValueType, attributeType)
|
|
|
|
return attrValue
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrValue
|
|
|
|
|
|
|
|
case *model.TupleConsExpression:
|
|
|
|
if schemaArrayType, ok := attributeType.(*schema.ArrayType); ok {
|
|
|
|
elementType := schemaArrayType.ElementType
|
|
|
|
for _, arrayExpr := range attrValue.Expressions {
|
|
|
|
annotateAttributeValue(arrayExpr, elementType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrValue
|
|
|
|
case *model.FunctionCallExpression:
|
|
|
|
if attrValue.Name == IntrinsicConvert {
|
|
|
|
converterArg := attrValue.Args[0]
|
|
|
|
annotateAttributeValue(converterArg, attributeType)
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrValue
|
|
|
|
default:
|
|
|
|
return expr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func AnnotateAttributeValue(expr model.Expression, attributeType schema.Type) model.Expression {
|
|
|
|
return annotateAttributeValue(expr, attributeType)
|
|
|
|
}
|
|
|
|
|
|
|
|
func AnnotateResourceInputs(node *Resource) {
|
2023-06-14 17:02:56 +00:00
|
|
|
if node.Schema == nil {
|
|
|
|
// skip annotations for resource which don't have a schema
|
|
|
|
return
|
|
|
|
}
|
2022-10-13 09:47:01 +00:00
|
|
|
resourceProperties := make(map[string]*schema.Property)
|
|
|
|
for _, property := range node.Schema.Properties {
|
|
|
|
resourceProperties[property.Name] = property
|
|
|
|
}
|
|
|
|
|
|
|
|
// add type annotations to the attributes
|
|
|
|
// and their nested objects
|
|
|
|
for index := range node.Inputs {
|
|
|
|
attr := node.Inputs[index]
|
|
|
|
if property, ok := resourceProperties[attr.Name]; ok {
|
|
|
|
node.Inputs[index] = &model.Attribute{
|
|
|
|
Tokens: attr.Tokens,
|
|
|
|
Name: attr.Name,
|
|
|
|
Syntax: attr.Syntax,
|
|
|
|
Value: AnnotateAttributeValue(attr.Value, property.Type),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[go/program-gen] Fix union type type resolution in Go program generation (#16297)
# Description
Fixes https://github.com/pulumi/pulumi-azure-native/issues/1554
### Context
The problem here is that when we compute `InputType: model.Type` in
`pcl.Resource`, we map the types of input properties of resources from
`schema.Type` into `model.Type`. When one of these properties is a
`schema.UnionType` (union of objects to be exact), we map that _as is_
to `model.UnionType` which trips up Go program-gen as it doesn't know
how to reduce the type to the actual _selected_ object type based on the
resource inputs.
### Resolution
The way to fix this is not in Go program-gen, instead we _reduce_ the
computed union types during the binding phase into the actual object
types based on the resource inputs so that all program generators only
work against explicit objects rather than having to deal with unions of
objects
### Example:
```pcl
resource "example" "azure-native:eventgrid:EventSubscription" {
destination = {
endpointType = "EventHub"
resourceId = "example"
}
expirationTimeUtc = "example"
scope = "example"
}
```
Before:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: eventgrid.EventHubEventSubscriptionDestination{
EndpointType: "EventHub",
ResourceId: "example",
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
After:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: &eventgrid.EventHubEventSubscriptionDestinationArgs{
EndpointType: pulumi.String("EventHub"),
ResourceId: pulumi.String("example"),
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- 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 Cloud,
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
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-06-06 19:13:19 +00:00
|
|
|
// resolveUnionOfObjects takes an object expression and its corresponding schema union type,
|
|
|
|
// then tries to find out which type from the union is the one that matches the object expression.
|
|
|
|
// We do this based on the discriminator field in the object expression.
|
|
|
|
func resolveUnionOfObjects(objectExpr *model.ObjectConsExpression, union *schema.UnionType) schema.Type {
|
|
|
|
var discriminatorValue string
|
|
|
|
for _, item := range objectExpr.Items {
|
|
|
|
if key, ok := item.Key.(*model.LiteralValueExpression); ok {
|
|
|
|
if key.Value.AsString() == union.Discriminator {
|
|
|
|
if value, ok := item.Value.(*model.LiteralValueExpression); ok {
|
|
|
|
discriminatorValue = value.Value.AsString()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if value, ok := item.Value.(*model.TemplateExpression); ok && len(value.Parts) == 1 {
|
|
|
|
if literalValue, ok := value.Parts[0].(*model.LiteralValueExpression); ok {
|
|
|
|
discriminatorValue = literalValue.Value.AsString()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if discriminatorValue == "" {
|
|
|
|
return union
|
|
|
|
}
|
|
|
|
|
|
|
|
if correspondingTypeToken, ok := union.Mapping[discriminatorValue]; ok {
|
|
|
|
for _, schemaType := range union.ElementTypes {
|
|
|
|
if schemaObjectType, ok := codegen.UnwrapType(schemaType).(*schema.ObjectType); ok {
|
|
|
|
parsedTypeToken, err := tokens.ParseTypeToken(correspondingTypeToken)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedObjectToken, err := tokens.ParseTypeToken(schemaObjectType.Token)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if string(parsedTypeToken.Name()) == string(parsedObjectToken.Name()) {
|
|
|
|
// found the corresponding object type
|
|
|
|
return schemaObjectType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return union
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolveInputUnions will take input expressions and their corresponding schema type,
|
|
|
|
// if the schema type is a union of objects and the input is an object, we use the
|
|
|
|
// the discriminator field to determine which object type to use and reduce the union into
|
|
|
|
// just an object type. This way program generators can easily work with the object type
|
|
|
|
// directly instead of working out which object type they should be using based on the union
|
|
|
|
func (b *binder) resolveInputUnions(
|
|
|
|
inputs map[string]model.Expression,
|
|
|
|
inputProperties []*schema.Property,
|
|
|
|
) []*schema.Property {
|
|
|
|
resolvedProperties := make([]*schema.Property, len(inputProperties))
|
|
|
|
for i, property := range inputProperties {
|
|
|
|
resolvedType := property.Type
|
|
|
|
if value, ok := inputs[property.Name]; ok {
|
|
|
|
switch schemaType := codegen.UnwrapType(property.Type).(type) {
|
|
|
|
case *schema.UnionType:
|
|
|
|
if objectExpr, ok := value.(*model.ObjectConsExpression); ok {
|
|
|
|
resolvedType = resolveUnionOfObjects(objectExpr, schemaType)
|
|
|
|
switch resolvedType := resolvedType.(type) {
|
|
|
|
case *schema.ObjectType:
|
|
|
|
// found the corresponding object type for this PCL expression, resolve nested unions if any
|
|
|
|
nestedInputs := map[string]model.Expression{}
|
|
|
|
for _, item := range objectExpr.Items {
|
|
|
|
if key, ok := literalExprValue(item.Key); ok && key.Type().Equals(cty.String) {
|
|
|
|
nestedInputs[key.AsString()] = item.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b.resolveInputUnions(nestedInputs, resolvedType.Properties)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resolvedProperties[i] = &schema.Property{
|
|
|
|
Type: resolvedType,
|
|
|
|
Name: property.Name,
|
|
|
|
DeprecationMessage: property.DeprecationMessage,
|
|
|
|
ConstValue: property.ConstValue,
|
|
|
|
Secret: property.Secret,
|
|
|
|
Plain: property.Plain,
|
|
|
|
Language: property.Language,
|
|
|
|
Comment: property.Comment,
|
|
|
|
DefaultValue: property.DefaultValue,
|
|
|
|
ReplaceOnChanges: property.ReplaceOnChanges,
|
|
|
|
WillReplaceOnChanges: property.WillReplaceOnChanges,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolvedProperties
|
|
|
|
}
|
|
|
|
|
|
|
|
// rawResourceInputs returns the raw inputs for a resource. This is useful when we need to resolve unions of objects
|
|
|
|
// and reduce them to just an object when possible. The inputs of a resource contain the discriminator field of which
|
|
|
|
// the value is used to determine which object type to use and thus reduce unions into objects.
|
|
|
|
func (b *binder) rawResourceInputs(node *Resource) map[string]model.Expression {
|
|
|
|
inputs := map[string]model.Expression{}
|
|
|
|
scopes := newResourceScopes(b.root, node, nil, nil)
|
|
|
|
block, _ := model.BindBlock(node.syntax, scopes, b.tokens, b.options.modelOptions()...)
|
|
|
|
for _, item := range block.Body.Items {
|
|
|
|
switch item := item.(type) {
|
|
|
|
case *model.Attribute:
|
|
|
|
inputs[item.Name] = item.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return inputs
|
|
|
|
}
|
|
|
|
|
|
|
|
// reduceInputUnionTypes reduces the input types of a resource which are unions of objects
|
|
|
|
// into just an object when possible. We use the actual inputs of the resource to determine which type object we should
|
|
|
|
// use because objects in a union have a discriminator field which is used to determine which object type to use.
|
|
|
|
func (b *binder) reduceInputUnionTypes(node *Resource, inputProperties []*schema.Property) []*schema.Property {
|
|
|
|
inputs := b.rawResourceInputs(node)
|
|
|
|
return b.resolveInputUnions(inputs, inputProperties)
|
|
|
|
}
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
// bindResourceTypes binds the input and output types for a resource.
|
|
|
|
func (b *binder) bindResourceTypes(node *Resource) hcl.Diagnostics {
|
|
|
|
// Set the input and output types to dynamic by default.
|
2020-04-16 23:44:34 +00:00
|
|
|
node.InputType, node.OutputType = model.DynamicType, model.DynamicType
|
2020-03-17 03:04:32 +00:00
|
|
|
|
|
|
|
// Find the resource's schema.
|
|
|
|
token, tokenRange := getResourceToken(node)
|
2020-04-07 02:43:16 +00:00
|
|
|
pkg, module, name, diagnostics := DecomposeToken(token, tokenRange)
|
2020-03-17 03:04:32 +00:00
|
|
|
if diagnostics.HasErrors() {
|
|
|
|
return diagnostics
|
|
|
|
}
|
|
|
|
|
2023-06-14 17:02:56 +00:00
|
|
|
makeResourceDynamic := func() {
|
|
|
|
// make the inputs and outputs of the resource dynamic
|
|
|
|
node.Token = token
|
|
|
|
node.OutputType = model.DynamicType
|
|
|
|
inferredInputProperties := map[string]model.Type{}
|
|
|
|
for _, attr := range node.Inputs {
|
|
|
|
inferredInputProperties[attr.Name] = attr.Type()
|
|
|
|
}
|
|
|
|
node.InputType = model.NewObjectType(inferredInputProperties)
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
isProvider := false
|
2022-10-20 20:37:10 +00:00
|
|
|
if pkg == "pulumi" && module == "providers" {
|
|
|
|
pkg, isProvider = name, true
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
2022-08-19 17:27:05 +00:00
|
|
|
var pkgSchema *packageSchema
|
2020-04-07 02:43:16 +00:00
|
|
|
|
2022-10-10 23:01:53 +00:00
|
|
|
// It is important that we call `loadPackageSchema` instead of `getPackageSchema` here
|
[go/program-gen] Fix union type type resolution in Go program generation (#16297)
# Description
Fixes https://github.com/pulumi/pulumi-azure-native/issues/1554
### Context
The problem here is that when we compute `InputType: model.Type` in
`pcl.Resource`, we map the types of input properties of resources from
`schema.Type` into `model.Type`. When one of these properties is a
`schema.UnionType` (union of objects to be exact), we map that _as is_
to `model.UnionType` which trips up Go program-gen as it doesn't know
how to reduce the type to the actual _selected_ object type based on the
resource inputs.
### Resolution
The way to fix this is not in Go program-gen, instead we _reduce_ the
computed union types during the binding phase into the actual object
types based on the resource inputs so that all program generators only
work against explicit objects rather than having to deal with unions of
objects
### Example:
```pcl
resource "example" "azure-native:eventgrid:EventSubscription" {
destination = {
endpointType = "EventHub"
resourceId = "example"
}
expirationTimeUtc = "example"
scope = "example"
}
```
Before:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: eventgrid.EventHubEventSubscriptionDestination{
EndpointType: "EventHub",
ResourceId: "example",
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
After:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: &eventgrid.EventHubEventSubscriptionDestinationArgs{
EndpointType: pulumi.String("EventHub"),
ResourceId: pulumi.String("example"),
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- 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 Cloud,
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
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-06-06 19:13:19 +00:00
|
|
|
// because the version may be wrong. When the version should not be empty,
|
2022-10-10 23:01:53 +00:00
|
|
|
// `loadPackageSchema` will load the default version while `getPackageSchema` will
|
|
|
|
// simply fail. We can't give a populated version field since we have not processed
|
|
|
|
// the body, and thus the version yet.
|
2022-10-07 20:57:51 +00:00
|
|
|
pkgSchema, err := b.options.packageCache.loadPackageSchema(b.options.loader, pkg, "")
|
|
|
|
if err != nil {
|
|
|
|
e := unknownPackage(pkg, tokenRange)
|
|
|
|
e.Detail = err.Error()
|
2023-06-14 17:02:56 +00:00
|
|
|
|
|
|
|
if b.options.skipResourceTypecheck {
|
|
|
|
makeResourceDynamic()
|
|
|
|
return hcl.Diagnostics{asWarningDiagnostic(e)}
|
|
|
|
}
|
|
|
|
|
2022-10-07 20:57:51 +00:00
|
|
|
return hcl.Diagnostics{e}
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 22:49:36 +00:00
|
|
|
var res *schema.Resource
|
2020-04-07 02:43:16 +00:00
|
|
|
var inputProperties, properties []*schema.Property
|
2022-07-14 22:49:36 +00:00
|
|
|
if isProvider {
|
|
|
|
r, err := pkgSchema.schema.Provider()
|
|
|
|
if err != nil {
|
2023-06-14 17:02:56 +00:00
|
|
|
if b.options.skipResourceTypecheck {
|
|
|
|
makeResourceDynamic()
|
|
|
|
return diagnostics
|
|
|
|
}
|
2022-05-23 22:44:35 +00:00
|
|
|
return hcl.Diagnostics{resourceLoadError(token, err, tokenRange)}
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
2022-07-14 22:49:36 +00:00
|
|
|
res = r
|
2020-04-07 02:43:16 +00:00
|
|
|
} else {
|
2022-07-14 22:49:36 +00:00
|
|
|
r, tk, ok, err := pkgSchema.LookupResource(token)
|
2022-05-23 22:44:35 +00:00
|
|
|
if err != nil {
|
2023-06-14 17:02:56 +00:00
|
|
|
if b.options.skipResourceTypecheck {
|
|
|
|
makeResourceDynamic()
|
|
|
|
return diagnostics
|
|
|
|
}
|
|
|
|
|
2022-05-23 22:44:35 +00:00
|
|
|
return hcl.Diagnostics{resourceLoadError(token, err, tokenRange)}
|
2022-07-14 22:49:36 +00:00
|
|
|
} else if !ok {
|
2023-06-14 17:02:56 +00:00
|
|
|
if b.options.skipResourceTypecheck {
|
|
|
|
makeResourceDynamic()
|
|
|
|
return diagnostics
|
|
|
|
}
|
|
|
|
|
2022-07-14 22:49:36 +00:00
|
|
|
return hcl.Diagnostics{unknownResourceType(token, tokenRange)}
|
2022-05-23 22:44:35 +00:00
|
|
|
}
|
2022-07-14 22:49:36 +00:00
|
|
|
res = r
|
|
|
|
token = tk
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
2022-07-14 22:49:36 +00:00
|
|
|
node.Schema = res
|
|
|
|
inputProperties, properties = res.InputProperties, res.Properties
|
2020-03-17 03:04:32 +00:00
|
|
|
node.Token = token
|
|
|
|
|
|
|
|
// Create input and output types for the schema.
|
[go/program-gen] Fix union type type resolution in Go program generation (#16297)
# Description
Fixes https://github.com/pulumi/pulumi-azure-native/issues/1554
### Context
The problem here is that when we compute `InputType: model.Type` in
`pcl.Resource`, we map the types of input properties of resources from
`schema.Type` into `model.Type`. When one of these properties is a
`schema.UnionType` (union of objects to be exact), we map that _as is_
to `model.UnionType` which trips up Go program-gen as it doesn't know
how to reduce the type to the actual _selected_ object type based on the
resource inputs.
### Resolution
The way to fix this is not in Go program-gen, instead we _reduce_ the
computed union types during the binding phase into the actual object
types based on the resource inputs so that all program generators only
work against explicit objects rather than having to deal with unions of
objects
### Example:
```pcl
resource "example" "azure-native:eventgrid:EventSubscription" {
destination = {
endpointType = "EventHub"
resourceId = "example"
}
expirationTimeUtc = "example"
scope = "example"
}
```
Before:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: eventgrid.EventHubEventSubscriptionDestination{
EndpointType: "EventHub",
ResourceId: "example",
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
After:
```go
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := eventgrid.NewEventSubscription(ctx, "example", &eventgrid.EventSubscriptionArgs{
Destination: &eventgrid.EventHubEventSubscriptionDestinationArgs{
EndpointType: pulumi.String("EventHub"),
ResourceId: pulumi.String("example"),
},
ExpirationTimeUtc: pulumi.String("example"),
Scope: pulumi.String("example"),
})
if err != nil {
return err
}
return nil
})
```
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- 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 Cloud,
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
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-06-06 19:13:19 +00:00
|
|
|
// first reduce property types which are unions of objects into just an object when possible
|
|
|
|
inputObjectType := &schema.ObjectType{Properties: b.reduceInputUnionTypes(node, inputProperties)}
|
|
|
|
inputType := b.schemaTypeToType(inputObjectType)
|
2020-03-17 03:04:32 +00:00
|
|
|
|
|
|
|
outputProperties := map[string]model.Type{
|
|
|
|
"id": model.NewOutputType(model.StringType),
|
|
|
|
"urn": model.NewOutputType(model.StringType),
|
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
for _, prop := range properties {
|
2020-04-21 16:18:09 +00:00
|
|
|
outputProperties[prop.Name] = model.NewOutputType(b.schemaTypeToType(prop.Type))
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
2020-04-21 16:18:09 +00:00
|
|
|
outputType := model.NewObjectType(outputProperties, &schema.ObjectType{Properties: properties})
|
2020-03-17 03:04:32 +00:00
|
|
|
|
2020-04-16 23:44:34 +00:00
|
|
|
node.InputType, node.OutputType = inputType, outputType
|
2022-10-13 09:47:01 +00:00
|
|
|
|
2024-07-08 23:23:47 +00:00
|
|
|
findTransitivePackageReferences := func(schemaType schema.Type) {
|
|
|
|
if objectType, ok := schemaType.(*schema.ObjectType); ok && objectType.PackageReference != nil {
|
|
|
|
ref := objectType.PackageReference
|
|
|
|
if _, found := b.referencedPackages[ref.Name()]; !found {
|
|
|
|
b.referencedPackages[ref.Name()] = ref
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
codegen.VisitTypeClosure(inputProperties, findTransitivePackageReferences)
|
|
|
|
codegen.VisitTypeClosure(properties, findTransitivePackageReferences)
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
return diagnostics
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
type resourceScopes struct {
|
|
|
|
root *model.Scope
|
|
|
|
withRange *model.Scope
|
|
|
|
resource *Resource
|
|
|
|
}
|
|
|
|
|
|
|
|
func newResourceScopes(root *model.Scope, resource *Resource, rangeKey, rangeValue model.Type) model.Scopes {
|
|
|
|
scopes := &resourceScopes{
|
|
|
|
root: root,
|
|
|
|
withRange: root,
|
|
|
|
resource: resource,
|
|
|
|
}
|
2020-04-16 23:44:34 +00:00
|
|
|
if rangeValue != nil {
|
|
|
|
properties := map[string]model.Type{
|
|
|
|
"value": rangeValue,
|
|
|
|
}
|
|
|
|
if rangeKey != nil {
|
|
|
|
properties["key"] = rangeKey
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
scopes.withRange = root.Push(syntax.None)
|
|
|
|
scopes.withRange.Define("range", &model.Variable{
|
2020-04-16 23:44:34 +00:00
|
|
|
Name: "range",
|
|
|
|
VariableType: model.NewObjectType(properties),
|
2020-04-07 02:43:16 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return scopes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *resourceScopes) GetScopesForBlock(block *hclsyntax.Block) (model.Scopes, hcl.Diagnostics) {
|
|
|
|
if block.Type == "options" {
|
|
|
|
return &optionsScopes{root: s.root, resource: s.resource}, nil
|
|
|
|
}
|
|
|
|
return model.StaticScope(s.withRange), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *resourceScopes) GetScopeForAttribute(attr *hclsyntax.Attribute) (*model.Scope, hcl.Diagnostics) {
|
|
|
|
return s.withRange, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type optionsScopes struct {
|
|
|
|
root *model.Scope
|
|
|
|
resource *Resource
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *optionsScopes) GetScopesForBlock(block *hclsyntax.Block) (model.Scopes, hcl.Diagnostics) {
|
|
|
|
return model.StaticScope(s.root), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *optionsScopes) GetScopeForAttribute(attr *hclsyntax.Attribute) (*model.Scope, hcl.Diagnostics) {
|
|
|
|
if attr.Name == "ignoreChanges" {
|
2020-06-29 23:33:52 +00:00
|
|
|
obj, ok := model.ResolveOutputs(s.resource.InputType).(*model.ObjectType)
|
2020-04-07 02:43:16 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
scope := model.NewRootScope(syntax.None)
|
|
|
|
for k, t := range obj.Properties {
|
|
|
|
scope.Define(k, &ResourceProperty{
|
|
|
|
Path: hcl.Traversal{hcl.TraverseRoot{Name: k}},
|
|
|
|
PropertyType: t,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return scope, nil
|
|
|
|
}
|
|
|
|
return s.root, nil
|
|
|
|
}
|
|
|
|
|
2023-03-08 13:21:34 +00:00
|
|
|
func bindResourceOptions(options *model.Block) (*ResourceOptions, hcl.Diagnostics) {
|
|
|
|
resourceOptions := &ResourceOptions{}
|
2023-06-08 16:33:48 +00:00
|
|
|
var diagnostics hcl.Diagnostics
|
2023-03-08 13:21:34 +00:00
|
|
|
for _, item := range options.Body.Items {
|
|
|
|
switch item := item.(type) {
|
|
|
|
case *model.Attribute:
|
|
|
|
var t model.Type
|
|
|
|
switch item.Name {
|
|
|
|
case "range":
|
|
|
|
t = model.NewUnionType(model.BoolType, model.NumberType, model.NewListType(model.DynamicType),
|
|
|
|
model.NewMapType(model.DynamicType))
|
|
|
|
resourceOptions.Range = item.Value
|
|
|
|
case "parent":
|
|
|
|
t = model.DynamicType
|
|
|
|
resourceOptions.Parent = item.Value
|
|
|
|
case "provider":
|
|
|
|
t = model.DynamicType
|
|
|
|
resourceOptions.Provider = item.Value
|
|
|
|
case "dependsOn":
|
|
|
|
t = model.NewListType(model.DynamicType)
|
|
|
|
resourceOptions.DependsOn = item.Value
|
|
|
|
case "protect":
|
|
|
|
t = model.BoolType
|
|
|
|
resourceOptions.Protect = item.Value
|
|
|
|
case "retainOnDelete":
|
|
|
|
t = model.BoolType
|
|
|
|
resourceOptions.RetainOnDelete = item.Value
|
|
|
|
case "ignoreChanges":
|
|
|
|
t = model.NewListType(ResourcePropertyType)
|
|
|
|
resourceOptions.IgnoreChanges = item.Value
|
|
|
|
case "version":
|
|
|
|
t = model.StringType
|
|
|
|
resourceOptions.Version = item.Value
|
|
|
|
case "pluginDownloadURL":
|
|
|
|
t = model.StringType
|
|
|
|
resourceOptions.PluginDownloadURL = item.Value
|
2024-07-19 14:17:45 +00:00
|
|
|
case "deletedWith":
|
|
|
|
t = model.DynamicType
|
|
|
|
resourceOptions.DeletedWith = item.Value
|
2023-03-08 13:21:34 +00:00
|
|
|
default:
|
|
|
|
diagnostics = append(diagnostics, unsupportedAttribute(item.Name, item.Syntax.NameRange))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if model.InputType(t).ConversionFrom(item.Value.Type()) == model.NoConversion {
|
|
|
|
diagnostics = append(diagnostics, model.ExprNotConvertible(model.InputType(t), item.Value))
|
|
|
|
}
|
|
|
|
case *model.Block:
|
|
|
|
diagnostics = append(diagnostics, unsupportedBlock(item.Type, item.Syntax.TypeRange))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resourceOptions, diagnostics
|
|
|
|
}
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
// bindResourceBody binds the body of a resource.
|
|
|
|
func (b *binder) bindResourceBody(node *Resource) hcl.Diagnostics {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
|
2023-06-20 11:50:18 +00:00
|
|
|
// Allow for lenient traversal when we choose to skip resource type-checking.
|
|
|
|
node.LenientTraversal = b.options.skipResourceTypecheck
|
|
|
|
node.VariableType = node.OutputType
|
2020-04-07 02:43:16 +00:00
|
|
|
// If the resource has a range option, we need to know the type of the collection being ranged over. Pre-bind the
|
|
|
|
// range expression now, but ignore the diagnostics.
|
|
|
|
var rangeKey, rangeValue model.Type
|
2020-04-21 17:24:42 +00:00
|
|
|
for _, block := range node.syntax.Body.Blocks {
|
2020-04-16 23:44:34 +00:00
|
|
|
if block.Type == "options" {
|
|
|
|
if rng, hasRange := block.Body.Attributes["range"]; hasRange {
|
2020-04-30 20:22:24 +00:00
|
|
|
expr, _ := model.BindExpression(rng.Expr, b.root, b.tokens, b.options.modelOptions()...)
|
2020-05-04 22:04:35 +00:00
|
|
|
typ := model.ResolveOutputs(expr.Type())
|
|
|
|
|
|
|
|
resourceVar := &model.Variable{
|
|
|
|
Name: "r",
|
|
|
|
VariableType: node.VariableType,
|
|
|
|
}
|
2020-04-16 23:44:34 +00:00
|
|
|
switch {
|
2020-05-04 22:04:35 +00:00
|
|
|
case model.InputType(model.BoolType).ConversionFrom(typ) == model.SafeConversion:
|
|
|
|
condExpr := &model.ConditionalExpression{
|
|
|
|
Condition: expr,
|
|
|
|
TrueResult: model.VariableReference(resourceVar),
|
|
|
|
FalseResult: model.ConstantReference(&model.Constant{
|
|
|
|
Name: "null",
|
|
|
|
ConstantValue: cty.NullVal(cty.DynamicPseudoType),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
diags := condExpr.Typecheck(false)
|
2023-02-17 01:23:09 +00:00
|
|
|
contract.Assertf(len(diags) == 0, "failed to typecheck conditional expression: %v", diags)
|
2020-05-04 22:04:35 +00:00
|
|
|
|
|
|
|
node.VariableType = condExpr.Type()
|
2023-06-08 19:43:54 +00:00
|
|
|
case model.InputType(model.NumberType).ConversionFrom(typ) == model.SafeConversion:
|
2023-07-12 17:13:57 +00:00
|
|
|
functions := pulumiBuiltins(b.options)
|
2020-05-04 22:04:35 +00:00
|
|
|
rangeArgs := []model.Expression{expr}
|
2023-07-12 17:13:57 +00:00
|
|
|
rangeSig, _ := functions["range"].GetSignature(rangeArgs)
|
2020-05-04 22:04:35 +00:00
|
|
|
|
|
|
|
rangeExpr := &model.ForExpression{
|
|
|
|
ValueVariable: &model.Variable{
|
|
|
|
Name: "_",
|
|
|
|
VariableType: model.NumberType,
|
|
|
|
},
|
|
|
|
Collection: &model.FunctionCallExpression{
|
|
|
|
Name: "range",
|
|
|
|
Signature: rangeSig,
|
|
|
|
Args: rangeArgs,
|
|
|
|
},
|
|
|
|
Value: model.VariableReference(resourceVar),
|
|
|
|
}
|
|
|
|
diags := rangeExpr.Typecheck(false)
|
2023-02-17 01:23:09 +00:00
|
|
|
contract.Assertf(len(diags) == 0, "failed to typecheck range expression: %v", diags)
|
2020-05-04 22:04:35 +00:00
|
|
|
|
2022-12-07 15:24:02 +00:00
|
|
|
rangeValue = model.IntType
|
|
|
|
|
2020-05-04 22:04:35 +00:00
|
|
|
node.VariableType = rangeExpr.Type()
|
2020-04-16 23:44:34 +00:00
|
|
|
default:
|
2023-07-12 17:13:57 +00:00
|
|
|
strictCollectionType := !b.options.skipRangeTypecheck
|
|
|
|
rk, rv, diags := model.GetCollectionTypes(typ, rng.Range(), strictCollectionType)
|
2020-05-04 22:04:35 +00:00
|
|
|
rangeKey, rangeValue, diagnostics = rk, rv, append(diagnostics, diags...)
|
|
|
|
|
|
|
|
iterationExpr := &model.ForExpression{
|
|
|
|
ValueVariable: &model.Variable{
|
|
|
|
Name: "_",
|
|
|
|
VariableType: rangeValue,
|
|
|
|
},
|
2023-07-12 17:13:57 +00:00
|
|
|
Collection: expr,
|
|
|
|
Value: model.VariableReference(resourceVar),
|
|
|
|
StrictCollectionTypechecking: strictCollectionType,
|
2020-05-04 22:04:35 +00:00
|
|
|
}
|
|
|
|
diags = iterationExpr.Typecheck(false)
|
|
|
|
contract.Ignore(diags) // Any relevant diagnostics were reported by GetCollectionTypes.
|
|
|
|
|
|
|
|
node.VariableType = iterationExpr.Type()
|
2020-04-16 23:44:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
// Bind the resource's body.
|
2020-04-17 15:24:44 +00:00
|
|
|
scopes := newResourceScopes(b.root, node, rangeKey, rangeValue)
|
2020-04-30 20:22:24 +00:00
|
|
|
block, blockDiags := model.BindBlock(node.syntax, scopes, b.tokens, b.options.modelOptions()...)
|
2020-03-17 03:04:32 +00:00
|
|
|
diagnostics = append(diagnostics, blockDiags...)
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
var options *model.Block
|
2020-03-17 03:04:32 +00:00
|
|
|
for _, item := range block.Body.Items {
|
|
|
|
switch item := item.(type) {
|
|
|
|
case *model.Attribute:
|
2022-04-25 22:07:25 +00:00
|
|
|
if item.Name == LogicalNamePropertyKey {
|
|
|
|
logicalName, lDiags := getStringAttrValue(item)
|
|
|
|
if lDiags != nil {
|
|
|
|
diagnostics = diagnostics.Append(lDiags)
|
|
|
|
} else {
|
|
|
|
node.logicalName = logicalName
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2020-03-17 03:04:32 +00:00
|
|
|
node.Inputs = append(node.Inputs, item)
|
|
|
|
case *model.Block:
|
|
|
|
switch item.Type {
|
|
|
|
case "options":
|
2020-04-07 02:43:16 +00:00
|
|
|
if options != nil {
|
|
|
|
diagnostics = append(diagnostics, duplicateBlock(item.Type, item.Syntax.TypeRange))
|
|
|
|
} else {
|
|
|
|
options = item
|
|
|
|
}
|
2020-03-17 03:04:32 +00:00
|
|
|
default:
|
|
|
|
diagnostics = append(diagnostics, unsupportedBlock(item.Type, item.Syntax.TypeRange))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-20 01:20:42 +00:00
|
|
|
resourceProperties := make(map[string]schema.Type)
|
|
|
|
if node.Schema != nil {
|
|
|
|
for _, property := range node.Schema.Properties {
|
|
|
|
resourceProperties[property.Name] = property.Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
// Typecheck the attributes.
|
2022-11-15 01:00:09 +00:00
|
|
|
if objectType, ok := node.InputType.(*model.ObjectType); ok {
|
|
|
|
diag := func(d *hcl.Diagnostic) {
|
|
|
|
if b.options.skipResourceTypecheck && d.Severity == hcl.DiagError {
|
|
|
|
d.Severity = hcl.DiagWarning
|
|
|
|
}
|
|
|
|
diagnostics = append(diagnostics, d)
|
|
|
|
}
|
2020-03-17 03:04:32 +00:00
|
|
|
attrNames := codegen.StringSet{}
|
|
|
|
for _, attr := range node.Inputs {
|
|
|
|
attrNames.Add(attr.Name)
|
|
|
|
|
|
|
|
if typ, ok := objectType.Properties[attr.Name]; ok {
|
2023-05-11 14:28:16 +00:00
|
|
|
conversion := typ.ConversionFrom(attr.Value.Type())
|
|
|
|
if !conversion.Exists() {
|
2023-12-20 01:20:42 +00:00
|
|
|
if propertyType, ok := resourceProperties[attr.Name]; ok {
|
|
|
|
attributeRange := attr.Value.SyntaxNode().Range()
|
|
|
|
diag(&hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Subject: &attributeRange,
|
|
|
|
Detail: fmt.Sprintf("Cannot assign value %s to attribute of type %q for resource %q",
|
|
|
|
attr.Value.Type().Pretty().String(),
|
|
|
|
propertyType.String(),
|
|
|
|
node.Token),
|
|
|
|
})
|
|
|
|
}
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-11-15 01:00:09 +00:00
|
|
|
diag(unsupportedAttribute(attr.Name, attr.Syntax.NameRange))
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, k := range codegen.SortedKeys(objectType.Properties) {
|
2022-11-22 01:15:05 +00:00
|
|
|
typ := objectType.Properties[k]
|
|
|
|
if model.IsOptionalType(typ) || attrNames.Has(k) {
|
|
|
|
// The type is present or optional. No error.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if model.IsConstType(objectType.Properties[k]) {
|
|
|
|
// The type is const, so the value is implied. No error.
|
|
|
|
continue
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
2022-11-22 01:15:05 +00:00
|
|
|
diag(missingRequiredAttribute(k, block.Body.Syntax.MissingItemRange()))
|
2020-03-17 03:04:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
// Typecheck the options block.
|
|
|
|
if options != nil {
|
2023-03-08 13:21:34 +00:00
|
|
|
resourceOptions, optionsDiags := bindResourceOptions(options)
|
|
|
|
diagnostics = append(diagnostics, optionsDiags...)
|
2020-04-07 02:43:16 +00:00
|
|
|
node.Options = resourceOptions
|
|
|
|
}
|
2020-03-17 03:04:32 +00:00
|
|
|
|
2020-04-21 17:24:42 +00:00
|
|
|
node.Definition = block
|
2020-03-17 03:04:32 +00:00
|
|
|
return diagnostics
|
|
|
|
}
|