@@ -3,9 +3,15 @@ package config
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "os"
6
7
"path/filepath"
7
8
"strings"
8
9
10
+ "github.com/hashicorp/hcl/v2/hclsyntax"
11
+
12
+ "github.com/gruntwork-io/terragrunt/util"
13
+ "github.com/hashicorp/hcl/v2/hclwrite"
14
+
9
15
"github.com/zclconf/go-cty/cty"
10
16
11
17
"github.com/gruntwork-io/terragrunt/config/hclparse"
@@ -14,7 +20,10 @@ import (
14
20
)
15
21
16
22
const (
17
- stackDir = ".terragrunt-stack"
23
+ stackDir = ".terragrunt-stack"
24
+ unitValuesFile = "terragrunt.values.hcl"
25
+ unitDirPerm = 0755
26
+ valueFilePerm = 0644
18
27
)
19
28
20
29
// StackConfigFile represents the structure of terragrunt.stack.hcl stack file.
@@ -87,6 +96,88 @@ func ReadStackConfigFile(ctx context.Context, opts *options.TerragruntOptions) (
87
96
return config , nil
88
97
}
89
98
99
+ // WriteUnitValues generates and writes unit values to a terragrunt.values.hcl file in the specified unit directory.
100
+ // If the unit has no values (Values is nil), the function logs a debug message and returns.
101
+ // Parameters:
102
+ // - opts: TerragruntOptions containing logger and other configuration
103
+ // - unit: Unit containing the values to write
104
+ // - unitDirectory: Target directory where the values file will be created
105
+ //
106
+ // Returns an error if the directory creation or file writing fails.
107
+ func WriteUnitValues (opts * options.TerragruntOptions , unit * Unit , unitDirectory string ) error {
108
+ if unitDirectory == "" {
109
+ return errors .New ("WriteUnitValues: unit directory path cannot be empty" )
110
+ }
111
+
112
+ if err := os .MkdirAll (unitDirectory , unitDirPerm ); err != nil {
113
+ return errors .Errorf ("failed to create directory %s: %w" , unitDirectory , err )
114
+ }
115
+
116
+ filePath := filepath .Join (unitDirectory , unitValuesFile )
117
+ if unit .Values == nil {
118
+ opts .Logger .Debugf ("No values to write for unit %s in %s" , unit .Name , filePath )
119
+ return nil
120
+ }
121
+
122
+ opts .Logger .Debugf ("Writing values for unit %s in %s" , unit .Name , filePath )
123
+
124
+ file := hclwrite .NewEmptyFile ()
125
+ body := file .Body ()
126
+ body .AppendUnstructuredTokens ([]* hclwrite.Token {
127
+ {
128
+ Type : hclsyntax .TokenComment ,
129
+ Bytes : []byte ("# Auto-generated by the terragrunt.stack.hcl file by Terragrunt. Do not edit manually\n " ),
130
+ },
131
+ })
132
+
133
+ for key , val := range unit .Values .AsValueMap () {
134
+ body .SetAttributeValue (key , val )
135
+ }
136
+
137
+ if err := os .WriteFile (filePath , file .Bytes (), valueFilePerm ); err != nil {
138
+ return errors .Errorf ("failed to write values file %s: %w" , filePath , err )
139
+ }
140
+
141
+ return nil
142
+ }
143
+
144
+ // ReadUnitValues reads the unit values from the terragrunt.values.hcl file.
145
+ func ReadUnitValues (ctx context.Context , opts * options.TerragruntOptions , unitDirectory string ) (* cty.Value , error ) {
146
+ if unitDirectory == "" {
147
+ return nil , errors .New ("ReadUnitValues: unit directory path cannot be empty" )
148
+ }
149
+
150
+ filePath := filepath .Join (unitDirectory , unitValuesFile )
151
+
152
+ if util .FileNotExists (filePath ) {
153
+ return nil , nil
154
+ }
155
+
156
+ opts .Logger .Debugf ("Reading Terragrunt stack values file at %s" , filePath )
157
+ parser := NewParsingContext (ctx , opts )
158
+ file , err := hclparse .NewParser (parser .ParserOptions ... ).ParseFromFile (filePath )
159
+
160
+ if err != nil {
161
+ return nil , errors .New (err )
162
+ }
163
+ //nolint:contextcheck
164
+ evalParsingContext , err := createTerragruntEvalContext (parser , file .ConfigPath )
165
+
166
+ if err != nil {
167
+ return nil , errors .New (err )
168
+ }
169
+
170
+ values := map [string ]cty.Value {}
171
+
172
+ if err := file .Decode (& values , evalParsingContext ); err != nil {
173
+ return nil , errors .New (err )
174
+ }
175
+
176
+ result := cty .ObjectVal (values )
177
+
178
+ return & result , nil
179
+ }
180
+
90
181
// ValidateStackConfig validates a StackConfigFile instance according to the rules:
91
182
// - Unit name, source, and path shouldn't be empty
92
183
// - Unit names should be unique
@@ -98,8 +189,9 @@ func ValidateStackConfig(config *StackConfigFile) error {
98
189
99
190
validationErrors := & errors.MultiError {}
100
191
101
- names := make (map [string ]bool )
102
- paths := make (map [string ]bool )
192
+ // Pre-allocate maps with known capacity to avoid resizing
193
+ names := make (map [string ]bool , len (config .Units ))
194
+ paths := make (map [string ]bool , len (config .Units ))
103
195
104
196
for i , unit := range config .Units {
105
197
name := strings .TrimSpace (unit .Name )
0 commit comments