Skip to content

Commit

Permalink
Merge pull request #172 from bryanv/bryanv/v1a2-webhooks
Browse files Browse the repository at this point in the history
Add v1a2 webhooks
  • Loading branch information
bryanv authored Jul 17, 2023
2 parents f35fc81 + 9869e81 commit 8f2552f
Show file tree
Hide file tree
Showing 42 changed files with 5,299 additions and 8 deletions.
2 changes: 0 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ linters-settings:
# it's a comma-separated list of prefixes
local-prefixes: github.com/vmware-tanzu
importas:
no-unaliased: true
alias:
# Kubernetes
- pkg: k8s.io/api/core/v1
alias: corev1
- pkg: github.com/vmware-tanzu/vm-operator/api/v1alpha1
Expand Down
9 changes: 5 additions & 4 deletions test/builder/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"

imgregv1a1 "github.com/vmware-tanzu/image-registry-operator-api/api/v1alpha1"

vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha1"

ncpv1alpha1 "github.com/vmware-tanzu/vm-operator/external/ncp/api/v1alpha1"
netopv1alpha1 "github.com/vmware-tanzu/vm-operator/external/net-operator/api/v1alpha1"
topologyv1 "github.com/vmware-tanzu/vm-operator/external/tanzu-topology/api/v1alpha1"
cnsv1alpha1 "github.com/vmware-tanzu/vm-operator/external/vsphere-csi-driver/pkg/syncer/cnsoperator/apis/cnsnodevmattachment/v1alpha1"

"github.com/vmware-tanzu/vm-operator/api/v1alpha1"
"github.com/vmware-tanzu/vm-operator/api/v1alpha2"
"github.com/vmware-tanzu/vm-operator/pkg/record"
)

Expand All @@ -35,7 +35,8 @@ func NewFakeRecorder() (record.Recorder, chan string) {
func NewScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
_ = clientgoscheme.AddToScheme(scheme)
_ = vmopv1.AddToScheme(scheme)
_ = v1alpha1.AddToScheme(scheme)
_ = v1alpha2.AddToScheme(scheme)
_ = ncpv1alpha1.AddToScheme(scheme)
_ = cnsv1alpha1.AddToScheme(scheme)
_ = netopv1alpha1.AddToScheme(scheme)
Expand Down
174 changes: 174 additions & 0 deletions test/builder/utila2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) 2023 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package builder

import (
"fmt"

"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha2"
)

func DummyVirtualMachineSetResourcePolicyA2() *vmopv1.VirtualMachineSetResourcePolicy {
return &vmopv1.VirtualMachineSetResourcePolicy{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Spec: vmopv1.VirtualMachineSetResourcePolicySpec{
ResourcePool: vmopv1.ResourcePoolSpec{
Name: "dummy-resource-pool",
Reservations: vmopv1.VirtualMachineResourceSpec{
Cpu: resource.MustParse("1Gi"),
Memory: resource.MustParse("2Gi"),
},
Limits: vmopv1.VirtualMachineResourceSpec{
Cpu: resource.MustParse("2Gi"),
Memory: resource.MustParse("4Gi"),
},
},
Folder: "dummy-folder",
ClusterModuleGroups: []string{"dummy-cluster-modules"},
},
}
}

func DummyVirtualMachineServiceA2() *vmopv1.VirtualMachineService {
return &vmopv1.VirtualMachineService{
ObjectMeta: metav1.ObjectMeta{
// Using image.GenerateName causes problems with unit tests
Name: fmt.Sprintf("test-%s", uuid.New()),
},
Spec: vmopv1.VirtualMachineServiceSpec{
Type: vmopv1.VirtualMachineServiceTypeLoadBalancer,
Ports: []vmopv1.VirtualMachineServicePort{
{
Name: "dummy-port",
Protocol: "TCP",
Port: 42,
TargetPort: 4242,
},
},
Selector: map[string]string{
"foo": "bar",
},
},
}
}

func DummyVirtualMachineA2() *vmopv1.VirtualMachine {
return &vmopv1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Labels: map[string]string{},
Annotations: map[string]string{},
},
Spec: vmopv1.VirtualMachineSpec{
ImageName: DummyImageName,
ClassName: DummyClassName,
PowerState: vmopv1.VirtualMachinePowerStateOn,
Volumes: []vmopv1.VirtualMachineVolume{
{
Name: DummyVolumeName,
VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{
PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{
PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{
ClaimName: DummyPVCName,
},
},
},
},
},
},
}
}

func DummyVirtualMachinePublishRequestA2(name, namespace, sourceName, itemName, clName string) *vmopv1.VirtualMachinePublishRequest {
return &vmopv1.VirtualMachinePublishRequest{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Finalizers: []string{"virtualmachinepublishrequest.vmoperator.vmware.com"},
},
Spec: vmopv1.VirtualMachinePublishRequestSpec{
Source: vmopv1.VirtualMachinePublishRequestSource{
Name: sourceName,
APIVersion: "vmoperator.vmware.com/v1alpha2",
Kind: "VirtualMachine",
},
Target: vmopv1.VirtualMachinePublishRequestTarget{
Item: vmopv1.VirtualMachinePublishRequestTargetItem{
Name: itemName,
},
Location: vmopv1.VirtualMachinePublishRequestTargetLocation{
Name: clName,
APIVersion: "imageregistry.vmware.com/v1alpha1",
Kind: "ContentLibrary",
},
},
},
}
}

