@@ -5,9 +5,13 @@ package mutation
5
5
6
6
import (
7
7
"context"
8
+ "encoding/json"
8
9
"fmt"
10
+ "maps"
9
11
"sync"
10
12
13
+ "dario.cat/mergo"
14
+ "github.com/samber/lo"
11
15
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
12
16
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13
17
"k8s.io/apimachinery/pkg/runtime"
@@ -89,6 +93,21 @@ func (mgp metaGeneratePatches) GeneratePatches(
89
93
) {
90
94
clusterKey := handlers .ClusterKeyFromReq (req )
91
95
clusterGetter := mgp .CreateClusterGetter (clusterKey )
96
+
97
+ // Create a map of variables from the request that can be overridden by machine deployment or control plane
98
+ // configuration.
99
+ // Filter out the "builtin" variable, which is already present in the vars map and should not be overridden by
100
+ // the global variables.
101
+ globalVars := lo .FilterSliceToMap (
102
+ req .Variables ,
103
+ func (v runtimehooksv1.Variable ) (string , apiextensionsv1.JSON , bool ) {
104
+ if v .Name == "builtin" {
105
+ return "" , apiextensionsv1.JSON {}, false
106
+ }
107
+ return v .Name , v .Value , true
108
+ },
109
+ )
110
+
92
111
topologymutation .WalkTemplates (
93
112
ctx ,
94
113
unstructured .UnstructuredJSONScheme ,
@@ -108,20 +127,88 @@ func (mgp metaGeneratePatches) GeneratePatches(
108
127
109
128
log .V (3 ).Info ("Starting mutation pipeline" , "handlerCount" , len (mgp .mutators ))
110
129
111
- for i , h := range mgp .mutators {
112
- handlerName := fmt .Sprintf ("%T" , h )
113
- log .V (5 ).Info ("Running mutator" , "index" , i , "handler" , handlerName )
130
+ // Merge the global variables to the current resource vars. This allows the handlers to access
131
+ // the variables defined in the cluster class or cluster configuration and use these correctly when
132
+ // overrides are specified on machine deployment or control plane configuration.
133
+ mergedVars , err := mergeVariableDefinitions (vars , globalVars )
134
+ if err != nil {
135
+ log .Error (err , "Failed to merge global variables" )
136
+ return err
137
+ }
114
138
115
- if err := h .Mutate (ctx , obj .(* unstructured.Unstructured ), vars , holderRef , clusterKey , clusterGetter ); err != nil {
116
- log .Error (err , "Mutator failed" , "index" , i , "handler" , handlerName )
139
+ for i , h := range mgp .mutators {
140
+ mutatorType := fmt .Sprintf ("%T" , h )
141
+ log .V (5 ).
142
+ Info ("Running mutator" , "index" , i , "handler" , mutatorType , "vars" , vars )
143
+
144
+ if err := h .Mutate (
145
+ ctx ,
146
+ obj .(* unstructured.Unstructured ),
147
+ mergedVars ,
148
+ holderRef ,
149
+ clusterKey ,
150
+ clusterGetter ,
151
+ ); err != nil {
152
+ log .Error (err , "Mutator failed" , "index" , i , "handler" , mutatorType )
117
153
return err
118
154
}
119
155
120
- log .V (5 ).Info ("Mutator completed successfully" , "index" , i , "handler" , handlerName )
156
+ log .V (5 ).Info ("Mutator completed successfully" , "index" , i , "handler" , mutatorType )
121
157
}
122
158
123
159
log .V (3 ).Info ("Mutation pipeline completed successfully" , "handlerCount" , len (mgp .mutators ))
124
160
return nil
125
161
},
126
162
)
127
163
}
164
+
165
+ func mergeVariableDefinitions (
166
+ vars , globalVars map [string ]apiextensionsv1.JSON ,
167
+ ) (map [string ]apiextensionsv1.JSON , error ) {
168
+ mergedVars := maps .Clone (vars )
169
+
170
+ for k , v := range globalVars {
171
+ // If the value of v is nil, skip it.
172
+ if v .Raw == nil {
173
+ continue
174
+ }
175
+
176
+ existingValue , exists := mergedVars [k ]
177
+
178
+ // If the variable does not exist in the mergedVars or the value is nil, add it and continue.
179
+ if ! exists || existingValue .Raw == nil {
180
+ mergedVars [k ] = v
181
+ continue
182
+ }
183
+
184
+ // If both values are non-nil, we need to merge them.
185
+ var val , globalVal map [string ]interface {}
186
+
187
+ // Wrap the value in a temporary key to ensure we can unmarshal to a map.
188
+ // This is necessary because the values could be scalars.
189
+ tempValJSON := fmt .Sprintf (`{"value": %s}` , string (existingValue .Raw ))
190
+ tempGlobalValJSON := fmt .Sprintf (`{"value": %s}` , string (v .Raw ))
191
+ if err := json .Unmarshal ([]byte (tempValJSON ), & val ); err != nil {
192
+ return nil , fmt .Errorf ("failed to unmarshal existing value for key %q: %w" , k , err )
193
+ }
194
+
195
+ if err := json .Unmarshal ([]byte (tempGlobalValJSON ), & globalVal ); err != nil {
196
+ return nil , fmt .Errorf ("failed to unmarshal global value for key %q: %w" , k , err )
197
+ }
198
+
199
+ // Now use mergo to perform a deep merge of the values, retaining the values in `val` if present.
200
+ if err := mergo .Merge (& val , globalVal ); err != nil {
201
+ return nil , fmt .Errorf ("failed to merge values for key %q: %w" , k , err )
202
+ }
203
+
204
+ // Marshal the merged value back to JSON.
205
+ mergedVal , err := json .Marshal (val ["value" ])
206
+ if err != nil {
207
+ return nil , fmt .Errorf ("failed to marshal merged value for key %q: %w" , k , err )
208
+ }
209
+
210
+ mergedVars [k ] = apiextensionsv1.JSON {Raw : mergedVal }
211
+ }
212
+
213
+ return mergedVars , nil
214
+ }
0 commit comments