Skip to content

Commit

Permalink
Fix #8306 (#406)
Browse files Browse the repository at this point in the history
* Fix issue with Const types not propagating to PCL via string labels

* Allow customizing the loader

* Fix the issue with attempting to load empty plugins; add tests

* Avoid loading aws through a real loader when testing

* Skip TestConvert on windows

* Generalize const types when inferring from default

* Use go mod tidy before testing
  • Loading branch information
t0yv0 authored Nov 3, 2021
1 parent 6b4c1ca commit 17edf2b
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 5 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ PROJECT := github.com/pulumi/pulumi-terraform-bridge
TESTPARALLELISM := 10

build::
go mod tidy
go build ${PROJECT}/v3/pkg/...
go build ${PROJECT}/v3/internal/...
go install ${PROJECT}/v3/cmd/...
Expand Down
9 changes: 6 additions & 3 deletions pkg/tf2pulumi/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ import (
"os"

"github.com/hashicorp/hcl/v2"
"github.com/spf13/afero"

"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tf2pulumi/il"
hcl2dotnet "github.com/pulumi/pulumi/pkg/v3/codegen/dotnet"
hcl2go "github.com/pulumi/pulumi/pkg/v3/codegen/go"
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
hcl2nodejs "github.com/pulumi/pulumi/pkg/v3/codegen/nodejs"
"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
hcl2python "github.com/pulumi/pulumi/pkg/v3/codegen/python"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/spf13/afero"

"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tf2pulumi/il"
)

const (
Expand Down Expand Up @@ -167,6 +168,8 @@ type Options struct {
PackageCache *pcl.PackageCache
// Optional plugin host.
PluginHost plugin.Host
// Optional Loader.
Loader schema.Loader
// Optional source for provider schema information.
ProviderInfoSource il.ProviderInfoSource
// Optional logger for diagnostic information.
Expand Down
139 changes: 139 additions & 0 deletions pkg/tf2pulumi/convert/model_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2016-2021, 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.

// This module provides helper functions to work with
// `github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model` that have not
// yet made it upstream.

package convert

import (
"fmt"
"github.com/hashicorp/hcl/v2"
//"strings"

"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
)

// Helps assign a type to a `Type="config"` block. It is currently not
// apparent from the types, but blocks carry 1 or 2 labels, with the
// second optional label encoding the type of the variable.
//
// Example block in HCL syntax:
//
// config foo "int" {
// default = 1
// }
//
// The type is decoded downstream (as in
// `github.com/pulumi/pulumi/pkg/codegen/pcl/binder.go`) in PCL
// processing. Currently we cannot guarantee that all posible
// `model.Type` values survive the string encoding and subsequent
// decoding. Thereore this function mimicks downstream decoding to see
// if the value turns around, and aborts processing if that is not the
// case.
func setConfigBlockType(block *model.Block, variableType model.Type) error {
if block.Type != "config" {
return fmt.Errorf("setConfigBlockType should only be used on block.Type='config' blocks")
}

if len(block.Labels) >= 2 {
return fmt.Errorf("setConfigBlockType refuses to overwrite block.Label[1]")
}

tempScope := model.TypeScope.Push(nil)
typeString := fmt.Sprintf("%v", variableType)
typeExpr, diags := model.BindExpressionText(
typeString,
tempScope,
hcl.Pos{})

if typeExpr == nil || diags.HasErrors() {
return fmt.Errorf(
"Type %s prints as '%s' but cannot be parsed back. Diagnostics: %v",
variableType.String(),
typeString,
diags)
}

if variableType.String() != typeExpr.Type().String() {
return fmt.Errorf(
"Type T1=%s prints as '%s' which parses as T2=%s, expected T1=T2",
variableType.String(),
typeString,
typeExpr.Type(),
)
}

block.Labels = append(block.Labels, typeString)
return nil
}

// Substitutes `model.ConstType` with the underlying type.
func generalizeConstType(t model.Type) model.Type {
return typeTransform(t, func(t model.Type) model.Type {
switch sT := t.(type) {
case *model.ConstType:
return sT.Type
default:
return t
}
})
}

// Views `model.Type` as a recursive tree and transforms a given tree
// `t` by wrapping the `transform` around every node.
func typeTransform(t model.Type, transform func(model.Type) model.Type) model.Type {
rec := func(t model.Type) model.Type {
return typeTransform(t, transform)
}
recSlice := func(ts []model.Type) []model.Type {
var result []model.Type
for _, t := range ts {
result = append(result, rec(t))
}
return result
}
recMap := func(ts map[string]model.Type) map[string]model.Type {
result := map[string]model.Type{}
for k, t := range ts {
result[k] = rec(t)
}
return result
}
var t2 model.Type
switch sT := t.(type) {
case *model.ConstType:
t2 = model.NewConstType(rec(sT.Type), sT.Value)
case *model.ListType:
t2 = model.NewListType(rec(sT.ElementType))
case *model.MapType:
t2 = model.NewMapType(rec(sT.ElementType))
case *model.ObjectType:
t2 = model.NewObjectType(recMap(sT.Properties), sT.Annotations...)
case *model.OutputType:
t2 = model.NewOutputType(rec(sT.ElementType))
case *model.PromiseType:
t2 = model.NewPromiseType(rec(sT.ElementType))
case *model.SetType:
t2 = model.NewSetType(rec(sT.ElementType))
case *model.TupleType:
t2 = model.NewTupleType(recSlice(sT.ElementTypes)...)
case *model.UnionType:
t2 = model.NewUnionType(recSlice(sT.ElementTypes)...)
default:
t2 = t
}
return transform(t2)
}
71 changes: 71 additions & 0 deletions pkg/tf2pulumi/convert/model_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2016-2021, 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.

package convert

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/zclconf/go-cty/cty"

"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
)

func TestSetConfigBlockType(t *testing.T) {
check := func(name string, ty model.Type, expectError bool) {
t.Run(name, func(t *testing.T) {
block := &model.Block{Type: "config", Labels: []string{"x"}}
err := setConfigBlockType(block, ty)
if expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%v", ty), block.Labels[1])
}
})
}

checkOk := func(name string, ty model.Type) {
check(name, ty, false)
}

checkError := func(name string, ty model.Type) {
check(name, ty, true)
}

checkOk("intType", model.IntType)
checkOk("boolType", model.BoolType)
checkOk("objType", model.NewObjectType(map[string]model.Type{"foo": model.IntType}))

// below we expect errors - this may improve in the future and
// stop failing; including to demonstrate the reason for why
// `setConfigBlockType` exists

constType := model.NewConstType(model.IntType, cty.NumberIntVal(1))
checkError("constType", constType)

objTypeWithConst := model.NewObjectType(map[string]model.Type{"foo": constType})
checkError("objectTypeWithConstType", objTypeWithConst)
}

