Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binary acceptance test driver #262

Merged
merged 4 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# 1.7.0 (Unreleased)

FEATURES:
* Binary acceptance test driver [GH-262]

DEPRECATED:

* helper/schema: `ResourceData.Partial` [GH-317]
Expand Down
26 changes: 26 additions & 0 deletions acctest/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package acctest

import (
"os"

"github.com/hashicorp/terraform-plugin-sdk/plugin"
tftest "github.com/hashicorp/terraform-plugin-test"
)

var TestHelper *tftest.Helper

func UseBinaryDriver(name string, providerFunc plugin.ProviderFunc) {
sourceDir, err := os.Getwd()
if err != nil {
panic(err)
}

if tftest.RunningAsPlugin() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: providerFunc,
})
os.Exit(0)
} else {
TestHelper = tftest.AutoInitProviderHelper(name, sourceDir)
}
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ require (
github.com/hashicorp/hcl/v2 v2.0.0
github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8
github.com/hashicorp/terraform-json v0.4.0
github.com/hashicorp/terraform-plugin-test v1.2.0
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba
Expand All @@ -42,7 +44,7 @@ require (
github.com/posener/complete v1.2.1 // indirect
github.com/spf13/afero v1.2.2
github.com/vmihailenco/msgpack v4.0.1+incompatible // indirect
github.com/zclconf/go-cty v1.1.0
github.com/zclconf/go-cty v1.2.1
github.com/zclconf/go-cty-yaml v1.0.1
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8 h1:+RyjwU+Gnd/aTJBPZVDNm903eXVjjqhbaR4Ypx3xYyY=
github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A=
github.com/hashicorp/terraform-json v0.4.0 h1:KNh29iNxozP5adfUFBJ4/fWd0Cu3taGgjHB38JYqOF4=
github.com/hashicorp/terraform-json v0.4.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU=
github.com/hashicorp/terraform-plugin-test v1.2.0 h1:AWFdqyfnOj04sxTdaAF57QqvW7XXrT8PseUHkbKsE8I=
github.com/hashicorp/terraform-plugin-test v1.2.0/go.mod h1:QIJHYz8j+xJtdtLrFTlzQVC0ocr3rf/OjIpgZLK56Hs=
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596 h1:hjyO2JsNZUKT1ym+FAdlBEkGPevazYsmVgIMw7dVELg=
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
Expand Down Expand Up @@ -181,6 +185,8 @@ github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
Expand Down
285 changes: 281 additions & 4 deletions helper/resource/state_shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package resource
import (
"encoding/json"
"fmt"
"strconv"

"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
"github.com/zclconf/go-cty/cty"

tfjson "github.com/hashicorp/terraform-json"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"

"github.com/hashicorp/terraform-plugin-sdk/internal/states"
"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/zclconf/go-cty/cty"
)

// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests
Expand Down Expand Up @@ -186,3 +187,279 @@ func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.R

return instanceState.Attributes, nil
}

type shimmedState struct {
state *terraform.State
}

func shimStateFromJson(jsonState *tfjson.State) (*terraform.State, error) {
state := terraform.NewState()
state.TFVersion = jsonState.TerraformVersion

if jsonState.Values == nil {
// the state is empty
return state, nil
}

for key, output := range jsonState.Values.Outputs {
os, err := shimOutputState(output)
if err != nil {
return nil, err
}
state.RootModule().Outputs[key] = os
}

ss := &shimmedState{state}
err := ss.shimStateModule(jsonState.Values.RootModule)
if err != nil {
return nil, err
}

return state, nil
}

func shimOutputState(so *tfjson.StateOutput) (*terraform.OutputState, error) {
os := &terraform.OutputState{
Sensitive: so.Sensitive,
}

switch v := so.Value.(type) {
case string:
os.Type = "string"
os.Value = v
return os, nil
case []interface{}:
os.Type = "list"
if len(v) == 0 {
os.Value = v
return os, nil
}
switch firstElem := v[0].(type) {
case string:
elements := make([]interface{}, len(v))
for i, el := range v {
elements[i] = el.(string)
}
os.Value = elements
case bool:
elements := make([]interface{}, len(v))
for i, el := range v {
elements[i] = el.(bool)
}
os.Value = elements
// unmarshalled number from JSON will always be float64
case float64:
elements := make([]interface{}, len(v))
for i, el := range v {
elements[i] = el.(float64)
}
os.Value = elements
case []interface{}:
os.Value = v
case map[string]interface{}:
os.Value = v
default:
return nil, fmt.Errorf("unexpected output list element type: %T", firstElem)
}
return os, nil
case map[string]interface{}:
os.Type = "map"
os.Value = v
return os, nil
case bool:
os.Type = "string"
os.Value = strconv.FormatBool(v)
return os, nil
// unmarshalled number from JSON will always be float64
case float64:
os.Type = "string"
os.Value = strconv.FormatFloat(v, 'f', -1, 64)
return os, nil
}

return nil, fmt.Errorf("unexpected output type: %T", so.Value)
}

func (ss *shimmedState) shimStateModule(sm *tfjson.StateModule) error {
var path addrs.ModuleInstance

if sm.Address == "" {
path = addrs.RootModuleInstance
} else {
var diags tfdiags.Diagnostics
path, diags = addrs.ParseModuleInstanceStr(sm.Address)
if diags.HasErrors() {
return diags.Err()
}
}

mod := ss.state.AddModule(path)
for _, res := range sm.Resources {
resourceState, err := shimResourceState(res)
if err != nil {
return err
}

key, err := shimResourceStateKey(res)
if err != nil {
return err
}

mod.Resources[key] = resourceState
}

if len(sm.ChildModules) > 0 {
return fmt.Errorf("Modules are not supported. Found %d modules.",
len(sm.ChildModules))
}
return nil
}

func shimResourceStateKey(res *tfjson.StateResource) (string, error) {
if res.Index == nil {
return res.Address, nil
}

var mode terraform.ResourceMode
switch res.Mode {
case tfjson.DataResourceMode:
mode = terraform.DataResourceMode
case tfjson.ManagedResourceMode:
mode = terraform.ManagedResourceMode
default:
return "", fmt.Errorf("unexpected resource mode for %q", res.Address)
}

var index int
switch idx := res.Index.(type) {
case float64:
index = int(idx)
default:
return "", fmt.Errorf("unexpected index type (%T) for %q, "+
"for_each is not supported", res.Index, res.Address)
}

rsk := &terraform.ResourceStateKey{
Mode: mode,
Type: res.Type,
Name: res.Name,
Index: index,
}

return rsk.String(), nil
}

func shimResourceState(res *tfjson.StateResource) (*terraform.ResourceState, error) {
sf := &shimmedFlatmap{}
err := sf.FromMap(res.AttributeValues)
if err != nil {
return nil, err
}
attributes := sf.Flatmap()

if _, ok := attributes["id"]; !ok {
return nil, fmt.Errorf("no %q found in attributes", "id")
}

return &terraform.ResourceState{
Provider: res.ProviderName,
Type: res.Type,
Primary: &terraform.InstanceState{
ID: attributes["id"],
Attributes: attributes,
Meta: map[string]interface{}{
"schema_version": int(res.SchemaVersion),
},
Tainted: res.Tainted,
},
Dependencies: res.DependsOn,
}, nil
}

type shimmedFlatmap struct {
m map[string]string
}

func (sf *shimmedFlatmap) FromMap(attributes map[string]interface{}) error {
if sf.m == nil {
sf.m = make(map[string]string, len(attributes))
}

return sf.AddMap("", attributes)
}

func (sf *shimmedFlatmap) AddMap(prefix string, m map[string]interface{}) error {
for key, value := range m {
k := key
if prefix != "" {
k = fmt.Sprintf("%s.%s", prefix, key)
}

err := sf.AddEntry(k, value)
if err != nil {
return err
}
}

mapLength := "%"
if prefix != "" {
mapLength = fmt.Sprintf("%s.%s", prefix, "%")
}

sf.AddEntry(mapLength, strconv.Itoa(len(m)))

return nil
}

func (sf *shimmedFlatmap) AddSlice(name string, elements []interface{}) error {
for i, elem := range elements {
key := fmt.Sprintf("%s.%d", name, i)
err := sf.AddEntry(key, elem)
if err != nil {
return err
}
}

sliceLength := fmt.Sprintf("%s.#", name)
sf.AddEntry(sliceLength, strconv.Itoa(len(elements)))

return nil
}

func (sf *shimmedFlatmap) AddEntry(key string, value interface{}) error {
switch el := value.(type) {
case nil:
// omit the entry
return nil
case bool:
sf.m[key] = strconv.FormatBool(el)
case float64:
sf.m[key] = strconv.FormatFloat(el, 'f', -1, 64)
case string:
sf.m[key] = el
case map[string]interface{}:
err := sf.AddMap(key, el)
if err != nil {
return err
}
case []interface{}:
err := sf.AddSlice(key, el)
if err != nil {
return err
}
default:
// This should never happen unless terraform-json
// changes how attributes (types) are represented.
//
// We handle all types which the JSON unmarshaler
// can possibly produce
// https://golang.org/pkg/encoding/json/#Unmarshal

return fmt.Errorf("%q: unexpected type (%T)", key, el)
}
return nil
}

func (sf *shimmedFlatmap) Flatmap() map[string]string {
return sf.m
}
Loading