Skip to content

Commit

Permalink
Revert "revert plugin framework code (GoogleCloudPlatform#7287)"
Browse files Browse the repository at this point in the history
This reverts commit 06f9b2e.
  • Loading branch information
megan07 committed Mar 28, 2023
1 parent fb44a3a commit 4bb0852
Show file tree
Hide file tree
Showing 5 changed files with 466 additions and 1 deletion.
242 changes: 242 additions & 0 deletions mmv1/third_party/terraform/framework_utils/framework_provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package google

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"

"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/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/helper/resource"
)

var fwProviders map[string]*frameworkTestProvider

type frameworkTestProvider struct {
ProdProvider frameworkProvider
TestName string
}

func NewFrameworkTestProvider(testName string) *frameworkTestProvider {
return &frameworkTestProvider{
ProdProvider: frameworkProvider{
version: "test",
},
TestName: testName,
}
}

// 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) {
if isVcrEnabled() {
configsLock.RLock()
_, ok := fwProviders[p.TestName]
configsLock.RUnlock()
if ok {
return
}
p.ProdProvider.Configure(ctx, req, resp)
if resp.Diagnostics.HasError() {
return
}
var vcrMode recorder.Mode
switch vcrEnv := os.Getenv("VCR_MODE"); vcrEnv {
case "RECORDING":
vcrMode = recorder.ModeRecording
case "REPLAYING":
vcrMode = recorder.ModeReplaying
// When replaying, set the poll interval low to speed up tests
p.ProdProvider.pollInterval = 10 * time.Millisecond
default:
tflog.Debug(ctx, fmt.Sprintf("No valid environment var set for VCR_MODE, expected RECORDING or REPLAYING, skipping VCR. VCR_MODE: %s", vcrEnv))
return
}

envPath := os.Getenv("VCR_PATH")
if envPath == "" {
tflog.Debug(ctx, "No environment var set for VCR_PATH, skipping VCR")
return
}
path := filepath.Join(envPath, vcrFileName(p.TestName))

rec, err := recorder.NewAsMode(path, vcrMode, p.ProdProvider.client.Transport)
if err != nil {
resp.Diagnostics.AddError("error creating record as new mode", err.Error())
return
}
// Defines how VCR will match requests to responses.
rec.SetMatcher(func(r *http.Request, i cassette.Request) bool {
// Default matcher compares method and URL only
if !cassette.DefaultMatcher(r, i) {
return false
}
if r.Body == nil {
return true
}
contentType := r.Header.Get("Content-Type")
// If body contains media, don't try to compare
if strings.Contains(contentType, "multipart/related") {
return true
}

var b bytes.Buffer
if _, err := b.ReadFrom(r.Body); err != nil {
tflog.Debug(ctx, fmt.Sprintf("Failed to read request body from cassette: %v", err))
return false
}
r.Body = ioutil.NopCloser(&b)
reqBody := b.String()
// If body matches identically, we are done
if reqBody == i.Body {
return true
}

// JSON might be the same, but reordered. Try parsing json and comparing
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))
return false
}
if err := json.Unmarshal([]byte(i.Body), &cassetteJson); err != nil {
tflog.Debug(ctx, fmt.Sprintf("Failed to unmarshall cassette json: %v", err))
return false
}
return reflect.DeepEqual(reqJson, cassetteJson)
}
return false
})
p.ProdProvider.client.Transport = rec
configsLock.Lock()
fwProviders[p.TestName] = p
configsLock.Unlock()
return
} else {
tflog.Debug(ctx, "VCR_PATH or VCR_MODE not set, skipping VCR")
}
}

func configureApiClient(ctx context.Context, p *frameworkTestProvider, diags *diag.Diagnostics) {
var data ProviderModel
var d diag.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.ProdProvider.ConfigureWithData(ctx, data, "test", diags)
}

