Skip to content

Commit 2df2cda

Browse files
authored
chore: errors block processing simplification (#4046)
* Errors block code cleanup * Errors block cleanup * Error block fix * Extracted common code * Removed unused code * Map to slice tests update * Fixed lint issues * Lint issues fix * Cleanp
1 parent 9f9929a commit 2df2cda

File tree

4 files changed

+161
-99
lines changed

4 files changed

+161
-99
lines changed

config/config.go

-1
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,6 @@ func decodeAsTerragruntConfigFile(ctx *ParsingContext, file *hclparse.File, eval
975975

976976
if err := file.Decode(&terragruntConfig, evalContext); err != nil {
977977
var diagErr hcl.Diagnostics
978-
// diagErr, ok := errors.Unwrap(err).(hcl.Diagnostics)
979978
ok := errors.As(err, &diagErr)
980979

981980
// in case of render-json command and inputs reference error, we update the inputs with default value

config/errors_block.go

+121-98
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99

1010
// ErrorsConfig represents the top-level errors configuration
1111
type ErrorsConfig struct {
12-
Retry []*RetryBlock `cty:"retry" hcl:"retry,block"`
13-
Ignore []*IgnoreBlock `cty:"ignore" hcl:"ignore,block"`
12+
Retry []*RetryBlock `cty:"retry" hcl:"retry,block"`
13+
Ignore []*IgnoreBlock `cty:"ignore" hcl:"ignore,block"`
1414
}
1515

1616
// RetryBlock represents a labeled retry block
@@ -29,146 +29,169 @@ type IgnoreBlock struct {
2929
IgnorableErrors []string `cty:"ignorable_errors" hcl:"ignorable_errors"`
3030
}
3131

32-
// Clone creates a deep copy of ErrorsConfig
32+
// Clone returns a deep copy of ErrorsConfig
3333
func (c *ErrorsConfig) Clone() *ErrorsConfig {
3434
if c == nil {
3535
return nil
3636
}
3737

38-
clone := &ErrorsConfig{
39-
Retry: make([]*RetryBlock, len(c.Retry)),
40-
Ignore: make([]*IgnoreBlock, len(c.Ignore)),
38+
return &ErrorsConfig{
39+
Retry: cloneRetryBlocks(c.Retry),
40+
Ignore: cloneIgnoreBlocks(c.Ignore),
4141
}
42+
}
4243

43-
// Clone Retry blocks
44-
for i, retry := range c.Retry {
45-
clone.Retry[i] = retry.Clone()
44+
// Merge combines the current ErrorsConfig with another one, prioritizing the other config
45+
func (c *ErrorsConfig) Merge(other *ErrorsConfig) {
46+
if c == nil || other == nil {
47+
return
4648
}
4749

48-
// Clone Ignore blocks
49-
for i, ignore := range c.Ignore {
50-
clone.Ignore[i] = ignore.Clone()
50+
c.Retry = mergeRetryBlocks(c.Retry, other.Retry)
51+
c.Ignore = mergeIgnoreBlocks(c.Ignore, other.Ignore)
52+
}
53+
54+
// Clone returns a deep copy of a RetryBlock
55+
func (r *RetryBlock) Clone() *RetryBlock {
56+
if r == nil {
57+
return nil
5158
}
5259

53-
return clone
60+
return &RetryBlock{
61+
Label: r.Label,
62+
RetryableErrors: cloneStringSlice(r.RetryableErrors),
63+
MaxAttempts: r.MaxAttempts,
64+
SleepIntervalSec: r.SleepIntervalSec,
65+
}
5466
}
5567

56-
// Merge combines the current ErrorsConfig with another one, with the other config taking precedence
57-
func (c *ErrorsConfig) Merge(other *ErrorsConfig) {
58-
if other == nil {
59-
return
68+
// Clone returns a deep copy of an IgnoreBlock
69+
func (i *IgnoreBlock) Clone() *IgnoreBlock {
70+
if i == nil {
71+
return nil
6072
}
6173

62-
if c == nil {
63-
*c = *other
64-
return
74+
return &IgnoreBlock{
75+
Label: i.Label,
76+
IgnorableErrors: cloneStringSlice(i.IgnorableErrors),
77+
Message: i.Message,
78+
Signals: cloneSignalsMap(i.Signals),
6579
}
80+
}
6681

67-
retryMap := make(map[string]*RetryBlock)
68-
for _, block := range c.Retry {
69-
retryMap[block.Label] = block
82+
// Helper function to deep copy a slice of RetryBlock
83+
func cloneRetryBlocks(blocks []*RetryBlock) []*RetryBlock {
84+
if blocks == nil {
85+
return nil
7086
}
7187

72-
ignoreMap := make(map[string]*IgnoreBlock)
73-
for _, block := range c.Ignore {
74-
ignoreMap[block.Label] = block
88+
cloned := make([]*RetryBlock, len(blocks))
89+
for i, block := range blocks {
90+
cloned[i] = block.Clone()
7591
}
7692

77-
// Merge retry blocks
78-
for _, otherBlock := range other.Retry {
79-
if existing, exists := retryMap[otherBlock.Label]; exists {
80-
existing.RetryableErrors = util.MergeStringSlices(existing.RetryableErrors, otherBlock.RetryableErrors)
81-
82-
if otherBlock.MaxAttempts > 0 {
83-
existing.MaxAttempts = otherBlock.MaxAttempts
84-
}
93+
return cloned
94+
}
8595

86-
if otherBlock.SleepIntervalSec > 0 {
87-
existing.SleepIntervalSec = otherBlock.SleepIntervalSec
88-
}
89-
} else {
90-
// Add new block
91-
retryMap[otherBlock.Label] = otherBlock
92-
}
96+
// Helper function to deep copy a slice of IgnoreBlock
97+
func cloneIgnoreBlocks(blocks []*IgnoreBlock) []*IgnoreBlock {
98+
if blocks == nil {
99+
return nil
93100
}
94101

95-
// Merge ignore blocks
96-
for _, otherBlock := range other.Ignore {
97-
if existing, exists := ignoreMap[otherBlock.Label]; exists {
98-
existing.IgnorableErrors = util.MergeStringSlices(existing.IgnorableErrors, otherBlock.IgnorableErrors)
99-
100-
if otherBlock.Message != "" {
101-
existing.Message = otherBlock.Message
102-
}
102+
cloned := make([]*IgnoreBlock, len(blocks))
103+
for i, block := range blocks {
104+
cloned[i] = block.Clone()
105+
}
103106

104-
if otherBlock.Signals != nil {
105-
if existing.Signals == nil {
106-
existing.Signals = make(map[string]cty.Value)
107-
}
107+
return cloned
108+
}
108109

109-
maps.Copy(existing.Signals, otherBlock.Signals)
110-
}
111-
} else {
112-
// Add new block
113-
ignoreMap[otherBlock.Label] = otherBlock
114-
}
110+
// Helper function to deep copy a slice of strings
111+
func cloneStringSlice(slice []string) []string {
112+
if slice == nil {
113+
return nil
115114
}
116115

117-
// Convert maps back to slices
118-
c.Retry = make([]*RetryBlock, 0, len(retryMap))
119-
for _, block := range retryMap {
120-
c.Retry = append(c.Retry, block)
121-
}
116+
cloned := make([]string, len(slice))
117+
copy(cloned, slice)
122118

123-
c.Ignore = make([]*IgnoreBlock, 0, len(ignoreMap))
124-
for _, block := range ignoreMap {
125-
c.Ignore = append(c.Ignore, block)
126-
}
119+
return cloned
127120
}
128121

129-
// Clone creates a deep copy of RetryBlock
130-
func (r *RetryBlock) Clone() *RetryBlock {
131-
if r == nil {
122+
// Helper function to deep copy a map of signals
123+
func cloneSignalsMap(signals map[string]cty.Value) map[string]cty.Value {
124+
if signals == nil {
132125
return nil
133126
}
134127

135-
clone := &RetryBlock{
136-
Label: r.Label,
137-
MaxAttempts: r.MaxAttempts,
138-
SleepIntervalSec: r.SleepIntervalSec,
128+
cloned := make(map[string]cty.Value, len(signals))
129+
maps.Copy(cloned, signals)
130+
131+
return cloned
132+
}
133+
134+
// Merges two slices of RetryBlock, prioritizing the second slice
135+
func mergeRetryBlocks(existing, other []*RetryBlock) []*RetryBlock {
136+
retryMap := make(map[string]*RetryBlock, len(existing)+len(other))
137+
138+
// Add existing retry blocks
139+
for _, block := range existing {
140+
retryMap[block.Label] = block
139141
}
140142

141-
// Deep copy RetryableErrors slice
142-
if r.RetryableErrors != nil {
143-
clone.RetryableErrors = make([]string, len(r.RetryableErrors))
144-
copy(clone.RetryableErrors, r.RetryableErrors)
143+
// Merge retry blocks from 'other'
144+
for _, otherBlock := range other {
145+
if existingBlock, found := retryMap[otherBlock.Label]; found {
146+
existingBlock.RetryableErrors = util.MergeStringSlices(existingBlock.RetryableErrors, otherBlock.RetryableErrors)
147+
148+
if otherBlock.MaxAttempts > 0 {
149+
existingBlock.MaxAttempts = otherBlock.MaxAttempts
150+
}
151+
152+
if otherBlock.SleepIntervalSec > 0 {
153+
existingBlock.SleepIntervalSec = otherBlock.SleepIntervalSec
154+
}
155+
156+
continue
157+
}
158+
159+
retryMap[otherBlock.Label] = otherBlock
145160
}
146161

147-
return clone
162+
return util.MapToSlice(retryMap)
148163
}
149164

150-
// Clone creates a deep copy of IgnoreBlock
151-
func (i *IgnoreBlock) Clone() *IgnoreBlock {
152-
if i == nil {
153-
return nil
154-
}
165+
// Merges two slices of IgnoreBlock, prioritizing the second slice
166+
func mergeIgnoreBlocks(existing, other []*IgnoreBlock) []*IgnoreBlock {
167+
ignoreMap := make(map[string]*IgnoreBlock, len(existing)+len(other))
155168

156-
clone := &IgnoreBlock{
157-
Label: i.Label,
158-
Message: i.Message,
169+
// Add existing ignore blocks
170+
for _, block := range existing {
171+
ignoreMap[block.Label] = block
159172
}
160173

161-
// Deep copy IgnorableErrors slice
162-
if i.IgnorableErrors != nil {
163-
clone.IgnorableErrors = make([]string, len(i.IgnorableErrors))
164-
copy(clone.IgnorableErrors, i.IgnorableErrors)
165-
}
174+
// Merge ignore blocks from 'other'
175+
for _, otherBlock := range other {
176+
if existingBlock, found := ignoreMap[otherBlock.Label]; found {
177+
existingBlock.IgnorableErrors = util.MergeStringSlices(existingBlock.IgnorableErrors, otherBlock.IgnorableErrors)
166178

167-
// Deep copy Signals map
168-
if i.Signals != nil {
169-
clone.Signals = make(map[string]cty.Value, len(i.Signals))
170-
maps.Copy(clone.Signals, i.Signals)
179+
if otherBlock.Message != "" {
180+
existingBlock.Message = otherBlock.Message
181+
}
182+
183+
if otherBlock.Signals != nil {
184+
if existingBlock.Signals == nil {
185+
existingBlock.Signals = make(map[string]cty.Value, len(otherBlock.Signals))
186+
}
187+
188+
maps.Copy(existingBlock.Signals, otherBlock.Signals)
189+
}
190+
} else {
191+
ignoreMap[otherBlock.Label] = otherBlock
192+
}
171193
}
172194

173-
return clone
195+
// Convert map back to slice
196+
return util.MapToSlice(ignoreMap)
174197
}

util/collections.go

+17
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,20 @@ func MergeStringSlices(a, b []string) []string {
225225

226226
return result
227227
}
228+
229+
// MapToSlice transforms a map with string keys and pointer values into a slice of pointers.
230+
// It extracts all values from the map and returns them as a slice while maintaining their original order in the map iteration.
231+
//
232+
// Parameters:
233+
// - m: A map where the keys are strings and the values are pointers to elements of type T.
234+
//
235+
// Returns:
236+
// - A slice containing all the pointer values from the input map.
237+
func MapToSlice[T any](m map[string]*T) []*T {
238+
result := make([]*T, 0, len(m))
239+
for _, block := range m {
240+
result = append(result, block)
241+
}
242+
243+
return result
244+
}

util/collections_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,26 @@ func TestStringListInsert(t *testing.T) {
273273
})
274274
}
275275
}
276+
277+
func TestMapToSlice(t *testing.T) {
278+
t.Parallel()
279+
280+
t.Run("Empty Map", func(t *testing.T) {
281+
t.Parallel()
282+
m := make(map[string]*int)
283+
result := util.MapToSlice(m)
284+
if len(result) != 0 {
285+
t.Errorf("Expected empty slice, got %v", result)
286+
}
287+
})
288+
289+
t.Run("Single Element Map", func(t *testing.T) {
290+
t.Parallel()
291+
val := 42
292+
m := map[string]*int{"key1": &val}
293+
result := util.MapToSlice(m)
294+
if len(result) != 1 || result[0] != &val {
295+
t.Errorf("Expected slice with one element %v, got %v", &val, result)
296+
}
297+
})
298+
}

0 commit comments

Comments
 (0)