Skip to content

Commit 168ea6b

Browse files
kmoeappilonradeksimko
committed
binary acceptance test driver
Co-authored-by: Katy Moe <[email protected]> Co-authored-by: Alex Pilon <[email protected]> Co-authored-by: Radek Simko <[email protected]>
1 parent 690c87a commit 168ea6b

File tree

7 files changed

+1917
-6
lines changed

7 files changed

+1917
-6
lines changed

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+
}

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+
}

helper/resource/testing.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import (
2020
"github.com/hashicorp/errwrap"
2121
"github.com/hashicorp/go-multierror"
2222
"github.com/hashicorp/logutils"
23-
"github.com/mitchellh/colorstring"
24-
23+
"github.com/hashicorp/terraform-plugin-sdk/acctest"
2524
"github.com/hashicorp/terraform-plugin-sdk/helper/logging"
2625
"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
2726
"github.com/hashicorp/terraform-plugin-sdk/internal/command/format"
@@ -32,6 +31,7 @@ import (
3231
"github.com/hashicorp/terraform-plugin-sdk/internal/states"
3332
"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
3433
"github.com/hashicorp/terraform-plugin-sdk/terraform"
34+
"github.com/mitchellh/colorstring"
3535
)
3636

3737
// flagSweep is a flag available when running tests on the command line. It
@@ -108,6 +108,9 @@ func TestMain(m *testing.M) {
108108
os.Exit(1)
109109
}
110110
} else {
111+
if acctest.TestHelper != nil {
112+
defer acctest.TestHelper.Close()
113+
}
111114
os.Exit(m.Run())
112115
}
113116
}
@@ -564,6 +567,12 @@ func Test(t TestT, c TestCase) {
564567
providers[name] = p
565568
}
566569

570+
if acctest.TestHelper != nil {
571+
// inject providers for ImportStateVerify
572+
RunNewTest(t.(*testing.T), c, providers)
573+
return
574+
}
575+
567576
providerResolver, err := testProviderResolver(c)
568577
if err != nil {
569578
t.Fatal(err)

0 commit comments

Comments
 (0)