8
8
9
9
"github.com/hashicorp/hcl/v2"
10
10
"github.com/hashicorp/hcl/v2/ext/typeexpr"
11
+ "github.com/samber/lo"
11
12
"github.com/zclconf/go-cty/cty"
12
13
"github.com/zclconf/go-cty/cty/convert"
13
14
"golang.org/x/exp/slices"
@@ -102,6 +103,7 @@ func (e *evaluator) evaluateStep() {
102
103
103
104
e .ctx .Set (e .getValuesByBlockType ("data" ), "data" )
104
105
e .ctx .Set (e .getValuesByBlockType ("output" ), "output" )
106
+ e .ctx .Set (e .getValuesByBlockType ("module" ), "module" )
105
107
}
106
108
107
109
// exportOutputs is used to export module outputs to the parent module
@@ -126,48 +128,100 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str
126
128
fsMap := make (map [string ]fs.FS )
127
129
fsMap [fsKey ] = e .filesystem
128
130
129
- var lastContext hcl.EvalContext
130
131
e .debug .Log ("Starting module evaluation..." )
131
- for i := 0 ; i < maxContextIterations ; i ++ {
132
+ e . evaluateSteps ()
132
133
133
- e .evaluateStep ()
134
+ // expand out resources and modules via count, for-each and dynamic
135
+ // (not a typo, we do this twice so every order is processed)
136
+ e .blocks = e .expandBlocks (e .blocks )
137
+ e .blocks = e .expandBlocks (e .blocks )
134
138
135
- // if ctx matches the last evaluation, we can bail, nothing left to resolve
136
- if i > 0 && reflect .DeepEqual (lastContext .Variables , e .ctx .Inner ().Variables ) {
137
- break
138
- }
139
+ e .debug .Log ("Starting submodule evaluation..." )
140
+ submodules := e .loadSubmodules (ctx )
139
141
140
- if len (e .ctx .Inner ().Variables ) != len (lastContext .Variables ) {
141
- lastContext .Variables = make (map [string ]cty.Value , len (e .ctx .Inner ().Variables ))
142
+ for i := 0 ; i < maxContextIterations ; i ++ {
143
+ changed := false
144
+ for _ , sm := range submodules {
145
+ changed = changed || e .evaluateSubmodule (ctx , sm )
142
146
}
143
- for k , v := range e .ctx .Inner ().Variables {
144
- lastContext .Variables [k ] = v
147
+ if ! changed {
148
+ e .debug .Log ("All submodules are evaluated at i=%d" , i )
149
+ break
145
150
}
146
151
}
147
152
148
- // expand out resources and modules via count, for-each and dynamic
149
- // (not a typo, we do this twice so every order is processed)
150
- e .blocks = e .expandBlocks (e .blocks )
151
- e .blocks = e .expandBlocks (e .blocks )
153
+ e .debug .Log ("Starting post-submodule evaluation..." )
154
+ e .evaluateSteps ()
152
155
153
- e .debug .Log ("Starting submodule evaluation..." )
154
156
var modules terraform.Modules
157
+ for _ , sm := range submodules {
158
+ modules = append (modules , sm .modules ... )
159
+ fsMap = lo .Assign (fsMap , sm .fsMap )
160
+ }
161
+
162
+ e .debug .Log ("Finished processing %d submodule(s)." , len (modules ))
163
+
164
+ e .debug .Log ("Module evaluation complete." )
165
+ rootModule := terraform .NewModule (e .projectRootPath , e .modulePath , e .blocks , e .ignores )
166
+ return append (terraform.Modules {rootModule }, modules ... ), fsMap
167
+ }
168
+
169
+ type submodule struct {
170
+ definition * ModuleDefinition
171
+ eval * evaluator
172
+ modules terraform.Modules
173
+ lastState map [string ]cty.Value
174
+ fsMap map [string ]fs.FS
175
+ }
176
+
177
+ func (e * evaluator ) loadSubmodules (ctx context.Context ) []* submodule {
178
+ var submodules []* submodule
179
+
155
180
for _ , definition := range e .loadModules (ctx ) {
156
- submodules , outputs , err := definition .Parser .EvaluateAll (ctx )
157
- if err != nil {
158
- e .debug .Log ("Failed to evaluate submodule '%s': %s." , definition .Name , err )
181
+ eval , err := definition .Parser .Load (ctx )
182
+ if errors .Is (err , ErrNoFiles ) {
183
+ continue
184
+ } else if err != nil {
185
+ e .debug .Log ("Failed to load submodule '%s': %s." , definition .Name , err )
159
186
continue
160
187
}
161
- // export module outputs
162
- e .ctx .Set (outputs , "module" , definition .Name )
163
- modules = append (modules , submodules ... )
164
- for key , val := range definition .Parser .GetFilesystemMap () {
165
- fsMap [key ] = val
188
+
189
+ submodules = append (submodules , & submodule {
190
+ definition : definition ,
191
+ eval : eval ,
192
+ fsMap : make (map [string ]fs.FS ),
193
+ })
194
+ }
195
+
196
+ return submodules
197
+ }
198
+
199
+ func (e * evaluator ) evaluateSubmodule (ctx context.Context , sm * submodule ) bool {
200
+ inputVars := sm .definition .inputVars ()
201
+ if len (sm .modules ) > 0 {
202
+ if reflect .DeepEqual (inputVars , sm .lastState ) {
203
+ e .debug .Log ("Submodule %s inputs unchanged" , sm .definition .Name )
204
+ return false
166
205
}
167
206
}
168
- e .debug .Log ("Finished processing %d submodule(s)." , len (modules ))
169
207
170
- e .debug .Log ("Starting post-submodule evaluation..." )
208
+ e .debug .Log ("Evaluating submodule %s" , sm .definition .Name )
209
+ sm .eval .inputVars = inputVars
210
+ sm .modules , sm .fsMap = sm .eval .EvaluateAll (ctx )
211
+ outputs := sm .eval .exportOutputs ()
212
+
213
+ // lastState needs to be captured after applying outputs – so that they
214
+ // don't get treated as changes – but before running post-submodule
215
+ // evaluation, so that changes from that can trigger re-evaluations of
216
+ // the submodule if/when they feed back into inputs.
217
+ e .ctx .Set (outputs , "module" , sm .definition .Name )
218
+ sm .lastState = sm .definition .inputVars ()
219
+ e .evaluateSteps ()
220
+ return true
221
+ }
222
+
223
+ func (e * evaluator ) evaluateSteps () {
224
+ var lastContext hcl.EvalContext
171
225
for i := 0 ; i < maxContextIterations ; i ++ {
172
226
173
227
e .evaluateStep ()
@@ -176,18 +230,13 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str
176
230
if i > 0 && reflect .DeepEqual (lastContext .Variables , e .ctx .Inner ().Variables ) {
177
231
break
178
232
}
179
-
180
233
if len (e .ctx .Inner ().Variables ) != len (lastContext .Variables ) {
181
234
lastContext .Variables = make (map [string ]cty.Value , len (e .ctx .Inner ().Variables ))
182
235
}
183
236
for k , v := range e .ctx .Inner ().Variables {
184
237
lastContext .Variables [k ] = v
185
238
}
186
239
}
187
-
188
- e .debug .Log ("Module evaluation complete." )
189
- rootModule := terraform .NewModule (e .projectRootPath , e .modulePath , e .blocks , e .ignores )
190
- return append (terraform.Modules {rootModule }, modules ... ), fsMap
191
240
}
192
241
193
242
func (e * evaluator ) expandBlocks (blocks terraform.Blocks ) terraform.Blocks {
@@ -217,7 +266,9 @@ func (e *evaluator) expandDynamicBlock(b *terraform.Block) {
217
266
b .InjectBlock (content , blockName )
218
267
}
219
268
}
220
- sub .MarkExpanded ()
269
+ if len (expanded ) > 0 {
270
+ sub .MarkExpanded ()
271
+ }
221
272
}
222
273
}
223
274
@@ -246,6 +297,10 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool
246
297
clones := make (map [string ]cty.Value )
247
298
_ = forEachAttr .Each (func (key cty.Value , val cty.Value ) {
248
299
300
+ if val .IsNull () {
301
+ return
302
+ }
303
+
249
304
// instances are identified by a map key (or set member) from the value provided to for_each
250
305
idx , err := convert .Convert (key , cty .String )
251
306
if err != nil {
0 commit comments