Skip to content

Commit

Permalink
Match upstream fix for Configure config to allow access to RawConfig …
Browse files Browse the repository at this point in the history
…by providers (#2263)

This matches an upstream fix in the terraform-plugin-sdk:
hashicorp/terraform-plugin-sdk#1271 for
hashicorp/terraform-plugin-sdk#1270

We fail to inherit it as we do not use the top-level GRPC methods but
hook in deeper.

The problem is that the `GetRawConfig` methods expect the `CtyValue` on
the `ResourceData` object to be filled

https://github.com/hashicorp/terraform-plugin-sdk/pull/1271/files#diff-7f0240b3a899f37961cbce63b21bcc4395864395501e3231cd0e0f15a11a9c47R599
This happens in
https://github.com/hashicorp/terraform-plugin-sdk/pull/1271/files#diff-cb044934d551a7bd462735476d13f6a21549a098d631f9ba5a2be806657d333fR595
but that is in the top-level GRPC method, which we do not use.

This PR aims to mimic the same for the bridge - when making the config
for the provider `Configure` method, we make sure to fill in the
`CtyValue`. As this is only done for `Configure` in the tf-plugin-sdk,
we match that.


To do that we add a new method to the `shim.Provider` interface
`NewProviderConfig`, in contrast with the old NewResourceConfig. This is
then used in the new schema.go function
`MakeTerraformConfigFromInputsWithOpts`, which has `ProviderConfig`
option, which is to be used by `Configure` and `CheckConfig`.


fixes #2262
  • Loading branch information
VenelinMartinov authored Jul 31, 2024
1 parent 0a30ea8 commit 6b95c60
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 3 deletions.
3 changes: 3 additions & 0 deletions pf/internal/schemashim/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ func (p *SchemaOnlyProvider) NewResourceConfig(context.Context, map[string]inter
panic("schemaOnlyProvider does not implement runtime operation ResourceConfig")
}

func (p *SchemaOnlyProvider) NewProviderConfig(context.Context, map[string]interface{}) shim.ResourceConfig {
panic("schemaOnlyProvider does not implement runtime operation ProviderConfig")
}
func (p *SchemaOnlyProvider) IsSet(context.Context, interface{}) ([]interface{}, bool) {
panic("schemaOnlyProvider does not implement runtime operation IsSet")
}
Expand Down
4 changes: 4 additions & 0 deletions pf/proto/unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (Provider) NewResourceConfig(ctx context.Context, object map[string]interfa
panic("Unimplemented")
}

func (Provider) NewProviderConfig(ctx context.Context, object map[string]interface{}) shim.ResourceConfig {
panic("Unimplemented")
}

// Checks if a value is representing a Set, and unpacks its elements on success.
func (Provider) IsSet(ctx context.Context, v interface{}) ([]interface{}, bool) {
panic("Unimplemented")
Expand Down
261 changes: 261 additions & 0 deletions pkg/tests/schema_pulumi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hexops/autogold/v2"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/internal/pulcheck"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/internal/tfcheck"
"github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview"
"github.com/pulumi/pulumi/sdk/v3/go/auto/optrefresh"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -1508,3 +1509,263 @@ runtime: yaml
})
}
}

func TestConfigureGetRawConfigDoesNotPanic(t *testing.T) {
// Regression test for [pulumi/pulumi-terraform-bridge#2262]
getOkExists := func(d *schema.ResourceData, key string) (interface{}, bool) {
v := d.GetRawConfig().GetAttr(key)
if v.IsNull() {
return nil, false
}
return d.Get(key), true
}
resMap := map[string]*schema.Resource{
"prov_test": {
Schema: map[string]*schema.Schema{
"test": {
Type: schema.TypeString,
Optional: true,
},
},
},
}

runConfigureTest := func(t *testing.T, configPresent bool) {
tfp := &schema.Provider{
ResourcesMap: resMap,
Schema: map[string]*schema.Schema{
"config": {
Type: schema.TypeString,
Optional: true,
},
},
ConfigureContextFunc: func(ctx context.Context, rd *schema.ResourceData) (interface{}, diag.Diagnostics) {
_, ok := getOkExists(rd, "config")
require.Equal(t, configPresent, ok, "Unexpected config value")
return nil, nil
},
}
bridgedProvider := pulcheck.BridgedProvider(t, "prov", tfp)
configVal := "val"
if !configPresent {
configVal = "null"
}
program := fmt.Sprintf(`
name: test
runtime: yaml
resources:
prov:
type: pulumi:providers:prov
defaultProvider: true
properties:
config: %s
mainRes:
type: prov:index:Test
properties:
test: "hello"
outputs:
testOut: ${mainRes.test}
`, configVal)
pt := pulcheck.PulCheck(t, bridgedProvider, program)
pt.Up()
}

t.Run("config exists", func(t *testing.T) {
runConfigureTest(t, true)
})

t.Run("config does not exist", func(t *testing.T) {
runConfigureTest(t, false)
})
}

