diff --git a/internal/controllers/topology/cluster/patches/inline/json_patch_generator_test.go b/internal/controllers/topology/cluster/patches/inline/json_patch_generator_test.go index f470ee44644b..0af03273a98a 100644 --- a/internal/controllers/topology/cluster/patches/inline/json_patch_generator_test.go +++ b/internal/controllers/topology/cluster/patches/inline/json_patch_generator_test.go @@ -243,7 +243,14 @@ func TestGenerate(t *testing.T) { JSONPatches: []clusterv1.JSONPatch{ { Op: "replace", - Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cluster-name", + Path: "/spec/template/spec/joinConfiguration/nodeRegistration/kubeletExtraArgs/cluster-name", + ValueFrom: &clusterv1.JSONPatchValue{ + Variable: pointer.String("builtin.cluster.name"), + }, + }, + { + Op: "replace", + Path: "/spec/template/spec/files", ValueFrom: &clusterv1.JSONPatchValue{ Template: pointer.String(` [{ @@ -254,6 +261,42 @@ func TestGenerate(t *testing.T) { } }, "owner":"root:root" +}]`), + }, + }, + }, + }, + { + Selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"default-mp-worker"}, + }, + }, + }, + JSONPatches: []clusterv1.JSONPatch{ + { + Op: "replace", + Path: "/spec/template/spec/joinConfiguration/nodeRegistration/kubeletExtraArgs/cluster-name", + ValueFrom: &clusterv1.JSONPatchValue{ + Variable: pointer.String("builtin.cluster.name"), + }, + }, + { + Op: "replace", + Path: "/spec/template/spec/files", + ValueFrom: &clusterv1.JSONPatchValue{ + Template: pointer.String(` +[{ + "contentFrom":{ + "secret":{ + "key":"worker-node-azure.json", + "name":"{{ .builtin.cluster.name }}-mp-0-azure-json" + } + }, + "owner":"root:root" }]`), }, }, @@ -311,6 +354,30 @@ func TestGenerate(t *testing.T) { }, }, }, + { + UID: "3", + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + Variables: []runtimehooksv1.Variable{ + { + Name: "builtin", + Value: apiextensionsv1.JSON{Raw: []byte(`{"machinePool":{"class":"default-mp-worker"}}`)}, + }, + }, + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + }, }, }, want: &runtimehooksv1.GeneratePatchesResponse{ @@ -327,7 +394,16 @@ func TestGenerate(t *testing.T) { { UID: "2", Patch: toJSONCompact(`[ -{"op":"replace","path":"/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cluster-name","value":[{"contentFrom":{"secret":{"key":"worker-node-azure.json","name":"cluster-name-md-0-azure-json"}},"owner":"root:root"}]} +{"op":"replace","path":"/spec/template/spec/joinConfiguration/nodeRegistration/kubeletExtraArgs/cluster-name","value":"cluster-name"}, +{"op":"replace","path":"/spec/template/spec/files","value":[{"contentFrom":{"secret":{"key":"worker-node-azure.json","name":"cluster-name-md-0-azure-json"}},"owner":"root:root"}]} +]`), + PatchType: runtimehooksv1.JSONPatchType, + }, + { + UID: "3", + Patch: toJSONCompact(`[ +{"op":"replace","path":"/spec/template/spec/joinConfiguration/nodeRegistration/kubeletExtraArgs/cluster-name","value":"cluster-name"}, +{"op":"replace","path":"/spec/template/spec/files","value":[{"contentFrom":{"secret":{"key":"worker-node-azure.json","name":"cluster-name-mp-0-azure-json"}},"owner":"root:root"}]} ]`), PatchType: runtimehooksv1.JSONPatchType, }, @@ -617,6 +693,39 @@ func TestMatchesSelector(t *testing.T) { }, match: true, }, + { + name: "Match MP BootstrapTemplate", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"classA"}, + }, + }, + }, + match: true, + }, { name: "Match all MD BootstrapTemplate", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -650,6 +759,39 @@ func TestMatchesSelector(t *testing.T) { }, match: true, }, + { + name: "Match all MP BootstrapTemplate", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"*"}, + }, + }, + }, + match: true, + }, { name: "Glob match MD BootstrapTemplate with -*", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -683,6 +825,39 @@ func TestMatchesSelector(t *testing.T) { }, match: true, }, + { + name: "Glob match MP BootstrapTemplate with -*", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"class-A"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"class-*"}, + }, + }, + }, + match: true, + }, { name: "Glob match MD BootstrapTemplate with *-", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -716,7 +891,39 @@ func TestMatchesSelector(t *testing.T) { }, match: true, }, - + { + name: "Glob match MP BootstrapTemplate with *-", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"class-A"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"*-A"}, + }, + }, + }, + match: true, + }, { name: "Don't match BootstrapTemplate, .matchResources.machineDeploymentClass.names is empty", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -750,6 +957,39 @@ func TestMatchesSelector(t *testing.T) { }, match: false, }, + { + name: "Don't match BootstrapTemplate, .matchResources.machinePoolClass.names is empty", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{}, + }, + }, + }, + match: false, + }, { name: "Do not match BootstrapTemplate, .matchResources.machineDeploymentClass is set to nil", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -781,6 +1021,37 @@ func TestMatchesSelector(t *testing.T) { }, match: false, }, + { + name: "Do not match BootstrapTemplate, .matchResources.machinePoolClass is set to nil", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: nil, + }, + }, + match: false, + }, { name: "Don't match BootstrapTemplate, .matchResources.machineDeploymentClass not set", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -810,6 +1081,35 @@ func TestMatchesSelector(t *testing.T) { }, match: false, }, + { + name: "Don't match BootstrapTemplate, .matchResources.machinePoolClass not set", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{}, + }, + match: false, + }, { name: "Don't match BootstrapTemplate, .matchResources.machineDeploymentClass does not match", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -843,6 +1143,39 @@ func TestMatchesSelector(t *testing.T) { }, match: false, }, + { + name: "Don't match BootstrapTemplate, .matchResources.machinePoolClass does not match", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", + "kind": "BootstrapTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", + Kind: "BootstrapTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"classB"}, + }, + }, + }, + match: false, + }, { name: "Match MD InfrastructureMachineTemplate", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -876,6 +1209,39 @@ func TestMatchesSelector(t *testing.T) { }, match: true, }, + { + name: "Match MP InfrastructureMachinePoolTemplate", + req: &runtimehooksv1.GeneratePatchesRequestItem{ + Object: runtime.RawExtension{ + Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", + "kind": "AzureMachinePoolTemplate", + }, + }, + }, + HolderReference: runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "MachinePool", + Name: "my-mp-0", + Namespace: "default", + FieldPath: "spec.template.spec.infrastructureRef", + }, + }, + templateVariables: map[string]apiextensionsv1.JSON{ + "builtin": {Raw: []byte(`{"machinePool":{"class":"classA"}}`)}, + }, + selector: clusterv1.PatchSelector{ + APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", + Kind: "AzureMachinePoolTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{ + Names: []string{"classA"}, + }, + }, + }, + match: true, + }, { name: "Don't match: unknown field path", req: &runtimehooksv1.GeneratePatchesRequestItem{ @@ -1800,7 +2166,39 @@ owner: root:root } }`)}, // Schema must either support complex objects with predefined keys/mdClasses or maps with additionalProperties. - patchvariables.BuiltinsName: {Raw: []byte(`{"machineDeployment":{"version":"v1.21.1","class":"mdClass2","name":"md1","topologyName":"md-topology","replicas":3}}`)}, + patchvariables.BuiltinsName: {Raw: []byte(`{ +"machineDeployment":{ + "version":"v1.21.1", + "class":"mdClass2", + "name":"md1", + "topologyName":"md-topology", + "replicas":3 +}}`)}, + }, + want: &apiextensionsv1.JSON{Raw: []byte(`"configValue2"`)}, + }, + // Pick up config for a specific MP Class + { + name: "Should render a object property with a lookup based on a builtin variable (class)", + template: `{{ (index .mpConfig .builtin.machinePool.class).config }}`, + variables: map[string]apiextensionsv1.JSON{ + "mpConfig": {Raw: []byte(`{ +"mpClass1":{ + "config":"configValue1" +}, +"mpClass2":{ + "config":"configValue2" +} +}`)}, + // Schema must either support complex objects with predefined keys/mdClasses or maps with additionalProperties. + patchvariables.BuiltinsName: {Raw: []byte(`{ +"machinePool":{ + "version":"v1.21.1", + "class":"mpClass2", + "name":"mp1", + "topologyName":"mp-topology", + "replicas":3 +}}`)}, }, want: &apiextensionsv1.JSON{Raw: []byte(`"configValue2"`)}, }, @@ -1822,6 +2220,23 @@ owner: root:root }, want: &apiextensionsv1.JSON{Raw: []byte(`"configValue2"`)}, }, + { + name: "Should render a object property with a lookup based on a builtin variable (version)", + template: `{{ (index .mpConfig .builtin.machinePool.version).config }}`, + variables: map[string]apiextensionsv1.JSON{ + "mpConfig": {Raw: []byte(`{ +"v1.21.0":{ + "config":"configValue1" +}, +"v1.21.1":{ + "config":"configValue2" +} +}`)}, + // Schema must either support complex objects with predefined keys/mpClasses or maps with additionalProperties. + patchvariables.BuiltinsName: {Raw: []byte(`{"machinePool":{"version":"v1.21.1","class":"mpClass2","name":"mp1","topologyName":"mp-topology","replicas":3}}`)}, + }, + want: &apiextensionsv1.JSON{Raw: []byte(`"configValue2"`)}, + }, } for _, tt := range tests { @@ -1884,7 +2299,7 @@ func TestCalculateTemplateData(t *testing.T) { { name: "Should handle nested variables correctly", variables: map[string]apiextensionsv1.JSON{ - "builtin": {Raw: []byte(`{"cluster":{"name":"cluster-name","namespace":"default","topology":{"class":"clusterClass1","version":"v1.22.0"}},"controlPlane":{"replicas":3},"machineDeployment":{"version":"v1.21.2"}}`)}, + "builtin": {Raw: []byte(`{"cluster":{"name":"cluster-name","namespace":"default","topology":{"class":"clusterClass1","version":"v1.22.0"}},"controlPlane":{"replicas":3},"machineDeployment":{"version":"v1.21.2"},"machinePool":{"version":"v1.21.2"}}`)}, "userVariable": {Raw: []byte(`"value"`)}, }, want: map[string]interface{}{ @@ -1903,6 +2318,9 @@ func TestCalculateTemplateData(t *testing.T) { "machineDeployment": map[string]interface{}{ "version": "v1.21.2", }, + "machinePool": map[string]interface{}{ + "version": "v1.21.2", + }, }, "userVariable": "value", },