Skip to content

Commit 077921f

Browse files
authored
Merge pull request #5925 from sbueringer/pr-allow-nested-valuefrom-variable
🐛 ClusterClass: allow accessing nestedFields via valueFrom.variable
2 parents 5d5dc7a + 335ae95 commit 077921f

File tree

2 files changed

+153
-1
lines changed

2 files changed

+153
-1
lines changed

internal/webhooks/patch_validation.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,12 @@ func validateJSONPatchValues(jsonPatch clusterv1.JSONPatch, variableSet map[stri
296296
))
297297
}
298298
} else {
299-
if _, ok := variableSet[*jsonPatch.ValueFrom.Variable]; !ok {
299+
// Note: We're only validating if the variable name exists without
300+
// validating if the whole path is an existing variable.
301+
// This could be done by re-using getVariableValue of the json patch
302+
// generator but requires a refactoring first.
303+
variableName := getVariableName(*jsonPatch.ValueFrom.Variable)
304+
if _, ok := variableSet[variableName]; !ok {
300305
allErrs = append(allErrs,
301306
field.Invalid(
302307
path.Child("valueFrom", "variable"),
@@ -309,6 +314,12 @@ func validateJSONPatchValues(jsonPatch clusterv1.JSONPatch, variableSet map[stri
309314
return allErrs
310315
}
311316

317+
func getVariableName(variable string) string {
318+
return strings.FieldsFunc(variable, func(r rune) bool {
319+
return r == '[' || r == '.'
320+
})[0]
321+
}
322+
312323
// This contains a list of all of the valid builtin variables.
313324
// TODO(killianmuldoon): Match this list to controllers/topology/internal/extensions/patches/variables as those structs become available across the code base i.e. public or top-level internal.
314325
var builtinVariables = sets.NewString(

internal/webhooks/patch_validation_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,115 @@ func TestValidatePatches(t *testing.T) {
11851185
},
11861186
wantErr: true,
11871187
},
1188+
{
1189+
name: "pass if jsonPatch uses a user-defined variable which is defined",
1190+
clusterClass: clusterv1.ClusterClass{
1191+
Spec: clusterv1.ClusterClassSpec{
1192+
ControlPlane: clusterv1.ControlPlaneClass{
1193+
LocalObjectTemplate: clusterv1.LocalObjectTemplate{
1194+
Ref: &corev1.ObjectReference{
1195+
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
1196+
Kind: "ControlPlaneTemplate",
1197+
},
1198+
},
1199+
},
1200+
Patches: []clusterv1.ClusterClassPatch{
1201+
{
1202+
Name: "patch1",
1203+
Definitions: []clusterv1.PatchDefinition{
1204+
{
1205+
Selector: clusterv1.PatchSelector{
1206+
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
1207+
Kind: "ControlPlaneTemplate",
1208+
MatchResources: clusterv1.PatchSelectorMatch{
1209+
ControlPlane: true,
1210+
},
1211+
},
1212+
JSONPatches: []clusterv1.JSONPatch{
1213+
{
1214+
Op: "add",
1215+
Path: "/spec/template/spec/",
1216+
ValueFrom: &clusterv1.JSONPatchValue{
1217+
Variable: pointer.String("variableName"),
1218+
},
1219+
},
1220+
},
1221+
},
1222+
},
1223+
},
1224+
},
1225+
Variables: []clusterv1.ClusterClassVariable{
1226+
{
1227+
Name: "variableName",
1228+
Required: true,
1229+
Schema: clusterv1.VariableSchema{
1230+
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
1231+
Type: "string",
1232+
},
1233+
},
1234+
},
1235+
},
1236+
},
1237+
},
1238+
wantErr: false,
1239+
},
1240+
{
1241+
name: "pass if jsonPatch uses a nested user-defined variable which is defined",
1242+
clusterClass: clusterv1.ClusterClass{
1243+
Spec: clusterv1.ClusterClassSpec{
1244+
ControlPlane: clusterv1.ControlPlaneClass{
1245+
LocalObjectTemplate: clusterv1.LocalObjectTemplate{
1246+
Ref: &corev1.ObjectReference{
1247+
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
1248+
Kind: "ControlPlaneTemplate",
1249+
},
1250+
},
1251+
},
1252+
Patches: []clusterv1.ClusterClassPatch{
1253+
{
1254+
Name: "patch1",
1255+
Definitions: []clusterv1.PatchDefinition{
1256+
{
1257+
Selector: clusterv1.PatchSelector{
1258+
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
1259+
Kind: "ControlPlaneTemplate",
1260+
MatchResources: clusterv1.PatchSelectorMatch{
1261+
ControlPlane: true,
1262+
},
1263+
},
1264+
JSONPatches: []clusterv1.JSONPatch{
1265+
{
1266+
Op: "add",
1267+
Path: "/spec/template/spec/",
1268+
ValueFrom: &clusterv1.JSONPatchValue{
1269+
Variable: pointer.String("variableName.nestedField"),
1270+
},
1271+
},
1272+
},
1273+
},
1274+
},
1275+
},
1276+
},
1277+
Variables: []clusterv1.ClusterClassVariable{
1278+
{
1279+
Name: "variableName",
1280+
Required: true,
1281+
Schema: clusterv1.VariableSchema{
1282+
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
1283+
Type: "object",
1284+
Properties: map[string]clusterv1.JSONSchemaProps{
1285+
"nestedField": {
1286+
Type: "string",
1287+
},
1288+
},
1289+
},
1290+
},
1291+
},
1292+
},
1293+
},
1294+
},
1295+
wantErr: false,
1296+
},
11881297
{
11891298
name: "error if jsonPatch uses a builtin variable which is not defined",
11901299
clusterClass: clusterv1.ClusterClass{
@@ -1628,3 +1737,35 @@ func Test_validateSelectors(t *testing.T) {
16281737
})
16291738
}
16301739
}
1740+
1741+
func TestGetVariableName(t *testing.T) {
1742+
tests := []struct {
1743+
name string
1744+
variable string
1745+
variableName string
1746+
}{
1747+
{
1748+
name: "simple variable",
1749+
variable: "variableA",
1750+
variableName: "variableA",
1751+
},
1752+
{
1753+
name: "variable object",
1754+
variable: "variableObject.field",
1755+
variableName: "variableObject",
1756+
},
1757+
{
1758+
name: "variable array",
1759+
variable: "variableArray[0]",
1760+
variableName: "variableArray",
1761+
},
1762+
}
1763+
1764+
for _, tt := range tests {
1765+
t.Run(tt.name, func(t *testing.T) {
1766+
g := NewWithT(t)
1767+
1768+
g.Expect(getVariableName(tt.variable)).To(Equal(tt.variableName))
1769+
})
1770+
}
1771+
}

0 commit comments

Comments
 (0)