func DummyVirtualMachineClassA2() *vmopv1.VirtualMachineClass {
return &vmopv1.VirtualMachineClass{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Spec: vmopv1.VirtualMachineClassSpec{
Hardware: vmopv1.VirtualMachineClassHardware{
Cpus: int64(2),
Memory: resource.MustParse("4Gi"),
},
Policies: vmopv1.VirtualMachineClassPolicies{
Resources: vmopv1.VirtualMachineClassResources{
Requests: vmopv1.VirtualMachineResourceSpec{
Cpu: resource.MustParse("1Gi"),
Memory: resource.MustParse("2Gi"),
},
Limits: vmopv1.VirtualMachineResourceSpec{
Cpu: resource.MustParse("2Gi"),
Memory: resource.MustParse("4Gi"),
},
},
},
},
}
}

func DummyInstanceStorageVirtualMachineVolumesA2() []vmopv1.VirtualMachineVolume {
return []vmopv1.VirtualMachineVolume{
{
Name: "instance-pvc-1",
VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{
PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{
PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "instance-pvc-1",
},
InstanceVolumeClaim: &vmopv1.InstanceVolumeClaimVolumeSource{
StorageClass: DummyStorageClassName,
Size: resource.MustParse("256Gi"),
},
},
},
},
{
Name: "instance-pvc-2",
VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{
PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{
PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "instance-pvc-2",
},
InstanceVolumeClaim: &vmopv1.InstanceVolumeClaimVolumeSource{
StorageClass: DummyStorageClassName,
Size: resource.MustParse("512Gi"),
},
},
},
},
}
}
144 changes: 144 additions & 0 deletions webhooks/virtualmachine/v1alpha2/mutation/virtualmachine_mutator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) 2019-2023 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package mutation

import (
"encoding/json"
"net/http"
"reflect"
"strings"
"time"

"github.com/pkg/errors"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlmgr "sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha2"
"github.com/vmware-tanzu/vm-operator/pkg/builder"
"github.com/vmware-tanzu/vm-operator/pkg/context"
)

const (
webHookName = "default"
)

// -kubebuilder:webhook:path=/default-mutate-vmoperator-vmware-com-v1alpha2-virtualmachine,mutating=true,failurePolicy=fail,groups=vmoperator.vmware.com,resources=virtualmachines,verbs=create;update,versions=v1alpha2,name=default.mutating.virtualmachine.v1alpha2.vmoperator.vmware.com,sideEffects=None,admissionReviewVersions=v1;v1beta1
// -kubebuilder:rbac:groups=vmoperator.vmware.com,resources=virtualmachine,verbs=get;list
// -kubebuilder:rbac:groups=vmoperator.vmware.com,resources=virtualmachine/status,verbs=get

// AddToManager adds the webhook to the provided manager.
func AddToManager(ctx *context.ControllerManagerContext, mgr ctrlmgr.Manager) error {
hook, err := builder.NewMutatingWebhook(ctx, mgr, webHookName, NewMutator(mgr.GetClient()))
if err != nil {
return errors.Wrapf(err, "failed to create mutation webhook")
}
mgr.GetWebhookServer().Register(hook.Path, hook)

return nil
}

// NewMutator returns the package's Mutator.
func NewMutator(client client.Client) builder.Mutator {
return mutator{
client: client,
converter: runtime.DefaultUnstructuredConverter,
}
}

type mutator struct {
client client.Client
converter runtime.UnstructuredConverter
}

func (m mutator) Mutate(ctx *context.WebhookRequestContext) admission.Response {
if ctx.Op == admissionv1.Delete {
return admission.Allowed("")
}

vm, err := m.vmFromUnstructured(ctx.Obj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

var wasMutated bool
original := vm
modified := original.DeepCopy()

//nolint:gocritic
switch ctx.Op {
case admissionv1.Update:
oldVM, err := m.vmFromUnstructured(ctx.OldObj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

if ok, err := SetNextRestartTime(ctx, modified, oldVM); err != nil {
return admission.Denied(err.Error())
} else if ok {
wasMutated = true
}
}

if !wasMutated {
return admission.Allowed("")
}

rawOriginal, err := json.Marshal(original)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
rawModified, err := json.Marshal(modified)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(rawOriginal, rawModified)
}

func (m mutator) For() schema.GroupVersionKind {
return vmopv1.SchemeGroupVersion.WithKind(reflect.TypeOf(vmopv1.VirtualMachine{}).Name())
}

// vmFromUnstructured returns the VirtualMachine from the unstructured object.
func (m mutator) vmFromUnstructured(obj runtime.Unstructured) (*vmopv1.VirtualMachine, error) {
vm := &vmopv1.VirtualMachine{}
if err := m.converter.FromUnstructured(obj.UnstructuredContent(), vm); err != nil {
return nil, err
}
return vm, nil
}

// SetNextRestartTime sets spec.nextRestartTime for a VM if the field's
// current value is equal to "now" (case-insensitive).
// Return true if set, otherwise false.
func SetNextRestartTime(
ctx *context.WebhookRequestContext,
newVM, oldVM *vmopv1.VirtualMachine) (bool, error) {

if newVM.Spec.NextRestartTime == "" {
newVM.Spec.NextRestartTime = oldVM.Spec.NextRestartTime
return oldVM.Spec.NextRestartTime != "", nil
}
if strings.EqualFold("now", newVM.Spec.NextRestartTime) {
if oldVM.Spec.PowerState != vmopv1.VirtualMachinePowerStateOn {
return false, field.Invalid(
field.NewPath("spec", "nextRestartTime"),
newVM.Spec.NextRestartTime,
"can only restart powered on vm")
}
newVM.Spec.NextRestartTime = time.Now().UTC().Format(time.RFC3339Nano)
return true, nil
}
if newVM.Spec.NextRestartTime == oldVM.Spec.NextRestartTime {
return false, nil
}
return false, field.Invalid(
field.NewPath("spec", "nextRestartTime"),
newVM.Spec.NextRestartTime,
`may only be set to "now"`)
}
Loading

0 comments on commit 8f2552f

Please sign in to comment.