sigs.k8s.io/cluster-api-provider-azure@v1.17.0/api/v1beta1/azuremachinetemplate_webhook_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 v1beta1
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    24  	. "github.com/onsi/gomega"
    25  	admissionv1 "k8s.io/api/admission/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/utils/ptr"
    28  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    29  )
    30  
    31  func TestAzureMachineTemplate_ValidateCreate(t *testing.T) {
    32  	tests := []struct {
    33  		name            string
    34  		machineTemplate *AzureMachineTemplate
    35  		wantErr         bool
    36  	}{
    37  		{
    38  			name: "azuremachinetemplate with marketplane image - full",
    39  			machineTemplate: createAzureMachineTemplateFromMachine(
    40  				createMachineWithMarketPlaceImage("PUB1234", "OFFER1234", "SKU1234", "1.0.0"),
    41  			),
    42  			wantErr: false,
    43  		},
    44  		{
    45  			name: "azuremachinetemplate with marketplace image - missing publisher",
    46  			machineTemplate: createAzureMachineTemplateFromMachine(
    47  				createMachineWithMarketPlaceImage("", "OFFER1234", "SKU1234", "1.0.0"),
    48  			),
    49  			wantErr: true,
    50  		},
    51  		{
    52  			name: "azuremachinetemplate with shared gallery image - full",
    53  			machineTemplate: createAzureMachineTemplateFromMachine(
    54  				createMachineWithSharedImage("SUB123", "RG123", "NAME123", "GALLERY1", "1.0.0"),
    55  			),
    56  			wantErr: false,
    57  		},
    58  		{
    59  			name: "azuremachinetemplate with marketplace image - missing subscription",
    60  			machineTemplate: createAzureMachineTemplateFromMachine(
    61  				createMachineWithSharedImage("", "RG123", "NAME123", "GALLERY2", "1.0.0"),
    62  			),
    63  			wantErr: true,
    64  		},
    65  		{
    66  			name: "azuremachinetemplate with image by - with id",
    67  			machineTemplate: createAzureMachineTemplateFromMachine(
    68  				createMachineWithImageByID("ID123"),
    69  			),
    70  			wantErr: false,
    71  		},
    72  		{
    73  			name: "azuremachinetemplate with image by - without id",
    74  			machineTemplate: createAzureMachineTemplateFromMachine(
    75  				createMachineWithImageByID(""),
    76  			),
    77  			wantErr: true,
    78  		},
    79  		{
    80  			name: "azuremachinetemplate with valid SSHPublicKey",
    81  			machineTemplate: createAzureMachineTemplateFromMachine(
    82  				createMachineWithSSHPublicKey(validSSHPublicKey),
    83  			),
    84  			wantErr: false,
    85  		},
    86  		{
    87  			name: "azuremachinetemplate without SSHPublicKey",
    88  			machineTemplate: createAzureMachineTemplateFromMachine(
    89  				createMachineWithSSHPublicKey(""),
    90  			),
    91  			wantErr: true,
    92  		},
    93  		{
    94  			name: "azuremachinetemplate with invalid SSHPublicKey",
    95  			machineTemplate: createAzureMachineTemplateFromMachine(
    96  				createMachineWithSSHPublicKey("invalid ssh key"),
    97  			),
    98  			wantErr: true,
    99  		},
   100  		{
   101  			name: "azuremachinetemplate with list of user-assigned identities",
   102  			machineTemplate: createAzureMachineTemplateFromMachine(
   103  				createMachineWithUserAssignedIdentities([]UserAssignedIdentity{
   104  					{ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-09091-control-plane-f1b2c"},
   105  					{ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-09091-control-plane-9a8b7"},
   106  				}),
   107  			),
   108  			wantErr: false,
   109  		},
   110  		{
   111  			name: "azuremachinetemplate with empty list of user-assigned identities",
   112  			machineTemplate: createAzureMachineTemplateFromMachine(
   113  				createMachineWithUserAssignedIdentities([]UserAssignedIdentity{}),
   114  			),
   115  			wantErr: true,
   116  		},
   117  		{
   118  			name: "azuremachinetemplate with valid osDisk cache type",
   119  			machineTemplate: createAzureMachineTemplateFromMachine(
   120  				createMachineWithOsDiskCacheType(string(armcompute.PossibleCachingTypesValues()[1])),
   121  			),
   122  			wantErr: false,
   123  		},
   124  		{
   125  			name: "azuremachinetemplate with invalid osDisk cache type",
   126  			machineTemplate: createAzureMachineTemplateFromMachine(
   127  				createMachineWithOsDiskCacheType("invalid_cache_type"),
   128  			),
   129  			wantErr: true,
   130  		},
   131  		{
   132  			name:            "azuremachinetemplate with SystemAssignedIdentityRoleName",
   133  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithSystemAssignedIdentityRoleName()),
   134  			wantErr:         true,
   135  		},
   136  		{
   137  			name:            "azuremachinetemplate without SystemAssignedIdentityRoleName",
   138  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithoutSystemAssignedIdentityRoleName()),
   139  			wantErr:         false,
   140  		},
   141  		{
   142  			name:            "azuremachinetemplate with RoleAssignmentName",
   143  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithRoleAssignmentName()),
   144  			wantErr:         true,
   145  		},
   146  		{
   147  			name:            "azuremachinetemplate with DisableExtensionOperations true and without VMExtensions",
   148  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithDisableExtenionOperations()),
   149  			wantErr:         false,
   150  		},
   151  		{
   152  			name:            "azuremachinetempalte with DisableExtensionOperations true and with VMExtension",
   153  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithDisableExtenionOperationsAndHasExtension()),
   154  			wantErr:         true,
   155  		},
   156  		{
   157  			name:            "azuremachinetemplate without RoleAssignmentName",
   158  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithoutRoleAssignmentName()),
   159  			wantErr:         false,
   160  		},
   161  		{
   162  			name: "azuremachinetemplate with network interfaces > 0 and subnet name",
   163  			machineTemplate: createAzureMachineTemplateFromMachine(
   164  				createMachineWithNetworkConfig(
   165  					"test-subnet",
   166  					nil,
   167  					[]NetworkInterface{
   168  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   169  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   170  					},
   171  				),
   172  			),
   173  			wantErr: true,
   174  		},
   175  		{
   176  			name: "azuremachinetemplate with network interfaces > 0 and no subnet name",
   177  			machineTemplate: createAzureMachineTemplateFromMachine(
   178  				createMachineWithNetworkConfig(
   179  					"",
   180  					nil,
   181  					[]NetworkInterface{
   182  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   183  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   184  					},
   185  				),
   186  			),
   187  			wantErr: false,
   188  		},
   189  		{
   190  			name: "azuremachinetemplate with network interfaces > 0 and AcceleratedNetworking not nil",
   191  			machineTemplate: createAzureMachineTemplateFromMachine(
   192  				createMachineWithNetworkConfig(
   193  					"",
   194  					ptr.To(true),
   195  					[]NetworkInterface{
   196  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   197  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   198  					},
   199  				),
   200  			),
   201  			wantErr: true,
   202  		},
   203  		{
   204  			name: "azuremachinetemplate with network interfaces > 0 and AcceleratedNetworking nil",
   205  			machineTemplate: createAzureMachineTemplateFromMachine(
   206  				createMachineWithNetworkConfig(
   207  					"",
   208  					nil,
   209  					[]NetworkInterface{
   210  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   211  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   212  					},
   213  				),
   214  			),
   215  			wantErr: false,
   216  		},
   217  		{
   218  			name: "azuremachinetemplate with network interfaces and PrivateIPConfigs < 1",
   219  			machineTemplate: createAzureMachineTemplateFromMachine(
   220  				createMachineWithNetworkConfig(
   221  					"",
   222  					nil,
   223  					[]NetworkInterface{
   224  						{SubnetName: "subnet1", PrivateIPConfigs: 0},
   225  						{SubnetName: "subnet2", PrivateIPConfigs: -1},
   226  					},
   227  				),
   228  			),
   229  			wantErr: true,
   230  		},
   231  		{
   232  			name: "azuremachinetemplate with network interfaces and PrivateIPConfigs >= 1",
   233  			machineTemplate: createAzureMachineTemplateFromMachine(
   234  				createMachineWithNetworkConfig(
   235  					"",
   236  					nil,
   237  					[]NetworkInterface{
   238  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   239  						{SubnetName: "subnet2", PrivateIPConfigs: 2},
   240  					},
   241  				),
   242  			),
   243  			wantErr: false,
   244  		},
   245  	}
   246  
   247  	for _, test := range tests {
   248  		test := test
   249  		t.Run(test.name, func(t *testing.T) {
   250  			t.Parallel()
   251  			g := NewWithT(t)
   252  			ctx := context.Background()
   253  			_, err := test.machineTemplate.ValidateCreate(ctx, test.machineTemplate)
   254  			if test.wantErr {
   255  				g.Expect(err).To(HaveOccurred())
   256  			} else {
   257  				g.Expect(err).NotTo(HaveOccurred())
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  func TestAzureMachineTemplate_ValidateUpdate(t *testing.T) {
   264  	failureDomain := "domaintest"
   265  
   266  	tests := []struct {
   267  		name        string
   268  		oldTemplate *AzureMachineTemplate
   269  		template    *AzureMachineTemplate
   270  		wantErr     bool
   271  	}{
   272  		{
   273  			name: "AzureMachineTemplate with immutable spec",
   274  			oldTemplate: &AzureMachineTemplate{
   275  				Spec: AzureMachineTemplateSpec{
   276  					Template: AzureMachineTemplateResource{
   277  						Spec: AzureMachineSpec{
   278  							VMSize:        "size",
   279  							FailureDomain: &failureDomain,
   280  							OSDisk: OSDisk{
   281  								OSType:     "type",
   282  								DiskSizeGB: ptr.To[int32](11),
   283  							},
   284  							DataDisks:    []DataDisk{},
   285  							SSHPublicKey: "",
   286  						},
   287  					},
   288  				},
   289  			},
   290  			template: &AzureMachineTemplate{
   291  				Spec: AzureMachineTemplateSpec{
   292  					Template: AzureMachineTemplateResource{
   293  						Spec: AzureMachineSpec{
   294  							VMSize:        "size1",
   295  							FailureDomain: &failureDomain,
   296  							OSDisk: OSDisk{
   297  								OSType:     "type",
   298  								DiskSizeGB: ptr.To[int32](11),
   299  							},
   300  							DataDisks:    []DataDisk{},
   301  							SSHPublicKey: "fake ssh key",
   302  						},
   303  					},
   304  				},
   305  			},
   306  			wantErr: true,
   307  		},
   308  		{
   309  			name: "AzureMachineTemplate with mutable metadata",
   310  			oldTemplate: &AzureMachineTemplate{
   311  				Spec: AzureMachineTemplateSpec{
   312  					Template: AzureMachineTemplateResource{
   313  						Spec: AzureMachineSpec{
   314  							VMSize:        "size",
   315  							FailureDomain: &failureDomain,
   316  							OSDisk: OSDisk{
   317  								OSType:     "type",
   318  								DiskSizeGB: ptr.To[int32](11),
   319  							},
   320  							DataDisks:    []DataDisk{},
   321  							SSHPublicKey: "fake ssh key",
   322  						},
   323  					},
   324  				},
   325  				ObjectMeta: metav1.ObjectMeta{
   326  					Name: "OldTemplate",
   327  				},
   328  			},
   329  			template: &AzureMachineTemplate{
   330  				Spec: AzureMachineTemplateSpec{
   331  					Template: AzureMachineTemplateResource{
   332  						Spec: AzureMachineSpec{
   333  							VMSize:        "size",
   334  							FailureDomain: &failureDomain,
   335  							OSDisk: OSDisk{
   336  								OSType:     "type",
   337  								DiskSizeGB: ptr.To[int32](11),
   338  							},
   339  							DataDisks:    []DataDisk{},
   340  							SSHPublicKey: "fake ssh key",
   341  						},
   342  					},
   343  				},
   344  				ObjectMeta: metav1.ObjectMeta{
   345  					Name: "NewTemplate",
   346  				},
   347  			},
   348  			wantErr: false,
   349  		},
   350  		{
   351  			name: "AzureMachineTemplate with default mismatch",
   352  			oldTemplate: &AzureMachineTemplate{
   353  				Spec: AzureMachineTemplateSpec{
   354  					Template: AzureMachineTemplateResource{
   355  						Spec: AzureMachineSpec{
   356  							VMSize:        "size",
   357  							FailureDomain: &failureDomain,
   358  							OSDisk: OSDisk{
   359  								OSType:      "type",
   360  								DiskSizeGB:  ptr.To[int32](11),
   361  								CachingType: "",
   362  							},
   363  							DataDisks:    []DataDisk{},
   364  							SSHPublicKey: "",
   365  						},
   366  					},
   367  				},
   368  				ObjectMeta: metav1.ObjectMeta{
   369  					Name: "OldTemplate",
   370  				},
   371  			},
   372  			template: &AzureMachineTemplate{
   373  				Spec: AzureMachineTemplateSpec{
   374  					Template: AzureMachineTemplateResource{
   375  						Spec: AzureMachineSpec{
   376  							VMSize:        "size",
   377  							FailureDomain: &failureDomain,
   378  							OSDisk: OSDisk{
   379  								OSType:      "type",
   380  								DiskSizeGB:  ptr.To[int32](11),
   381  								CachingType: "None",
   382  							},
   383  							DataDisks:    []DataDisk{},
   384  							SSHPublicKey: "fake ssh key",
   385  							NetworkInterfaces: []NetworkInterface{{
   386  								PrivateIPConfigs: 1,
   387  							}},
   388  						},
   389  					},
   390  				},
   391  				ObjectMeta: metav1.ObjectMeta{
   392  					Name: "NewTemplate",
   393  				},
   394  			},
   395  			wantErr: false,
   396  		},
   397  		{
   398  			name: "AzureMachineTemplate ssh key removed",
   399  			oldTemplate: &AzureMachineTemplate{
   400  				Spec: AzureMachineTemplateSpec{
   401  					Template: AzureMachineTemplateResource{
   402  						Spec: AzureMachineSpec{
   403  							VMSize:        "size",
   404  							FailureDomain: &failureDomain,
   405  							OSDisk: OSDisk{
   406  								OSType:      "type",
   407  								DiskSizeGB:  ptr.To[int32](11),
   408  								CachingType: "None",
   409  							},
   410  							DataDisks:    []DataDisk{},
   411  							SSHPublicKey: "some key",
   412  						},
   413  					},
   414  				},
   415  				ObjectMeta: metav1.ObjectMeta{
   416  					Name: "OldTemplate",
   417  				},
   418  			},
   419  			template: &AzureMachineTemplate{
   420  				Spec: AzureMachineTemplateSpec{
   421  					Template: AzureMachineTemplateResource{
   422  						Spec: AzureMachineSpec{
   423  							VMSize:        "size",
   424  							FailureDomain: &failureDomain,
   425  							OSDisk: OSDisk{
   426  								OSType:      "type",
   427  								DiskSizeGB:  ptr.To[int32](11),
   428  								CachingType: "None",
   429  							},
   430  							DataDisks:    []DataDisk{},
   431  							SSHPublicKey: "",
   432  						},
   433  					},
   434  				},
   435  				ObjectMeta: metav1.ObjectMeta{
   436  					Name: "NewTemplate",
   437  				},
   438  			},
   439  			wantErr: true,
   440  		},
   441  		{
   442  			name: "AzureMachineTemplate with legacy subnetName updated to new networkInterfaces",
   443  			oldTemplate: &AzureMachineTemplate{
   444  				Spec: AzureMachineTemplateSpec{
   445  					Template: AzureMachineTemplateResource{
   446  						Spec: AzureMachineSpec{
   447  							VMSize:        "size",
   448  							FailureDomain: &failureDomain,
   449  							OSDisk: OSDisk{
   450  								OSType:      "type",
   451  								DiskSizeGB:  ptr.To[int32](11),
   452  								CachingType: "None",
   453  							},
   454  							DataDisks:             []DataDisk{},
   455  							SSHPublicKey:          "fake ssh key",
   456  							SubnetName:            "subnet1",
   457  							AcceleratedNetworking: ptr.To(true),
   458  						},
   459  					},
   460  				},
   461  			},
   462  			template: &AzureMachineTemplate{
   463  				Spec: AzureMachineTemplateSpec{
   464  					Template: AzureMachineTemplateResource{
   465  						Spec: AzureMachineSpec{
   466  							VMSize:        "size",
   467  							FailureDomain: &failureDomain,
   468  							OSDisk: OSDisk{
   469  								OSType:      "type",
   470  								DiskSizeGB:  ptr.To[int32](11),
   471  								CachingType: "None",
   472  							},
   473  							DataDisks:             []DataDisk{},
   474  							SSHPublicKey:          "fake ssh key",
   475  							SubnetName:            "",
   476  							AcceleratedNetworking: nil,
   477  							NetworkInterfaces: []NetworkInterface{
   478  								{
   479  									SubnetName:            "subnet1",
   480  									AcceleratedNetworking: ptr.To(true),
   481  									PrivateIPConfigs:      1,
   482  								},
   483  							},
   484  						},
   485  					},
   486  				},
   487  			},
   488  			wantErr: false,
   489  		},
   490  		{
   491  			name: "AzureMachineTemplate with legacy AcceleratedNetworking updated to new networkInterfaces",
   492  			oldTemplate: &AzureMachineTemplate{
   493  				Spec: AzureMachineTemplateSpec{
   494  					Template: AzureMachineTemplateResource{
   495  						Spec: AzureMachineSpec{
   496  							VMSize:        "size",
   497  							FailureDomain: &failureDomain,
   498  							OSDisk: OSDisk{
   499  								OSType:      "type",
   500  								DiskSizeGB:  ptr.To[int32](11),
   501  								CachingType: "None",
   502  							},
   503  							DataDisks:             []DataDisk{},
   504  							SSHPublicKey:          "fake ssh key",
   505  							SubnetName:            "",
   506  							AcceleratedNetworking: ptr.To(true),
   507  							NetworkInterfaces:     []NetworkInterface{},
   508  						},
   509  					},
   510  				},
   511  			},
   512  			template: &AzureMachineTemplate{
   513  				Spec: AzureMachineTemplateSpec{
   514  					Template: AzureMachineTemplateResource{
   515  						Spec: AzureMachineSpec{
   516  							VMSize:        "size",
   517  							FailureDomain: &failureDomain,
   518  							OSDisk: OSDisk{
   519  								OSType:      "type",
   520  								DiskSizeGB:  ptr.To[int32](11),
   521  								CachingType: "None",
   522  							},
   523  							DataDisks:             []DataDisk{},
   524  							SSHPublicKey:          "fake ssh key",
   525  							SubnetName:            "",
   526  							AcceleratedNetworking: nil,
   527  							NetworkInterfaces: []NetworkInterface{
   528  								{
   529  									SubnetName:            "",
   530  									AcceleratedNetworking: ptr.To(true),
   531  									PrivateIPConfigs:      1,
   532  								},
   533  							},
   534  						},
   535  					},
   536  				},
   537  			},
   538  			wantErr: false,
   539  		},
   540  		{
   541  			name: "AzureMachineTemplate with modified networkInterfaces is immutable",
   542  			oldTemplate: &AzureMachineTemplate{
   543  				Spec: AzureMachineTemplateSpec{
   544  					Template: AzureMachineTemplateResource{
   545  						Spec: AzureMachineSpec{
   546  							VMSize:        "size",
   547  							FailureDomain: &failureDomain,
   548  							OSDisk: OSDisk{
   549  								OSType:      "type",
   550  								DiskSizeGB:  ptr.To[int32](11),
   551  								CachingType: "None",
   552  							},
   553  							DataDisks:    []DataDisk{},
   554  							SSHPublicKey: "fake ssh key",
   555  							NetworkInterfaces: []NetworkInterface{
   556  								{
   557  									SubnetName:            "subnet1",
   558  									AcceleratedNetworking: ptr.To(true),
   559  									PrivateIPConfigs:      1,
   560  								},
   561  							},
   562  						},
   563  					},
   564  				},
   565  			},
   566  			template: &AzureMachineTemplate{
   567  				Spec: AzureMachineTemplateSpec{
   568  					Template: AzureMachineTemplateResource{
   569  						Spec: AzureMachineSpec{
   570  							VMSize:        "size",
   571  							FailureDomain: &failureDomain,
   572  							OSDisk: OSDisk{
   573  								OSType:      "type",
   574  								DiskSizeGB:  ptr.To[int32](11),
   575  								CachingType: "None",
   576  							},
   577  							DataDisks:    []DataDisk{},
   578  							SSHPublicKey: "fake ssh key",
   579  							NetworkInterfaces: []NetworkInterface{
   580  								{
   581  									SubnetName:            "subnet2",
   582  									AcceleratedNetworking: ptr.To(true),
   583  									PrivateIPConfigs:      1,
   584  								},
   585  							},
   586  						},
   587  					},
   588  				},
   589  			},
   590  			wantErr: true,
   591  		},
   592  	}
   593  
   594  	// dry-run=true
   595  	for _, amt := range tests {
   596  		amt := amt
   597  		t.Run(amt.name, func(t *testing.T) {
   598  			g := NewWithT(t)
   599  			ctx := admission.NewContextWithRequest(context.Background(), admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(true)}})
   600  			_, err := amt.template.ValidateUpdate(ctx, amt.oldTemplate, amt.template)
   601  			if amt.wantErr {
   602  				g.Expect(err).To(HaveOccurred())
   603  			} else {
   604  				g.Expect(err).NotTo(HaveOccurred())
   605  			}
   606  		})
   607  	}
   608  	// dry-run=false
   609  	for _, amt := range tests {
   610  		amt := amt
   611  		t.Run(amt.name, func(t *testing.T) {
   612  			t.Parallel()
   613  			g := NewWithT(t)
   614  			ctx := admission.NewContextWithRequest(context.Background(), admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}})
   615  			_, err := amt.template.ValidateUpdate(ctx, amt.oldTemplate, amt.template)
   616  			if amt.wantErr {
   617  				g.Expect(err).To(HaveOccurred())
   618  			} else {
   619  				g.Expect(err).NotTo(HaveOccurred())
   620  			}
   621  		})
   622  	}
   623  }
   624  
   625  func createAzureMachineTemplateFromMachine(machine *AzureMachine) *AzureMachineTemplate {
   626  	return &AzureMachineTemplate{
   627  		Spec: AzureMachineTemplateSpec{
   628  			Template: AzureMachineTemplateResource{
   629  				Spec: machine.Spec,
   630  			},
   631  		},
   632  	}
   633  }