func TestGeneralizeConstType(t *testing.T) {
constType := model.NewConstType(model.IntType, cty.NumberIntVal(1))
assert.True(t, generalizeConstType(constType).Equals(model.IntType))

objType := model.NewObjectType(map[string]model.Type{"foo": model.IntType})
objTypeWithConst := model.NewObjectType(map[string]model.Type{"foo": constType})
assert.True(t, generalizeConstType(objTypeWithConst).Equals(objType))
}
22 changes: 20 additions & 2 deletions pkg/tf2pulumi/convert/tf12.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ func convertTF12(files []*syntax.File, opts Options) ([]*syntax.File, *pcl.Progr
if opts.SkipResourceTypechecking {
pulumiOptions = append(pulumiOptions, pcl.SkipResourceTypechecking)
}
if opts.Loader != nil {
pulumiOptions = append(pulumiOptions, pcl.Loader(opts.Loader))
}

// Bind the files into a module.
binder := &tf12binder{
Expand Down Expand Up @@ -711,7 +714,7 @@ func (b *tf12binder) bindVariable(v *variable) hcl.Diagnostics {
if typeDecl, hasType := block.Body.Attribute("type"); hasType {
variableType = typeDecl.Value.Type()
} else if defaultDecl, hasDefault := block.Body.Attribute("default"); hasDefault {
variableType = defaultDecl.Value.Type()
variableType = generalizeConstType(defaultDecl.Value.Type())
}

v.terraformType, v.block = variableType, block
Expand Down Expand Up @@ -897,7 +900,22 @@ func (b *tf12binder) genVariable(w io.Writer, v *variable) hcl.Diagnostics {
v.block.Type = "config"
v.block.Labels[0] = v.pulumiName
if v.terraformType != model.DynamicType {
v.block.Labels = append(v.block.Labels, fmt.Sprintf("%v", v.terraformType))
err := setConfigBlockType(v.block, v.terraformType)
if err != nil {
msg := fmt.Sprintf(`Ignoring inferred type for %s.
The default value implies that the variable has type '%s', but this type fails
to encode correctly. Error:
%v`,
v.pulumiName,
v.terraformType.String(),
err)
diagnostics = append(diagnostics, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: msg,
})
}
}

_, err := fmt.Fprintf(w, "%v", v.block)
Expand Down
Loading

0 comments on commit 17edf2b

Please sign in to comment.