sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/patches/variables/variables_test.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package variables
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"testing"
    23  
    24  	. "github.com/onsi/gomega"
    25  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/utils/ptr"
    29  
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    32  	runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
    33  	"sigs.k8s.io/cluster-api/internal/test/builder"
    34  )
    35  
    36  func TestGlobal(t *testing.T) {
    37  	tests := []struct {
    38  		name                        string
    39  		clusterTopology             *clusterv1.Topology
    40  		cluster                     *clusterv1.Cluster
    41  		forPatch                    string
    42  		variableDefinitionsForPatch map[string]bool
    43  		want                        []runtimehooksv1.Variable
    44  	}{
    45  		{
    46  			name:                        "Should calculate global variables",
    47  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
    48  			forPatch:                    "patch1",
    49  			clusterTopology: &clusterv1.Topology{
    50  				Variables: []clusterv1.ClusterVariable{
    51  					{
    52  						Name:  "location",
    53  						Value: toJSON("\"us-central\""),
    54  					},
    55  					{
    56  						Name:  "cpu",
    57  						Value: toJSON("8"),
    58  					},
    59  					{
    60  						// This is blocked by a webhook, but let's make sure that the user-defined
    61  						// variable is overwritten by the builtin variable anyway.
    62  						Name:  "builtin",
    63  						Value: toJSON("8"),
    64  					},
    65  				},
    66  			},
    67  			cluster: &clusterv1.Cluster{
    68  				ObjectMeta: metav1.ObjectMeta{
    69  					Name:      "cluster1",
    70  					Namespace: metav1.NamespaceDefault,
    71  				},
    72  				Spec: clusterv1.ClusterSpec{
    73  					Topology: &clusterv1.Topology{
    74  						Class:   "clusterClass1",
    75  						Version: "v1.21.1",
    76  					},
    77  					ClusterNetwork: &clusterv1.ClusterNetwork{
    78  						Services: &clusterv1.NetworkRanges{
    79  							CIDRBlocks: []string{"10.10.10.1/24"},
    80  						},
    81  						Pods: &clusterv1.NetworkRanges{
    82  							CIDRBlocks: []string{"11.10.10.1/24"},
    83  						},
    84  						ServiceDomain: "cluster.local",
    85  					},
    86  				},
    87  			},
    88  			want: []runtimehooksv1.Variable{
    89  				{
    90  					Name:  "location",
    91  					Value: toJSON("\"us-central\""),
    92  				},
    93  				{
    94  					Name:  "cpu",
    95  					Value: toJSON("8"),
    96  				},
    97  				{
    98  					Name: runtimehooksv1.BuiltinsName,
    99  					Value: toJSONCompact(`{
   100  					"cluster":{
   101  						"name": "cluster1",
   102    						"namespace": "default",
   103   						 "topology":{
   104    						  	"version": "v1.21.1",
   105   						   	"class": "clusterClass1"
   106    						},
   107    						"network":{
   108  							"serviceDomain":"cluster.local",
   109    						 	"services":["10.10.10.1/24"],
   110     							"pods":["11.10.10.1/24"],
   111      						"ipFamily": "IPv4"
   112  						}
   113  					}}`),
   114  				},
   115  			},
   116  		},
   117  		{
   118  			name:                        "Should calculate global variables for a given forPatch",
   119  			forPatch:                    "patch1",
   120  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   121  			clusterTopology: &clusterv1.Topology{
   122  				Variables: []clusterv1.ClusterVariable{
   123  					{
   124  						Name:           "location",
   125  						Value:          toJSON("\"us-central\""),
   126  						DefinitionFrom: "patch1",
   127  					},
   128  					{
   129  						Name:  "location",
   130  						Value: toJSON("\"internal.proxy.com\""),
   131  						// This variable should be excluded because it is defined for a different patch.
   132  						DefinitionFrom: "anotherPatch",
   133  					},
   134  					{
   135  						Name:  "https-proxy",
   136  						Value: toJSON("\"internal.proxy.com\""),
   137  						// This variable should be excluded because it is not among variableDefinitionsForPatch.
   138  						DefinitionFrom: "",
   139  					},
   140  
   141  					{
   142  						Name:  "cpu",
   143  						Value: toJSON("8"),
   144  						// This variable should be included because it is defined for all patches.
   145  					},
   146  				},
   147  			},
   148  			cluster: &clusterv1.Cluster{
   149  				ObjectMeta: metav1.ObjectMeta{
   150  					Name:      "cluster1",
   151  					Namespace: metav1.NamespaceDefault,
   152  				},
   153  				Spec: clusterv1.ClusterSpec{
   154  					Topology: &clusterv1.Topology{
   155  						Class:   "clusterClass1",
   156  						Version: "v1.21.1",
   157  					},
   158  					ClusterNetwork: &clusterv1.ClusterNetwork{
   159  						Services: &clusterv1.NetworkRanges{
   160  							CIDRBlocks: []string{"10.10.10.1/24"},
   161  						},
   162  						Pods: &clusterv1.NetworkRanges{
   163  							CIDRBlocks: []string{"11.10.10.1/24"},
   164  						},
   165  						ServiceDomain: "cluster.local",
   166  					},
   167  				},
   168  			},
   169  			want: []runtimehooksv1.Variable{
   170  				{
   171  					Name:  "location",
   172  					Value: toJSON("\"us-central\""),
   173  				},
   174  				{
   175  					Name:  "cpu",
   176  					Value: toJSON("8"),
   177  				},
   178  				{
   179  					Name: runtimehooksv1.BuiltinsName,
   180  					Value: toJSONCompact(`{
   181  					"cluster":{
   182  						"name": "cluster1",
   183    						"namespace": "default",
   184   						 "topology":{
   185    						  	"version": "v1.21.1",
   186   						   	"class": "clusterClass1"
   187    						},
   188    						"network":{
   189  							"serviceDomain":"cluster.local",
   190    						 	"services":["10.10.10.1/24"],
   191     							"pods":["11.10.10.1/24"],
   192      						"ipFamily": "IPv4"
   193  						}
   194  					}}`),
   195  				},
   196  			},
   197  		},
   198  		{
   199  			name:                        "Should calculate when serviceDomain is not set",
   200  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   201  			forPatch:                    "patch1",
   202  			clusterTopology: &clusterv1.Topology{
   203  				Variables: []clusterv1.ClusterVariable{
   204  					{
   205  						Name:  "location",
   206  						Value: toJSON("\"us-central\""),
   207  					},
   208  					{
   209  						Name:  "cpu",
   210  						Value: toJSON("8"),
   211  					},
   212  					{
   213  						// This is blocked by a webhook, but let's make sure that the user-defined
   214  						// variable is overwritten by the builtin variable anyway.
   215  						Name:  "builtin",
   216  						Value: toJSON("8"),
   217  					},
   218  				},
   219  			},
   220  			cluster: &clusterv1.Cluster{
   221  				ObjectMeta: metav1.ObjectMeta{
   222  					Name:      "cluster1",
   223  					Namespace: metav1.NamespaceDefault,
   224  				},
   225  				Spec: clusterv1.ClusterSpec{
   226  					Topology: &clusterv1.Topology{
   227  						Class:   "clusterClass1",
   228  						Version: "v1.21.1",
   229  					},
   230  					ClusterNetwork: &clusterv1.ClusterNetwork{
   231  						Services: &clusterv1.NetworkRanges{
   232  							CIDRBlocks: []string{"10.10.10.1/24"},
   233  						},
   234  						Pods: &clusterv1.NetworkRanges{
   235  							CIDRBlocks: []string{"11.10.10.1/24"},
   236  						},
   237  					},
   238  				},
   239  			},
   240  			want: []runtimehooksv1.Variable{
   241  				{
   242  					Name:  "location",
   243  					Value: toJSON("\"us-central\""),
   244  				},
   245  				{
   246  					Name:  "cpu",
   247  					Value: toJSON("8"),
   248  				},
   249  				{
   250  					Name: runtimehooksv1.BuiltinsName,
   251  					Value: toJSONCompact(`{
   252  					"cluster":{
   253  						"name": "cluster1",
   254    						"namespace": "default",
   255   						 "topology":{
   256    						  	"version": "v1.21.1",
   257   						   	"class": "clusterClass1"
   258    						},
   259    						"network":{
   260    						 	"services":["10.10.10.1/24"],
   261     							"pods":["11.10.10.1/24"],
   262      						"ipFamily": "IPv4"
   263  						}
   264  					}}`),
   265  				},
   266  			},
   267  		},
   268  		{
   269  			name:                        "Should calculate where some variables are nil",
   270  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   271  			forPatch:                    "patch1",
   272  			clusterTopology: &clusterv1.Topology{
   273  				Variables: []clusterv1.ClusterVariable{
   274  					{
   275  						Name:  "location",
   276  						Value: toJSON("\"us-central\""),
   277  					},
   278  					{
   279  						Name:  "cpu",
   280  						Value: toJSON("8"),
   281  					},
   282  					{
   283  						// This is blocked by a webhook, but let's make sure that the user-defined
   284  						// variable is overwritten by the builtin variable anyway.
   285  						Name:  "builtin",
   286  						Value: toJSON("8"),
   287  					},
   288  				},
   289  			},
   290  			cluster: &clusterv1.Cluster{
   291  				ObjectMeta: metav1.ObjectMeta{
   292  					Name:      "cluster1",
   293  					Namespace: metav1.NamespaceDefault,
   294  				},
   295  				Spec: clusterv1.ClusterSpec{
   296  					Topology: &clusterv1.Topology{
   297  						Class:   "clusterClass1",
   298  						Version: "v1.21.1",
   299  					},
   300  					ClusterNetwork: &clusterv1.ClusterNetwork{
   301  						Services:      nil,
   302  						Pods:          &clusterv1.NetworkRanges{},
   303  						ServiceDomain: "cluster.local",
   304  					},
   305  				},
   306  			},
   307  			want: []runtimehooksv1.Variable{
   308  				{
   309  					Name:  "location",
   310  					Value: toJSON("\"us-central\""),
   311  				},
   312  				{
   313  					Name:  "cpu",
   314  					Value: toJSON("8"),
   315  				},
   316  				{
   317  					Name: runtimehooksv1.BuiltinsName,
   318  					Value: toJSONCompact(`{
   319  					"cluster":{
   320    						"name": "cluster1",
   321    						"namespace": "default",
   322   						"topology":{
   323      						"version": "v1.21.1",
   324      						"class": "clusterClass1"
   325    						},
   326    						"network":{
   327      						"serviceDomain":"cluster.local",
   328      						"ipFamily": "IPv4"
   329  						}
   330  					}}`),
   331  				},
   332  			},
   333  		},
   334  		{
   335  			name:                        "Should calculate where ClusterNetwork is nil",
   336  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   337  			forPatch:                    "patch1",
   338  			clusterTopology: &clusterv1.Topology{
   339  				Variables: []clusterv1.ClusterVariable{
   340  					{
   341  						Name:  "location",
   342  						Value: toJSON("\"us-central\""),
   343  					},
   344  					{
   345  						Name:  "cpu",
   346  						Value: toJSON("8"),
   347  					},
   348  					{
   349  						// This is blocked by a webhook, but let's make sure that the user-defined
   350  						// variable is overwritten by the builtin variable anyway.
   351  						Name:  "builtin",
   352  						Value: toJSON("8"),
   353  					},
   354  				},
   355  			},
   356  			cluster: &clusterv1.Cluster{
   357  				ObjectMeta: metav1.ObjectMeta{
   358  					Name:      "cluster1",
   359  					Namespace: metav1.NamespaceDefault,
   360  				},
   361  				Spec: clusterv1.ClusterSpec{
   362  					Topology: &clusterv1.Topology{
   363  						Class:   "clusterClass1",
   364  						Version: "v1.21.1",
   365  					},
   366  					ClusterNetwork: nil,
   367  				},
   368  			},
   369  			want: []runtimehooksv1.Variable{
   370  				{
   371  					Name:  "location",
   372  					Value: toJSON("\"us-central\""),
   373  				},
   374  				{
   375  					Name:  "cpu",
   376  					Value: toJSON("8"),
   377  				},
   378  				{
   379  					Name: runtimehooksv1.BuiltinsName,
   380  					Value: toJSONCompact(`{
   381  					"cluster":{
   382    						"name": "cluster1",
   383    						"namespace": "default",
   384    						"topology":{
   385  							"version": "v1.21.1",
   386     						 	"class": "clusterClass1"
   387  						}
   388  					}}`),
   389  				},
   390  			},
   391  		},
   392  	}
   393  	for _, tt := range tests {
   394  		t.Run(tt.name, func(t *testing.T) {
   395  			g := NewWithT(t)
   396  
   397  			got, err := Global(tt.clusterTopology, tt.cluster, tt.forPatch, tt.variableDefinitionsForPatch)
   398  			g.Expect(err).ToNot(HaveOccurred())
   399  			g.Expect(got).To(BeComparableTo(tt.want))
   400  		})
   401  	}
   402  }
   403  
   404  func TestControlPlane(t *testing.T) {
   405  	tests := []struct {
   406  		name                                      string
   407  		controlPlaneTopology                      *clusterv1.ControlPlaneTopology
   408  		controlPlane                              *unstructured.Unstructured
   409  		controlPlaneInfrastructureMachineTemplate *unstructured.Unstructured
   410  		want                                      []runtimehooksv1.Variable
   411  	}{
   412  		{
   413  			name: "Should calculate ControlPlane variables",
   414  			controlPlaneTopology: &clusterv1.ControlPlaneTopology{
   415  				Replicas: ptr.To[int32](3),
   416  			},
   417  			controlPlane: builder.ControlPlane(metav1.NamespaceDefault, "controlPlane1").
   418  				WithReplicas(3).
   419  				WithVersion("v1.21.1").
   420  				Build(),
   421  			want: []runtimehooksv1.Variable{
   422  				{
   423  					Name: runtimehooksv1.BuiltinsName,
   424  					Value: toJSONCompact(`{
   425  					"controlPlane":{
   426  						"version": "v1.21.1",
   427  						"name":"controlPlane1",
   428  						"replicas":3
   429  					}}`),
   430  				},
   431  			},
   432  		},
   433  		{
   434  			name:                 "Should calculate ControlPlane variables, replicas not set",
   435  			controlPlaneTopology: &clusterv1.ControlPlaneTopology{},
   436  			controlPlane: builder.ControlPlane(metav1.NamespaceDefault, "controlPlane1").
   437  				WithVersion("v1.21.1").
   438  				Build(),
   439  			want: []runtimehooksv1.Variable{
   440  				{
   441  					Name: runtimehooksv1.BuiltinsName,
   442  					Value: toJSONCompact(`{
   443  					"controlPlane":{
   444  						"version": "v1.21.1",
   445  						"name":"controlPlane1"
   446  					}}`),
   447  				},
   448  			},
   449  		},
   450  		{
   451  			name: "Should calculate ControlPlane variables with InfrastructureMachineTemplate",
   452  			controlPlaneTopology: &clusterv1.ControlPlaneTopology{
   453  				Replicas: ptr.To[int32](3),
   454  			},
   455  			controlPlane: builder.ControlPlane(metav1.NamespaceDefault, "controlPlane1").
   456  				WithReplicas(3).
   457  				WithVersion("v1.21.1").
   458  				Build(),
   459  			controlPlaneInfrastructureMachineTemplate: builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "controlPlaneInfrastructureMachineTemplate1").
   460  				Build(),
   461  			want: []runtimehooksv1.Variable{
   462  				{
   463  					Name: runtimehooksv1.BuiltinsName,
   464  					Value: toJSONCompact(`{
   465  					"controlPlane":{
   466  						"version": "v1.21.1",
   467  						"name":"controlPlane1",
   468  						"replicas":3,
   469  						"machineTemplate":{
   470  							"infrastructureRef":{
   471  								"name": "controlPlaneInfrastructureMachineTemplate1"
   472  							}
   473  						}
   474  					}}`),
   475  				},
   476  			},
   477  		},
   478  	}
   479  	for _, tt := range tests {
   480  		t.Run(tt.name, func(t *testing.T) {
   481  			g := NewWithT(t)
   482  
   483  			got, err := ControlPlane(tt.controlPlaneTopology, tt.controlPlane, tt.controlPlaneInfrastructureMachineTemplate)
   484  			g.Expect(err).ToNot(HaveOccurred())
   485  			g.Expect(got).To(BeComparableTo(tt.want))
   486  		})
   487  	}
   488  }
   489  
   490  func TestMachineDeployment(t *testing.T) {
   491  	tests := []struct {
   492  		name                            string
   493  		mdTopology                      *clusterv1.MachineDeploymentTopology
   494  		forPatch                        string
   495  		variableDefinitionsForPatch     map[string]bool
   496  		md                              *clusterv1.MachineDeployment
   497  		mdBootstrapTemplate             *unstructured.Unstructured
   498  		mdInfrastructureMachineTemplate *unstructured.Unstructured
   499  		want                            []runtimehooksv1.Variable
   500  	}{
   501  		{
   502  			name:                        "Should calculate MachineDeployment variables",
   503  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   504  			forPatch:                    "patch1",
   505  			mdTopology: &clusterv1.MachineDeploymentTopology{
   506  				Replicas: ptr.To[int32](3),
   507  				Name:     "md-topology",
   508  				Class:    "md-class",
   509  				Variables: &clusterv1.MachineDeploymentVariables{
   510  					Overrides: []clusterv1.ClusterVariable{
   511  						{
   512  							Name:  "location",
   513  							Value: toJSON("\"us-central\""),
   514  						},
   515  						{
   516  							Name:  "cpu",
   517  							Value: toJSON("8"),
   518  						},
   519  					},
   520  				},
   521  			},
   522  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   523  				WithReplicas(3).
   524  				WithVersion("v1.21.1").
   525  				Build(),
   526  			want: []runtimehooksv1.Variable{
   527  				{
   528  					Name:  "location",
   529  					Value: toJSON("\"us-central\""),
   530  				},
   531  				{
   532  					Name:  "cpu",
   533  					Value: toJSON("8"),
   534  				},
   535  				{
   536  					Name: runtimehooksv1.BuiltinsName,
   537  					Value: toJSONCompact(`{
   538  					"machineDeployment":{
   539  						"version": "v1.21.1",
   540  						"class": "md-class",
   541  						"name": "md1",
   542  						"topologyName": "md-topology",
   543  						"replicas":3
   544  					}}`),
   545  				},
   546  			},
   547  		},
   548  		{
   549  			name:     "Should calculate MachineDeployment variables for a given patch name",
   550  			forPatch: "patch1",
   551  			variableDefinitionsForPatch: map[string]bool{
   552  				"location": true,
   553  				"cpu":      true,
   554  			},
   555  			mdTopology: &clusterv1.MachineDeploymentTopology{
   556  				Replicas: ptr.To[int32](3),
   557  				Name:     "md-topology",
   558  				Class:    "md-class",
   559  				Variables: &clusterv1.MachineDeploymentVariables{
   560  					Overrides: []clusterv1.ClusterVariable{
   561  						{
   562  							Name:           "location",
   563  							Value:          toJSON("\"us-central\""),
   564  							DefinitionFrom: "patch1",
   565  						},
   566  						{
   567  							Name:  "location",
   568  							Value: toJSON("\"us-east\""),
   569  							// This variable should be excluded because it is defined for a different patch.
   570  							DefinitionFrom: "anotherPatch",
   571  						},
   572  
   573  						{
   574  							Name:  "http-proxy",
   575  							Value: toJSON("\"internal.proxy.com\""),
   576  							// This variable should be excluded because it is not in variableDefinitionsForPatch.
   577  							DefinitionFrom: "",
   578  						},
   579  						{
   580  							Name:  "cpu",
   581  							Value: toJSON("8"),
   582  							// This variable should be included because it is defined for all patches.
   583  						},
   584  					},
   585  				},
   586  			},
   587  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   588  				WithReplicas(3).
   589  				WithVersion("v1.21.1").
   590  				Build(),
   591  			want: []runtimehooksv1.Variable{
   592  				{
   593  					Name:  "location",
   594  					Value: toJSON("\"us-central\""),
   595  				},
   596  				{
   597  					Name:  "cpu",
   598  					Value: toJSON("8"),
   599  				},
   600  				{
   601  					Name: runtimehooksv1.BuiltinsName,
   602  					Value: toJSONCompact(`{
   603  					"machineDeployment":{
   604  						"version": "v1.21.1",
   605  						"class": "md-class",
   606  						"name": "md1",
   607  						"topologyName": "md-topology",
   608  						"replicas":3
   609  					}}`),
   610  				},
   611  			},
   612  		},
   613  		{
   614  			name:                        "Should calculate MachineDeployment variables (without overrides)",
   615  			forPatch:                    "patch1",
   616  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   617  			mdTopology: &clusterv1.MachineDeploymentTopology{
   618  				Replicas: ptr.To[int32](3),
   619  				Name:     "md-topology",
   620  				Class:    "md-class",
   621  			},
   622  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   623  				WithReplicas(3).
   624  				WithVersion("v1.21.1").
   625  				Build(),
   626  			want: []runtimehooksv1.Variable{
   627  				{
   628  					Name: runtimehooksv1.BuiltinsName,
   629  					Value: toJSONCompact(`{
   630  					"machineDeployment":{
   631  						"version": "v1.21.1",
   632  						"class": "md-class",
   633  						"name": "md1",
   634  						"topologyName": "md-topology",
   635  						"replicas":3
   636  					}}`),
   637  				},
   638  			},
   639  		},
   640  		{
   641  			name:                        "Should calculate MachineDeployment variables, replicas not set",
   642  			forPatch:                    "patch1",
   643  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   644  			mdTopology: &clusterv1.MachineDeploymentTopology{
   645  				Name:  "md-topology",
   646  				Class: "md-class",
   647  				Variables: &clusterv1.MachineDeploymentVariables{
   648  					Overrides: []clusterv1.ClusterVariable{
   649  						{
   650  							Name:  "location",
   651  							Value: toJSON("\"us-central\""),
   652  						},
   653  						{
   654  							Name:  "cpu",
   655  							Value: toJSON("8"),
   656  						},
   657  					},
   658  				},
   659  			},
   660  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   661  				WithVersion("v1.21.1").
   662  				Build(),
   663  			want: []runtimehooksv1.Variable{
   664  				{
   665  					Name:  "location",
   666  					Value: toJSON("\"us-central\""),
   667  				},
   668  				{
   669  					Name:  "cpu",
   670  					Value: toJSON("8"),
   671  				},
   672  				{
   673  					Name: runtimehooksv1.BuiltinsName,
   674  					Value: toJSONCompact(`{
   675  					"machineDeployment":{
   676  						"version": "v1.21.1",
   677  						"class": "md-class",
   678  						"name": "md1",
   679  						"topologyName": "md-topology"
   680  					}}`),
   681  				},
   682  			},
   683  		},
   684  		{
   685  			name:                        "Should calculate MachineDeployment variables with BoostrapTemplate",
   686  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   687  			forPatch:                    "patch1",
   688  			mdTopology: &clusterv1.MachineDeploymentTopology{
   689  				Replicas: ptr.To[int32](3),
   690  				Name:     "md-topology",
   691  				Class:    "md-class",
   692  				Variables: &clusterv1.MachineDeploymentVariables{
   693  					Overrides: []clusterv1.ClusterVariable{
   694  						{
   695  							Name:  "location",
   696  							Value: toJSON("\"us-central\""),
   697  						},
   698  						{
   699  							Name:  "cpu",
   700  							Value: toJSON("8"),
   701  						},
   702  					},
   703  				},
   704  			},
   705  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   706  				WithReplicas(3).
   707  				WithVersion("v1.21.1").
   708  				Build(),
   709  			mdBootstrapTemplate: builder.BootstrapTemplate(metav1.NamespaceDefault, "mdBT1").Build(),
   710  			want: []runtimehooksv1.Variable{
   711  				{
   712  					Name:  "location",
   713  					Value: toJSON("\"us-central\""),
   714  				},
   715  				{
   716  					Name:  "cpu",
   717  					Value: toJSON("8"),
   718  				},
   719  				{
   720  					Name: runtimehooksv1.BuiltinsName,
   721  					Value: toJSONCompact(`{
   722  					"machineDeployment":{
   723  						"version": "v1.21.1",
   724  						"class": "md-class",
   725  						"name": "md1",
   726  						"topologyName": "md-topology",
   727  						"replicas":3,
   728  						"bootstrap":{
   729  							"configRef":{
   730  								"name": "mdBT1"
   731  							}
   732  						}
   733  					}}`),
   734  				},
   735  			},
   736  		},
   737  		{
   738  			name:                        "Should calculate MachineDeployment variables with InfrastructureMachineTemplate",
   739  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   740  			forPatch:                    "patch1",
   741  			mdTopology: &clusterv1.MachineDeploymentTopology{
   742  				Replicas: ptr.To[int32](3),
   743  				Name:     "md-topology",
   744  				Class:    "md-class",
   745  				Variables: &clusterv1.MachineDeploymentVariables{
   746  					Overrides: []clusterv1.ClusterVariable{
   747  						{
   748  							Name:  "location",
   749  							Value: toJSON("\"us-central\""),
   750  						},
   751  						{
   752  							Name:  "cpu",
   753  							Value: toJSON("8"),
   754  						},
   755  					},
   756  				},
   757  			},
   758  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   759  				WithReplicas(3).
   760  				WithVersion("v1.21.1").
   761  				Build(),
   762  			mdInfrastructureMachineTemplate: builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "mdIMT1").Build(),
   763  			want: []runtimehooksv1.Variable{
   764  				{
   765  					Name:  "location",
   766  					Value: toJSON("\"us-central\""),
   767  				},
   768  				{
   769  					Name:  "cpu",
   770  					Value: toJSON("8"),
   771  				},
   772  				{
   773  					Name: runtimehooksv1.BuiltinsName,
   774  					Value: toJSONCompact(`{
   775  					"machineDeployment":{
   776  						"version": "v1.21.1",
   777  						"class": "md-class",
   778  						"name": "md1",
   779  						"topologyName": "md-topology",
   780  						"replicas":3,
   781  						"infrastructureRef":{
   782  							"name": "mdIMT1"
   783  						}
   784  					}}`),
   785  				},
   786  			},
   787  		},
   788  		{
   789  			name:                        "Should calculate MachineDeployment variables with BootstrapTemplate and InfrastructureMachineTemplate",
   790  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   791  			forPatch:                    "patch1",
   792  			mdTopology: &clusterv1.MachineDeploymentTopology{
   793  				Replicas: ptr.To[int32](3),
   794  				Name:     "md-topology",
   795  				Class:    "md-class",
   796  				Variables: &clusterv1.MachineDeploymentVariables{
   797  					Overrides: []clusterv1.ClusterVariable{
   798  						{
   799  							Name:  "location",
   800  							Value: toJSON("\"us-central\""),
   801  						},
   802  						{
   803  							Name:  "cpu",
   804  							Value: toJSON("8"),
   805  						},
   806  					},
   807  				},
   808  			},
   809  			md: builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   810  				WithReplicas(3).
   811  				WithVersion("v1.21.1").
   812  				Build(),
   813  			mdBootstrapTemplate:             builder.BootstrapTemplate(metav1.NamespaceDefault, "mdBT1").Build(),
   814  			mdInfrastructureMachineTemplate: builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "mdIMT1").Build(),
   815  			want: []runtimehooksv1.Variable{
   816  				{
   817  					Name:  "location",
   818  					Value: toJSON("\"us-central\""),
   819  				},
   820  				{
   821  					Name:  "cpu",
   822  					Value: toJSON("8"),
   823  				},
   824  				{
   825  					Name: runtimehooksv1.BuiltinsName,
   826  					Value: toJSONCompact(`{
   827  					"machineDeployment":{
   828  						"version": "v1.21.1",
   829  						"class": "md-class",
   830  						"name": "md1",
   831  						"topologyName": "md-topology",
   832  						"replicas":3,
   833  						"bootstrap":{
   834  							"configRef":{
   835  								"name": "mdBT1"
   836  							}
   837  						},
   838  						"infrastructureRef":{
   839  							"name": "mdIMT1"
   840  						}
   841  					}}`),
   842  				},
   843  			},
   844  		},
   845  	}
   846  	for _, tt := range tests {
   847  		t.Run(tt.name, func(t *testing.T) {
   848  			g := NewWithT(t)
   849  
   850  			got, err := MachineDeployment(tt.mdTopology, tt.md, tt.mdBootstrapTemplate, tt.mdInfrastructureMachineTemplate, tt.forPatch, tt.variableDefinitionsForPatch)
   851  			g.Expect(err).ToNot(HaveOccurred())
   852  			g.Expect(got).To(BeComparableTo(tt.want))
   853  		})
   854  	}
   855  }
   856  
   857  func TestMachinePool(t *testing.T) {
   858  	tests := []struct {
   859  		name                        string
   860  		mpTopology                  *clusterv1.MachinePoolTopology
   861  		forPatch                    string
   862  		variableDefinitionsForPatch map[string]bool
   863  		mp                          *expv1.MachinePool
   864  		mpBootstrapConfig           *unstructured.Unstructured
   865  		mpInfrastructureMachinePool *unstructured.Unstructured
   866  		want                        []runtimehooksv1.Variable
   867  	}{
   868  		{
   869  			name:                        "Should calculate MachinePool variables",
   870  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   871  			forPatch:                    "patch1",
   872  			mpTopology: &clusterv1.MachinePoolTopology{
   873  				Replicas: ptr.To[int32](3),
   874  				Name:     "mp-topology",
   875  				Class:    "mp-class",
   876  				Variables: &clusterv1.MachinePoolVariables{
   877  					Overrides: []clusterv1.ClusterVariable{
   878  						{
   879  							Name:  "location",
   880  							Value: toJSON("\"us-central\""),
   881  						},
   882  						{
   883  							Name:  "cpu",
   884  							Value: toJSON("8"),
   885  						},
   886  					},
   887  				},
   888  			},
   889  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
   890  				WithReplicas(3).
   891  				WithVersion("v1.21.1").
   892  				Build(),
   893  			want: []runtimehooksv1.Variable{
   894  				{
   895  					Name:  "location",
   896  					Value: toJSON("\"us-central\""),
   897  				},
   898  				{
   899  					Name:  "cpu",
   900  					Value: toJSON("8"),
   901  				},
   902  				{
   903  					Name: runtimehooksv1.BuiltinsName,
   904  					Value: toJSONCompact(`{
   905  					"machinePool":{
   906  						"version": "v1.21.1",
   907  						"class": "mp-class",
   908  						"name": "mp1",
   909  						"topologyName": "mp-topology",
   910  						"replicas":3
   911  					}}`),
   912  				},
   913  			},
   914  		},
   915  		{
   916  			name:     "Should calculate MachinePool variables for a given patch name",
   917  			forPatch: "patch1",
   918  			variableDefinitionsForPatch: map[string]bool{
   919  				"location": true,
   920  				"cpu":      true,
   921  			},
   922  			mpTopology: &clusterv1.MachinePoolTopology{
   923  				Replicas: ptr.To[int32](3),
   924  				Name:     "mp-topology",
   925  				Class:    "mp-class",
   926  				Variables: &clusterv1.MachinePoolVariables{
   927  					Overrides: []clusterv1.ClusterVariable{
   928  						{
   929  							Name:           "location",
   930  							Value:          toJSON("\"us-central\""),
   931  							DefinitionFrom: "patch1",
   932  						},
   933  						{
   934  							Name:  "location",
   935  							Value: toJSON("\"us-east\""),
   936  							// This variable should be excluded because it is defined for a different patch.
   937  							DefinitionFrom: "anotherPatch",
   938  						},
   939  
   940  						{
   941  							Name:  "http-proxy",
   942  							Value: toJSON("\"internal.proxy.com\""),
   943  							// This variable should be excluded because it is not in variableDefinitionsForPatch.
   944  							DefinitionFrom: "",
   945  						},
   946  						{
   947  							Name:  "cpu",
   948  							Value: toJSON("8"),
   949  							// This variable should be included because it is defined for all patches.
   950  						},
   951  					},
   952  				},
   953  			},
   954  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
   955  				WithReplicas(3).
   956  				WithVersion("v1.21.1").
   957  				Build(),
   958  			want: []runtimehooksv1.Variable{
   959  				{
   960  					Name:  "location",
   961  					Value: toJSON("\"us-central\""),
   962  				},
   963  				{
   964  					Name:  "cpu",
   965  					Value: toJSON("8"),
   966  				},
   967  				{
   968  					Name: runtimehooksv1.BuiltinsName,
   969  					Value: toJSONCompact(`{
   970  					"machinePool":{
   971  						"version": "v1.21.1",
   972  						"class": "mp-class",
   973  						"name": "mp1",
   974  						"topologyName": "mp-topology",
   975  						"replicas":3
   976  					}}`),
   977  				},
   978  			},
   979  		},
   980  		{
   981  			name:                        "Should calculate MachinePool variables (without overrides)",
   982  			forPatch:                    "patch1",
   983  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
   984  			mpTopology: &clusterv1.MachinePoolTopology{
   985  				Replicas: ptr.To[int32](3),
   986  				Name:     "mp-topology",
   987  				Class:    "mp-class",
   988  			},
   989  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
   990  				WithReplicas(3).
   991  				WithVersion("v1.21.1").
   992  				Build(),
   993  			want: []runtimehooksv1.Variable{
   994  				{
   995  					Name: runtimehooksv1.BuiltinsName,
   996  					Value: toJSONCompact(`{
   997  					"machinePool":{
   998  						"version": "v1.21.1",
   999  						"class": "mp-class",
  1000  						"name": "mp1",
  1001  						"topologyName": "mp-topology",
  1002  						"replicas":3
  1003  					}}`),
  1004  				},
  1005  			},
  1006  		},
  1007  		{
  1008  			name:                        "Should calculate MachinePool variables, replicas not set",
  1009  			forPatch:                    "patch1",
  1010  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
  1011  			mpTopology: &clusterv1.MachinePoolTopology{
  1012  				Name:  "mp-topology",
  1013  				Class: "mp-class",
  1014  				Variables: &clusterv1.MachinePoolVariables{
  1015  					Overrides: []clusterv1.ClusterVariable{
  1016  						{
  1017  							Name:  "location",
  1018  							Value: toJSON("\"us-central\""),
  1019  						},
  1020  						{
  1021  							Name:  "cpu",
  1022  							Value: toJSON("8"),
  1023  						},
  1024  					},
  1025  				},
  1026  			},
  1027  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
  1028  				WithVersion("v1.21.1").
  1029  				Build(),
  1030  			want: []runtimehooksv1.Variable{
  1031  				{
  1032  					Name:  "location",
  1033  					Value: toJSON("\"us-central\""),
  1034  				},
  1035  				{
  1036  					Name:  "cpu",
  1037  					Value: toJSON("8"),
  1038  				},
  1039  				{
  1040  					Name: runtimehooksv1.BuiltinsName,
  1041  					Value: toJSONCompact(`{
  1042  					"machinePool":{
  1043  						"version": "v1.21.1",
  1044  						"class": "mp-class",
  1045  						"name": "mp1",
  1046  						"topologyName": "mp-topology"
  1047  					}}`),
  1048  				},
  1049  			},
  1050  		},
  1051  		{
  1052  			name:                        "Should calculate MachinePool variables with BoostrapConfig",
  1053  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
  1054  			forPatch:                    "patch1",
  1055  			mpTopology: &clusterv1.MachinePoolTopology{
  1056  				Replicas: ptr.To[int32](3),
  1057  				Name:     "mp-topology",
  1058  				Class:    "mp-class",
  1059  				Variables: &clusterv1.MachinePoolVariables{
  1060  					Overrides: []clusterv1.ClusterVariable{
  1061  						{
  1062  							Name:  "location",
  1063  							Value: toJSON("\"us-central\""),
  1064  						},
  1065  						{
  1066  							Name:  "cpu",
  1067  							Value: toJSON("8"),
  1068  						},
  1069  					},
  1070  				},
  1071  			},
  1072  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
  1073  				WithReplicas(3).
  1074  				WithVersion("v1.21.1").
  1075  				Build(),
  1076  			mpBootstrapConfig: builder.BootstrapConfig(metav1.NamespaceDefault, "mpBC1").Build(),
  1077  			want: []runtimehooksv1.Variable{
  1078  				{
  1079  					Name:  "location",
  1080  					Value: toJSON("\"us-central\""),
  1081  				},
  1082  				{
  1083  					Name:  "cpu",
  1084  					Value: toJSON("8"),
  1085  				},
  1086  				{
  1087  					Name: runtimehooksv1.BuiltinsName,
  1088  					Value: toJSONCompact(`{
  1089  					"machinePool":{
  1090  						"version": "v1.21.1",
  1091  						"class": "mp-class",
  1092  						"name": "mp1",
  1093  						"topologyName": "mp-topology",
  1094  						"replicas":3,
  1095  						"bootstrap":{
  1096  							"configRef":{
  1097  								"name": "mpBC1"
  1098  							}
  1099  						}
  1100  					}}`),
  1101  				},
  1102  			},
  1103  		},
  1104  		{
  1105  			name:                        "Should calculate MachinePool variables with InfrastructureMachinePool",
  1106  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
  1107  			forPatch:                    "patch1",
  1108  			mpTopology: &clusterv1.MachinePoolTopology{
  1109  				Replicas: ptr.To[int32](3),
  1110  				Name:     "mp-topology",
  1111  				Class:    "mp-class",
  1112  				Variables: &clusterv1.MachinePoolVariables{
  1113  					Overrides: []clusterv1.ClusterVariable{
  1114  						{
  1115  							Name:  "location",
  1116  							Value: toJSON("\"us-central\""),
  1117  						},
  1118  						{
  1119  							Name:  "cpu",
  1120  							Value: toJSON("8"),
  1121  						},
  1122  					},
  1123  				},
  1124  			},
  1125  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
  1126  				WithReplicas(3).
  1127  				WithVersion("v1.21.1").
  1128  				Build(),
  1129  			mpInfrastructureMachinePool: builder.InfrastructureMachinePool(metav1.NamespaceDefault, "mpIMP1").Build(),
  1130  			want: []runtimehooksv1.Variable{
  1131  				{
  1132  					Name:  "location",
  1133  					Value: toJSON("\"us-central\""),
  1134  				},
  1135  				{
  1136  					Name:  "cpu",
  1137  					Value: toJSON("8"),
  1138  				},
  1139  				{
  1140  					Name: runtimehooksv1.BuiltinsName,
  1141  					Value: toJSONCompact(`{
  1142  					"machinePool":{
  1143  						"version": "v1.21.1",
  1144  						"class": "mp-class",
  1145  						"name": "mp1",
  1146  						"topologyName": "mp-topology",
  1147  						"replicas":3,
  1148  						"infrastructureRef":{
  1149  							"name": "mpIMP1"
  1150  						}
  1151  					}}`),
  1152  				},
  1153  			},
  1154  		},
  1155  		{
  1156  			name:                        "Should calculate MachinePool variables with BootstrapConfig and InfrastructureMachinePool",
  1157  			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
  1158  			forPatch:                    "patch1",
  1159  			mpTopology: &clusterv1.MachinePoolTopology{
  1160  				Replicas: ptr.To[int32](3),
  1161  				Name:     "mp-topology",
  1162  				Class:    "mp-class",
  1163  				Variables: &clusterv1.MachinePoolVariables{
  1164  					Overrides: []clusterv1.ClusterVariable{
  1165  						{
  1166  							Name:  "location",
  1167  							Value: toJSON("\"us-central\""),
  1168  						},
  1169  						{
  1170  							Name:  "cpu",
  1171  							Value: toJSON("8"),
  1172  						},
  1173  					},
  1174  				},
  1175  			},
  1176  			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
  1177  				WithReplicas(3).
  1178  				WithVersion("v1.21.1").
  1179  				Build(),
  1180  			mpBootstrapConfig:           builder.BootstrapConfig(metav1.NamespaceDefault, "mpBC1").Build(),
  1181  			mpInfrastructureMachinePool: builder.InfrastructureMachinePool(metav1.NamespaceDefault, "mpIMP1").Build(),
  1182  			want: []runtimehooksv1.Variable{
  1183  				{
  1184  					Name:  "location",
  1185  					Value: toJSON("\"us-central\""),
  1186  				},
  1187  				{
  1188  					Name:  "cpu",
  1189  					Value: toJSON("8"),
  1190  				},
  1191  				{
  1192  					Name: runtimehooksv1.BuiltinsName,
  1193  					Value: toJSONCompact(`{
  1194  					"machinePool":{
  1195  						"version": "v1.21.1",
  1196  						"class": "mp-class",
  1197  						"name": "mp1",
  1198  						"topologyName": "mp-topology",
  1199  						"replicas":3,
  1200  						"bootstrap":{
  1201  							"configRef":{
  1202  								"name": "mpBC1"
  1203  							}
  1204  						},
  1205  						"infrastructureRef":{
  1206  							"name": "mpIMP1"
  1207  						}
  1208  					}}`),
  1209  				},
  1210  			},
  1211  		},
  1212  	}
  1213  	for _, tt := range tests {
  1214  		t.Run(tt.name, func(t *testing.T) {
  1215  			g := NewWithT(t)
  1216  
  1217  			got, err := MachinePool(tt.mpTopology, tt.mp, tt.mpBootstrapConfig, tt.mpInfrastructureMachinePool, tt.forPatch, tt.variableDefinitionsForPatch)
  1218  			g.Expect(err).ToNot(HaveOccurred())
  1219  			g.Expect(got).To(BeComparableTo(tt.want))
  1220  		})
  1221  	}
  1222  }
  1223  
  1224  func toJSON(value string) apiextensionsv1.JSON {
  1225  	return apiextensionsv1.JSON{Raw: []byte(value)}
  1226  }
  1227  
  1228  func toJSONCompact(value string) apiextensionsv1.JSON {
  1229  	var compactValue bytes.Buffer
  1230  	if err := json.Compact(&compactValue, []byte(value)); err != nil {
  1231  		panic(err)
  1232  	}
  1233  	return apiextensionsv1.JSON{Raw: compactValue.Bytes()}
  1234  }