@@ -5,9 +5,13 @@ package mutation
55
66import (
77 "context"
8+ "encoding/json"
89 "fmt"
10+ "maps"
911 "sync"
1012
13+ "dario.cat/mergo"
14+ "github.com/samber/lo"
1115 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1216 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1317 "k8s.io/apimachinery/pkg/runtime"
@@ -89,6 +93,21 @@ func (mgp metaGeneratePatches) GeneratePatches(
8993) {
9094 clusterKey := handlers .ClusterKeyFromReq (req )
9195 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+
92111 topologymutation .WalkTemplates (
93112 ctx ,
94113 unstructured .UnstructuredJSONScheme ,
@@ -108,20 +127,88 @@ func (mgp metaGeneratePatches) GeneratePatches(
108127
109128 log .V (3 ).Info ("Starting mutation pipeline" , "handlerCount" , len (mgp .mutators ))
110129
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+ }
114138
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 )
117153 return err
118154 }
119155
120- log .V (5 ).Info ("Mutator completed successfully" , "index" , i , "handler" , handlerName )
156+ log .V (5 ).Info ("Mutator completed successfully" , "index" , i , "handler" , mutatorType )
121157 }
122158
123159 log .V (3 ).Info ("Mutation pipeline completed successfully" , "handlerCount" , len (mgp .mutators ))
124160 return nil
125161 },
126162 )
127163}
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+ // Wrap the value in a temporary key to ensure we can unmarshal to a map.
185+ // This is necessary because the values could be scalars.
186+ tempValJSON := fmt .Sprintf (`{"value": %s}` , string (existingValue .Raw ))
187+ tempGlobalValJSON := fmt .Sprintf (`{"value": %s}` , string (v .Raw ))
188+
189+ // Unmarshal the existing value and the global value into maps.
190+ var val , globalVal map [string ]interface {}
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