Skip to content

Commit 3595c17

Browse files
authored
Merge pull request #262 from hashicorp/acctests-poc-wip-kem
Binary acceptance test driver
2 parents f0259f1 + 79d26e6 commit 3595c17

File tree

10 files changed

+1934
-7
lines changed

10 files changed

+1934
-7
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# 1.7.0 (Unreleased)
22

3+
FEATURES:
4+
* Binary acceptance test driver [GH-262]
5+
36
DEPRECATED:
47

58
* helper/schema: `ResourceData.Partial` [GH-317]

acctest/helper.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package acctest
2+
3+
import (
4+
"os"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/plugin"
7+
tftest "github.com/hashicorp/terraform-plugin-test"
8+
)
9+
10+
var TestHelper *tftest.Helper
11+
12+
func UseBinaryDriver(name string, providerFunc plugin.ProviderFunc) {
13+
sourceDir, err := os.Getwd()
14+
if err != nil {
15+
panic(err)
16+
}
17+
18+
if tftest.RunningAsPlugin() {
19+
plugin.Serve(&plugin.ServeOpts{
20+
ProviderFunc: providerFunc,
21+
})
22+
os.Exit(0)
23+
} else {
24+
TestHelper = tftest.AutoInitProviderHelper(name, sourceDir)
25+
}
26+
}

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ require (
2727
github.com/hashicorp/hcl/v2 v2.0.0
2828
github.com/hashicorp/logutils v1.0.0
2929
github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8
30+
github.com/hashicorp/terraform-json v0.4.0
31+
github.com/hashicorp/terraform-plugin-test v1.2.0
3032
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596
3133
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
3234
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba
@@ -42,7 +44,7 @@ require (
4244
github.com/posener/complete v1.2.1 // indirect
4345
github.com/spf13/afero v1.2.2
4446
github.com/vmihailenco/msgpack v4.0.1+incompatible // indirect
45-
github.com/zclconf/go-cty v1.1.0
47+
github.com/zclconf/go-cty v1.2.1
4648
github.com/zclconf/go-cty-yaml v1.0.1
4749
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
4850
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI
103103
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
104104
github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8 h1:+RyjwU+Gnd/aTJBPZVDNm903eXVjjqhbaR4Ypx3xYyY=
105105
github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A=
106+
github.com/hashicorp/terraform-json v0.4.0 h1:KNh29iNxozP5adfUFBJ4/fWd0Cu3taGgjHB38JYqOF4=
107+
github.com/hashicorp/terraform-json v0.4.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU=
108+
github.com/hashicorp/terraform-plugin-test v1.2.0 h1:AWFdqyfnOj04sxTdaAF57QqvW7XXrT8PseUHkbKsE8I=
109+
github.com/hashicorp/terraform-plugin-test v1.2.0/go.mod h1:QIJHYz8j+xJtdtLrFTlzQVC0ocr3rf/OjIpgZLK56Hs=
106110
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596 h1:hjyO2JsNZUKT1ym+FAdlBEkGPevazYsmVgIMw7dVELg=
107111
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
108112
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@@ -181,6 +185,8 @@ github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac
181185
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
182186
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
183187
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
188+
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
189+
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
184190
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
185191
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
186192
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=

helper/resource/state_shim.go

Lines changed: 281 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ package resource
33
import (
44
"encoding/json"
55
"fmt"
6+
"strconv"
67

7-
"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
8-
"github.com/zclconf/go-cty/cty"
9-
8+
tfjson "github.com/hashicorp/terraform-json"
109
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
10+
"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
1111
"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
12-
1312
"github.com/hashicorp/terraform-plugin-sdk/internal/states"
13+
"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
1414
"github.com/hashicorp/terraform-plugin-sdk/terraform"
15+
"github.com/zclconf/go-cty/cty"
1516
)
1617

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

187188
return instanceState.Attributes, nil
188189
}
190+
191+
type shimmedState struct {
192+
state *terraform.State
193+
}
194+
195+
func shimStateFromJson(jsonState *tfjson.State) (*terraform.State, error) {
196+
state := terraform.NewState()
197+
state.TFVersion = jsonState.TerraformVersion
198+
199+
if jsonState.Values == nil {
200+
// the state is empty
201+
return state, nil
202+
}
203+
204+
for key, output := range jsonState.Values.Outputs {
205+
os, err := shimOutputState(output)
206+
if err != nil {
207+
return nil, err
208+
}
209+
state.RootModule().Outputs[key] = os
210+
}
211+
212+
ss := &shimmedState{state}
213+
err := ss.shimStateModule(jsonState.Values.RootModule)
214+
if err != nil {
215+
return nil, err
216+
}
217+
218+
return state, nil
219+
}
220+
221+
func shimOutputState(so *tfjson.StateOutput) (*terraform.OutputState, error) {
222+
os := &terraform.OutputState{
223+
Sensitive: so.Sensitive,
224+
}
225+
226+
switch v := so.Value.(type) {
227+
case string:
228+
os.Type = "string"
229+
os.Value = v
230+
return os, nil
231+
case []interface{}:
232+
os.Type = "list"
233+
if len(v) == 0 {
234+
os.Value = v
235+
return os, nil
236+
}
237+
switch firstElem := v[0].(type) {
238+
case string:
239+
elements := make([]interface{}, len(v))
240+
for i, el := range v {
241+
elements[i] = el.(string)
242+
}
243+
os.Value = elements
244+
case bool:
245+
elements := make([]interface{}, len(v))
246+
for i, el := range v {
247+
elements[i] = el.(bool)
248+
}
249+
os.Value = elements
250+
// unmarshalled number from JSON will always be float64
251+
case float64:
252+
elements := make([]interface{}, len(v))
253+
for i, el := range v {
254+
elements[i] = el.(float64)
255+
}
256+
os.Value = elements
257+
case []interface{}:
258+
os.Value = v
259+
case map[string]interface{}:
260+
os.Value = v
261+
default:
262+
return nil, fmt.Errorf("unexpected output list element type: %T", firstElem)
263+
}
264+
return os, nil
265+
case map[string]interface{}:
266+
os.Type = "map"
267+
os.Value = v
268+
return os, nil
269+
case bool:
270+
os.Type = "string"
271+
os.Value = strconv.FormatBool(v)
272+
return os, nil
273+
// unmarshalled number from JSON will always be float64
274+
case float64:
275+
os.Type = "string"
276+
os.Value = strconv.FormatFloat(v, 'f', -1, 64)
277+
return os, nil
278+
}
279+
280+
return nil, fmt.Errorf("unexpected output type: %T", so.Value)
281+
}
282+
283+
func (ss *shimmedState) shimStateModule(sm *tfjson.StateModule) error {
284+
var path addrs.ModuleInstance
285+
286+
if sm.Address == "" {
287+
path = addrs.RootModuleInstance
288+
} else {
289+
var diags tfdiags.Diagnostics
290+
path, diags = addrs.ParseModuleInstanceStr(sm.Address)
291+
if diags.HasErrors() {
292+
return diags.Err()
293+
}
294+
}
295+
296+
mod := ss.state.AddModule(path)
297+
for _, res := range sm.Resources {
298+
resourceState, err := shimResourceState(res)
299+
if err != nil {
300+
return err
301+
}
302+
303+
key, err := shimResourceStateKey(res)
304+
if err != nil {
305+
return err
306+
}
307+
308+
mod.Resources[key] = resourceState
309+
}
310+
311+
if len(sm.ChildModules) > 0 {
312+
return fmt.Errorf("Modules are not supported. Found %d modules.",
313+
len(sm.ChildModules))
314+
}
315+
return nil
316+
}
317+
318+
func shimResourceStateKey(res *tfjson.StateResource) (string, error) {
319+
if res.Index == nil {
320+
return res.Address, nil
321+
}
322+
323+
var mode terraform.ResourceMode
324+
switch res.Mode {
325+
case tfjson.DataResourceMode:
326+
mode = terraform.DataResourceMode
327+
case tfjson.ManagedResourceMode:
328+
mode = terraform.ManagedResourceMode
329+
default:
330+
return "", fmt.Errorf("unexpected resource mode for %q", res.Address)
331+
}
332+
333+
var index int
334+
switch idx := res.Index.(type) {
335+
case float64:
336+
index = int(idx)
337+
default:
338+
return "", fmt.Errorf("unexpected index type (%T) for %q, "+
339+
"for_each is not supported", res.Index, res.Address)
340+
}
341+
342+
rsk := &terraform.ResourceStateKey{
343+
Mode: mode,
344+
Type: res.Type,
345+
Name: res.Name,
346+
Index: index,
347+
}
348+
349+
return rsk.String(), nil
350+
}
351+
352+
func shimResourceState(res *tfjson.StateResource) (*terraform.ResourceState, error) {
353+
sf := &shimmedFlatmap{}
354+
err := sf.FromMap(res.AttributeValues)
355+
if err != nil {
356+
return nil, err
357+
}
358+
attributes := sf.Flatmap()
359+
360+
if _, ok := attributes["id"]; !ok {
361+
return nil, fmt.Errorf("no %q found in attributes", "id")
362+
}
363+
364+
return &terraform.ResourceState{
365+
Provider: res.ProviderName,
366+
Type: res.Type,
367+
Primary: &terraform.InstanceState{
368+
ID: attributes["id"],
369+
Attributes: attributes,
370+
Meta: map[string]interface{}{
371+
"schema_version": int(res.SchemaVersion),
372+
},
373+
Tainted: res.Tainted,
374+
},
375+
Dependencies: res.DependsOn,
376+
}, nil
377+
}
378+
379+
type shimmedFlatmap struct {
380+
m map[string]string
381+
}
382+
383+
func (sf *shimmedFlatmap) FromMap(attributes map[string]interface{}) error {
384+
if sf.m == nil {
385+
sf.m = make(map[string]string, len(attributes))
386+
}
387+
388+
return sf.AddMap("", attributes)
389+
}
390+
391+
func (sf *shimmedFlatmap) AddMap(prefix string, m map[string]interface{}) error {
392+
for key, value := range m {
393+
k := key
394+
if prefix != "" {
395+
k = fmt.Sprintf("%s.%s", prefix, key)
396+
}
397+
398+
err := sf.AddEntry(k, value)
399+
if err != nil {
400+
return err
401+
}
402+
}
403+
404+
mapLength := "%"
405+
if prefix != "" {
406+
mapLength = fmt.Sprintf("%s.%s", prefix, "%")
407+
}
408+
409+
sf.AddEntry(mapLength, strconv.Itoa(len(m)))
410+
411+
return nil
412+
}
413+
414+
func (sf *shimmedFlatmap) AddSlice(name string, elements []interface{}) error {
415+
for i, elem := range elements {
416+
key := fmt.Sprintf("%s.%d", name, i)
417+
err := sf.AddEntry(key, elem)
418+
if err != nil {
419+
return err
420+
}
421+
}
422+
423+
sliceLength := fmt.Sprintf("%s.#", name)
424+
sf.AddEntry(sliceLength, strconv.Itoa(len(elements)))
425+
426+
return nil
427+
}
428+
429+
func (sf *shimmedFlatmap) AddEntry(key string, value interface{}) error {
430+
switch el := value.(type) {
431+
case nil:
432+
// omit the entry
433+
return nil
434+
case bool:
435+
sf.m[key] = strconv.FormatBool(el)
436+
case float64:
437+
sf.m[key] = strconv.FormatFloat(el, 'f', -1, 64)
438+
case string:
439+
sf.m[key] = el
440+
case map[string]interface{}:
441+
err := sf.AddMap(key, el)
442+
if err != nil {
443+
return err
444+
}
445+
case []interface{}:
446+
err := sf.AddSlice(key, el)
447+
if err != nil {
448+
return err
449+
}
450+
default:
451+
// This should never happen unless terraform-json
452+
// changes how attributes (types) are represented.
453+
//
454+
// We handle all types which the JSON unmarshaler
455+
// can possibly produce
456+
// https://golang.org/pkg/encoding/json/#Unmarshal
457+
458+
return fmt.Errorf("%q: unexpected type (%T)", key, el)
459+
}
460+
return nil
461+
}
462+
463+
func (sf *shimmedFlatmap) Flatmap() map[string]string {
464+
return sf.m
465+
}

0 commit comments

Comments
 (0)