func getTestAccFrameworkProviders(testName string, c resource.TestCase) map[string]func() (tfprotov5.ProviderServer, error) {
myFunc := func() (tfprotov5.ProviderServer, error) {
prov, err := MuxedProviders(testName)
return prov(), err
}

var testProvider string
providerMapKeys := reflect.ValueOf(c.ProtoV5ProviderFactories).MapKeys()
if len(providerMapKeys) > 0. {
if strings.Contains(providerMapKeys[0].String(), "google-beta") {
testProvider = "google-beta"
} else {
testProvider = "google"
}
return map[string]func() (tfprotov5.ProviderServer, error){
testProvider: myFunc,
}
}
return map[string]func() (tfprotov5.ProviderServer, error){}
}

func getTestFwProvider(t *testing.T) *frameworkTestProvider {
configsLock.RLock()
fwProvider, ok := fwProviders[t.Name()]
configsLock.RUnlock()
if ok {
return fwProvider
}

var diags diag.Diagnostics
p := NewFrameworkTestProvider(t.Name())
configureApiClient(context.Background(), p, &diags)
if diags.HasError() {
log.Fatalf("%d errors when configuring test provider client: first is %s", diags.ErrorsCount(), diags.Errors()[0].Detail())
}

return p
}

func TestAccFrameworkProviderMeta_setModuleName(t *testing.T) {
t.Parallel()

moduleName := "my-module"
managedZoneName := fmt.Sprintf("tf-test-zone-%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){
"google": func() (tfprotov5.ProviderServer, error) {
provider, err := MuxedProviders(t.Name())
return provider(), err
},
},
// CheckDestroy: testAccCheckComputeAddressDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccFrameworkProviderMeta_setModuleName(moduleName, managedZoneName, randString(t, 10)),
},
},
})
}

func testAccFrameworkProviderMeta_setModuleName(key, managedZoneName, recordSetName string) string {
return fmt.Sprintf(`
terraform {
provider_meta "google" {
module_name = "%s"
}
}
provider "google" {}
resource "google_dns_managed_zone" "zone" {
name = "test-zone"
dns_name = "%s.hashicorptest.com."
}
resource "google_dns_record_set" "rs" {
managed_zone = google_dns_managed_zone.zone.name
name = "%s.${google_dns_managed_zone.zone.dns_name}"
type = "A"
ttl = 300
rrdatas = [
"192.168.1.0",
]
}
data "google_dns_record_set" "rs" {
managed_zone = google_dns_record_set.rs.managed_zone
name = google_dns_record_set.rs.name
type = google_dns_record_set.rs.type
}`, key, managedZoneName, recordSetName)
}
95 changes: 95 additions & 0 deletions mmv1/third_party/terraform/framework_utils/framework_test_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package google

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-mux/tf5muxserver"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

// General test utils
func MuxedProviders(testName string) (func() tfprotov5.ProviderServer, error) {
ctx := context.Background()

providers := []func() tfprotov5.ProviderServer{
providerserver.NewProtocol5(New("test")), // framework provider
Provider().GRPCProvider, // sdk provider
}

muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...)

if err != nil {
return nil, err
}

return muxServer.ProviderServer, nil
}

// testExtractResourceAttr navigates a test's state to find the specified resource (or data source) attribute and makes the value
// accessible via the attributeValue string pointer.
func testExtractResourceAttr(resourceName string, attributeName string, attributeValue *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName] // To find a datasource, include `data.` at the start of the resourceName value

if !ok {
return fmt.Errorf("resource name %s not found in state", resourceName)
}

attrValue, ok := rs.Primary.Attributes[attributeName]

if !ok {
return fmt.Errorf("attribute %s not found in resource %s state", attributeName, resourceName)
}

*attributeValue = attrValue

return nil
}
}

// testCheckAttributeValuesEqual compares two string pointers, which have been used to retrieve attribute values from the test's state.
func testCheckAttributeValuesEqual(i *string, j *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testStringValue(i) != testStringValue(j) {
return fmt.Errorf("attribute values are different, got %s and %s", testStringValue(i), testStringValue(j))
}

return nil
}
}

// testStringValue returns string values from string pointers, handling nil pointers.
func testStringValue(sPtr *string) string {
if sPtr == nil {
return ""
}

return *sPtr
}

