Skip to content

Commit

Permalink
Fix how the plugin-framework is used in VCR tests
Browse files Browse the repository at this point in the history
- This carries over the idea of configuring the PF provider using the SDK provider. The changes in the MuxedProviders func mimic changes already made in main.go.
- Remove unnecessary duplication of cached configs per test name; now one used regardless of PF/SDK
- Updates to some DestroyProducer functions so they access the cached SDK Config struct to get a client
- Remove GetFwTestProvider and the file containing it - this isn't needed.
  • Loading branch information
SarahFrench committed Oct 3, 2024
1 parent 4ba69e0 commit 1b0712f
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 117 deletions.
27 changes: 0 additions & 27 deletions mmv1/third_party/terraform/acctest/framework_test_utils.go.tmpl

This file was deleted.

1 change: 0 additions & 1 deletion mmv1/third_party/terraform/acctest/provider_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ var testAccProvider *schema.Provider

func init() {
configs = make(map[string]*transport_tpg.Config)
fwProviders = make(map[string]*frameworkTestProvider)
sources = make(map[string]VcrSource)
testAccProvider = provider.Provider()
TestAccProviders = map[string]*schema.Provider{
Expand Down
8 changes: 6 additions & 2 deletions mmv1/third_party/terraform/acctest/test_utils.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ func CheckDataSourceStateMatchesResourceStateWithIgnores(dataSourceName, resourc
func MuxedProviders(testName string) (func() tfprotov5.ProviderServer, error) {
ctx := context.Background()

// primary is the SDKv2 implementation of the provider
// If tests are run in VCR mode, the provider will use a cached config specific to the test name
primary := GetSDKProvider(testName)

providers := []func() tfprotov5.ProviderServer{
providerserver.NewProtocol5(NewFrameworkTestProvider(testName)), // framework provider
GetSDKProvider(testName).GRPCProvider, // sdk provider
primary.GRPCProvider, // sdk provider
providerserver.NewProtocol5(NewFrameworkTestProvider(testName, primary)), // framework provider
}

muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...)
Expand Down
98 changes: 20 additions & 78 deletions mmv1/third_party/terraform/acctest/vcr_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"testing"
"time"

"github.com/hashicorp/terraform-provider-google/google/fwmodels"
"github.com/hashicorp/terraform-provider-google/google/fwprovider"
tpgprovider "github.com/hashicorp/terraform-provider-google/google/provider"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
Expand All @@ -30,12 +29,9 @@ import (
"github.com/dnaeon/go-vcr/cassette"
"github.com/dnaeon/go-vcr/recorder"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"

fwDiags "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand All @@ -53,7 +49,6 @@ var configsLock = sync.RWMutex{}
var sourcesLock = sync.RWMutex{}

var configs map[string]*transport_tpg.Config
var fwProviders map[string]*frameworkTestProvider

var sources map[string]VcrSource

Expand Down Expand Up @@ -203,39 +198,6 @@ func closeRecorder(t *testing.T) {
delete(sources, t.Name())
sourcesLock.Unlock()
}

configsLock.RLock()
fwProvider, fwOk := fwProviders[t.Name()]
configsLock.RUnlock()
if fwOk {
// We did not cache the config if it does not use VCR
if !t.Failed() && IsVcrEnabled() {
// If a test succeeds, write new seed/yaml to files
err := fwProvider.Client.Transport.(*recorder.Recorder).Stop()
if err != nil {
t.Error(err)
}
envPath := os.Getenv("VCR_PATH")

sourcesLock.RLock()
vcrSource, ok := sources[t.Name()]
sourcesLock.RUnlock()
if ok {
err = writeSeedToFile(vcrSource.seed, vcrSeedFile(envPath, t.Name()))
if err != nil {
t.Error(err)
}
}
}
// Clean up test config
configsLock.Lock()
delete(fwProviders, t.Name())
configsLock.Unlock()

sourcesLock.Lock()
delete(sources, t.Name())
sourcesLock.Unlock()
}
}

func isReleaseDiffEnabled() bool {
Expand Down Expand Up @@ -319,6 +281,11 @@ func reformConfigWithProvider(config, provider string) string {
return string(resourceHeader.ReplaceAll(configBytes, providerReplacementBytes))
}

// HandleVCRConfiguration configures the recorder (github.com/dnaeon/go-vcr/recorder) used in the VCR test
// This includes:
// - Setting the recording/replaying mode
// - Determining the path to the file API interactions will be recorded to/read from
// - Determining the logic used to match requests against recorded HTTP interactions (see rec.SetMatcher)
func HandleVCRConfiguration(ctx context.Context, testName string, rndTripper http.RoundTripper, pollInterval time.Duration) (time.Duration, http.RoundTripper, fwDiags.Diagnostics) {
var diags fwDiags.Diagnostics
var vcrMode recorder.Mode
Expand Down Expand Up @@ -377,11 +344,11 @@ func HandleVCRConfiguration(ctx context.Context, testName string, rndTripper htt
if strings.Contains(contentType, "application/json") {
var reqJson, cassetteJson interface{}
if err := json.Unmarshal([]byte(reqBody), &reqJson); err != nil {
tflog.Debug(ctx, fmt.Sprintf("Failed to unmarshall request json: %v", err))
tflog.Debug(ctx, fmt.Sprintf("Failed to unmarshal request json: %v", err))
return false
}
if err := json.Unmarshal([]byte(i.Body), &cassetteJson); err != nil {
tflog.Debug(ctx, fmt.Sprintf("Failed to unmarshall cassette json: %v", err))
tflog.Debug(ctx, fmt.Sprintf("Failed to unmarshal cassette json: %v", err))
return false
}
return reflect.DeepEqual(reqJson, cassetteJson)
Expand All @@ -397,45 +364,32 @@ func HandleVCRConfiguration(ctx context.Context, testName string, rndTripper htt
// test versions of the provider that will call the same configure function, only append the VCR
// configuration to it.

func NewFrameworkTestProvider(testName string) *frameworkTestProvider {
func NewFrameworkTestProvider(testName string, primary *schema.Provider) *frameworkTestProvider {
return &frameworkTestProvider{
FrameworkProvider: fwprovider.FrameworkProvider{
Version: "test",
Primary: primary,
},
TestName: testName,
}
}

// frameworkTestProvider is a test version of the plugin-framework version of the provider
// that embeds FrameworkProvider whose configure function we can use
// the Configure function is overwritten in the framework_provider_test file
// frameworkTestProvider is a test version of the plugin-framework version of the provider.
// This facilitates overwriting the Configure function that's used during acceptance tests.
type frameworkTestProvider struct {
fwprovider.FrameworkProvider
TestName string
}

// Configure is here to overwrite the FrameworkProvider configure function for VCR testing
func (p *frameworkTestProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
p.FrameworkProvider.Configure(ctx, req, resp)
if IsVcrEnabled() {
if resp.Diagnostics.HasError() {
return
}

var diags fwDiags.Diagnostics
p.PollInterval, p.Client.Transport, diags = HandleVCRConfiguration(ctx, p.TestName, p.Client.Transport, p.PollInterval)
if diags.HasError() {
resp.Diagnostics.Append(diags...)
return
}

configsLock.Lock()
fwProviders[p.TestName] = p
configsLock.Unlock()
return
} else {
tflog.Debug(ctx, "VCR_PATH or VCR_MODE not set, skipping VCR")
}
// When creating the frameworkTestProvider struct we took in a pointer to the the SDK provider.
// That SDK provider was configured using `GetSDKProvider` and `getCachedConfig`, so this framework provider will also
// use a cached client for the correct test name.
// No extra logic is required here, but in future when the SDK provider is removed this function will
// need to be updated with logic similar to that in `GetSDKProvider`.
p.FrameworkProvider.Configure(ctx, req, resp)
}

// DataSources overrides the provider's DataSources function so that we can append test-specific data sources to the list of data sources on the provider.
Expand All @@ -446,21 +400,9 @@ func (p *frameworkTestProvider) DataSources(ctx context.Context) []func() dataso
return ds
}

func configureApiClient(ctx context.Context, p *fwprovider.FrameworkProvider, diags *fwDiags.Diagnostics) {
var data fwmodels.ProviderModel
var d fwDiags.Diagnostics

// Set defaults if needed - the only attribute without a default is ImpersonateServiceAccountDelegates
// this is a bit of a hack, but we'll just initialize it here so that it's been initialized at least
data.ImpersonateServiceAccountDelegates, d = types.ListValue(types.StringType, []attr.Value{})
diags.Append(d...)
if diags.HasError() {
return
}
p.LoadAndValidateFramework(ctx, &data, "test", diags, p.Version)
}

// GetSDKProvider gets the SDK provider with an overwritten configure function to be called by MuxedProviders
// GetSDKProvider gets the SDK provider for use in acceptance tests
// If VCR is in use, the configure function is overwritten.
// See usage in MuxedProviders
func GetSDKProvider(testName string) *schema.Provider {
prov := tpgprovider.Provider()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/fwresource"
"github.com/hashicorp/terraform-provider-google/google/fwtransport"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)
Expand Down Expand Up @@ -221,21 +220,28 @@ func testAccCheckDNSManagedZoneDestroyProducerFramework(t *testing.T) func(s *te
continue
}

p := acctest.GetFwTestProvider(t)
config := acctest.GoogleProviderConfig(t)

url, err := fwresource.ReplaceVarsForFrameworkTest(&p.FrameworkProvider.FrameworkProviderConfig, rs, "{{"{{"}}DNSBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/managedZones/{{"{{"}}name{{"}}"}}")
url, err := fwresource.ReplaceVarsForFrameworkTest(config, rs, "{{"{{"}}DNSBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/managedZones/{{"{{"}}name{{"}}"}}")
if err != nil {
return err
}

billingProject := ""

if !p.BillingProject.IsNull() && p.BillingProject.String() != "" {
billingProject = p.BillingProject.String()
if config.BillingProject != "" {
billingProject = config.BillingProject
}

_, diags := fwtransport.SendFrameworkRequest(&p.FrameworkProvider.FrameworkProviderConfig, "GET", billingProject, url, p.UserAgent, nil)
if !diags.HasError() {
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: url,
UserAgent: config.UserAgent,
})

if err == nil {
return fmt.Errorf("DNSManagedZone still exists at %s", url)
}
}
Expand Down
4 changes: 2 additions & 2 deletions mmv1/third_party/terraform/fwresource/field_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-google/google/fwtransport"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)

// GetProject reads the "project" field from the given resource and falls
Expand Down Expand Up @@ -69,7 +69,7 @@ func ParseProjectFieldValueFramework(resourceType, fieldValue, projectSchemaFiel

// This function isn't a test of transport.go; instead, it is used as an alternative
// to ReplaceVars inside tests.
func ReplaceVarsForFrameworkTest(prov *fwtransport.FrameworkProviderConfig, rs *terraform.ResourceState, linkTmpl string) (string, error) {
func ReplaceVarsForFrameworkTest(prov *transport_tpg.Config, rs *terraform.ResourceState, linkTmpl string) (string, error) {
re := regexp.MustCompile("{{([[:word:]]+)}}")
var project, region, zone string

Expand Down

0 comments on commit 1b0712f

Please sign in to comment.