// TODO[pulumi/pulumi-terraform-bridge#2274]: Move to actual cross-test suite once the plumbing is done
func TestConfigureCrossTest(t *testing.T) {
resMap := map[string]*schema.Resource{
"prov_test": {
Schema: map[string]*schema.Schema{
"test": {
Type: schema.TypeString,
Optional: true,
},
},
},
}

runTest := func(t *testing.T, sch map[string]*schema.Schema, pulumiProgram, tfProgram string) {
var tfRd *schema.ResourceData
var puRd *schema.ResourceData
_ = puRd // ignore unused warning
tfp := &schema.Provider{
ResourcesMap: resMap,
Schema: sch,
ConfigureContextFunc: func(ctx context.Context, rd *schema.ResourceData) (interface{}, diag.Diagnostics) {
if tfRd == nil {
tfRd = rd
} else {
puRd = rd
}

return nil, nil
},
}

tfdriver := tfcheck.NewTfDriver(t, t.TempDir(), "prov", tfp)
tfdriver.Write(t, tfProgram)
tfdriver.Plan(t)
require.NotNil(t, tfRd)
require.Nil(t, puRd)

bridgedProvider := pulcheck.BridgedProvider(t, "prov", tfp)

pt := pulcheck.PulCheck(t, bridgedProvider, pulumiProgram)
pt.Preview()
require.NotNil(t, puRd)
require.Equal(t, tfRd.GetRawConfig(), puRd.GetRawConfig())
}

t.Run("string attr", func(t *testing.T) {
runTest(t,
map[string]*schema.Schema{
"config": {
Type: schema.TypeString,
Optional: true,
},
},
`
name: test
runtime: yaml
resources:
prov:
type: pulumi:providers:prov
defaultProvider: true
properties:
config: val
mainRes:
type: prov:index:Test
properties:
test: "val"
`,
`
provider "prov" {
config = "val"
}
resource "prov_test" "test" {
test = "val"
}`)
})

t.Run("object block", func(t *testing.T) {
runTest(t,
map[string]*schema.Schema{
"config": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"prop": {
Type: schema.TypeString,
Optional: true,
},
},
},
MaxItems: 1,
},
},
`
name: test
runtime: yaml
resources:
prov:
type: pulumi:providers:prov
defaultProvider: true
properties:
config: {"prop": "val"}
mainRes:
type: prov:index:Test
properties:
test: "val"
`,
`
provider "prov" {
config {
prop = "val"
}
}
resource "prov_test" "test" {
test = "val"
}`)
})

t.Run("list config", func(t *testing.T) {
runTest(t,
map[string]*schema.Schema{
"config": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
`
name: test
runtime: yaml
resources:
prov:
type: pulumi:providers:prov
defaultProvider: true
properties:
configs: ["val"]
mainRes:
type: prov:index:Test
properties:
test: "val"
`,
`
provider "prov" {
config = ["val"]
}
resource "prov_test" "test" {
test = "val"
}`)
})

t.Run("set config", func(t *testing.T) {
runTest(t,
map[string]*schema.Schema{
"config": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
`
name: test
runtime: yaml
resources:
prov:
type: pulumi:providers:prov
defaultProvider: true
properties:
configs: ["val"]
mainRes:
type: prov:index:Test
properties:
test: "val"
`,
`
provider "prov" {
config = ["val"]
}
resource "prov_test" "test" {
test = "val"
}`)
})
}
2 changes: 1 addition & 1 deletion pkg/tfbridge/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ func buildTerraformConfig(ctx context.Context, p *Provider, vars resource.Proper
return nil, err
}

return MakeTerraformConfigFromInputs(ctx, p.tf, inputs), nil
return MakeTerraformConfigFromInputsWithOpts(ctx, p.tf, inputs, MakeTerraformInputsOptions{ProviderConfig: true}), nil
}

