1/* 2Copyright 2016 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package setdefault 18 19import ( 20 "context" 21 "fmt" 22 "io" 23 24 "k8s.io/klog/v2" 25 26 storagev1 "k8s.io/api/storage/v1" 27 "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/labels" 29 "k8s.io/apiserver/pkg/admission" 30 genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" 31 "k8s.io/client-go/informers" 32 storagev1listers "k8s.io/client-go/listers/storage/v1" 33 api "k8s.io/kubernetes/pkg/apis/core" 34 "k8s.io/kubernetes/pkg/apis/core/helper" 35 storageutil "k8s.io/kubernetes/pkg/apis/storage/util" 36) 37 38const ( 39 // PluginName is the name of this admission controller plugin 40 PluginName = "DefaultStorageClass" 41) 42 43// Register registers a plugin 44func Register(plugins *admission.Plugins) { 45 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { 46 plugin := newPlugin() 47 return plugin, nil 48 }) 49} 50 51// claimDefaulterPlugin holds state for and implements the admission plugin. 52type claimDefaulterPlugin struct { 53 *admission.Handler 54 55 lister storagev1listers.StorageClassLister 56} 57 58var _ admission.Interface = &claimDefaulterPlugin{} 59var _ admission.MutationInterface = &claimDefaulterPlugin{} 60var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&claimDefaulterPlugin{}) 61 62// newPlugin creates a new admission plugin. 63func newPlugin() *claimDefaulterPlugin { 64 return &claimDefaulterPlugin{ 65 Handler: admission.NewHandler(admission.Create), 66 } 67} 68 69func (a *claimDefaulterPlugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { 70 informer := f.Storage().V1().StorageClasses() 71 a.lister = informer.Lister() 72 a.SetReadyFunc(informer.Informer().HasSynced) 73} 74 75// ValidateInitialization ensures lister is set. 76func (a *claimDefaulterPlugin) ValidateInitialization() error { 77 if a.lister == nil { 78 return fmt.Errorf("missing lister") 79 } 80 return nil 81} 82 83// Admit sets the default value of a PersistentVolumeClaim's storage class, in case the user did 84// not provide a value. 85// 86// 1. Find available StorageClasses. 87// 2. Figure which is the default 88// 3. Write to the PVClaim 89func (a *claimDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { 90 if attr.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") { 91 return nil 92 } 93 94 if len(attr.GetSubresource()) != 0 { 95 return nil 96 } 97 98 pvc, ok := attr.GetObject().(*api.PersistentVolumeClaim) 99 // if we can't convert then we don't handle this object so just return 100 if !ok { 101 return nil 102 } 103 104 if helper.PersistentVolumeClaimHasClass(pvc) { 105 // The user asked for a class. 106 return nil 107 } 108 109 klog.V(4).Infof("no storage class for claim %s (generate: %s)", pvc.Name, pvc.GenerateName) 110 111 def, err := getDefaultClass(a.lister) 112 if err != nil { 113 return admission.NewForbidden(attr, err) 114 } 115 if def == nil { 116 // No default class selected, do nothing about the PVC. 117 return nil 118 } 119 120 klog.V(4).Infof("defaulting storage class for claim %s (generate: %s) to %s", pvc.Name, pvc.GenerateName, def.Name) 121 pvc.Spec.StorageClassName = &def.Name 122 return nil 123} 124 125// getDefaultClass returns the default StorageClass from the store, or nil. 126func getDefaultClass(lister storagev1listers.StorageClassLister) (*storagev1.StorageClass, error) { 127 list, err := lister.List(labels.Everything()) 128 if err != nil { 129 return nil, err 130 } 131 132 defaultClasses := []*storagev1.StorageClass{} 133 for _, class := range list { 134 if storageutil.IsDefaultAnnotation(class.ObjectMeta) { 135 defaultClasses = append(defaultClasses, class) 136 klog.V(4).Infof("getDefaultClass added: %s", class.Name) 137 } 138 } 139 140 if len(defaultClasses) == 0 { 141 return nil, nil 142 } 143 if len(defaultClasses) > 1 { 144 klog.V(4).Infof("getDefaultClass %d defaults found", len(defaultClasses)) 145 return nil, errors.NewInternalError(fmt.Errorf("%d default StorageClasses were found", len(defaultClasses))) 146 } 147 return defaultClasses[0], nil 148} 149