Skip to content

Commit 8f2552f

Browse files
authored
Merge pull request #172 from bryanv/bryanv/v1a2-webhooks
Add v1a2 webhooks
2 parents f35fc81 + 9869e81 commit 8f2552f

File tree

42 files changed

+5299
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+5299
-8
lines changed

.golangci.yml

-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ linters-settings:
1414
# it's a comma-separated list of prefixes
1515
local-prefixes: github.com/vmware-tanzu
1616
importas:
17-
no-unaliased: true
1817
alias:
19-
# Kubernetes
2018
- pkg: k8s.io/api/core/v1
2119
alias: corev1
2220
- pkg: github.com/vmware-tanzu/vm-operator/api/v1alpha1

test/builder/fake.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1212

1313
imgregv1a1 "github.com/vmware-tanzu/image-registry-operator-api/api/v1alpha1"
14-
15-
vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha1"
16-
1714
ncpv1alpha1 "github.com/vmware-tanzu/vm-operator/external/ncp/api/v1alpha1"
1815
netopv1alpha1 "github.com/vmware-tanzu/vm-operator/external/net-operator/api/v1alpha1"
1916
topologyv1 "github.com/vmware-tanzu/vm-operator/external/tanzu-topology/api/v1alpha1"
2017
cnsv1alpha1 "github.com/vmware-tanzu/vm-operator/external/vsphere-csi-driver/pkg/syncer/cnsoperator/apis/cnsnodevmattachment/v1alpha1"
18+
19+
"github.com/vmware-tanzu/vm-operator/api/v1alpha1"
20+
"github.com/vmware-tanzu/vm-operator/api/v1alpha2"
2121
"github.com/vmware-tanzu/vm-operator/pkg/record"
2222
)
2323

@@ -35,7 +35,8 @@ func NewFakeRecorder() (record.Recorder, chan string) {
3535
func NewScheme() *runtime.Scheme {
3636
scheme := runtime.NewScheme()
3737
_ = clientgoscheme.AddToScheme(scheme)
38-
_ = vmopv1.AddToScheme(scheme)
38+
_ = v1alpha1.AddToScheme(scheme)
39+
_ = v1alpha2.AddToScheme(scheme)
3940
_ = ncpv1alpha1.AddToScheme(scheme)
4041
_ = cnsv1alpha1.AddToScheme(scheme)
4142
_ = netopv1alpha1.AddToScheme(scheme)

test/builder/utila2.go

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright (c) 2023 VMware, Inc. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package builder
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/google/uuid"
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/api/resource"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
14+
vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha2"
15+
)
16+
17+
func DummyVirtualMachineSetResourcePolicyA2() *vmopv1.VirtualMachineSetResourcePolicy {
18+
return &vmopv1.VirtualMachineSetResourcePolicy{
19+
ObjectMeta: metav1.ObjectMeta{
20+
GenerateName: "test-",
21+
},
22+
Spec: vmopv1.VirtualMachineSetResourcePolicySpec{
23+
ResourcePool: vmopv1.ResourcePoolSpec{
24+
Name: "dummy-resource-pool",
25+
Reservations: vmopv1.VirtualMachineResourceSpec{
26+
Cpu: resource.MustParse("1Gi"),
27+
Memory: resource.MustParse("2Gi"),
28+
},
29+
Limits: vmopv1.VirtualMachineResourceSpec{
30+
Cpu: resource.MustParse("2Gi"),
31+
Memory: resource.MustParse("4Gi"),
32+
},
33+
},
34+
Folder: "dummy-folder",
35+
ClusterModuleGroups: []string{"dummy-cluster-modules"},
36+
},
37+
}
38+
}
39+
40+
func DummyVirtualMachineServiceA2() *vmopv1.VirtualMachineService {
41+
return &vmopv1.VirtualMachineService{
42+
ObjectMeta: metav1.ObjectMeta{
43+
// Using image.GenerateName causes problems with unit tests
44+
Name: fmt.Sprintf("test-%s", uuid.New()),
45+
},
46+
Spec: vmopv1.VirtualMachineServiceSpec{
47+
Type: vmopv1.VirtualMachineServiceTypeLoadBalancer,
48+
Ports: []vmopv1.VirtualMachineServicePort{
49+
{
50+
Name: "dummy-port",
51+
Protocol: "TCP",
52+
Port: 42,
53+
TargetPort: 4242,
54+
},
55+
},
56+
Selector: map[string]string{
57+
"foo": "bar",
58+
},
59+
},
60+
}
61+
}
62+
63+
func DummyVirtualMachineA2() *vmopv1.VirtualMachine {
64+
return &vmopv1.VirtualMachine{
65+
ObjectMeta: metav1.ObjectMeta{
66+
GenerateName: "test-",
67+
Labels: map[string]string{},
68+
Annotations: map[string]string{},
69+
},
70+
Spec: vmopv1.VirtualMachineSpec{
71+
ImageName: DummyImageName,
72+
ClassName: DummyClassName,
73+
PowerState: vmopv1.VirtualMachinePowerStateOn,
74+
Volumes: []vmopv1.VirtualMachineVolume{
75+
{
76+
Name: DummyVolumeName,
77+
VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{
78+
PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{
79+
PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{
80+
ClaimName: DummyPVCName,
81+
},
82+
},
83+
},
84+
},
85+
},
86+
},
87+
}
88+
}
89+
90+
func DummyVirtualMachinePublishRequestA2(name, namespace, sourceName, itemName, clName string) *vmopv1.VirtualMachinePublishRequest {
91+
return &vmopv1.VirtualMachinePublishRequest{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: name,
94+
Namespace: namespace,
95+
Finalizers: []string{"virtualmachinepublishrequest.vmoperator.vmware.com"},
96+
},
97+
Spec: vmopv1.VirtualMachinePublishRequestSpec{
98+
Source: vmopv1.VirtualMachinePublishRequestSource{
99+
Name: sourceName,
100+
APIVersion: "vmoperator.vmware.com/v1alpha2",
101+
Kind: "VirtualMachine",
102+
},
103+
Target: vmopv1.VirtualMachinePublishRequestTarget{
104+
Item: vmopv1.VirtualMachinePublishRequestTargetItem{
105+
Name: itemName,
106+
},
107+
Location: vmopv1.VirtualMachinePublishRequestTargetLocation{
108+
Name: clName,
109+
APIVersion: "imageregistry.vmware.com/v1alpha1",
110+
Kind: "ContentLibrary",
111+
},
112+
},
113+
},
114+
}
115+
}
116+
117+
func DummyVirtualMachineClassA2() *vmopv1.VirtualMachineClass {
118+
return &vmopv1.VirtualMachineClass{
119+
ObjectMeta: metav1.ObjectMeta{
120+
GenerateName: "test-",
121+
},
122+
Spec: vmopv1.VirtualMachineClassSpec{
123+
Hardware: vmopv1.VirtualMachineClassHardware{
124+
Cpus: int64(2),
125+
Memory: resource.MustParse("4Gi"),
126+
},
127+
Policies: vmopv1.VirtualMachineClassPolicies{
128+
Resources: vmopv1.VirtualMachineClassResources{
129+
Requests: vmopv1.VirtualMachineResourceSpec{
130+
Cpu: resource.MustParse("1Gi"),
131+
Memory: resource.MustParse("2Gi"),
132+
},
133+
Limits: vmopv1.VirtualMachineResourceSpec{
134+
Cpu: resource.MustParse("2Gi"),
135+
Memory: resource.MustParse("4Gi"),
136+
},
137+
},
138+
},
139+
},
140+
}
141+
}
142+
143+
func DummyInstanceStorageVirtualMachineVolumesA2() []vmopv1.VirtualMachineVolume {
144+
return []vmopv1.VirtualMachineVolume{
145+
{
146+
Name: "instance-pvc-1",
147+
VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{
148+
PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{
149+
PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{
150+
ClaimName: "instance-pvc-1",
151+
},
152+
InstanceVolumeClaim: &vmopv1.InstanceVolumeClaimVolumeSource{
153+
StorageClass: DummyStorageClassName,
154+
Size: resource.MustParse("256Gi"),
155+
},
156+
},
157+
},
158+
},
159+
{
160+
Name: "instance-pvc-2",
161+
VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{
162+
PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{
163+
PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{
164+
ClaimName: "instance-pvc-2",
165+
},
166+
InstanceVolumeClaim: &vmopv1.InstanceVolumeClaimVolumeSource{
167+
StorageClass: DummyStorageClassName,
168+
Size: resource.MustParse("512Gi"),
169+
},
170+
},
171+
},
172+
},
173+
}
174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2019-2023 VMware, Inc. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package mutation
5+
6+
import (
7+
"encoding/json"
8+
"net/http"
9+
"reflect"
10+
"strings"
11+
"time"
12+
13+
"github.com/pkg/errors"
14+
admissionv1 "k8s.io/api/admission/v1"
15+
"k8s.io/apimachinery/pkg/runtime"
16+
"k8s.io/apimachinery/pkg/runtime/schema"
17+
"k8s.io/apimachinery/pkg/util/validation/field"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
ctrlmgr "sigs.k8s.io/controller-runtime/pkg/manager"
20+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
21+
22+
vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha2"
23+
"github.com/vmware-tanzu/vm-operator/pkg/builder"
24+
"github.com/vmware-tanzu/vm-operator/pkg/context"
25+
)
26+
27+
const (
28+
webHookName = "default"
29+
)
30+
31+
// -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
32+
// -kubebuilder:rbac:groups=vmoperator.vmware.com,resources=virtualmachine,verbs=get;list
33+
// -kubebuilder:rbac:groups=vmoperator.vmware.com,resources=virtualmachine/status,verbs=get
34+
35+
// AddToManager adds the webhook to the provided manager.
36+
func AddToManager(ctx *context.ControllerManagerContext, mgr ctrlmgr.Manager) error {
37+
hook, err := builder.NewMutatingWebhook(ctx, mgr, webHookName, NewMutator(mgr.GetClient()))
38+
if err != nil {
39+
return errors.Wrapf(err, "failed to create mutation webhook")
40+
}
41+
mgr.GetWebhookServer().Register(hook.Path, hook)
42+
43+
return nil
44+
}
45+
46+
// NewMutator returns the package's Mutator.
47+
func NewMutator(client client.Client) builder.Mutator {
48+
return mutator{
49+
client: client,
50+
converter: runtime.DefaultUnstructuredConverter,
51+
}
52+
}
53+
54+
type mutator struct {
55+
client client.Client
56+
converter runtime.UnstructuredConverter
57+
}
58+
59+
func (m mutator) Mutate(ctx *context.WebhookRequestContext) admission.Response {
60+
if ctx.Op == admissionv1.Delete {
61+
return admission.Allowed("")
62+
}
63+
64+
vm, err := m.vmFromUnstructured(ctx.Obj)
65+
if err != nil {
66+
return admission.Errored(http.StatusInternalServerError, err)
67+
}
68+
69+
var wasMutated bool
70+
original := vm
71+
modified := original.DeepCopy()
72+
73+
//nolint:gocritic
74+
switch ctx.Op {
75+
case admissionv1.Update:
76+
oldVM, err := m.vmFromUnstructured(ctx.OldObj)
77+
if err != nil {
78+
return admission.Errored(http.StatusInternalServerError, err)
79+
}
80+
81+
if ok, err := SetNextRestartTime(ctx, modified, oldVM); err != nil {
82+
return admission.Denied(err.Error())
83+
} else if ok {
84+
wasMutated = true
85+
}
86+
}
87+
88+
if !wasMutated {
89+
return admission.Allowed("")
90+
}
91+
92+
rawOriginal, err := json.Marshal(original)
93+
if err != nil {
94+
return admission.Errored(http.StatusInternalServerError, err)
95+
}
96+
rawModified, err := json.Marshal(modified)
97+
if err != nil {
98+
return admission.Errored(http.StatusInternalServerError, err)
99+
}
100+
return admission.PatchResponseFromRaw(rawOriginal, rawModified)
101+
}
102+
103+
func (m mutator) For() schema.GroupVersionKind {
104+
return vmopv1.SchemeGroupVersion.WithKind(reflect.TypeOf(vmopv1.VirtualMachine{}).Name())
105+
}
106+
107+
// vmFromUnstructured returns the VirtualMachine from the unstructured object.
108+
func (m mutator) vmFromUnstructured(obj runtime.Unstructured) (*vmopv1.VirtualMachine, error) {
109+
vm := &vmopv1.VirtualMachine{}
110+
if err := m.converter.FromUnstructured(obj.UnstructuredContent(), vm); err != nil {
111+
return nil, err
112+
}
113+
return vm, nil
114+
}
115+
116+
// SetNextRestartTime sets spec.nextRestartTime for a VM if the field's
117+
// current value is equal to "now" (case-insensitive).
118+
// Return true if set, otherwise false.
119+
func SetNextRestartTime(
120+
ctx *context.WebhookRequestContext,
121+
newVM, oldVM *vmopv1.VirtualMachine) (bool, error) {
122+
123+
if newVM.Spec.NextRestartTime == "" {
124+
newVM.Spec.NextRestartTime = oldVM.Spec.NextRestartTime
125+
return oldVM.Spec.NextRestartTime != "", nil
126+
}
127+
if strings.EqualFold("now", newVM.Spec.NextRestartTime) {
128+
if oldVM.Spec.PowerState != vmopv1.VirtualMachinePowerStateOn {
129+
return false, field.Invalid(
130+
field.NewPath("spec", "nextRestartTime"),
131+
newVM.Spec.NextRestartTime,
132+
"can only restart powered on vm")
133+
}
134+
newVM.Spec.NextRestartTime = time.Now().UTC().Format(time.RFC3339Nano)
135+
return true, nil
136+
}
137+
if newVM.Spec.NextRestartTime == oldVM.Spec.NextRestartTime {
138+
return false, nil
139+
}
140+
return false, field.Invalid(
141+
field.NewPath("spec", "nextRestartTime"),
142+
newVM.Spec.NextRestartTime,
143+
`may only be set to "now"`)
144+
}

0 commit comments

Comments
 (0)