-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add validating webhook for proxmoxmachine
- Loading branch information
Showing
5 changed files
with
265 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/* | ||
Copyright 2023 IONOS Cloud. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
// Package webhook contains webhooks for the custom resources. | ||
package webhook | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/util/validation/field" | ||
|
||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
||
infrav1 "github.com/ionos-cloud/cluster-api-provider-proxmox/api/v1alpha1" | ||
) | ||
|
||
// ProxmoxMachine is a type that implements | ||
// the interfaces from the admission package. | ||
type ProxmoxMachine struct{} | ||
|
||
// SetupWebhookWithManager sets up the webhook with the | ||
// custom interfaces. | ||
func (p *ProxmoxMachine) SetupWebhookWithManager(mgr ctrl.Manager) error { | ||
return ctrl.NewWebhookManagedBy(mgr). | ||
For(&infrav1.ProxmoxMachine{}). | ||
WithValidator(p). | ||
Complete() | ||
} | ||
|
||
//+kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1alpha1-proxmoxmachine,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=proxmoxmachines,versions=v1alpha1,name=validation.proxmoxmachine.infrastructure.cluster.x-k8s.io,admissionReviewVersions=v1 | ||
|
||
// ValidateCreate implements the creation validation function. | ||
func (p *ProxmoxMachine) ValidateCreate(_ context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { | ||
machine, ok := obj.(*infrav1.ProxmoxMachine) | ||
if !ok { | ||
return warnings, apierrors.NewBadRequest(fmt.Sprintf("expected a ProxmoxMachine but got %T", obj)) | ||
} | ||
|
||
err = validateNetworks(machine) | ||
if err != nil { | ||
warnings = append(warnings, fmt.Sprintf("cannot create proxmox machine %s", machine.GetName())) | ||
return warnings, err | ||
} | ||
|
||
return warnings, nil | ||
} | ||
|
||
// ValidateUpdate implements the update validation function. | ||
func (p *ProxmoxMachine) ValidateUpdate(_ context.Context, _, newObj runtime.Object) (warnings admission.Warnings, err error) { | ||
newMachine, ok := newObj.(*infrav1.ProxmoxMachine) | ||
if !ok { | ||
return warnings, apierrors.NewBadRequest(fmt.Sprintf("expected a ProxmoxMachine but got %T", newObj)) | ||
} | ||
|
||
err = validateNetworks(newMachine) | ||
if err != nil { | ||
warnings = append(warnings, fmt.Sprintf("cannot update proxmox machine %s", newMachine.GetName())) | ||
return warnings, err | ||
} | ||
|
||
return warnings, nil | ||
} | ||
|
||
// ValidateDelete implements the deletion validation function. | ||
func (p *ProxmoxMachine) ValidateDelete(_ context.Context, _ runtime.Object) (warnings admission.Warnings, err error) { | ||
return nil, nil | ||
} | ||
|
||
func validateNetworks(machine *infrav1.ProxmoxMachine) error { | ||
gk, name := machine.GroupVersionKind().GroupKind(), machine.GetName() | ||
|
||
if machine.Spec.Network.Default != nil { | ||
err := validateNetworkDevice(machine.Spec.Network.Default) | ||
if err != nil { | ||
return apierrors.NewInvalid( | ||
gk, | ||
name, | ||
field.ErrorList{ | ||
field.Invalid( | ||
field.NewPath("spec", "network", "default", "mtu"), machine.Spec.Network.Default, err.Error()), | ||
}) | ||
} | ||
} | ||
|
||
for i := range machine.Spec.Network.AdditionalDevices { | ||
err := validateNetworkDevice(&machine.Spec.Network.AdditionalDevices[i].NetworkDevice) | ||
if err != nil { | ||
return apierrors.NewInvalid( | ||
gk, | ||
name, | ||
field.ErrorList{ | ||
field.Invalid( | ||
field.NewPath("spec", "network", "additionalDevices", fmt.Sprint(i), "mtu"), machine.Spec.Network.Default, err.Error()), | ||
}) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func validateNetworkDevice(device *infrav1.NetworkDevice) error { | ||
if device.MTU == nil { | ||
return nil | ||
} | ||
|
||
if *device.MTU == 1 { | ||
return nil | ||
} | ||
|
||
if *device.MTU > 999 { | ||
return nil | ||
} | ||
|
||
return fmt.Errorf("mtu must be at least 1000 or 1, but was %d", *device.MTU) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
Copyright 2023 IONOS Cloud. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package webhook | ||
|
||
import ( | ||
"time" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/utils/ptr" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
infrav1 "github.com/ionos-cloud/cluster-api-provider-proxmox/api/v1alpha1" | ||
) | ||
|
||
var _ = Describe("Controller Test", func() { | ||
g := NewWithT(GinkgoT()) | ||
|
||
Context("create proxmox machine", func() { | ||
It("should disallow invalid network mtu", func() { | ||
machine := invalidProxmoxMachine("test-machine") | ||
g.Expect(k8sClient.Create(testEnv.GetContext(), &machine)).To(MatchError(ContainSubstring("spec.network.default.mtu: Invalid value"))) | ||
}) | ||
|
||
It("should create a valid proxmox machine", func() { | ||
machine := validProxmoxMachine("test-machine") | ||
g.Expect(k8sClient.Create(testEnv.GetContext(), &machine)).To(Succeed()) | ||
}) | ||
}) | ||
|
||
Context("update proxmox cluster", func() { | ||
It("should disallow invalid network mtu", func() { | ||
clusterName := "test-cluster" | ||
machine := validProxmoxMachine(clusterName) | ||
g.Expect(k8sClient.Create(testEnv.GetContext(), &machine)).To(Succeed()) | ||
|
||
g.Expect(k8sClient.Get(testEnv.GetContext(), client.ObjectKeyFromObject(&machine), &machine)).To(Succeed()) | ||
machine.Spec.Network.Default.MTU = ptr.To(uint16(50)) | ||
|
||
g.Expect(k8sClient.Create(testEnv.GetContext(), &machine)).To(MatchError(ContainSubstring("spec.network.default.mtu: Invalid value"))) | ||
|
||
g.Eventually(func(g Gomega) { | ||
g.Expect(client.IgnoreNotFound(k8sClient.Delete(testEnv.GetContext(), &machine))).To(Succeed()) | ||
}).WithTimeout(time.Second * 10). | ||
WithPolling(time.Second). | ||
Should(Succeed()) | ||
}) | ||
}) | ||
}) | ||
|
||
func validProxmoxMachine(name string) infrav1.ProxmoxMachine { | ||
return infrav1.ProxmoxMachine{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
Namespace: "default", | ||
}, | ||
Spec: infrav1.ProxmoxMachineSpec{ | ||
VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{ | ||
SourceNode: "pve", | ||
}, | ||
NumSockets: 1, | ||
NumCores: 1, | ||
MemoryMiB: 1024, | ||
Disks: &infrav1.Storage{ | ||
BootVolume: &infrav1.DiskSize{ | ||
Disk: "scsi[0]", | ||
SizeGB: 10, | ||
}, | ||
}, | ||
Network: &infrav1.NetworkSpec{ | ||
Default: &infrav1.NetworkDevice{ | ||
Bridge: "vmbr1", | ||
Model: ptr.To("virtio"), | ||
MTU: ptr.To(uint16(1500)), | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func invalidProxmoxMachine(name string) infrav1.ProxmoxMachine { | ||
machine := validProxmoxMachine(name) | ||
machine.Spec.Network.Default = &infrav1.NetworkDevice{ | ||
Bridge: "vmbr1", | ||
Model: ptr.To("virtio"), | ||
MTU: ptr.To(uint16(50)), | ||
} | ||
return machine | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters