Skip to content

Commit

Permalink
feat: type check values based on pulumi schema
Browse files Browse the repository at this point in the history
This adds an initial type checking implementation to `pkg/tfbridge`. The
actually type checker is pretty generic and only has a dependency on
`pulumi/pulumi` so it would be possible to pull this out into a separate
library as a general type checker.

**Feature Support**
- [X] required properties
- [X] property names
- [X] `string` types
- [X] `number` types
- [X] `object` types
- [X] `array` types
- [X] `null` types
- [X] `bool` types
- [X] `output` types
- [X] `secret` types
- [ ] `asset` types
- [ ] `archive` types
- [ ] `resourceReference` types
- [ ] String Enums
- [ ] Number Enums
    - *It knows about enums, but it does not validate enum values since
      enums are not exhaustive*

Along with the type checker it will also build an error message that
contains the path to the property.

re #1328
  • Loading branch information
corymhall committed Mar 27, 2024
1 parent 90733a0 commit 1d44b17
Show file tree
Hide file tree
Showing 3 changed files with 2,284 additions and 5 deletions.
33 changes: 28 additions & 5 deletions pkg/tfbridge/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"time"
"unicode"

pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"

"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"

Expand Down Expand Up @@ -720,6 +722,7 @@ func (p *Provider) Configure(ctx context.Context,
func (p *Provider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
ctx = p.loggingContext(ctx, resource.URN(req.GetUrn()))
urn := resource.URN(req.GetUrn())
failures := []*pulumirpc.CheckFailure{}
t := urn.Type()
res, has := p.resources[t]
if !has {
Expand Down Expand Up @@ -759,6 +762,26 @@ func (p *Provider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pul
return nil, err
}

schemaMap, schemaInfos := res.TF.Schema(), res.Schema.GetFields()
if p.pulumiSchema != nil {
var schema pschema.PackageSpec
if err := json.Unmarshal(p.pulumiSchema, &schema); err != nil {
return nil, err
}
iv := NewInputValidator(urn, schema)
typeFailures := iv.ValidateInputs(news)
if typeFailures != nil {
for _, e := range *typeFailures {
pp := NewCheckFailurePath(schemaMap, schemaInfos, e.resourcePath)
cf := NewCheckFailure(MiscFailure, e.reason, &pp, urn, false, p.module, schemaMap, schemaInfos)
failures = append(failures, &pulumirpc.CheckFailure{
Reason: cf.Reason,
Property: string(cf.Property),
})
}
}
}

if check := res.Schema.PreCheckCallback; check != nil {
news, err = check(ctx, news, p.configValues.Copy())
if err != nil {
Expand All @@ -769,7 +792,7 @@ func (p *Provider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pul
tfname := res.TFName
inputs, _, err := makeTerraformInputsWithOptions(ctx,
&PulumiResource{URN: urn, Properties: news, Seed: req.RandomSeed},
p.configValues, olds, news, res.TF.Schema(), res.Schema.Fields,
p.configValues, olds, news, schemaMap, res.Schema.Fields,
makeTerraformInputsOptions{DisableTFDefaults: true})
if err != nil {
return nil, err
Expand All @@ -792,22 +815,22 @@ func (p *Provider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pul
}

// Now produce CheckFalures for any properties that failed verification.
failures := p.adaptCheckFailures(ctx, urn, false /*isProvider*/, res.TF.Schema(), res.Schema.GetFields(), errs)
failures = append(failures, p.adaptCheckFailures(ctx, urn, false /*isProvider*/, schemaMap, schemaInfos, errs)...)

// Now re-generate the inputs WITH the TF defaults
inputs, assets, err := MakeTerraformInputs(ctx,
&PulumiResource{URN: urn, Properties: news, Seed: req.RandomSeed},
p.configValues, olds, news, res.TF.Schema(), res.Schema.Fields)
p.configValues, olds, news, schemaMap, res.Schema.Fields)
if err != nil {
return nil, err
}

// After all is said and done, we need to go back and return only what got populated as a diff from the origin.
pinputs := MakeTerraformOutputs(
ctx, p.tf, inputs, res.TF.Schema(), res.Schema.Fields, assets, false, p.supportsSecrets,
ctx, p.tf, inputs, schemaMap, res.Schema.Fields, assets, false, p.supportsSecrets,
)

pinputsWithSecrets := MarkSchemaSecrets(ctx, res.TF.Schema(), res.Schema.Fields,
pinputsWithSecrets := MarkSchemaSecrets(ctx, schemaMap, res.Schema.Fields,
resource.NewObjectProperty(pinputs)).ObjectValue()

minputs, err := plugin.MarshalProperties(pinputsWithSecrets, plugin.MarshalOptions{
Expand Down
Loading

0 comments on commit 1d44b17

Please sign in to comment.