From ba04167d69349e217d47830e18a77caca493aed7 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Fri, 25 Aug 2023 17:10:53 -0400 Subject: [PATCH] internal/testing: Initial testsdk and testprovider packages (#166) Reference: https://github.com/hashicorp/terraform-plugin-testing/issues/151 Creates an internal terraform-plugin-go based SDK and declarative provider for unit testing for within this Go module. The new `helper/resource` tests should be equivalent to the existing terraform-plugin-sdk based `TestTest_TestStep_ExternalProviders_To_ProviderFactories` and `TestTest_TestStep_ProviderFactories_To_ExternalProviders` tests. --- helper/resource/teststep_providers_test.go | 111 +++ internal/testing/doc.go | 6 + internal/testing/testprovider/datasource.go | 38 + internal/testing/testprovider/doc.go | 6 + internal/testing/testprovider/provider.go | 75 ++ internal/testing/testprovider/resource.go | 88 ++ .../testing/testsdk/datasource/datasource.go | 41 + internal/testing/testsdk/datasource/doc.go | 5 + internal/testing/testsdk/doc.go | 6 + internal/testing/testsdk/provider/doc.go | 5 + internal/testing/testsdk/provider/provider.go | 51 ++ .../testsdk/providerserver/datasources.go | 24 + .../testing/testsdk/providerserver/doc.go | 5 + .../testsdk/providerserver/providerserver.go | 753 ++++++++++++++++++ .../testsdk/providerserver/resources.go | 24 + .../testing/testsdk/providerserver/schema.go | 57 ++ .../testing/testsdk/providerserver/tftypes.go | 65 ++ internal/testing/testsdk/resource/doc.go | 5 + internal/testing/testsdk/resource/resource.go | 106 +++ 19 files changed, 1471 insertions(+) create mode 100644 internal/testing/doc.go create mode 100644 internal/testing/testprovider/datasource.go create mode 100644 internal/testing/testprovider/doc.go create mode 100644 internal/testing/testprovider/provider.go create mode 100644 internal/testing/testprovider/resource.go create mode 100644 internal/testing/testsdk/datasource/datasource.go create mode 100644 internal/testing/testsdk/datasource/doc.go create mode 100644 internal/testing/testsdk/doc.go create mode 100644 internal/testing/testsdk/provider/doc.go create mode 100644 internal/testing/testsdk/provider/provider.go create mode 100644 internal/testing/testsdk/providerserver/datasources.go create mode 100644 internal/testing/testsdk/providerserver/doc.go create mode 100644 internal/testing/testsdk/providerserver/providerserver.go create mode 100644 internal/testing/testsdk/providerserver/resources.go create mode 100644 internal/testing/testsdk/providerserver/schema.go create mode 100644 internal/testing/testsdk/providerserver/tftypes.go create mode 100644 internal/testing/testsdk/resource/doc.go create mode 100644 internal/testing/testsdk/resource/resource.go diff --git a/helper/resource/teststep_providers_test.go b/helper/resource/teststep_providers_test.go index 114395578..8c96ea509 100644 --- a/helper/resource/teststep_providers_test.go +++ b/helper/resource/teststep_providers_test.go @@ -17,12 +17,16 @@ import ( "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" ) @@ -1142,6 +1146,53 @@ func TestTest_TestStep_ExternalProvidersAndProviderFactories_NonHashiCorpNamespa }) } +func TestTest_TestStep_ExternalProviders_To_ProtoV6ProviderFactories(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + Config: `resource "null_resource" "test" {}`, + ExternalProviders: map[string]ExternalProvider{ + "null": { + Source: "registry.terraform.io/hashicorp/null", + VersionConstraint: "3.1.1", + }, + }, + }, + { + Config: `resource "null_resource" "test" {}`, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "null": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "null_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "triggers", + Type: tftypes.Map{ElementType: tftypes.String}, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + }, + }, + }) +} + func TestTest_TestStep_ExternalProviders_To_ProviderFactories(t *testing.T) { t.Parallel() @@ -1406,6 +1457,66 @@ func TestTest_TestStep_ProtoV6ProviderFactories_Error(t *testing.T) { }) } +func TestTest_TestStep_ProtoV6ProviderFactories_To_ExternalProviders(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + Config: `resource "null_resource" "test" {}`, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "null": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "null_resource": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "triggers": tftypes.Map{ElementType: tftypes.String}, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "test"), + "triggers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "triggers", + Type: tftypes.Map{ElementType: tftypes.String}, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + }, + { + Config: `resource "null_resource" "test" {}`, + ExternalProviders: map[string]ExternalProvider{ + "null": { + Source: "registry.terraform.io/hashicorp/null", + }, + }, + }, + }, + }) +} + func TestTest_TestStep_ProviderFactories(t *testing.T) { t.Parallel() diff --git a/internal/testing/doc.go b/internal/testing/doc.go new file mode 100644 index 000000000..763953104 --- /dev/null +++ b/internal/testing/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package testing contains functionality and helpers for unit testing within +// this Go module. +package testing diff --git a/internal/testing/testprovider/datasource.go b/internal/testing/testprovider/datasource.go new file mode 100644 index 000000000..66b29e26b --- /dev/null +++ b/internal/testing/testprovider/datasource.go @@ -0,0 +1,38 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package testprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" +) + +var _ datasource.DataSource = DataSource{} + +type DataSource struct { + ReadResponse *datasource.ReadResponse + SchemaResponse *datasource.SchemaResponse + ValidateConfigResponse *datasource.ValidateConfigResponse +} + +func (d DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + if d.ReadResponse != nil { + resp.Diagnostics = d.ReadResponse.Diagnostics + resp.State = d.ReadResponse.State + } +} + +func (d DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + if d.SchemaResponse != nil { + resp.Diagnostics = d.SchemaResponse.Diagnostics + resp.Schema = d.SchemaResponse.Schema + } +} + +func (d DataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) { + if d.ValidateConfigResponse != nil { + resp.Diagnostics = d.ValidateConfigResponse.Diagnostics + } +} diff --git a/internal/testing/testprovider/doc.go b/internal/testing/testprovider/doc.go new file mode 100644 index 000000000..e997e521c --- /dev/null +++ b/internal/testing/testprovider/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package testprovider is a declarative provider for implementing unit testing +// within this Go module. +package testprovider diff --git a/internal/testing/testprovider/provider.go b/internal/testing/testprovider/provider.go new file mode 100644 index 000000000..6e7f9d9a7 --- /dev/null +++ b/internal/testing/testprovider/provider.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package testprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/provider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" +) + +var _ provider.Provider = Provider{} + +// Provider is a declarative provider implementation for unit testing in this +// Go module. +type Provider struct { + ConfigureResponse *provider.ConfigureResponse + DataSources map[string]DataSource + Resources map[string]Resource + SchemaResponse *provider.SchemaResponse + StopResponse *provider.StopResponse + ValidateConfigResponse *provider.ValidateConfigResponse +} + +func (p Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + if p.ConfigureResponse != nil { + resp.Diagnostics = p.ConfigureResponse.Diagnostics + } +} + +func (p Provider) DataSourcesMap() map[string]datasource.DataSource { + datasources := make(map[string]datasource.DataSource, len(p.DataSources)) + + for typeName, d := range p.DataSources { + datasources[typeName] = d + } + + return datasources +} + +func (p Provider) ResourcesMap() map[string]resource.Resource { + resources := make(map[string]resource.Resource, len(p.Resources)) + + for typeName, d := range p.Resources { + resources[typeName] = d + } + + return resources +} + +func (p Provider) Stop(ctx context.Context, req provider.StopRequest, resp *provider.StopResponse) { + if p.StopResponse != nil { + resp.Error = p.StopResponse.Error + } +} + +func (p Provider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + if p.SchemaResponse != nil { + resp.Diagnostics = p.SchemaResponse.Diagnostics + resp.Schema = p.SchemaResponse.Schema + } + + resp.Schema = &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{}, + } +} + +func (p Provider) ValidateConfig(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) { + if p.ValidateConfigResponse != nil { + resp.Diagnostics = p.ValidateConfigResponse.Diagnostics + } +} diff --git a/internal/testing/testprovider/resource.go b/internal/testing/testprovider/resource.go new file mode 100644 index 000000000..8421e54d1 --- /dev/null +++ b/internal/testing/testprovider/resource.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package testprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" +) + +var _ resource.Resource = Resource{} + +type Resource struct { + CreateResponse *resource.CreateResponse + DeleteResponse *resource.DeleteResponse + ImportStateResponse *resource.ImportStateResponse + + // Planning happens multiple ways during a single TestStep, so statically + // defining only the response is very problematic. + PlanChangeFunc func(context.Context, resource.PlanChangeRequest, *resource.PlanChangeResponse) + + ReadResponse *resource.ReadResponse + SchemaResponse *resource.SchemaResponse + UpdateResponse *resource.UpdateResponse + UpgradeStateResponse *resource.UpgradeStateResponse + ValidateConfigResponse *resource.ValidateConfigResponse +} + +func (r Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + if r.CreateResponse != nil { + resp.Diagnostics = r.CreateResponse.Diagnostics + resp.NewState = r.CreateResponse.NewState + } +} + +func (r Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + if r.DeleteResponse != nil { + resp.Diagnostics = r.DeleteResponse.Diagnostics + } +} + +func (r Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + if r.ImportStateResponse != nil { + resp.Diagnostics = r.ImportStateResponse.Diagnostics + resp.State = r.ImportStateResponse.State + } +} + +func (r Resource) PlanChange(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) { + if r.PlanChangeFunc != nil { + r.PlanChangeFunc(ctx, req, resp) + } +} + +func (r Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + if r.ReadResponse != nil { + resp.Diagnostics = r.ReadResponse.Diagnostics + resp.NewState = r.ReadResponse.NewState + } +} + +func (r Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + if r.SchemaResponse != nil { + resp.Diagnostics = r.SchemaResponse.Diagnostics + resp.Schema = r.SchemaResponse.Schema + } +} + +func (r Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + if r.UpdateResponse != nil { + resp.Diagnostics = r.UpdateResponse.Diagnostics + resp.NewState = r.UpdateResponse.NewState + } +} + +func (r Resource) UpgradeState(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + if r.UpgradeStateResponse != nil { + resp.Diagnostics = r.UpgradeStateResponse.Diagnostics + resp.UpgradedState = r.UpgradeStateResponse.UpgradedState + } +} + +func (r Resource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + if r.ValidateConfigResponse != nil { + resp.Diagnostics = r.ValidateConfigResponse.Diagnostics + } +} diff --git a/internal/testing/testsdk/datasource/datasource.go b/internal/testing/testsdk/datasource/datasource.go new file mode 100644 index 000000000..12200fb0c --- /dev/null +++ b/internal/testing/testsdk/datasource/datasource.go @@ -0,0 +1,41 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package datasource + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type DataSource interface { + Read(context.Context, ReadRequest, *ReadResponse) + Schema(context.Context, SchemaRequest, *SchemaResponse) + ValidateConfig(context.Context, ValidateConfigRequest, *ValidateConfigResponse) +} + +type ReadRequest struct { + Config tftypes.Value +} + +type ReadResponse struct { + Diagnostics []*tfprotov6.Diagnostic + State tftypes.Value +} + +type SchemaRequest struct{} + +type SchemaResponse struct { + Diagnostics []*tfprotov6.Diagnostic + Schema *tfprotov6.Schema +} + +type ValidateConfigRequest struct { + Config tftypes.Value +} + +type ValidateConfigResponse struct { + Diagnostics []*tfprotov6.Diagnostic +} diff --git a/internal/testing/testsdk/datasource/doc.go b/internal/testing/testsdk/datasource/doc.go new file mode 100644 index 000000000..6499dded5 --- /dev/null +++ b/internal/testing/testsdk/datasource/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package datasource provides testsdk handling of the data resource concept. +package datasource diff --git a/internal/testing/testsdk/doc.go b/internal/testing/testsdk/doc.go new file mode 100644 index 000000000..2ff15aaf5 --- /dev/null +++ b/internal/testing/testsdk/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package testsdk provides a lightweight terraform-plugin-go SDK for +// implementing unit testing within this Go module. +package testsdk diff --git a/internal/testing/testsdk/provider/doc.go b/internal/testing/testsdk/provider/doc.go new file mode 100644 index 000000000..d0457580a --- /dev/null +++ b/internal/testing/testsdk/provider/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package provider provides testsdk handling of the provider concept. +package provider diff --git a/internal/testing/testsdk/provider/provider.go b/internal/testing/testsdk/provider/provider.go new file mode 100644 index 000000000..82c65b9f4 --- /dev/null +++ b/internal/testing/testsdk/provider/provider.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" +) + +type Provider interface { + Configure(context.Context, ConfigureRequest, *ConfigureResponse) + DataSourcesMap() map[string]datasource.DataSource + ResourcesMap() map[string]resource.Resource + Schema(context.Context, SchemaRequest, *SchemaResponse) + Stop(context.Context, StopRequest, *StopResponse) + ValidateConfig(context.Context, ValidateConfigRequest, *ValidateConfigResponse) +} + +type ConfigureRequest struct { + Config tftypes.Value +} + +type ConfigureResponse struct { + Diagnostics []*tfprotov6.Diagnostic +} + +type SchemaRequest struct{} + +type SchemaResponse struct { + Diagnostics []*tfprotov6.Diagnostic + Schema *tfprotov6.Schema +} + +type StopRequest struct{} + +type StopResponse struct { + Error error +} + +type ValidateConfigRequest struct { + Config tftypes.Value +} + +type ValidateConfigResponse struct { + Diagnostics []*tfprotov6.Diagnostic +} diff --git a/internal/testing/testsdk/providerserver/datasources.go b/internal/testing/testsdk/providerserver/datasources.go new file mode 100644 index 000000000..ea1c74068 --- /dev/null +++ b/internal/testing/testsdk/providerserver/datasources.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package providerserver + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/provider" +) + +func ProviderDataSource(p provider.Provider, typeName string) (datasource.DataSource, *tfprotov6.Diagnostic) { + d, ok := p.DataSourcesMap()[typeName] + + if !ok { + return nil, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Missing Data Source Type", + Detail: "The provider does not define the data source type: " + typeName, + } + } + + return d, nil +} diff --git a/internal/testing/testsdk/providerserver/doc.go b/internal/testing/testsdk/providerserver/doc.go new file mode 100644 index 000000000..edf1d2cdb --- /dev/null +++ b/internal/testing/testsdk/providerserver/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package providerserver provides testsdk handling of serving a provider. +package providerserver diff --git a/internal/testing/testsdk/providerserver/providerserver.go b/internal/testing/testsdk/providerserver/providerserver.go new file mode 100644 index 000000000..b3665c6ca --- /dev/null +++ b/internal/testing/testsdk/providerserver/providerserver.go @@ -0,0 +1,753 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package providerserver + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/provider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" +) + +var _ tfprotov6.ProviderServer = ProviderServer{} + +// NewProviderServer returns a lightweight protocol version 6 provider server +// for consumption with ProtoV6ProviderFactories. +func NewProviderServer(p provider.Provider) func() (tfprotov6.ProviderServer, error) { + return NewProviderServerWithError(p, nil) +} + +// NewProviderServerWithError returns a lightweight protocol version 6 provider +// server and an associated error for consumption with ProtoV6ProviderFactories. +func NewProviderServerWithError(p provider.Provider, err error) func() (tfprotov6.ProviderServer, error) { + providerServer := ProviderServer{ + Provider: p, + } + + return func() (tfprotov6.ProviderServer, error) { + return providerServer, err + } +} + +// ProviderServer is a lightweight protocol version 6 provider server which +// is assumed to be well-behaved, e.g. does not return gRPC errors. +// +// This implementation intends to reduce the heaviest parts of +// terraform-plugin-go based provider development: +// +// - Converting *tfprotov6.DynamicValue to tftypes.Value using schema +// - Splitting ApplyResourceChange into Create/Update/Delete calls +// - Set PlanResourceChange null config values of Computed attributes to unknown +// - Roundtrip UpgradeResourceState with equal schema version +// +// By default, the following data is copied automatically: +// +// - ApplyResourceChange (create): req.Config -> resp.NewState +// - ApplyResourceChange (delete): req.PlannedState -> resp.NewState +// - ApplyResourceChange (update): req.PlannedState -> resp.NewState +// - PlanResourceChange: req.ProposedNewState -> resp.PlannedState +// - ReadDataSource: req.Config -> resp.State +// - ReadResource: req.CurrentState -> resp.NewState +type ProviderServer struct { + Provider provider.Provider +} + +func (s ProviderServer) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { + resp := &tfprotov6.ApplyResourceChangeResponse{} + + r, diag := ProviderResource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + config, diag := DynamicValueToValue(schemaResp.Schema, req.Config) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + plannedState, diag := DynamicValueToValue(schemaResp.Schema, req.PlannedState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + priorState, diag := DynamicValueToValue(schemaResp.Schema, req.PriorState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + if priorState.IsNull() { + createReq := resource.CreateRequest{ + Config: config, + } + createResp := &resource.CreateResponse{ + NewState: config.Copy(), + } + + r.Create(ctx, createReq, createResp) + + resp.Diagnostics = createResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + newState, diag := ValuetoDynamicValue(schemaResp.Schema, createResp.NewState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.NewState = newState + } else if plannedState.IsNull() { + deleteReq := resource.DeleteRequest{ + PriorState: priorState, + } + deleteResp := &resource.DeleteResponse{} + + r.Delete(ctx, deleteReq, deleteResp) + + resp.Diagnostics = deleteResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + resp.NewState = req.PlannedState + } else { + updateReq := resource.UpdateRequest{ + Config: config, + PlannedState: plannedState, + PriorState: priorState, + } + updateResp := &resource.UpdateResponse{ + NewState: plannedState.Copy(), + } + + r.Update(ctx, updateReq, updateResp) + + resp.Diagnostics = updateResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + newState, diag := ValuetoDynamicValue(schemaResp.Schema, updateResp.NewState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.NewState = newState + } + + return resp, nil +} + +func (s ProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { + resp := &tfprotov6.ConfigureProviderResponse{} + + schemaReq := provider.SchemaRequest{} + schemaResp := &provider.SchemaResponse{} + + s.Provider.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + config, diag := DynamicValueToValue(schemaResp.Schema, req.Config) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + configureReq := provider.ConfigureRequest{ + Config: config, + } + configureResp := &provider.ConfigureResponse{} + + s.Provider.Configure(ctx, configureReq, configureResp) + + resp.Diagnostics = configureResp.Diagnostics + + return resp, nil +} + +func (s ProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { + providerReq := provider.SchemaRequest{} + providerResp := &provider.SchemaResponse{} + + s.Provider.Schema(ctx, providerReq, providerResp) + + resp := &tfprotov6.GetProviderSchemaResponse{ + DataSourceSchemas: map[string]*tfprotov6.Schema{}, + Diagnostics: providerResp.Diagnostics, + Provider: providerResp.Schema, + ResourceSchemas: map[string]*tfprotov6.Schema{}, + ServerCapabilities: &tfprotov6.ServerCapabilities{ + PlanDestroy: true, + }, + } + + for typeName, d := range s.Provider.DataSourcesMap() { + schemaReq := datasource.SchemaRequest{} + schemaResp := &datasource.SchemaResponse{} + + d.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = append(resp.Diagnostics, schemaResp.Diagnostics...) + + resp.DataSourceSchemas[typeName] = schemaResp.Schema + } + + for typeName, r := range s.Provider.ResourcesMap() { + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = append(resp.Diagnostics, schemaResp.Diagnostics...) + + resp.ResourceSchemas[typeName] = schemaResp.Schema + } + + return resp, nil +} + +func (s ProviderServer) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { + resp := &tfprotov6.ImportResourceStateResponse{} + + r, diag := ProviderResource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + importReq := resource.ImportStateRequest{ + ID: req.ID, + } + importResp := &resource.ImportStateResponse{} + + r.ImportState(ctx, importReq, importResp) + + resp.Diagnostics = importResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + if importResp.State.IsNull() { + return resp, nil + } + + state, diag := ValuetoDynamicValue(schemaResp.Schema, importResp.State) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.ImportedResources = []*tfprotov6.ImportedResource{ + { + State: state, + TypeName: req.TypeName, + }, + } + + return resp, nil +} + +func (s ProviderServer) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { + resp := &tfprotov6.PlanResourceChangeResponse{} + + r, diag := ProviderResource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + config, diag := DynamicValueToValue(schemaResp.Schema, req.Config) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + priorState, diag := DynamicValueToValue(schemaResp.Schema, req.PriorState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + proposedNewState, diag := DynamicValueToValue(schemaResp.Schema, req.ProposedNewState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + if !proposedNewState.IsNull() && !proposedNewState.Equal(priorState) { + modifiedProposedNewState, err := tftypes.Transform(proposedNewState, func(path *tftypes.AttributePath, val tftypes.Value) (tftypes.Value, error) { + // we are only modifying attributes, not the entire resource + if len(path.Steps()) < 1 { + return val, nil + } + + configValIface, _, err := tftypes.WalkAttributePath(config, path) + + if err != nil && err != tftypes.ErrInvalidStep { + return val, fmt.Errorf("error walking attribute/block path during unknown marking: %w", err) + } + + configVal, ok := configValIface.(tftypes.Value) + + if !ok { + return val, fmt.Errorf("unexpected type during unknown marking: %T", configValIface) + } + + if !configVal.IsNull() { + return val, nil + } + + attribute := SchemaAttributeAtPath(schemaResp.Schema, path) + + if attribute == nil { + return val, nil + } + + if !attribute.Computed { + return val, nil + } + + return tftypes.NewValue(val.Type(), tftypes.UnknownValue), nil + }) + + if err != nil { + diag := &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error Modifying ProposedNewState", + Detail: err.Error(), + } + + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil //nolint:nilerr // error via diagnostic, not gRPC + } + + proposedNewState = modifiedProposedNewState + } + + planReq := resource.PlanChangeRequest{ + Config: config, + PriorState: priorState, + ProposedNewState: proposedNewState, + } + planResp := &resource.PlanChangeResponse{ + PlannedState: proposedNewState.Copy(), + } + + r.PlanChange(ctx, planReq, planResp) + + resp.Diagnostics = planResp.Diagnostics + resp.RequiresReplace = planResp.RequiresReplace + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + plannedState, diag := ValuetoDynamicValue(schemaResp.Schema, planResp.PlannedState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.PlannedState = plannedState + + return resp, nil +} + +func (s ProviderServer) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) { + resp := &tfprotov6.ReadDataSourceResponse{} + + d, diag := ProviderDataSource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := datasource.SchemaRequest{} + schemaResp := &datasource.SchemaResponse{} + + d.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + config, diag := DynamicValueToValue(schemaResp.Schema, req.Config) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + readReq := datasource.ReadRequest{ + Config: config, + } + readResp := &datasource.ReadResponse{ + State: config.Copy(), + } + + d.Read(ctx, readReq, readResp) + + resp.Diagnostics = readResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + state, diag := ValuetoDynamicValue(schemaResp.Schema, readResp.State) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.State = state + + return resp, nil +} + +func (s ProviderServer) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { + resp := &tfprotov6.ReadResourceResponse{} + + r, diag := ProviderResource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + currentState, diag := DynamicValueToValue(schemaResp.Schema, req.CurrentState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + readReq := resource.ReadRequest{ + CurrentState: currentState, + } + readResp := &resource.ReadResponse{ + NewState: currentState.Copy(), + } + + r.Read(ctx, readReq, readResp) + + resp.Diagnostics = readResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + newState, diag := ValuetoDynamicValue(schemaResp.Schema, readResp.NewState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.NewState = newState + + return resp, nil +} + +func (s ProviderServer) StopProvider(ctx context.Context, req *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) { + providerReq := provider.StopRequest{} + providerResp := &provider.StopResponse{} + + s.Provider.Stop(ctx, providerReq, providerResp) + + resp := &tfprotov6.StopProviderResponse{} + + if providerResp.Error != nil { + resp.Error = providerResp.Error.Error() + } + + return resp, nil +} + +func (s ProviderServer) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { + resp := &tfprotov6.UpgradeResourceStateResponse{} + + r, diag := ProviderResource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + // Define options to be used when unmarshalling raw state. + // IgnoreUndefinedAttributes will silently skip over fields in the JSON + // that do not have a matching entry in the schema. + unmarshalOpts := tfprotov6.UnmarshalOpts{ + ValueFromJSONOpts: tftypes.ValueFromJSONOpts{ + IgnoreUndefinedAttributes: true, + }, + } + + // Terraform CLI can call UpgradeResourceState even if the stored state + // version matches the current schema. Presumably this is to account for + // the previous terraform-plugin-sdk implementation, which handled some + // state fixups on behalf of Terraform CLI. This will attempt to roundtrip + // the prior RawState to a state matching the current schema. + if req.Version == schemaResp.Schema.Version { + rawStateValue, err := req.RawState.UnmarshalWithOpts(schemaResp.Schema.ValueType(), unmarshalOpts) + + if err != nil { + diag := &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Unable to Read Previously Saved State for UpgradeResourceState", + Detail: "There was an error reading the saved resource state using the current resource schema: " + err.Error(), + } + + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil //nolint:nilerr // error via diagnostic, not gRPC + } + + upgradedState, diag := ValuetoDynamicValue(schemaResp.Schema, rawStateValue) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.UpgradedState = upgradedState + + return resp, nil + } + + upgradeReq := resource.UpgradeStateRequest{} + upgradeResp := &resource.UpgradeStateResponse{} + + r.UpgradeState(ctx, upgradeReq, upgradeResp) + + resp.Diagnostics = upgradeResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + upgradedState, diag := ValuetoDynamicValue(schemaResp.Schema, upgradeResp.UpgradedState) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + resp.UpgradedState = upgradedState + + return resp, nil +} + +func (s ProviderServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { + resp := &tfprotov6.ValidateDataResourceConfigResponse{} + + d, diag := ProviderDataSource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := datasource.SchemaRequest{} + schemaResp := &datasource.SchemaResponse{} + + d.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + config, diag := DynamicValueToValue(schemaResp.Schema, req.Config) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + validateReq := datasource.ValidateConfigRequest{ + Config: config, + } + validateResp := &datasource.ValidateConfigResponse{} + + d.ValidateConfig(ctx, validateReq, validateResp) + + resp.Diagnostics = validateResp.Diagnostics + + return resp, nil +} + +func (s ProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { + providerReq := provider.ValidateConfigRequest{} + providerResp := &provider.ValidateConfigResponse{} + + s.Provider.ValidateConfig(ctx, providerReq, providerResp) + + resp := &tfprotov6.ValidateProviderConfigResponse{ + Diagnostics: providerResp.Diagnostics, + PreparedConfig: req.Config, + } + + return resp, nil +} + +func (s ProviderServer) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { + resp := &tfprotov6.ValidateResourceConfigResponse{} + + r, diag := ProviderResource(s.Provider, req.TypeName) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + schemaReq := resource.SchemaRequest{} + schemaResp := &resource.SchemaResponse{} + + r.Schema(ctx, schemaReq, schemaResp) + + resp.Diagnostics = schemaResp.Diagnostics + + if len(resp.Diagnostics) > 0 { + return resp, nil + } + + config, diag := DynamicValueToValue(schemaResp.Schema, req.Config) + + if diag != nil { + resp.Diagnostics = append(resp.Diagnostics, diag) + + return resp, nil + } + + validateReq := resource.ValidateConfigRequest{ + Config: config, + } + validateResp := &resource.ValidateConfigResponse{} + + r.ValidateConfig(ctx, validateReq, validateResp) + + resp.Diagnostics = validateResp.Diagnostics + + return resp, nil +} diff --git a/internal/testing/testsdk/providerserver/resources.go b/internal/testing/testsdk/providerserver/resources.go new file mode 100644 index 000000000..21307dd84 --- /dev/null +++ b/internal/testing/testsdk/providerserver/resources.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package providerserver + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/provider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" +) + +func ProviderResource(p provider.Provider, typeName string) (resource.Resource, *tfprotov6.Diagnostic) { + r, ok := p.ResourcesMap()[typeName] + + if !ok { + return nil, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Missing Resource Type", + Detail: "The provider does not define the resource type: " + typeName, + } + } + + return r, nil +} diff --git a/internal/testing/testsdk/providerserver/schema.go b/internal/testing/testsdk/providerserver/schema.go new file mode 100644 index 000000000..42600be44 --- /dev/null +++ b/internal/testing/testsdk/providerserver/schema.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package providerserver + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func SchemaAttributeAtPath(schema *tfprotov6.Schema, path *tftypes.AttributePath) *tfprotov6.SchemaAttribute { + if schema == nil || schema.Block == nil || path == nil || len(path.Steps()) == 0 { + return nil + } + + steps := path.Steps() + nextStep := steps[0] + remainingSteps := steps[1:] + + switch nextStep := nextStep.(type) { + case tftypes.AttributeName: + for _, attribute := range schema.Block.Attributes { + if attribute == nil { + continue + } + + if attribute.Name != string(nextStep) { + continue + } + + if len(remainingSteps) == 0 { + return attribute + } + + // If needed, recursive attribute.NestedType handling would go here. + } + + for _, block := range schema.Block.BlockTypes { + if block == nil { + continue + } + + if block.TypeName != string(nextStep) { + continue + } + + // Blocks cannot be computed. + if len(remainingSteps) == 0 { + return nil + } + + // If needed, recursive block handling would go here. + } + } + + return nil +} diff --git a/internal/testing/testsdk/providerserver/tftypes.go b/internal/testing/testsdk/providerserver/tftypes.go new file mode 100644 index 000000000..4b9e07ec7 --- /dev/null +++ b/internal/testing/testsdk/providerserver/tftypes.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package providerserver + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func DynamicValueToValue(schema *tfprotov6.Schema, dynamicValue *tfprotov6.DynamicValue) (tftypes.Value, *tfprotov6.Diagnostic) { + if schema == nil { + diag := &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Unable to Convert DynamicValue", + Detail: "Converting the DynamicValue to Value returned an unexpected error: missing schema", + } + + return tftypes.NewValue(tftypes.Object{}, nil), diag + } + + if dynamicValue == nil { + return tftypes.NewValue(schema.ValueType(), nil), nil + } + + value, err := dynamicValue.Unmarshal(schema.ValueType()) + + if err != nil { + diag := &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Unable to Convert DynamicValue", + Detail: "Converting the DynamicValue to Value returned an unexpected error: " + err.Error(), + } + + return value, diag + } + + return value, nil +} + +func ValuetoDynamicValue(schema *tfprotov6.Schema, value tftypes.Value) (*tfprotov6.DynamicValue, *tfprotov6.Diagnostic) { + if schema == nil { + diag := &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Unable to Convert Value", + Detail: "Converting the Value to DynamicValue returned an unexpected error: missing schema", + } + + return nil, diag + } + + dynamicValue, err := tfprotov6.NewDynamicValue(schema.ValueType(), value) + + if err != nil { + diag := &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Unable to Convert Value", + Detail: "Converting the Value to DynamicValue returned an unexpected error: " + err.Error(), + } + + return &dynamicValue, diag + } + + return &dynamicValue, nil +} diff --git a/internal/testing/testsdk/resource/doc.go b/internal/testing/testsdk/resource/doc.go new file mode 100644 index 000000000..aa04d35c3 --- /dev/null +++ b/internal/testing/testsdk/resource/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package resource provides testsdk handling of the managed resource concept. +package resource diff --git a/internal/testing/testsdk/resource/resource.go b/internal/testing/testsdk/resource/resource.go new file mode 100644 index 000000000..f053b135f --- /dev/null +++ b/internal/testing/testsdk/resource/resource.go @@ -0,0 +1,106 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type Resource interface { + Create(context.Context, CreateRequest, *CreateResponse) + Delete(context.Context, DeleteRequest, *DeleteResponse) + ImportState(context.Context, ImportStateRequest, *ImportStateResponse) + PlanChange(context.Context, PlanChangeRequest, *PlanChangeResponse) + Read(context.Context, ReadRequest, *ReadResponse) + Schema(context.Context, SchemaRequest, *SchemaResponse) + Update(context.Context, UpdateRequest, *UpdateResponse) + UpgradeState(context.Context, UpgradeStateRequest, *UpgradeStateResponse) + ValidateConfig(context.Context, ValidateConfigRequest, *ValidateConfigResponse) +} + +type CreateRequest struct { + Config tftypes.Value +} + +type CreateResponse struct { + Diagnostics []*tfprotov6.Diagnostic + NewState tftypes.Value +} + +type DeleteRequest struct { + PriorState tftypes.Value +} + +type DeleteResponse struct { + Diagnostics []*tfprotov6.Diagnostic +} + +type ImportStateRequest struct { + ID string +} + +type ImportStateResponse struct { + Diagnostics []*tfprotov6.Diagnostic + State tftypes.Value +} + +type PlanChangeRequest struct { + Config tftypes.Value + PriorState tftypes.Value + ProposedNewState tftypes.Value +} + +type PlanChangeResponse struct { + Diagnostics []*tfprotov6.Diagnostic + PlannedState tftypes.Value + RequiresReplace []*tftypes.AttributePath +} + +type ReadRequest struct { + CurrentState tftypes.Value +} + +type ReadResponse struct { + Diagnostics []*tfprotov6.Diagnostic + NewState tftypes.Value +} + +type SchemaRequest struct{} + +type SchemaResponse struct { + Diagnostics []*tfprotov6.Diagnostic + Schema *tfprotov6.Schema +} + +type UpdateRequest struct { + Config tftypes.Value + PlannedState tftypes.Value + PriorState tftypes.Value +} + +type UpdateResponse struct { + Diagnostics []*tfprotov6.Diagnostic + NewState tftypes.Value +} + +type UpgradeStateRequest struct { + RawState *tfprotov6.RawState + Version int64 +} + +type UpgradeStateResponse struct { + Diagnostics []*tfprotov6.Diagnostic + UpgradedState tftypes.Value +} + +type ValidateConfigRequest struct { + Config tftypes.Value +} + +type ValidateConfigResponse struct { + Diagnostics []*tfprotov6.Diagnostic +}