From 5ef669c0ccad3313879da5e3db57df57c72fbd5f Mon Sep 17 00:00:00 2001 From: Bryan Venteicher Date: Mon, 9 Dec 2024 11:14:23 -0600 Subject: [PATCH] Add option for CloudInit to default to global NS and SearchDomains The CloudInit bootstrap only supports per-interface Nameservers and SearchDomains while others bootstrap like LinuxPrep support global. To simplify the UX and to make it easier to switch between bootstrap providers, add two *bool fields to the CloudInit spec: - UseGlobalNameserversAsDefault - UseGlobalSearchDomainsAsDefault that when either nil or true, use the global value when the corresponding per-interface field is unset. In v1a2, the validation wehbook would have rejected an attempt to use the global Nameservers or SearchDomains with CloudInit, so this change should not result it any behavior change for an existing VM definition. --- api/v1alpha1/virtualmachine_conversion.go | 2 + .../virtualmachine_conversion_test.go | 38 +++++++++- .../virtualmachine_bootstrap_types.go | 20 ++++++ .../virtualmachine_conversion_test.go | 6 +- api/v1alpha2/virtualmachine_network_types.go | 17 ++++- api/v1alpha2/zz_generated.conversion.go | 4 ++ api/v1alpha2/zz_generated.deepcopy.go | 10 +++ .../virtualmachine_bootstrap_types.go | 20 ++++++ api/v1alpha3/virtualmachine_network_types.go | 17 ++++- api/v1alpha3/zz_generated.deepcopy.go | 10 +++ ....vmware.com_virtualmachinereplicasets.yaml | 33 ++++++++- ...vmoperator.vmware.com_virtualmachines.yaml | 66 ++++++++++++++++-- docs/ref/api/v1alpha2.md | 29 +++++++- docs/ref/api/v1alpha3.md | 29 +++++++- pkg/providers/vsphere/network/network.go | 40 +++++++++-- pkg/providers/vsphere/network/network_test.go | 69 +++++++++++++------ .../vsphere/session/session_vm_update.go | 2 +- pkg/providers/vsphere/vmprovider_vm.go | 2 +- .../validation/virtualmachine_validator.go | 23 ++++++- .../virtualmachine_validator_unit_test.go | 59 ++++++++++++++-- 20 files changed, 437 insertions(+), 59 deletions(-) diff --git a/api/v1alpha1/virtualmachine_conversion.go b/api/v1alpha1/virtualmachine_conversion.go index eca4492ea..cabbc1985 100644 --- a/api/v1alpha1/virtualmachine_conversion.go +++ b/api/v1alpha1/virtualmachine_conversion.go @@ -869,6 +869,8 @@ func restore_v1alpha3_VirtualMachineBootstrapSpec( dstCloudInit.CloudConfig = srcCloudInit.CloudConfig dstCloudInit.RawCloudConfig = mergeSecretKeySelector(dstCloudInit.RawCloudConfig, srcCloudInit.RawCloudConfig) dstCloudInit.SSHAuthorizedKeys = srcCloudInit.SSHAuthorizedKeys + dstCloudInit.UseGlobalNameserversAsDefault = srcCloudInit.UseGlobalNameserversAsDefault + dstCloudInit.UseGlobalSearchDomainsAsDefault = srcCloudInit.UseGlobalSearchDomainsAsDefault } } diff --git a/api/v1alpha1/virtualmachine_conversion_test.go b/api/v1alpha1/virtualmachine_conversion_test.go index 03888354e..3f14d97eb 100644 --- a/api/v1alpha1/virtualmachine_conversion_test.go +++ b/api/v1alpha1/virtualmachine_conversion_test.go @@ -89,7 +89,9 @@ func TestVirtualMachineConversion(t *testing.T) { Name: "cloud-init-secret", Key: "user-data", }, - SSHAuthorizedKeys: []string{"my-ssh-key"}, + SSHAuthorizedKeys: []string{"my-ssh-key"}, + UseGlobalNameserversAsDefault: ptrOf(true), + UseGlobalSearchDomainsAsDefault: ptrOf(true), }, }, Network: &vmopv1.VirtualMachineNetworkSpec{ @@ -258,7 +260,9 @@ func TestVirtualMachineConversion(t *testing.T) { Name: "cloudinit-secret", Key: "my-key", }, - SSHAuthorizedKeys: []string{"my-ssh-key"}, + SSHAuthorizedKeys: []string{"my-ssh-key"}, + UseGlobalNameserversAsDefault: ptrOf(true), + UseGlobalSearchDomainsAsDefault: ptrOf(false), }, }, }, @@ -282,7 +286,9 @@ func TestVirtualMachineConversion(t *testing.T) { }, }, }, - SSHAuthorizedKeys: []string{"my-ssh-key"}, + SSHAuthorizedKeys: []string{"my-ssh-key"}, + UseGlobalNameserversAsDefault: ptrOf(false), + UseGlobalSearchDomainsAsDefault: ptrOf(true), }, }, }, @@ -307,6 +313,32 @@ func TestVirtualMachineConversion(t *testing.T) { hubSpokeHub(g, &hub, &vmopv1a1.VirtualMachine{}) }) + t.Run("VirtualMachine hub-spoke-hub with CloudInit w/ just the UseGlobal...AsDefaults", func(t *testing.T) { + g := NewWithT(t) + + hub1 := vmopv1.VirtualMachine{ + Spec: vmopv1.VirtualMachineSpec{ + Bootstrap: &vmopv1.VirtualMachineBootstrapSpec{ + CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{ + UseGlobalNameserversAsDefault: ptrOf(true), + }, + }, + }, + } + hubSpokeHub(g, &hub1, &vmopv1a1.VirtualMachine{}) + + hub2 := vmopv1.VirtualMachine{ + Spec: vmopv1.VirtualMachineSpec{ + Bootstrap: &vmopv1.VirtualMachineBootstrapSpec{ + CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{ + UseGlobalSearchDomainsAsDefault: ptrOf(true), + }, + }, + }, + } + hubSpokeHub(g, &hub2, &vmopv1a1.VirtualMachine{}) + }) + t.Run("VirtualMachine hub-spoke-hub with LinuxPrep", func(t *testing.T) { g := NewWithT(t) diff --git a/api/v1alpha2/virtualmachine_bootstrap_types.go b/api/v1alpha2/virtualmachine_bootstrap_types.go index c05b69b4e..334f8a619 100644 --- a/api/v1alpha2/virtualmachine_bootstrap_types.go +++ b/api/v1alpha2/virtualmachine_bootstrap_types.go @@ -109,6 +109,26 @@ type VirtualMachineBootstrapCloudInitSpec struct { // // +optional SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"` + + // UseGlobalNameserversAsDefault will use the global nameservers specified in the + // NetworkSpec as the per-interface nameservers when the per-interface nameservers + // is not provided. + // + // Defaults to true if omitted. + // + // +optional + // +kubebuilder:default:true + UseGlobalNameserversAsDefault *bool `json:"useGlobalNameserversAsDefault,omitempty"` + + // UseGlobalSearchDomainsAsDefault will use the global search domains specified in the + // NetworkSpec as the per-interface search domains when the per-interface search domains + // is not provided. + // + // Defaults to true if omitted. + // + // +optional + // +kubebuilder:default:true + UseGlobalSearchDomainsAsDefault *bool `json:"useGlobalSearchDomainsAsDefault,omitempty"` } // VirtualMachineBootstrapLinuxPrepSpec describes the LinuxPrep configuration diff --git a/api/v1alpha2/virtualmachine_conversion_test.go b/api/v1alpha2/virtualmachine_conversion_test.go index 996ec2360..af294325a 100644 --- a/api/v1alpha2/virtualmachine_conversion_test.go +++ b/api/v1alpha2/virtualmachine_conversion_test.go @@ -67,10 +67,12 @@ func TestVirtualMachineConversion(t *testing.T) { Name: "cloud-init-secret", Key: "user-data", }, - SSHAuthorizedKeys: []string{"my-ssh-key"}, + SSHAuthorizedKeys: []string{"my-ssh-key"}, + UseGlobalNameserversAsDefault: ptrOf(true), + UseGlobalSearchDomainsAsDefault: ptrOf(true), }, LinuxPrep: &vmopv1.VirtualMachineBootstrapLinuxPrepSpec{ - HardwareClockIsUTC: &[]bool{true}[0], + HardwareClockIsUTC: ptrOf(true), TimeZone: "my-tz", }, Sysprep: &vmopv1.VirtualMachineBootstrapSysprepSpec{ diff --git a/api/v1alpha2/virtualmachine_network_types.go b/api/v1alpha2/virtualmachine_network_types.go index 9169ef700..f2d8dbaef 100644 --- a/api/v1alpha2/virtualmachine_network_types.go +++ b/api/v1alpha2/virtualmachine_network_types.go @@ -143,6 +143,10 @@ type VirtualMachineNetworkInterfaceSpec struct { // Please note this feature is available only with the following bootstrap // providers: CloudInit and Sysprep. // + // When using CloudInit and UseGlobalNameserversAsDefault is either unset or + // true, if nameservers is not provided, the global nameservers will be used + // instead. + // // Please note that Linux allows only three nameservers // (https://linux.die.net/man/5/resolv.conf). // @@ -163,6 +167,10 @@ type VirtualMachineNetworkInterfaceSpec struct { // Please note this feature is available only with the following bootstrap // providers: CloudInit. // + // When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset + // or true, if search domains is not provided, the global search domains + // will be used instead. + // // +optional SearchDomains []string `json:"searchDomains,omitempty"` } @@ -195,7 +203,10 @@ type VirtualMachineNetworkSpec struct { // // Please note global nameservers are only available with the following // bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - // provider supports per-interface nameservers. + // provider supports per-interface nameservers. However, when Cloud-Init + // is used and UseGlobalNameserversAsDefault is true, the global + // nameservers will be used when the per-interface nameservers is not + // provided. // // Please note that Linux allows only three nameservers // (https://linux.die.net/man/5/resolv.conf). @@ -208,7 +219,9 @@ type VirtualMachineNetworkSpec struct { // // Please note global search domains are only available with the following // bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - // provider supports per-interface search domains. + // provider supports per-interface search domains. However, when Cloud-Init + // is used and UseGlobalSearchDomainsAsDefault is true, the global search + // domains will be used when the per-interface search domains is not provided. // // +optional SearchDomains []string `json:"searchDomains,omitempty"` diff --git a/api/v1alpha2/zz_generated.conversion.go b/api/v1alpha2/zz_generated.conversion.go index c1da86e90..bd02f1144 100644 --- a/api/v1alpha2/zz_generated.conversion.go +++ b/api/v1alpha2/zz_generated.conversion.go @@ -1445,6 +1445,8 @@ func autoConvert_v1alpha2_VirtualMachineBootstrapCloudInitSpec_To_v1alpha3_Virtu out.CloudConfig = (*cloudinit.CloudConfig)(unsafe.Pointer(in.CloudConfig)) out.RawCloudConfig = (*common.SecretKeySelector)(unsafe.Pointer(in.RawCloudConfig)) out.SSHAuthorizedKeys = *(*[]string)(unsafe.Pointer(&in.SSHAuthorizedKeys)) + out.UseGlobalNameserversAsDefault = (*bool)(unsafe.Pointer(in.UseGlobalNameserversAsDefault)) + out.UseGlobalSearchDomainsAsDefault = (*bool)(unsafe.Pointer(in.UseGlobalSearchDomainsAsDefault)) return nil } @@ -1458,6 +1460,8 @@ func autoConvert_v1alpha3_VirtualMachineBootstrapCloudInitSpec_To_v1alpha2_Virtu out.CloudConfig = (*v1alpha2cloudinit.CloudConfig)(unsafe.Pointer(in.CloudConfig)) out.RawCloudConfig = (*v1alpha2common.SecretKeySelector)(unsafe.Pointer(in.RawCloudConfig)) out.SSHAuthorizedKeys = *(*[]string)(unsafe.Pointer(&in.SSHAuthorizedKeys)) + out.UseGlobalNameserversAsDefault = (*bool)(unsafe.Pointer(in.UseGlobalNameserversAsDefault)) + out.UseGlobalSearchDomainsAsDefault = (*bool)(unsafe.Pointer(in.UseGlobalSearchDomainsAsDefault)) return nil } diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 18544a50d..eb2962a9c 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -455,6 +455,16 @@ func (in *VirtualMachineBootstrapCloudInitSpec) DeepCopyInto(out *VirtualMachine *out = make([]string, len(*in)) copy(*out, *in) } + if in.UseGlobalNameserversAsDefault != nil { + in, out := &in.UseGlobalNameserversAsDefault, &out.UseGlobalNameserversAsDefault + *out = new(bool) + **out = **in + } + if in.UseGlobalSearchDomainsAsDefault != nil { + in, out := &in.UseGlobalSearchDomainsAsDefault, &out.UseGlobalSearchDomainsAsDefault + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineBootstrapCloudInitSpec. diff --git a/api/v1alpha3/virtualmachine_bootstrap_types.go b/api/v1alpha3/virtualmachine_bootstrap_types.go index 6e02eb90b..50a9021e2 100644 --- a/api/v1alpha3/virtualmachine_bootstrap_types.go +++ b/api/v1alpha3/virtualmachine_bootstrap_types.go @@ -115,6 +115,26 @@ type VirtualMachineBootstrapCloudInitSpec struct { // SSHAuthorizedKeys is a list of public keys that CloudInit will apply to // the guest's default user. SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"` + + // +optional + // +kubebuilder:default:true + + // UseGlobalNameserversAsDefault will use the global nameservers specified in the + // NetworkSpec as the per-interface nameservers when the per-interface nameservers + // is not provided. + // + // Defaults to true if omitted. + UseGlobalNameserversAsDefault *bool `json:"useGlobalNameserversAsDefault,omitempty"` + + // +optional + // +kubebuilder:default:true + + // UseGlobalSearchDomainsAsDefault will use the global search domains specified in the + // NetworkSpec as the per-interface search domains when the per-interface search domains + // is not provided. + // + // Defaults to true if omitted. + UseGlobalSearchDomainsAsDefault *bool `json:"useGlobalSearchDomainsAsDefault,omitempty"` } // VirtualMachineBootstrapLinuxPrepSpec describes the LinuxPrep configuration diff --git a/api/v1alpha3/virtualmachine_network_types.go b/api/v1alpha3/virtualmachine_network_types.go index 46f3dd041..d36bde8d4 100644 --- a/api/v1alpha3/virtualmachine_network_types.go +++ b/api/v1alpha3/virtualmachine_network_types.go @@ -145,6 +145,10 @@ type VirtualMachineNetworkInterfaceSpec struct { // Please note this feature is available only with the following bootstrap // providers: CloudInit and Sysprep. // + // When using CloudInit and UseGlobalNameserversAsDefault is either unset or + // true, if nameservers is not provided, the global nameservers will be used + // instead. + // // Please note that Linux allows only three nameservers // (https://linux.die.net/man/5/resolv.conf). Nameservers []string `json:"nameservers,omitempty"` @@ -164,6 +168,10 @@ type VirtualMachineNetworkInterfaceSpec struct { // // Please note this feature is available only with the following bootstrap // providers: CloudInit. + // + // When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset + // or true, if search domains is not provided, the global search domains + // will be used instead. SearchDomains []string `json:"searchDomains,omitempty"` } @@ -244,7 +252,10 @@ type VirtualMachineNetworkSpec struct { // // Please note global nameservers are only available with the following // bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - // provider supports per-interface nameservers. + // provider supports per-interface nameservers. However, when Cloud-Init + // is used and UseGlobalNameserversAsDefault is true, the global + // nameservers will be used when the per-interface nameservers is not + // provided. // // Please note that Linux allows only three nameservers // (https://linux.die.net/man/5/resolv.conf). @@ -257,7 +268,9 @@ type VirtualMachineNetworkSpec struct { // // Please note global search domains are only available with the following // bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - // provider supports per-interface search domains. + // provider supports per-interface search domains. However, when Cloud-Init + // is used and UseGlobalSearchDomainsAsDefault is true, the global search + // domains will be used when the per-interface search domains is not provided. SearchDomains []string `json:"searchDomains,omitempty"` // +optional diff --git a/api/v1alpha3/zz_generated.deepcopy.go b/api/v1alpha3/zz_generated.deepcopy.go index f30440350..593fd7706 100644 --- a/api/v1alpha3/zz_generated.deepcopy.go +++ b/api/v1alpha3/zz_generated.deepcopy.go @@ -455,6 +455,16 @@ func (in *VirtualMachineBootstrapCloudInitSpec) DeepCopyInto(out *VirtualMachine *out = make([]string, len(*in)) copy(*out, *in) } + if in.UseGlobalNameserversAsDefault != nil { + in, out := &in.UseGlobalNameserversAsDefault, &out.UseGlobalNameserversAsDefault + *out = new(bool) + **out = **in + } + if in.UseGlobalSearchDomainsAsDefault != nil { + in, out := &in.UseGlobalSearchDomainsAsDefault, &out.UseGlobalSearchDomainsAsDefault + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineBootstrapCloudInitSpec. diff --git a/config/crd/bases/vmoperator.vmware.com_virtualmachinereplicasets.yaml b/config/crd/bases/vmoperator.vmware.com_virtualmachinereplicasets.yaml index 06d8a856d..2b0ec30e7 100644 --- a/config/crd/bases/vmoperator.vmware.com_virtualmachinereplicasets.yaml +++ b/config/crd/bases/vmoperator.vmware.com_virtualmachinereplicasets.yaml @@ -569,6 +569,22 @@ spec: items: type: string type: array + useGlobalNameserversAsDefault: + description: |- + UseGlobalNameserversAsDefault will use the global nameservers specified in the + NetworkSpec as the per-interface nameservers when the per-interface nameservers + is not provided. + + Defaults to true if omitted. + type: boolean + useGlobalSearchDomainsAsDefault: + description: |- + UseGlobalSearchDomainsAsDefault will use the global search domains specified in the + NetworkSpec as the per-interface search domains when the per-interface search domains + is not provided. + + Defaults to true if omitted. + type: boolean type: object linuxPrep: description: |- @@ -1410,6 +1426,10 @@ spec: Please note this feature is available only with the following bootstrap providers: CloudInit and Sysprep. + When using CloudInit and UseGlobalNameserversAsDefault is either unset or + true, if nameservers is not provided, the global nameservers will be used + instead. + Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). items: @@ -1480,6 +1500,10 @@ spec: Please note this feature is available only with the following bootstrap providers: CloudInit. + + When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset + or true, if search domains is not provided, the global search domains + will be used instead. items: type: string type: array @@ -1498,7 +1522,10 @@ spec: Please note global nameservers are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - provider supports per-interface nameservers. + provider supports per-interface nameservers. However, when Cloud-Init + is used and UseGlobalNameserversAsDefault is true, the global + nameservers will be used when the per-interface nameservers is not + provided. Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). @@ -1512,7 +1539,9 @@ spec: Please note global search domains are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - provider supports per-interface search domains. + provider supports per-interface search domains. However, when Cloud-Init + is used and UseGlobalSearchDomainsAsDefault is true, the global search + domains will be used when the per-interface search domains is not provided. items: type: string type: array diff --git a/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml b/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml index 7703d826f..4b130fa63 100644 --- a/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml +++ b/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml @@ -1123,6 +1123,22 @@ spec: items: type: string type: array + useGlobalNameserversAsDefault: + description: |- + UseGlobalNameserversAsDefault will use the global nameservers specified in the + NetworkSpec as the per-interface nameservers when the per-interface nameservers + is not provided. + + Defaults to true if omitted. + type: boolean + useGlobalSearchDomainsAsDefault: + description: |- + UseGlobalSearchDomainsAsDefault will use the global search domains specified in the + NetworkSpec as the per-interface search domains when the per-interface search domains + is not provided. + + Defaults to true if omitted. + type: boolean type: object linuxPrep: description: |- @@ -1686,6 +1702,10 @@ spec: Please note this feature is available only with the following bootstrap providers: CloudInit and Sysprep. + When using CloudInit and UseGlobalNameserversAsDefault is either unset or + true, if nameservers is not provided, the global nameservers will be used + instead. + Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). items: @@ -1756,6 +1776,10 @@ spec: Please note this feature is available only with the following bootstrap providers: CloudInit. + + When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset + or true, if search domains is not provided, the global search domains + will be used instead. items: type: string type: array @@ -1774,7 +1798,10 @@ spec: Please note global nameservers are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - provider supports per-interface nameservers. + provider supports per-interface nameservers. However, when Cloud-Init + is used and UseGlobalNameserversAsDefault is true, the global + nameservers will be used when the per-interface nameservers is not + provided. Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). @@ -1788,7 +1815,9 @@ spec: Please note global search domains are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - provider supports per-interface search domains. + provider supports per-interface search domains. However, when Cloud-Init + is used and UseGlobalSearchDomainsAsDefault is true, the global search + domains will be used when the per-interface search domains is not provided. items: type: string type: array @@ -3356,6 +3385,22 @@ spec: items: type: string type: array + useGlobalNameserversAsDefault: + description: |- + UseGlobalNameserversAsDefault will use the global nameservers specified in the + NetworkSpec as the per-interface nameservers when the per-interface nameservers + is not provided. + + Defaults to true if omitted. + type: boolean + useGlobalSearchDomainsAsDefault: + description: |- + UseGlobalSearchDomainsAsDefault will use the global search domains specified in the + NetworkSpec as the per-interface search domains when the per-interface search domains + is not provided. + + Defaults to true if omitted. + type: boolean type: object linuxPrep: description: |- @@ -4194,6 +4239,10 @@ spec: Please note this feature is available only with the following bootstrap providers: CloudInit and Sysprep. + When using CloudInit and UseGlobalNameserversAsDefault is either unset or + true, if nameservers is not provided, the global nameservers will be used + instead. + Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). items: @@ -4264,6 +4313,10 @@ spec: Please note this feature is available only with the following bootstrap providers: CloudInit. + + When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset + or true, if search domains is not provided, the global search domains + will be used instead. items: type: string type: array @@ -4282,7 +4335,10 @@ spec: Please note global nameservers are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - provider supports per-interface nameservers. + provider supports per-interface nameservers. However, when Cloud-Init + is used and UseGlobalNameserversAsDefault is true, the global + nameservers will be used when the per-interface nameservers is not + provided. Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). @@ -4296,7 +4352,9 @@ spec: Please note global search domains are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap - provider supports per-interface search domains. + provider supports per-interface search domains. However, when Cloud-Init + is used and UseGlobalSearchDomainsAsDefault is true, the global search + domains will be used when the per-interface search domains is not provided. items: type: string type: array diff --git a/docs/ref/api/v1alpha2.md b/docs/ref/api/v1alpha2.md index f5345dfb3..69c854096 100644 --- a/docs/ref/api/v1alpha2.md +++ b/docs/ref/api/v1alpha2.md @@ -494,6 +494,16 @@ base64-encoded, or gzipped and base64-encoded. Please note this field and CloudConfig are mutually exclusive. | | `sshAuthorizedKeys` _string array_ | SSHAuthorizedKeys is a list of public keys that CloudInit will apply to the guest's default user. | +| `useGlobalNameserversAsDefault` _boolean_ | UseGlobalNameserversAsDefault will use the global nameservers specified in the +NetworkSpec as the per-interface nameservers when the per-interface nameservers +is not provided. + +Defaults to true if omitted. | +| `useGlobalSearchDomainsAsDefault` _boolean_ | UseGlobalSearchDomainsAsDefault will use the global search domains specified in the +NetworkSpec as the per-interface search domains when the per-interface search domains +is not provided. + +Defaults to true if omitted. | ### VirtualMachineBootstrapLinuxPrepSpec @@ -1232,6 +1242,10 @@ nameservers. Please note this feature is available only with the following bootstrap providers: CloudInit and Sysprep. +When using CloudInit and UseGlobalNameserversAsDefault is either unset or +true, if nameservers is not provided, the global nameservers will be used +instead. + Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). | | `routes` _[VirtualMachineNetworkRouteSpec](#virtualmachinenetworkroutespec) array_ | Routes is a list of optional, static routes. @@ -1242,7 +1256,11 @@ providers: CloudInit. | addresses with DNS. Please note this feature is available only with the following bootstrap -providers: CloudInit. | +providers: CloudInit. + +When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset +or true, if search domains is not provided, the global search domains +will be used instead. | ### VirtualMachineNetworkInterfaceStatus @@ -1312,7 +1330,10 @@ nameservers. These are applied globally. Please note global nameservers are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap -provider supports per-interface nameservers. +provider supports per-interface nameservers. However, when Cloud-Init +is used and UseGlobalNameserversAsDefault is true, the global +nameservers will be used when the per-interface nameservers is not +provided. Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). | @@ -1321,7 +1342,9 @@ addresses with DNS. These are applied globally. Please note global search domains are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap -provider supports per-interface search domains. | +provider supports per-interface search domains. However, when Cloud-Init +is used and UseGlobalSearchDomainsAsDefault is true, the global search +domains will be used when the per-interface search domains is not provided. | | `interfaces` _[VirtualMachineNetworkInterfaceSpec](#virtualmachinenetworkinterfacespec) array_ | Interfaces is the list of network interfaces used by this VM. If the Interfaces field is empty and the Disabled field is false, then diff --git a/docs/ref/api/v1alpha3.md b/docs/ref/api/v1alpha3.md index f8ed32856..5848cff69 100644 --- a/docs/ref/api/v1alpha3.md +++ b/docs/ref/api/v1alpha3.md @@ -516,6 +516,16 @@ base64-encoded, or gzipped and base64-encoded. Please note this field and CloudConfig are mutually exclusive. | | `sshAuthorizedKeys` _string array_ | SSHAuthorizedKeys is a list of public keys that CloudInit will apply to the guest's default user. | +| `useGlobalNameserversAsDefault` _boolean_ | UseGlobalNameserversAsDefault will use the global nameservers specified in the +NetworkSpec as the per-interface nameservers when the per-interface nameservers +is not provided. + +Defaults to true if omitted. | +| `useGlobalSearchDomainsAsDefault` _boolean_ | UseGlobalSearchDomainsAsDefault will use the global search domains specified in the +NetworkSpec as the per-interface search domains when the per-interface search domains +is not provided. + +Defaults to true if omitted. | ### VirtualMachineBootstrapLinuxPrepSpec @@ -1433,6 +1443,10 @@ nameservers. Please note this feature is available only with the following bootstrap providers: CloudInit and Sysprep. +When using CloudInit and UseGlobalNameserversAsDefault is either unset or +true, if nameservers is not provided, the global nameservers will be used +instead. + Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). | | `routes` _[VirtualMachineNetworkRouteSpec](#virtualmachinenetworkroutespec) array_ | Routes is a list of optional, static routes. @@ -1443,7 +1457,11 @@ providers: CloudInit. | addresses with DNS. Please note this feature is available only with the following bootstrap -providers: CloudInit. | +providers: CloudInit. + +When using CloudInit and UseGlobalSearchDomainsAsDefault is either unset +or true, if search domains is not provided, the global search domains +will be used instead. | ### VirtualMachineNetworkInterfaceStatus @@ -1556,7 +1574,10 @@ nameservers. These are applied globally. Please note global nameservers are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap -provider supports per-interface nameservers. +provider supports per-interface nameservers. However, when Cloud-Init +is used and UseGlobalNameserversAsDefault is true, the global +nameservers will be used when the per-interface nameservers is not +provided. Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). | @@ -1565,7 +1586,9 @@ addresses with DNS. These are applied globally. Please note global search domains are only available with the following bootstrap providers: LinuxPrep and Sysprep. The Cloud-Init bootstrap -provider supports per-interface search domains. | +provider supports per-interface search domains. However, when Cloud-Init +is used and UseGlobalSearchDomainsAsDefault is true, the global search +domains will be used when the per-interface search domains is not provided. | | `interfaces` _[VirtualMachineNetworkInterfaceSpec](#virtualmachinenetworkinterfacespec) array_ | Interfaces is the list of network interfaces used by this VM. If the Interfaces field is empty and the Disabled field is false, then diff --git a/pkg/providers/vsphere/network/network.go b/pkg/providers/vsphere/network/network.go index c35835774..fcc526d87 100644 --- a/pkg/providers/vsphere/network/network.go +++ b/pkg/providers/vsphere/network/network.go @@ -32,6 +32,7 @@ import ( pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config" pkgctx "github.com/vmware-tanzu/vm-operator/pkg/context" "github.com/vmware-tanzu/vm-operator/pkg/providers/vsphere/constants" + "github.com/vmware-tanzu/vm-operator/pkg/util/ptr" ) type NetworkInterfaceResults struct { @@ -113,17 +114,23 @@ func CreateAndWaitForNetworkInterfaces( vimClient *vim25.Client, finder *find.Finder, clusterMoRef *vimtypes.ManagedObjectReference, - interfaces []vmopv1.VirtualMachineNetworkInterfaceSpec) (NetworkInterfaceResults, error) { + networkSpec *vmopv1.VirtualMachineNetworkSpec) (NetworkInterfaceResults, error) { networkType := pkgcfg.FromContext(vmCtx).NetworkProviderType if networkType == "" { return NetworkInterfaceResults{}, fmt.Errorf("no network provider set") } - results := make([]NetworkInterfaceResult, 0, len(interfaces)) + var defaultToGlobalNameservers, defaultToGlobalSearchDomains bool + if bootstrap := vmCtx.VM.Spec.Bootstrap; bootstrap != nil && bootstrap.CloudInit != nil { + defaultToGlobalNameservers = ptr.DerefWithDefault(bootstrap.CloudInit.UseGlobalNameserversAsDefault, true) + defaultToGlobalSearchDomains = ptr.DerefWithDefault(bootstrap.CloudInit.UseGlobalSearchDomainsAsDefault, true) + } + + results := make([]NetworkInterfaceResult, 0, len(networkSpec.Interfaces)) - for i := range interfaces { - interfaceSpec := &interfaces[i] + for i := range networkSpec.Interfaces { + interfaceSpec := &networkSpec.Interfaces[i] var result *NetworkInterfaceResult var err error @@ -146,7 +153,13 @@ func CreateAndWaitForNetworkInterfaces( fmt.Errorf("network interface %q error: %w", interfaceSpec.Name, err) } - applyInterfaceSpecToResult(interfaceSpec, result) + applyInterfaceSpecToResult( + networkSpec, + interfaceSpec, + defaultToGlobalNameservers, + defaultToGlobalSearchDomains, + result) + results = append(results, *result) } @@ -162,7 +175,10 @@ func CreateAndWaitForNetworkInterfaces( // applyInterfaceSpecToResult applies the InterfaceSpec to results. Much of the InterfaceSpec - like DHCP - // cannot be specified to the underlying network provider so apply those overrides to the results. func applyInterfaceSpecToResult( + networkSpec *vmopv1.VirtualMachineNetworkSpec, interfaceSpec *vmopv1.VirtualMachineNetworkInterfaceSpec, + defaultToGlobalNameservers bool, + defaultToGlobalSearchDomains bool, result *NetworkInterfaceResult) { // We don't really support IPv6 yet so don't enable it when the underlying provider didn't return any IPs. @@ -206,8 +222,18 @@ func applyInterfaceSpecToResult( result.DHCP4 = dhcp4 result.DHCP6 = dhcp6 - result.Nameservers = interfaceSpec.Nameservers - result.SearchDomains = interfaceSpec.SearchDomains + + if n := interfaceSpec.Nameservers; len(n) > 0 { + result.Nameservers = n + } else if defaultToGlobalNameservers { + result.Nameservers = networkSpec.Nameservers + } + + if d := interfaceSpec.SearchDomains; len(d) > 0 { + result.SearchDomains = d + } else if defaultToGlobalSearchDomains { + result.SearchDomains = networkSpec.SearchDomains + } if interfaceSpec.MTU != nil { result.MTU = *interfaceSpec.MTU diff --git a/pkg/providers/vsphere/network/network_test.go b/pkg/providers/vsphere/network/network_test.go index 5418538d5..ca4a50ba1 100644 --- a/pkg/providers/vsphere/network/network_test.go +++ b/pkg/providers/vsphere/network/network_test.go @@ -34,9 +34,9 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f testConfig builder.VCSimTestConfig ctx *builder.TestContextForVCSim - vmCtx pkgctx.VirtualMachineContext - vm *vmopv1.VirtualMachine - interfaceSpecs []vmopv1.VirtualMachineNetworkInterfaceSpec + vmCtx pkgctx.VirtualMachineContext + vm *vmopv1.VirtualMachine + networkSpec *vmopv1.VirtualMachineNetworkSpec results network.NetworkInterfaceResults err error @@ -53,7 +53,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f }, } - interfaceSpecs = nil + networkSpec = &vmopv1.VirtualMachineNetworkSpec{} }) JustBeforeEach(func() { @@ -71,7 +71,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, nil, - interfaceSpecs) + networkSpec) }) AfterEach(func() { @@ -90,7 +90,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f Context("network exists", func() { BeforeEach(func() { - interfaceSpecs = []vmopv1.VirtualMachineNetworkInterfaceSpec{ + networkSpec.Interfaces = []vmopv1.VirtualMachineNetworkInterfaceSpec{ { Name: "eth0", Network: &common.PartialObjectRef{Name: networkName}, @@ -119,7 +119,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f Context("Overrides with provided InterfaceSpec", func() { BeforeEach(func() { - interfaceSpecs = []vmopv1.VirtualMachineNetworkInterfaceSpec{ + networkSpec.Interfaces = []vmopv1.VirtualMachineNetworkInterfaceSpec{ { Name: "my-network-interface", GuestDeviceName: "eth42", @@ -179,19 +179,46 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f }) Expect(result.MTU).To(BeEquivalentTo(9000)) - Expect(result.Nameservers).To(ConsistOf("9.9.9.9")) - Expect(result.SearchDomains).To(ConsistOf("vmware.com")) + Expect(result.Nameservers).To(HaveExactElements("9.9.9.9")) + Expect(result.SearchDomains).To(HaveExactElements("vmware.com")) Expect(result.Routes).To(HaveLen(1)) Expect(result.Routes[0].To).To(Equal("10.10.10.10")) Expect(result.Routes[0].Via).To(Equal("5.5.5.5")) Expect(result.Routes[0].Metric).To(BeEquivalentTo(42)) }) + + Context("Bootstrap has use globals as defaults", func() { + BeforeEach(func() { + vm.Spec.Bootstrap = &vmopv1.VirtualMachineBootstrapSpec{ + CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{ + UseGlobalNameserversAsDefault: ptr.To(true), + UseGlobalSearchDomainsAsDefault: ptr.To(true), + }, + } + + networkSpec.Nameservers = []string{"149.112.112.112"} + networkSpec.SearchDomains = []string{"broadcom.net"} + networkSpec.Interfaces[0].Nameservers = nil + networkSpec.Interfaces[0].SearchDomains = nil + }) + + It("returns success", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(results.Results).To(HaveLen(1)) + + result := results.Results[0] + By("has expected values", func() { + Expect(result.Nameservers).To(HaveExactElements("149.112.112.112")) + Expect(result.SearchDomains).To(HaveExactElements("broadcom.net")) + }) + }) + }) }) }) Context("network does not exist", func() { BeforeEach(func() { - interfaceSpecs = []vmopv1.VirtualMachineNetworkInterfaceSpec{ + networkSpec.Interfaces = []vmopv1.VirtualMachineNetworkInterfaceSpec{ { Name: "eth0", Network: &common.PartialObjectRef{Name: "bogus"}, @@ -220,7 +247,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f Context("Simulate workflow", func() { BeforeEach(func() { - interfaceSpecs = []vmopv1.VirtualMachineNetworkInterfaceSpec{ + networkSpec.Interfaces = []vmopv1.VirtualMachineNetworkInterfaceSpec{ { Name: interfaceName, Network: &common.PartialObjectRef{ @@ -280,7 +307,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, nil, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) @@ -370,7 +397,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, nil, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) @@ -412,7 +439,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f Context("Simulate workflow", func() { BeforeEach(func() { - interfaceSpecs = []vmopv1.VirtualMachineNetworkInterfaceSpec{ + networkSpec.Interfaces = []vmopv1.VirtualMachineNetworkInterfaceSpec{ { Name: interfaceName, Network: &common.PartialObjectRef{ @@ -473,7 +500,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, nil, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) @@ -503,7 +530,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, &clusterMoRef, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) Expect(results.Results[0].Backing).ToNot(BeNil()) @@ -575,7 +602,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, nil, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) @@ -605,7 +632,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, &clusterMoRef, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) Expect(results.Results[0].Backing).ToNot(BeNil()) @@ -630,7 +657,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f Context("Simulate workflow", func() { BeforeEach(func() { - interfaceSpecs = []vmopv1.VirtualMachineNetworkInterfaceSpec{ + networkSpec.Interfaces = []vmopv1.VirtualMachineNetworkInterfaceSpec{ { Name: interfaceName, Network: &common.PartialObjectRef{ @@ -692,7 +719,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, nil, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) @@ -722,7 +749,7 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", Label(testlabels.VCSim), f ctx.VCClient.Client, ctx.Finder, &clusterMoRef, - interfaceSpecs) + networkSpec) Expect(err).ToNot(HaveOccurred()) Expect(results.Results).To(HaveLen(1)) Expect(results.Results[0].Backing).ToNot(BeNil()) diff --git a/pkg/providers/vsphere/session/session_vm_update.go b/pkg/providers/vsphere/session/session_vm_update.go index 9ba22cdf8..01bea974f 100644 --- a/pkg/providers/vsphere/session/session_vm_update.go +++ b/pkg/providers/vsphere/session/session_vm_update.go @@ -616,7 +616,7 @@ func (s *Session) ensureNetworkInterfaces( s.Client.VimClient(), s.Finder, &s.ClusterMoRef, - networkSpec.Interfaces) + networkSpec) if err != nil { return network2.NetworkInterfaceResults{}, err } diff --git a/pkg/providers/vsphere/vmprovider_vm.go b/pkg/providers/vsphere/vmprovider_vm.go index 8cfc497e7..dacf0e960 100644 --- a/pkg/providers/vsphere/vmprovider_vm.go +++ b/pkg/providers/vsphere/vmprovider_vm.go @@ -1187,7 +1187,7 @@ func (vs *vSphereVMProvider) vmCreateDoNetworking( vcClient.VimClient(), vcClient.Finder(), nil, // Don't know the CCR yet (needed to resolve backings for NSX-T) - networkSpec.Interfaces) + networkSpec) if err != nil { conditions.MarkFalse(vmCtx.VM, vmopv1.VirtualMachineConditionNetworkReady, "NotReady", err.Error()) return err diff --git a/webhooks/virtualmachine/validation/virtualmachine_validator.go b/webhooks/virtualmachine/validation/virtualmachine_validator.go index 7a6543d56..98d034cc9 100644 --- a/webhooks/virtualmachine/validation/virtualmachine_validator.go +++ b/webhooks/virtualmachine/validation/virtualmachine_validator.go @@ -44,6 +44,7 @@ import ( "github.com/vmware-tanzu/vm-operator/pkg/util/annotations" cloudinitvalidate "github.com/vmware-tanzu/vm-operator/pkg/util/cloudinit/validate" kubeutil "github.com/vmware-tanzu/vm-operator/pkg/util/kube" + "github.com/vmware-tanzu/vm-operator/pkg/util/ptr" vmopv1util "github.com/vmware-tanzu/vm-operator/pkg/util/vmopv1" "github.com/vmware-tanzu/vm-operator/webhooks/common" ) @@ -747,17 +748,27 @@ func (v validator) validateNetworkSpecWithBootStrap( } var ( + cloudInit *vmopv1.VirtualMachineBootstrapCloudInitSpec linuxPrep *vmopv1.VirtualMachineBootstrapLinuxPrepSpec sysPrep *vmopv1.VirtualMachineBootstrapSysprepSpec ) if vm.Spec.Bootstrap != nil { + cloudInit = vm.Spec.Bootstrap.CloudInit linuxPrep = vm.Spec.Bootstrap.LinuxPrep sysPrep = vm.Spec.Bootstrap.Sysprep } if len(networkSpec.Nameservers) > 0 { - if linuxPrep == nil && sysPrep == nil { + if cloudInit != nil { + if !ptr.DerefWithDefault(cloudInit.UseGlobalNameserversAsDefault, true) { + allErrs = append(allErrs, field.Invalid( + field.NewPath("spec", "network", "nameservers"), + strings.Join(networkSpec.Nameservers, ","), + "nameservers is only available for CloudInit when UseGlobalNameserversAsDefault is true", + )) + } + } else if linuxPrep == nil && sysPrep == nil { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "network", "nameservers"), strings.Join(networkSpec.Nameservers, ","), @@ -767,7 +778,15 @@ func (v validator) validateNetworkSpecWithBootStrap( } if len(networkSpec.SearchDomains) > 0 { - if linuxPrep == nil && sysPrep == nil { + if cloudInit != nil { + if !ptr.DerefWithDefault(cloudInit.UseGlobalSearchDomainsAsDefault, true) { + allErrs = append(allErrs, field.Invalid( + field.NewPath("spec", "network", "searchDomains"), + strings.Join(networkSpec.SearchDomains, ","), + "searchDomains is only available for CloudInit when UseGlobalSearchDomainsAsDefault is true", + )) + } + } else if linuxPrep == nil && sysPrep == nil { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "network", "searchDomains"), strings.Join(networkSpec.SearchDomains, ","), diff --git a/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go b/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go index 4c8f9996f..f2774d24e 100644 --- a/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go +++ b/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go @@ -1720,15 +1720,63 @@ func unitTestsValidateCreate() { }, ), - Entry("disallow global nameservers and search domains with CloudInit", + Entry("allow global nameservers and search domains with CloudInit when UseGlobals...AsDefaults are nil", testParams{ setup: func(ctx *unitValidatingWebhookContext) { ctx.vm.Spec.Bootstrap = &vmopv1.VirtualMachineBootstrapSpec{ - CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{}, + CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{ + UseGlobalNameserversAsDefault: nil, + UseGlobalSearchDomainsAsDefault: nil, + }, + } + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Nameservers: []string{ + "8.8.8.8", + "2001:4860:4860::8888", + }, + SearchDomains: []string{ + "dev.local", + }, + } + }, + expectAllowed: true, + }, + ), + + Entry("allow global nameservers and search domains with CloudInit when UseGlobals...AsDefaults are true", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + ctx.vm.Spec.Bootstrap = &vmopv1.VirtualMachineBootstrapSpec{ + CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{ + UseGlobalNameserversAsDefault: ptr.To(true), + UseGlobalSearchDomainsAsDefault: ptr.To(true), + }, + } + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Nameservers: []string{ + "8.8.8.8", + "2001:4860:4860::8888", + }, + SearchDomains: []string{ + "dev.local", + }, + } + }, + expectAllowed: true, + }, + ), + + Entry("disallow global nameservers and search domains with CloudInit when UseGlobals...AsDefaults are false", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + ctx.vm.Spec.Bootstrap = &vmopv1.VirtualMachineBootstrapSpec{ + CloudInit: &vmopv1.VirtualMachineBootstrapCloudInitSpec{ + UseGlobalNameserversAsDefault: ptr.To(false), + UseGlobalSearchDomainsAsDefault: ptr.To(false), + }, } ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ Nameservers: []string{ - "not-an-ip", "8.8.8.8", "2001:4860:4860::8888", }, @@ -1738,9 +1786,8 @@ func unitTestsValidateCreate() { } }, validate: doValidateWithMsg( - `spec.network.nameservers: Invalid value: "not-an-ip,8.8.8.8,2001:4860:4860::8888": nameservers is available only with the following bootstrap providers: LinuxPrep and Sysprep`, - `spec.network.searchDomains: Invalid value: "dev.local": searchDomains is available only with the following bootstrap providers: LinuxPrep and Sysprep`, - `spec.network.nameservers[0]: Invalid value: "not-an-ip": must be an IPv4 or IPv6 address`, + `spec.network.nameservers: Invalid value: "8.8.8.8,2001:4860:4860::8888": nameservers is only available for CloudInit when UseGlobalNameserversAsDefault is true`, + `spec.network.searchDomains: Invalid value: "dev.local": searchDomains is only available for CloudInit when UseGlobalSearchDomainsAsDefault is true`, ), }, ),