-
Notifications
You must be signed in to change notification settings - Fork 374
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d30ffe6
commit 2f27cb0
Showing
8 changed files
with
986 additions
and
19 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,244 @@ | ||
/* | ||
Copyright 2023 The Kubernetes Authors. | ||
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 ( | ||
"fmt" | ||
"reflect" | ||
|
||
volumegroupsnapshotv1alpha1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumegroupsnapshot/v1alpha1" | ||
groupSnapshotListers "github.com/kubernetes-csi/external-snapshotter/client/v6/listers/volumegroupsnapshot/v1alpha1" | ||
"github.com/kubernetes-csi/external-snapshotter/v6/pkg/utils" | ||
v1 "k8s.io/api/admission/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/labels" | ||
"k8s.io/klog/v2" | ||
) | ||
|
||
var ( | ||
// GroupSnapshotV1Alpha1GVR is GroupVersionResource for v1alpha1 VolumeSnapshots | ||
GroupSnapshotV1Alpha1GVR = metav1.GroupVersionResource{Group: volumegroupsnapshotv1alpha1.GroupName, Version: "v1alpha1", Resource: "volumegroupsnapshots"} | ||
// GroupSnapshotContentV1Apha1GVR is GroupVersionResource for v1alpha1 VolumeGroupSnapshotContents | ||
GroupSnapshotContentV1Apha1GVR = metav1.GroupVersionResource{Group: volumegroupsnapshotv1alpha1.GroupName, Version: "v1alpha1", Resource: "volumegroupsnapshotcontents"} | ||
// GroupSnapshotContentV1Apha1GVR is GroupVersionResource for v1alpha1 VolumeGroupSnapshotContents | ||
GroupSnapshotClassV1Apha1GVR = metav1.GroupVersionResource{Group: volumegroupsnapshotv1alpha1.GroupName, Version: "v1alpha1", Resource: "volumegroupsnapshotclasses"} | ||
) | ||
|
||
type GroupSnapshotAdmitter interface { | ||
Admit(v1.AdmissionReview) *v1.AdmissionResponse | ||
} | ||
|
||
type groupSnapshotAdmitter struct { | ||
lister groupSnapshotListers.VolumeGroupSnapshotClassLister | ||
} | ||
|
||
func NewGroupSnapshotAdmitter(lister groupSnapshotListers.VolumeGroupSnapshotClassLister) GroupSnapshotAdmitter { | ||
return &groupSnapshotAdmitter{ | ||
lister: lister, | ||
} | ||
} | ||
|
||
// Add a label {"added-label": "yes"} to the object | ||
func (a groupSnapshotAdmitter) Admit(ar v1.AdmissionReview) *v1.AdmissionResponse { | ||
klog.V(2).Info("admitting volumegroupsnapshots volumegroupsnapshotcontents " + | ||
"or volumegroupsnapshotclasses") | ||
|
||
reviewResponse := &v1.AdmissionResponse{ | ||
Allowed: true, | ||
Result: &metav1.Status{}, | ||
} | ||
|
||
// Admit requests other than Update and Create | ||
if !(ar.Request.Operation == v1.Update || ar.Request.Operation == v1.Create) { | ||
return reviewResponse | ||
} | ||
isUpdate := ar.Request.Operation == v1.Update | ||
|
||
raw := ar.Request.Object.Raw | ||
oldRaw := ar.Request.OldObject.Raw | ||
|
||
deserializer := codecs.UniversalDeserializer() | ||
switch ar.Request.Resource { | ||
case GroupSnapshotV1Alpha1GVR: | ||
groupSnapshot := &volumegroupsnapshotv1alpha1.VolumeGroupSnapshot{} | ||
if _, _, err := deserializer.Decode(raw, nil, groupSnapshot); err != nil { | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
oldGroupSnapshot := &volumegroupsnapshotv1alpha1.VolumeGroupSnapshot{} | ||
if _, _, err := deserializer.Decode(oldRaw, nil, oldGroupSnapshot); err != nil { | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
return decideGroupSnapshotV1Alpha1(groupSnapshot, oldGroupSnapshot, isUpdate) | ||
case GroupSnapshotContentV1Apha1GVR: | ||
groupSnapContent := &volumegroupsnapshotv1alpha1.VolumeGroupSnapshotContent{} | ||
if _, _, err := deserializer.Decode(raw, nil, groupSnapContent); err != nil { | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
oldGroupSnapContent := &volumegroupsnapshotv1alpha1.VolumeGroupSnapshotContent{} | ||
if _, _, err := deserializer.Decode(oldRaw, nil, oldGroupSnapContent); err != nil { | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
return decideGroupSnapshotContentV1Alpha1(groupSnapContent, oldGroupSnapContent, isUpdate) | ||
case GroupSnapshotClassV1Apha1GVR: | ||
groupSnapClass := &volumegroupsnapshotv1alpha1.VolumeGroupSnapshotClass{} | ||
if _, _, err := deserializer.Decode(raw, nil, groupSnapClass); err != nil { | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
oldGroupSnapClass := &volumegroupsnapshotv1alpha1.VolumeGroupSnapshotClass{} | ||
if _, _, err := deserializer.Decode(oldRaw, nil, oldGroupSnapClass); err != nil { | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
return decideGroupSnapshotClassV1Alpha1(groupSnapClass, oldGroupSnapClass, a.lister) | ||
default: | ||
err := fmt.Errorf("expect resource to be %s, %s, or %s, but found %v", | ||
GroupSnapshotV1Alpha1GVR, GroupSnapshotContentV1Apha1GVR, | ||
GroupSnapshotClassV1Apha1GVR, ar.Request.Resource) | ||
klog.Error(err) | ||
return toV1AdmissionResponse(err) | ||
} | ||
} | ||
|
||
func decideGroupSnapshotV1Alpha1(groupSnapshot, oldGroupSnapshot *volumegroupsnapshotv1alpha1.VolumeGroupSnapshot, isUpdate bool) *v1.AdmissionResponse { | ||
reviewResponse := &v1.AdmissionResponse{ | ||
Allowed: true, | ||
Result: &metav1.Status{}, | ||
} | ||
|
||
if isUpdate { | ||
// if it is an UPDATE and oldSnapshot is valid, check immutable fields | ||
if err := checkGroupSnapshotImmutableFieldsV1Alpha1(groupSnapshot, oldGroupSnapshot); err != nil { | ||
reviewResponse.Allowed = false | ||
reviewResponse.Result.Message = err.Error() | ||
return reviewResponse | ||
} | ||
} | ||
// Enforce strict validation for CREATE requests. Immutable checks don't apply for CREATE requests. | ||
// Enforce strict validation for UPDATE requests where old is valid and passes immutability check. | ||
if err := ValidateV1Alpha1GroupSnapshot(groupSnapshot); err != nil { | ||
reviewResponse.Allowed = false | ||
reviewResponse.Result.Message = err.Error() | ||
} | ||
return reviewResponse | ||
} | ||
|
||
func decideGroupSnapshotContentV1Alpha1(groupSnapcontent, oldSnapcontent *volumegroupsnapshotv1alpha1.VolumeGroupSnapshotContent, isUpdate bool) *v1.AdmissionResponse { | ||
reviewResponse := &v1.AdmissionResponse{ | ||
Allowed: true, | ||
Result: &metav1.Status{}, | ||
} | ||
|
||
if isUpdate { | ||
// if it is an UPDATE and oldSnapcontent is valid, check immutable fields | ||
if err := checkGroupSnapshotContentImmutableFieldsV1Alpha1(groupSnapcontent, oldSnapcontent); err != nil { | ||
reviewResponse.Allowed = false | ||
reviewResponse.Result.Message = err.Error() | ||
return reviewResponse | ||
} | ||
} | ||
// Enforce strict validation for all CREATE requests. Immutable checks don't apply for CREATE requests. | ||
// Enforce strict validation for UPDATE requests where old is valid and passes immutability check. | ||
if err := ValidateV1Alpha1GroupSnapshotContent(groupSnapcontent); err != nil { | ||
reviewResponse.Allowed = false | ||
reviewResponse.Result.Message = err.Error() | ||
} | ||
return reviewResponse | ||
} | ||
|
||
func decideGroupSnapshotClassV1Alpha1(snapClass, oldSnapClass *volumegroupsnapshotv1alpha1.VolumeGroupSnapshotClass, lister groupSnapshotListers.VolumeGroupSnapshotClassLister) *v1.AdmissionResponse { | ||
reviewResponse := &v1.AdmissionResponse{ | ||
Allowed: true, | ||
Result: &metav1.Status{}, | ||
} | ||
|
||
// Only Validate when a new group snapshot class is being set as a default. | ||
if snapClass.Annotations[utils.IsDefaultSnapshotClassAnnotation] != "true" { | ||
return reviewResponse | ||
} | ||
|
||
// If Old group snapshot class has this, then we can assume that it was validated if driver is the same. | ||
if oldSnapClass.Annotations[utils.IsDefaultSnapshotClassAnnotation] == "true" && oldSnapClass.Driver == snapClass.Driver { | ||
return reviewResponse | ||
} | ||
|
||
ret, err := lister.List(labels.Everything()) | ||
if err != nil { | ||
reviewResponse.Allowed = false | ||
reviewResponse.Result.Message = err.Error() | ||
return reviewResponse | ||
} | ||
|
||
for _, snapshotClass := range ret { | ||
if snapshotClass.Annotations[utils.IsDefaultSnapshotClassAnnotation] != "true" { | ||
continue | ||
} | ||
if snapshotClass.Driver == snapClass.Driver { | ||
reviewResponse.Allowed = false | ||
reviewResponse.Result.Message = fmt.Sprintf("default group snapshot class: %v already exists for driver: %v", snapshotClass.Name, snapClass.Driver) | ||
return reviewResponse | ||
} | ||
} | ||
|
||
return reviewResponse | ||
} | ||
|
||
func checkGroupSnapshotImmutableFieldsV1Alpha1(groupSnapshot, oldGroupSnapshot *volumegroupsnapshotv1alpha1.VolumeGroupSnapshot) error { | ||
if groupSnapshot == nil { | ||
return fmt.Errorf("VolumeGroupSnapshot is nil") | ||
} | ||
if oldGroupSnapshot == nil { | ||
return fmt.Errorf("old VolumeGroupSnapshot is nil") | ||
} | ||
|
||
source := groupSnapshot.Spec.Source | ||
oldSource := oldGroupSnapshot.Spec.Source | ||
|
||
if !reflect.DeepEqual(source.Selector, oldSource.Selector) { | ||
return fmt.Errorf("Spec.Source.Selector is immutable but was changed from %s to %s", oldSource.Selector, source.Selector) | ||
} | ||
if !reflect.DeepEqual(source.VolumeGroupSnapshotContentName, oldSource.VolumeGroupSnapshotContentName) { | ||
return fmt.Errorf("Spec.Source.VolumeGroupSnapshotContentName is immutable but was changed from %s to %s", strPtrDereference(oldSource.VolumeGroupSnapshotContentName), strPtrDereference(source.VolumeGroupSnapshotContentName)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func checkGroupSnapshotContentImmutableFieldsV1Alpha1(groupSnapcontent, oldGroupSnapcontent *volumegroupsnapshotv1alpha1.VolumeGroupSnapshotContent) error { | ||
if groupSnapcontent == nil { | ||
return fmt.Errorf("VolumeGroupSnapshotContent is nil") | ||
} | ||
if oldGroupSnapcontent == nil { | ||
return fmt.Errorf("old VolumeGroupSnapshotContent is nil") | ||
} | ||
|
||
source := groupSnapcontent.Spec.Source | ||
oldSource := oldGroupSnapcontent.Spec.Source | ||
|
||
if !reflect.DeepEqual(source.VolumeGroupSnapshotHandle, oldSource.VolumeGroupSnapshotHandle) { | ||
return fmt.Errorf("Spec.Source.VolumeGroupSnapshotHandle is immutable but was changed from %s to %s", strPtrDereference(oldSource.VolumeGroupSnapshotHandle), strPtrDereference(source.VolumeGroupSnapshotHandle)) | ||
} | ||
if !reflect.DeepEqual(source.PersistentVolumeNames, oldSource.PersistentVolumeNames) { | ||
return fmt.Errorf("Spec.Source.PersistentVolumeNames is immutable but was changed from %v to %v", oldSource.PersistentVolumeNames, source.PersistentVolumeNames) | ||
} | ||
|
||
return nil | ||
} |
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
Oops, something went wrong.