@@ -22,43 +22,71 @@ import (
2222 "github.com/hashicorp/terraform/internal/tfdiags"
2323)
2424
25+ // ImportGroup represents one or more resource and import configuration blocks.
26+ type ImportGroup struct {
27+ Imports []ResourceImport
28+ }
29+
30+ // ResourceImport pairs up the import and associated resource when generating
31+ // configuration, so that query output can be more structured for easier
32+ // consumption.
33+ type ResourceImport struct {
34+ ImportBody []byte
35+ Resource Resource
36+ }
37+
2538type Resource struct {
39+ Addr addrs.AbsResourceInstance
40+
2641 // HCL Body of the resource, which is the attributes and blocks
2742 // that are part of the resource.
2843 Body []byte
44+ }
2945
30- // Import is the HCL code for the import block. This is only
31- // generated for list resource results.
32- Import []byte
33- Addr addrs.AbsResourceInstance
34- Results []* Resource
46+ func (r Resource ) String () string {
47+ var buf strings.Builder
48+ buf .WriteString (fmt .Sprintf ("resource %q %q {\n " , r .Addr .Resource .Resource .Type , r .Addr .Resource .Resource .Name ))
49+ buf .Write (r .Body )
50+ buf .WriteString ("}" )
51+
52+ formatted := hclwrite .Format ([]byte (buf .String ()))
53+ return string (formatted )
3554}
3655
37- func (r * Resource ) String () string {
56+ func (i ImportGroup ) String () string {
3857 var buf strings.Builder
39- switch r .Addr .Resource .Resource .Mode {
40- case addrs .ListResourceMode :
41- last := len (r .Results ) - 1
42- // sort the results by their keys so the output is consistent
43- for idx , managed := range r .Results {
44- if managed .Body != nil {
45- buf .WriteString (managed .String ())
46- buf .WriteString ("\n " )
47- }
48- if managed .Import != nil {
49- buf .WriteString (string (managed .Import ))
50- buf .WriteString ("\n " )
51- }
52- if idx != last {
53- buf .WriteString ("\n " )
54- }
55- }
56- case addrs .ManagedResourceMode :
57- buf .WriteString (fmt .Sprintf ("resource %q %q {\n " , r .Addr .Resource .Resource .Type , r .Addr .Resource .Resource .Name ))
58- buf .Write (r .Body )
59- buf .WriteString ("}" )
60- default :
61- panic (fmt .Errorf ("unsupported resource mode %s" , r .Addr .Resource .Resource .Mode ))
58+
59+ for _ , imp := range i .Imports {
60+ buf .WriteString (imp .Resource .String ())
61+ buf .WriteString ("\n \n " )
62+ buf .WriteString (string (imp .ImportBody ))
63+ buf .WriteString ("\n \n " )
64+ }
65+
66+ // The output better be valid HCL which can be parsed and formatted.
67+ formatted := hclwrite .Format ([]byte (buf .String ()))
68+ return string (formatted )
69+ }
70+
71+ func (i ImportGroup ) ResourcesString () string {
72+ var buf strings.Builder
73+
74+ for _ , imp := range i .Imports {
75+ buf .WriteString (imp .Resource .String ())
76+ buf .WriteString ("\n " )
77+ }
78+
79+ // The output better be valid HCL which can be parsed and formatted.
80+ formatted := hclwrite .Format ([]byte (buf .String ()))
81+ return string (formatted )
82+ }
83+
84+ func (i ImportGroup ) ImportsString () string {
85+ var buf strings.Builder
86+
87+ for _ , imp := range i .Imports {
88+ buf .WriteString (string (imp .ImportBody ))
89+ buf .WriteString ("\n " )
6290 }
6391
6492 // The output better be valid HCL which can be parsed and formatted.
@@ -75,9 +103,9 @@ func (r *Resource) String() string {
75103func GenerateResourceContents (addr addrs.AbsResourceInstance ,
76104 schema * configschema.Block ,
77105 pc addrs.LocalProviderConfig ,
78- stateVal cty.Value ,
106+ configVal cty.Value ,
79107 forceProviderAddr bool ,
80- ) (* Resource , tfdiags.Diagnostics ) {
108+ ) (Resource , tfdiags.Diagnostics ) {
81109 var buf strings.Builder
82110
83111 var diags tfdiags.Diagnostics
@@ -89,49 +117,40 @@ func GenerateResourceContents(addr addrs.AbsResourceInstance,
89117 buf .WriteString (fmt .Sprintf ("provider = %s\n " , pc .StringCompact ()))
90118 }
91119
92- // This is generating configuration, so the only marks should be coming from
93- // the schema itself.
94- stateVal , _ = stateVal .UnmarkDeep ()
95-
96- // filter the state down to a suitable config value
97- stateVal = extractConfigFromState (schema , stateVal )
98-
99- if stateVal .RawEquals (cty .NilVal ) {
120+ if configVal .RawEquals (cty .NilVal ) {
100121 diags = diags .Append (writeConfigAttributes (addr , & buf , schema .Attributes , 2 ))
101122 diags = diags .Append (writeConfigBlocks (addr , & buf , schema .BlockTypes , 2 ))
102123 } else {
103- diags = diags .Append (writeConfigAttributesFromExisting (addr , & buf , stateVal , schema .Attributes , 2 ))
104- diags = diags .Append (writeConfigBlocksFromExisting (addr , & buf , stateVal , schema .BlockTypes , 2 ))
124+ diags = diags .Append (writeConfigAttributesFromExisting (addr , & buf , configVal , schema .Attributes , 2 ))
125+ diags = diags .Append (writeConfigBlocksFromExisting (addr , & buf , configVal , schema .BlockTypes , 2 ))
105126 }
106127
107128 // The output better be valid HCL which can be parsed and formatted.
108129 formatted := hclwrite .Format ([]byte (buf .String ()))
109- return & Resource {
110- Body : formatted ,
111- Addr : addr ,
112- }, diags
130+ return Resource {Addr : addr , Body : formatted }, diags
131+ }
132+
133+ // ResourceListElement is a single Resource state and identity pair derived from
134+ // a list resource response.
135+ type ResourceListElement struct {
136+ // Config is the cty value extracted from the resource state which is
137+ // intended to be written into the HCL resource block.
138+ Config cty.Value
139+
140+ Identity cty.Value
113141}
114142
115143func GenerateListResourceContents (addr addrs.AbsResourceInstance ,
116144 schema * configschema.Block ,
117145 idSchema * configschema.Object ,
118146 pc addrs.LocalProviderConfig ,
119- stateVal cty.Value ,
120- ) (* Resource , tfdiags.Diagnostics ) {
147+ resources []ResourceListElement ,
148+ ) (ImportGroup , tfdiags.Diagnostics ) {
149+
121150 var diags tfdiags.Diagnostics
122- if ! stateVal .CanIterateElements () {
123- diags = diags .Append (
124- hcl.Diagnostic {
125- Severity : hcl .DiagError ,
126- Summary : "Invalid resource instance value" ,
127- Detail : fmt .Sprintf ("Resource instance %s has nil or non-iterable value" , addr ),
128- })
129- return nil , diags
130- }
151+ ret := ImportGroup {}
131152
132- ret := make ([]* Resource , stateVal .LengthInt ())
133- iter := stateVal .ElementIterator ()
134- for idx := 0 ; iter .Next (); idx ++ {
153+ for idx , res := range resources {
135154 // Generate a unique resource name for each instance in the list.
136155 resAddr := addrs.AbsResourceInstance {
137156 Module : addr .Module ,
@@ -144,39 +163,34 @@ func GenerateListResourceContents(addr addrs.AbsResourceInstance,
144163 Key : addr .Resource .Key ,
145164 },
146165 }
147- ls := & Resource {Addr : resAddr }
148- ret [idx ] = ls
149-
150- _ , val := iter .Element ()
151- // we still need to generate the resource block even if the state is not given,
152- // so that the import block can reference it.
153- stateVal := cty .NilVal
154- if val .Type ().HasAttribute ("state" ) {
155- stateVal = val .GetAttr ("state" )
156- }
157- content , gDiags := GenerateResourceContents (resAddr , schema , pc , stateVal , true )
166+
167+ content , gDiags := GenerateResourceContents (resAddr , schema , pc , res .Config , true )
158168 if gDiags .HasErrors () {
159169 diags = diags .Append (gDiags )
160170 continue
161171 }
162- ls .Body = content .Body
163172
164- idVal := val .GetAttr ("identity" )
165- importContent , gDiags := generateImportBlock (resAddr , idSchema , pc , idVal )
173+ resImport := ResourceImport {
174+ Resource : Resource {
175+ Addr : resAddr ,
176+ Body : content .Body ,
177+ },
178+ }
179+
180+ importContent , gDiags := GenerateImportBlock (resAddr , idSchema , pc , res .Identity )
166181 if gDiags .HasErrors () {
167182 diags = diags .Append (gDiags )
168183 continue
169184 }
170- ls .Import = bytes .TrimSpace (hclwrite .Format ([]byte (importContent )))
185+
186+ resImport .ImportBody = bytes .TrimSpace (hclwrite .Format (importContent .ImportBody ))
187+ ret .Imports = append (ret .Imports , resImport )
171188 }
172189
173- return & Resource {
174- Results : ret ,
175- Addr : addr ,
176- }, diags
190+ return ret , diags
177191}
178192
179- func generateImportBlock (addr addrs.AbsResourceInstance , idSchema * configschema.Object , pc addrs.LocalProviderConfig , identity cty.Value ) (string , tfdiags.Diagnostics ) {
193+ func GenerateImportBlock (addr addrs.AbsResourceInstance , idSchema * configschema.Object , pc addrs.LocalProviderConfig , identity cty.Value ) (ResourceImport , tfdiags.Diagnostics ) {
180194 var buf strings.Builder
181195 var diags tfdiags.Diagnostics
182196
@@ -190,7 +204,7 @@ func generateImportBlock(addr addrs.AbsResourceInstance, idSchema *configschema.
190204 buf .WriteString ("}\n }\n " )
191205
192206 formatted := hclwrite .Format ([]byte (buf .String ()))
193- return string ( formatted ) , diags
207+ return ResourceImport { ImportBody : formatted } , diags
194208}
195209
196210func writeConfigAttributes (addr addrs.AbsResourceInstance , buf * strings.Builder , attrs map [string ]* configschema.Attribute , indent int ) tfdiags.Diagnostics {
@@ -638,11 +652,11 @@ func hclEscapeString(str string) string {
638652 return str
639653}
640654
641- // extractConfigFromState takes the state value of a resource, and filters the
655+ // ExtractLegacyConfigFromState takes the state value of a resource, and filters the
642656// value down to what would be acceptable as a resource configuration value.
643657// This is used when the provider does not implement GenerateResourceConfig to
644658// create a suitable value.
645- func extractConfigFromState (schema * configschema.Block , state cty.Value ) cty.Value {
659+ func ExtractLegacyConfigFromState (schema * configschema.Block , state cty.Value ) cty.Value {
646660 config , _ := cty .Transform (state , func (path cty.Path , v cty.Value ) (cty.Value , error ) {
647661 if v .IsNull () {
648662 return v , nil
0 commit comments