func validateProviderConfig(
Expand Down
17 changes: 15 additions & 2 deletions pkg/tfbridge/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -1272,12 +1272,25 @@ func makeConfig(v interface{}) interface{} {
}
}

type MakeTerraformInputsOptions struct {
ProviderConfig bool
}

func MakeTerraformConfigFromInputsWithOpts(
ctx context.Context, p shim.Provider, inputs map[string]interface{}, opts MakeTerraformInputsOptions,
) shim.ResourceConfig {
raw := makeConfig(inputs).(map[string]interface{})
if opts.ProviderConfig {
return p.NewProviderConfig(ctx, raw)
}
return p.NewResourceConfig(ctx, raw)
}

// MakeTerraformConfigFromInputs creates a new Terraform configuration object from a set of Terraform inputs.
func MakeTerraformConfigFromInputs(
ctx context.Context, p shim.Provider, inputs map[string]interface{},
) shim.ResourceConfig {
raw := makeConfig(inputs).(map[string]interface{})
return p.NewResourceConfig(ctx, raw)
return MakeTerraformConfigFromInputsWithOpts(ctx, p, inputs, MakeTerraformInputsOptions{})
}

type makeTerraformStateOptions struct {
Expand Down
6 changes: 6 additions & 0 deletions pkg/tfshim/schema/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ func (ProviderShim) NewResourceConfig(
panic("this provider is schema-only and does not support runtime operations")
}

func (ProviderShim) NewProviderConfig(
ctx context.Context, object map[string]interface{},
) shim.ResourceConfig {
panic("this provider is schema-only and does not support runtime operations")
}

func (ProviderShim) IsSet(ctx context.Context, v interface{}) ([]interface{}, bool) {
panic("this provider is schema-only and does not support runtime operations")
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/tfshim/sdk-v1/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,15 @@ func (p v1Provider) NewResourceConfig(
}}
}

func (p v1Provider) NewProviderConfig(
ctx context.Context, object map[string]interface{},
) shim.ResourceConfig {
return v1ResourceConfig{&terraform.ResourceConfig{
Raw: object,
Config: object,
}}
}

func (p v1Provider) IsSet(_ context.Context, v interface{}) ([]interface{}, bool) {
if set, ok := v.(*schema.Set); ok {
return set.List(), true
Expand Down
19 changes: 19 additions & 0 deletions pkg/tfshim/sdk-v2/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"

"github.com/golang/glog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
Expand Down Expand Up @@ -217,6 +218,24 @@ func (p v2Provider) NewResourceConfig(
}}
}

func (p v2Provider) NewProviderConfig(
_ context.Context, object map[string]interface{},
) shim.ResourceConfig {
tfConfig := &terraform.ResourceConfig{
Raw: object,
Config: object,
}
typ := schema.InternalMap(p.tf.Schema).CoreConfigSchema().ImpliedType()
ctyVal, err := recoverCtyValueOfObjectType(typ, object)
if err != nil {
glog.V(9).Infof("Failed to recover cty value of object type: %v, falling back to old behaviour", err)
return v2ResourceConfig{tfConfig}
}

tfConfig.CtyValue = ctyVal
return v2ResourceConfig{tfConfig}
}

func (p v2Provider) IsSet(_ context.Context, v interface{}) ([]interface{}, bool) {
if set, ok := v.(*schema.Set); ok {
return set.List(), true
Expand Down
1 change: 1 addition & 0 deletions pkg/tfshim/shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ type Provider interface {
NewDestroyDiff(ctx context.Context, t string, opts TimeoutOptions) InstanceDiff

NewResourceConfig(ctx context.Context, object map[string]interface{}) ResourceConfig
NewProviderConfig(ctx context.Context, object map[string]interface{}) ResourceConfig

// Checks if a value is representing a Set, and unpacks its elements on success.
IsSet(ctx context.Context, v interface{}) ([]interface{}, bool)
Expand Down
Loading

0 comments on commit 6b95c60

Please sign in to comment.