From 7d61a7ca2bcd928a56f57122e35a0413e6fde9dc Mon Sep 17 00:00:00 2001 From: Venelin Date: Tue, 16 Jul 2024 12:20:49 +0300 Subject: [PATCH 1/6] pf integration tests --- pf/tests/provider_schema_test.go | 29 ++++++++++ pf/tests/schemas.go | 4 +- pf/tests/util.go | 95 +++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 pf/tests/provider_schema_test.go diff --git a/pf/tests/provider_schema_test.go b/pf/tests/provider_schema_test.go new file mode 100644 index 000000000..f0917b53f --- /dev/null +++ b/pf/tests/provider_schema_test.go @@ -0,0 +1,29 @@ +package tfbridgetests + +import ( + "testing" + + pschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" + rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/pulumi/pulumi-terraform-bridge/pf/tests/internal/providerbuilder" +) + +func TestBasic(t *testing.T) { + provBuilder := providerbuilder.Provider{ + TypeName: "prov", + Version: "0.0.1", + ProviderSchema: pschema.Schema{}, + AllResources: []providerbuilder.Resource{ + { + Name: "test_res", + ResourceSchema: rschema.Schema{}, + }, + }, + } + + prov := BridgedProvider(t, &provBuilder) + + program := `` + + PulCheck(t, prov, program) +} diff --git a/pf/tests/schemas.go b/pf/tests/schemas.go index 2e6e1f259..72f4e4163 100644 --- a/pf/tests/schemas.go +++ b/pf/tests/schemas.go @@ -32,7 +32,7 @@ import ( tfgen0 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen" ) -func genMetadata(t *testing.T, info tfbridge0.ProviderInfo) tfpf.ProviderMetadata { +func genMetadata(t T, info tfbridge0.ProviderInfo) tfpf.ProviderMetadata { generated, err := tfgen.GenerateSchema(context.Background(), tfgen.GenerateSchemaOptions{ ProviderInfo: info, DiagnosticsSink: testSink(t), @@ -41,7 +41,7 @@ func genMetadata(t *testing.T, info tfbridge0.ProviderInfo) tfpf.ProviderMetadat return generated.ProviderMetadata } -func testSink(t *testing.T) diag.Sink { +func testSink(t T) diag.Sink { var stdout, stderr bytes.Buffer testSink := diag.DefaultSink(&stdout, &stderr, diag.FormatOptions{ diff --git a/pf/tests/util.go b/pf/tests/util.go index c5d0313c7..2f975743b 100644 --- a/pf/tests/util.go +++ b/pf/tests/util.go @@ -16,17 +16,31 @@ package tfbridgetests import ( "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" "testing" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "gotest.tools/assert" + "github.com/pulumi/providertest/providers" + "github.com/pulumi/providertest/pulumitest" + "github.com/pulumi/providertest/pulumitest/opttest" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil" pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" + "github.com/pulumi/pulumi-terraform-bridge/pf/tests/internal/providerbuilder" "github.com/pulumi/pulumi-terraform-bridge/pf/tfbridge" tfbridge0 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/tokens" ) -func newProviderServer(t *testing.T, info tfbridge0.ProviderInfo) pulumirpc.ResourceProviderServer { +func newProviderServer(t T, info tfbridge0.ProviderInfo) pulumirpc.ResourceProviderServer { ctx := context.Background() meta := genMetadata(t, info) srv, err := tfbridge.NewProviderServer(ctx, nil, info, meta) @@ -41,3 +55,82 @@ func newMuxedProviderServer(t *testing.T, info tfbridge0.ProviderInfo) pulumirpc require.NoError(t, err) return p } + +// TODO: deduplicate? +// This is an experimental API. +type T interface { + Logf(string, ...any) + TempDir() string + Skip(...any) + require.TestingT + assert.TestingT + pulumitest.PT +} + +func skipUnlessLinux(t T) { + if ci, ok := os.LookupEnv("CI"); ok && ci == "true" && !strings.Contains(strings.ToLower(runtime.GOOS), "linux") { + t.Skip("Skipping on non-Linux platforms as our CI does not yet install Terraform CLI required for these tests") + } +} + +func BridgedProvider(t T, prov *providerbuilder.Provider) info.Provider { + shimProvider := tfbridge.ShimProvider(prov) + + provider := tfbridge0.ProviderInfo{ + P: shimProvider, + Name: prov.TypeName, + Version: "0.0.1", + MetadataInfo: &tfbridge0.MetadataInfo{}, + } + + makeToken := func(module, name string) (string, error) { + return tokens.MakeStandard(prov.TypeName)(module, name) + } + provider.MustComputeTokens(tokens.SingleModule(prov.TypeName, "index", makeToken)) + + return provider +} + +// This is an experimental API. +func StartPulumiProvider(t T, name, version string, providerInfo tfbridge0.ProviderInfo) (*rpcutil.ServeHandle, error) { + prov := newProviderServer(t, providerInfo) + + handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{ + Init: func(srv *grpc.Server) error { + pulumirpc.RegisterResourceProviderServer(srv, prov) + return nil + }, + }) + if err != nil { + return nil, fmt.Errorf("rpcutil.ServeWithOptions failed: %w", err) + } + + return &handle, nil +} + +// TODO: deduplicate? +// This is an experimental API. +func PulCheck(t T, bridgedProvider info.Provider, program string) *pulumitest.PulumiTest { + skipUnlessLinux(t) + puwd := t.TempDir() + p := filepath.Join(puwd, "Pulumi.yaml") + + err := os.WriteFile(p, []byte(program), 0o600) + require.NoError(t, err) + + opts := []opttest.Option{ + opttest.Env("DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "true"), + opttest.TestInPlace(), + opttest.SkipInstall(), + opttest.AttachProvider( + bridgedProvider.Name, + func(ctx context.Context, pt providers.PulumiTest) (providers.Port, error) { + handle, err := StartPulumiProvider(t, bridgedProvider.Name, bridgedProvider.Version, bridgedProvider) + require.NoError(t, err) + return providers.Port(handle.Port), nil + }, + ), + } + + return pulumitest.NewPulumiTest(t, puwd, opts...) +} From 7ac1022482104ef17f086490d7c1168b17b01d2d Mon Sep 17 00:00:00 2001 From: Venelin Date: Tue, 16 Jul 2024 17:56:55 +0300 Subject: [PATCH 2/6] simplify --- pf/tests/provider_schema_test.go | 4 ++-- pf/tests/schemas.go | 4 ++-- pf/tests/util.go | 25 ++++++------------------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/pf/tests/provider_schema_test.go b/pf/tests/provider_schema_test.go index f0917b53f..4e421dadb 100644 --- a/pf/tests/provider_schema_test.go +++ b/pf/tests/provider_schema_test.go @@ -21,9 +21,9 @@ func TestBasic(t *testing.T) { }, } - prov := BridgedProvider(t, &provBuilder) + prov := bridgedProvider(t, &provBuilder) program := `` - PulCheck(t, prov, program) + pulCheck(t, prov, program) } diff --git a/pf/tests/schemas.go b/pf/tests/schemas.go index 72f4e4163..2e6e1f259 100644 --- a/pf/tests/schemas.go +++ b/pf/tests/schemas.go @@ -32,7 +32,7 @@ import ( tfgen0 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen" ) -func genMetadata(t T, info tfbridge0.ProviderInfo) tfpf.ProviderMetadata { +func genMetadata(t *testing.T, info tfbridge0.ProviderInfo) tfpf.ProviderMetadata { generated, err := tfgen.GenerateSchema(context.Background(), tfgen.GenerateSchemaOptions{ ProviderInfo: info, DiagnosticsSink: testSink(t), @@ -41,7 +41,7 @@ func genMetadata(t T, info tfbridge0.ProviderInfo) tfpf.ProviderMetadata { return generated.ProviderMetadata } -func testSink(t T) diag.Sink { +func testSink(t *testing.T) diag.Sink { var stdout, stderr bytes.Buffer testSink := diag.DefaultSink(&stdout, &stderr, diag.FormatOptions{ diff --git a/pf/tests/util.go b/pf/tests/util.go index 2f975743b..5c78bd2d7 100644 --- a/pf/tests/util.go +++ b/pf/tests/util.go @@ -25,7 +25,6 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" - "gotest.tools/assert" "github.com/pulumi/providertest/providers" "github.com/pulumi/providertest/pulumitest" @@ -40,7 +39,7 @@ import ( "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/tokens" ) -func newProviderServer(t T, info tfbridge0.ProviderInfo) pulumirpc.ResourceProviderServer { +func newProviderServer(t *testing.T, info tfbridge0.ProviderInfo) pulumirpc.ResourceProviderServer { ctx := context.Background() meta := genMetadata(t, info) srv, err := tfbridge.NewProviderServer(ctx, nil, info, meta) @@ -56,24 +55,14 @@ func newMuxedProviderServer(t *testing.T, info tfbridge0.ProviderInfo) pulumirpc return p } -// TODO: deduplicate? -// This is an experimental API. -type T interface { - Logf(string, ...any) - TempDir() string - Skip(...any) - require.TestingT - assert.TestingT - pulumitest.PT -} -func skipUnlessLinux(t T) { +func skipUnlessLinux(t *testing.T) { if ci, ok := os.LookupEnv("CI"); ok && ci == "true" && !strings.Contains(strings.ToLower(runtime.GOOS), "linux") { t.Skip("Skipping on non-Linux platforms as our CI does not yet install Terraform CLI required for these tests") } } -func BridgedProvider(t T, prov *providerbuilder.Provider) info.Provider { +func bridgedProvider(t *testing.T, prov *providerbuilder.Provider) info.Provider { shimProvider := tfbridge.ShimProvider(prov) provider := tfbridge0.ProviderInfo{ @@ -91,8 +80,7 @@ func BridgedProvider(t T, prov *providerbuilder.Provider) info.Provider { return provider } -// This is an experimental API. -func StartPulumiProvider(t T, name, version string, providerInfo tfbridge0.ProviderInfo) (*rpcutil.ServeHandle, error) { +func startPulumiProvider(t *testing.T, name, version string, providerInfo tfbridge0.ProviderInfo) (*rpcutil.ServeHandle, error) { prov := newProviderServer(t, providerInfo) handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{ @@ -109,8 +97,7 @@ func StartPulumiProvider(t T, name, version string, providerInfo tfbridge0.Provi } // TODO: deduplicate? -// This is an experimental API. -func PulCheck(t T, bridgedProvider info.Provider, program string) *pulumitest.PulumiTest { +func pulCheck(t *testing.T, bridgedProvider info.Provider, program string) *pulumitest.PulumiTest { skipUnlessLinux(t) puwd := t.TempDir() p := filepath.Join(puwd, "Pulumi.yaml") @@ -125,7 +112,7 @@ func PulCheck(t T, bridgedProvider info.Provider, program string) *pulumitest.Pu opttest.AttachProvider( bridgedProvider.Name, func(ctx context.Context, pt providers.PulumiTest) (providers.Port, error) { - handle, err := StartPulumiProvider(t, bridgedProvider.Name, bridgedProvider.Version, bridgedProvider) + handle, err := startPulumiProvider(t, bridgedProvider.Name, bridgedProvider.Version, bridgedProvider) require.NoError(t, err) return providers.Port(handle.Port), nil }, From 130b3f87ce5179ba44717f0af5b92cbc56a87aa5 Mon Sep 17 00:00:00 2001 From: Venelin Date: Tue, 16 Jul 2024 18:20:19 +0300 Subject: [PATCH 3/6] simplify, add test --- ...ema_test.go => schema_and_program_test.go} | 23 ++++++++--- pf/tests/util.go | 40 ++++++++++++------- 2 files changed, 43 insertions(+), 20 deletions(-) rename pf/tests/{provider_schema_test.go => schema_and_program_test.go} (56%) diff --git a/pf/tests/provider_schema_test.go b/pf/tests/schema_and_program_test.go similarity index 56% rename from pf/tests/provider_schema_test.go rename to pf/tests/schema_and_program_test.go index 4e421dadb..8b947f1b7 100644 --- a/pf/tests/provider_schema_test.go +++ b/pf/tests/schema_and_program_test.go @@ -15,15 +15,28 @@ func TestBasic(t *testing.T) { ProviderSchema: pschema.Schema{}, AllResources: []providerbuilder.Resource{ { - Name: "test_res", - ResourceSchema: rschema.Schema{}, + Name: "test", + ResourceSchema: rschema.Schema{ + Attributes: map[string]rschema.Attribute{ + "s": rschema.StringAttribute{Optional: true}, + }, + }, }, }, } - prov := bridgedProvider(t, &provBuilder) + prov := bridgedProvider(&provBuilder) - program := `` + program := ` +name: test +runtime: yaml +resources: + mainRes: + type: prov:index:Test + properties: + s: "hello"` - pulCheck(t, prov, program) + pt := pulCheck(t, prov, program) + + pt.Up() } diff --git a/pf/tests/util.go b/pf/tests/util.go index 5c78bd2d7..50ff92bbc 100644 --- a/pf/tests/util.go +++ b/pf/tests/util.go @@ -19,10 +19,12 @@ import ( "fmt" "os" "path/filepath" - "runtime" - "strings" "testing" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -55,21 +57,31 @@ func newMuxedProviderServer(t *testing.T, info tfbridge0.ProviderInfo) pulumirpc return p } - -func skipUnlessLinux(t *testing.T) { - if ci, ok := os.LookupEnv("CI"); ok && ci == "true" && !strings.Contains(strings.ToLower(runtime.GOOS), "linux") { - t.Skip("Skipping on non-Linux platforms as our CI does not yet install Terraform CLI required for these tests") +func ensureProviderValid(prov *providerbuilder.Provider) { + for i := range prov.AllResources { + r := &prov.AllResources[i] + if r.ResourceSchema.Attributes["id"] == nil { + r.ResourceSchema.Attributes["id"] = rschema.StringAttribute{Computed: true} + } + if r.CreateFunc == nil { + r.CreateFunc = func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + resp.State = tfsdk.State(req.Config) + resp.State.SetAttribute(ctx, path.Root("id"), "test-id") + } + } } + } -func bridgedProvider(t *testing.T, prov *providerbuilder.Provider) info.Provider { +func bridgedProvider(prov *providerbuilder.Provider) info.Provider { + ensureProviderValid(prov) shimProvider := tfbridge.ShimProvider(prov) provider := tfbridge0.ProviderInfo{ - P: shimProvider, - Name: prov.TypeName, - Version: "0.0.1", - MetadataInfo: &tfbridge0.MetadataInfo{}, + P: shimProvider, + Name: prov.TypeName, + Version: "0.0.1", + MetadataInfo: &tfbridge0.MetadataInfo{}, } makeToken := func(module, name string) (string, error) { @@ -80,7 +92,7 @@ func bridgedProvider(t *testing.T, prov *providerbuilder.Provider) info.Provider return provider } -func startPulumiProvider(t *testing.T, name, version string, providerInfo tfbridge0.ProviderInfo) (*rpcutil.ServeHandle, error) { +func startPulumiProvider(t *testing.T, providerInfo tfbridge0.ProviderInfo) (*rpcutil.ServeHandle, error) { prov := newProviderServer(t, providerInfo) handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{ @@ -96,9 +108,7 @@ func startPulumiProvider(t *testing.T, name, version string, providerInfo tfbrid return &handle, nil } -// TODO: deduplicate? func pulCheck(t *testing.T, bridgedProvider info.Provider, program string) *pulumitest.PulumiTest { - skipUnlessLinux(t) puwd := t.TempDir() p := filepath.Join(puwd, "Pulumi.yaml") @@ -112,7 +122,7 @@ func pulCheck(t *testing.T, bridgedProvider info.Provider, program string) *pulu opttest.AttachProvider( bridgedProvider.Name, func(ctx context.Context, pt providers.PulumiTest) (providers.Port, error) { - handle, err := startPulumiProvider(t, bridgedProvider.Name, bridgedProvider.Version, bridgedProvider) + handle, err := startPulumiProvider(t, bridgedProvider) require.NoError(t, err) return providers.Port(handle.Port), nil }, From 2d242d8da75f3780c926ba8f24dc64b67afeeca3 Mon Sep 17 00:00:00 2001 From: Venelin Date: Wed, 17 Jul 2024 13:46:48 +0300 Subject: [PATCH 4/6] review --- pf/tests/util.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pf/tests/util.go b/pf/tests/util.go index 50ff92bbc..f6b4e850c 100644 --- a/pf/tests/util.go +++ b/pf/tests/util.go @@ -84,10 +84,7 @@ func bridgedProvider(prov *providerbuilder.Provider) info.Provider { MetadataInfo: &tfbridge0.MetadataInfo{}, } - makeToken := func(module, name string) (string, error) { - return tokens.MakeStandard(prov.TypeName)(module, name) - } - provider.MustComputeTokens(tokens.SingleModule(prov.TypeName, "index", makeToken)) + provider.MustComputeTokens(tokens.SingleModule(prov.TypeName, "index", tokens.MakeStandard(prov.TypeName))) return provider } From 34076ba33c5bb68f608acac742e897911c227d88 Mon Sep 17 00:00:00 2001 From: Venelin Date: Sun, 21 Jul 2024 16:00:21 +0300 Subject: [PATCH 5/6] test util additions --- .../internal/providerbuilder/build_resource.go | 10 +++++++++- pf/tests/util.go | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/pf/tests/internal/providerbuilder/build_resource.go b/pf/tests/internal/providerbuilder/build_resource.go index e50d9ca82..a7b6150dc 100644 --- a/pf/tests/internal/providerbuilder/build_resource.go +++ b/pf/tests/internal/providerbuilder/build_resource.go @@ -29,6 +29,7 @@ type Resource struct { ReadFunc func(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) UpdateFunc func(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) DeleteFunc func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) + ImportStateFunc func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) } func (r *Resource) Metadata(ctx context.Context, req resource.MetadataRequest, re *resource.MetadataResponse) { @@ -67,4 +68,11 @@ func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp r.DeleteFunc(ctx, req, resp) } -var _ resource.Resource = &Resource{} +func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + if r.ImportStateFunc == nil { + return + } + r.ImportStateFunc(ctx, req, resp) +} + +var _ resource.ResourceWithImportState = &Resource{} diff --git a/pf/tests/util.go b/pf/tests/util.go index f6b4e850c..e5765f88d 100644 --- a/pf/tests/util.go +++ b/pf/tests/util.go @@ -24,6 +24,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -61,7 +63,12 @@ func ensureProviderValid(prov *providerbuilder.Provider) { for i := range prov.AllResources { r := &prov.AllResources[i] if r.ResourceSchema.Attributes["id"] == nil { - r.ResourceSchema.Attributes["id"] = rschema.StringAttribute{Computed: true} + r.ResourceSchema.Attributes["id"] = rschema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + } } if r.CreateFunc == nil { r.CreateFunc = func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -69,8 +76,12 @@ func ensureProviderValid(prov *providerbuilder.Provider) { resp.State.SetAttribute(ctx, path.Root("id"), "test-id") } } + if r.UpdateFunc == nil { + r.UpdateFunc = func(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.State = tfsdk.State(req.Config) + } + } } - } func bridgedProvider(prov *providerbuilder.Provider) info.Provider { From cb7a40c4d18155116b4531265b859fefbb684929 Mon Sep 17 00:00:00 2001 From: Venelin Date: Mon, 22 Jul 2024 18:00:04 +0300 Subject: [PATCH 6/6] lint --- pf/tests/internal/providerbuilder/build_resource.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pf/tests/internal/providerbuilder/build_resource.go b/pf/tests/internal/providerbuilder/build_resource.go index a7b6150dc..df8f83584 100644 --- a/pf/tests/internal/providerbuilder/build_resource.go +++ b/pf/tests/internal/providerbuilder/build_resource.go @@ -25,10 +25,10 @@ type Resource struct { Name string ResourceSchema schema.Schema - CreateFunc func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) - ReadFunc func(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) - UpdateFunc func(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) - DeleteFunc func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) + CreateFunc func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) + ReadFunc func(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) + UpdateFunc func(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) + DeleteFunc func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) ImportStateFunc func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) }