@@ -3,15 +3,16 @@ package resource
33import (
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