// protoV5ProviderFactories returns a muxed ProviderServer that uses the provider code from this repo (SDK and plugin-framework).
// Used to set ProtoV5ProviderFactories in a resource.TestStep within an acceptance test.
func protoV5ProviderFactories(t *testing.T) map[string]func() (tfprotov5.ProviderServer, error) {
return map[string]func() (tfprotov5.ProviderServer, error){
"google": func() (tfprotov5.ProviderServer, error) {
provider, err := MuxedProviders(t.Name())
return provider(), err
},
}
}

// providerVersion450 returns information allowing tests to download TPG v4.50.0 from the Registry during `init`
// Used to set ExternalProviders in a resource.TestStep within an acceptance test.
func providerVersion450() map[string]resource.ExternalProvider {
return map[string]resource.ExternalProvider{
"google": {
VersionConstraint: "4.50.0",
Source: "hashicorp/google",
},
}
}
9 changes: 9 additions & 0 deletions mmv1/third_party/terraform/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,16 @@ github.com/hashicorp/terraform-exec v0.17.3 h1:MX14Kvnka/oWGmIkyuyvL6POx25ZmKrjl
github.com/hashicorp/terraform-exec v0.17.3/go.mod h1:+NELG0EqQekJzhvikkeQsOAZpsw0cv/03rbeQJqscAI=
github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s=
github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM=
github.com/hashicorp/terraform-plugin-framework v1.0.1 h1:apX2jtaEKa15+do6H2izBJdl1dEH2w5BPVkDJ3Q3mKA=
github.com/hashicorp/terraform-plugin-framework v1.0.1/go.mod h1:FV97t2BZOARkL7NNlsc/N25c84MyeSSz72uPp7Vq1lg=
github.com/hashicorp/terraform-plugin-framework v1.1.1 h1:PbnEKHsIU8KTTzoztHQGgjZUWx7Kk8uGtpGMMc1p+oI=
github.com/hashicorp/terraform-plugin-framework v1.1.1/go.mod h1:DyZPxQA+4OKK5ELxFIIcqggcszqdWWUpTLPHAhS/tkY=
github.com/hashicorp/terraform-plugin-framework-validators v0.9.0 h1:LYz4bXh3t7bTEydXOmPDPupRRnA480B/9+jV8yZvxBA=
github.com/hashicorp/terraform-plugin-framework-validators v0.9.0/go.mod h1:+BVERsnfdlhYR2YkXMBtPnmn9UsL19U3qUtSZ+Y/5MY=
github.com/hashicorp/terraform-plugin-go v0.14.0 h1:ttnSlS8bz3ZPYbMb84DpcPhY4F5DsQtcAS7cHo8uvP4=
github.com/hashicorp/terraform-plugin-go v0.14.0/go.mod h1:2nNCBeRLaenyQEi78xrGrs9hMbulveqG/zDMQSvVJTE=
github.com/hashicorp/terraform-plugin-go v0.14.2 h1:rhsVEOGCnY04msNymSvbUsXfRLKh9znXZmHlf5e8mhE=
github.com/hashicorp/terraform-plugin-go v0.14.2/go.mod h1:Q12UjumPNGiFsZffxOsA40Tlz1WVXt2Evh865Zj0+UA=
github.com/hashicorp/terraform-plugin-go v0.14.3 h1:nlnJ1GXKdMwsC8g1Nh05tK2wsC3+3BL/DBBxFEki+j0=
github.com/hashicorp/terraform-plugin-go v0.14.3/go.mod h1:7ees7DMZ263q8wQ6E4RdIdR6nHHJtrdt4ogX5lPkX1A=
github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs=
Expand All @@ -281,6 +287,8 @@ github.com/hashicorp/terraform-plugin-mux v0.8.0 h1:WCTP66mZ+iIaIrCNJnjPEYnVjawT
github.com/hashicorp/terraform-plugin-mux v0.8.0/go.mod h1:vdW0daEi8Kd4RFJmet5Ot+SIVB/B8SwQVJiYKQwdCy8=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 h1:FtCLTiTcykdsURXPt/ku7fYXm3y19nbzbZcUxHx9RbI=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0/go.mod h1:80wf5oad1tW+oLnbXS4UTYmDCrl7BuN1Q+IA91X1a4Y=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U=
github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
Expand Down Expand Up @@ -363,6 +371,7 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand Down
Loading

0 comments on commit 4bb0852

Please sign in to comment.