1/* 2Copyright 2017 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 customresourcedefinition 18 19import ( 20 "context" 21 "fmt" 22 23 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apiserver/pkg/registry/generic" 28 genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" 29 "k8s.io/apiserver/pkg/registry/rest" 30 "k8s.io/apiserver/pkg/storage" 31 storageerr "k8s.io/apiserver/pkg/storage/errors" 32 "k8s.io/apiserver/pkg/util/dryrun" 33) 34 35// rest implements a RESTStorage for API services against etcd 36type REST struct { 37 *genericregistry.Store 38} 39 40// NewREST returns a RESTStorage object that will work against API services. 41func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST { 42 strategy := NewStrategy(scheme) 43 44 store := &genericregistry.Store{ 45 NewFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinition{} }, 46 NewListFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinitionList{} }, 47 PredicateFunc: MatchCustomResourceDefinition, 48 DefaultQualifiedResource: apiextensions.Resource("customresourcedefinitions"), 49 50 CreateStrategy: strategy, 51 UpdateStrategy: strategy, 52 DeleteStrategy: strategy, 53 } 54 options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs} 55 if err := store.CompleteWithOptions(options); err != nil { 56 panic(err) // TODO: Propagate error up 57 } 58 return &REST{store} 59} 60 61// Implement ShortNamesProvider 62var _ rest.ShortNamesProvider = &REST{} 63 64// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource. 65func (r *REST) ShortNames() []string { 66 return []string{"crd", "crds"} 67} 68 69// Delete adds the CRD finalizer to the list 70func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { 71 obj, err := r.Get(ctx, name, &metav1.GetOptions{}) 72 if err != nil { 73 return nil, false, err 74 } 75 76 crd := obj.(*apiextensions.CustomResourceDefinition) 77 78 // Ensure we have a UID precondition 79 if options == nil { 80 options = metav1.NewDeleteOptions(0) 81 } 82 if options.Preconditions == nil { 83 options.Preconditions = &metav1.Preconditions{} 84 } 85 if options.Preconditions.UID == nil { 86 options.Preconditions.UID = &crd.UID 87 } else if *options.Preconditions.UID != crd.UID { 88 err = apierrors.NewConflict( 89 apiextensions.Resource("customresourcedefinitions"), 90 name, 91 fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *options.Preconditions.UID, crd.UID), 92 ) 93 return nil, false, err 94 } 95 if options.Preconditions.ResourceVersion != nil && *options.Preconditions.ResourceVersion != crd.ResourceVersion { 96 err = apierrors.NewConflict( 97 apiextensions.Resource("customresourcedefinitions"), 98 name, 99 fmt.Errorf("Precondition failed: ResourceVersion in precondition: %v, ResourceVersion in object meta: %v", *options.Preconditions.ResourceVersion, crd.ResourceVersion), 100 ) 101 return nil, false, err 102 } 103 104 // upon first request to delete, add our finalizer and then delegate 105 if crd.DeletionTimestamp.IsZero() { 106 key, err := r.Store.KeyFunc(ctx, name) 107 if err != nil { 108 return nil, false, err 109 } 110 111 preconditions := storage.Preconditions{UID: options.Preconditions.UID, ResourceVersion: options.Preconditions.ResourceVersion} 112 113 out := r.Store.NewFunc() 114 err = r.Store.Storage.GuaranteedUpdate( 115 ctx, key, out, false, &preconditions, 116 storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) { 117 existingCRD, ok := existing.(*apiextensions.CustomResourceDefinition) 118 if !ok { 119 // wrong type 120 return nil, fmt.Errorf("expected *apiextensions.CustomResourceDefinition, got %v", existing) 121 } 122 if err := deleteValidation(ctx, existingCRD); err != nil { 123 return nil, err 124 } 125 126 // Set the deletion timestamp if needed 127 if existingCRD.DeletionTimestamp.IsZero() { 128 now := metav1.Now() 129 existingCRD.DeletionTimestamp = &now 130 } 131 132 if !apiextensions.CRDHasFinalizer(existingCRD, apiextensions.CustomResourceCleanupFinalizer) { 133 existingCRD.Finalizers = append(existingCRD.Finalizers, apiextensions.CustomResourceCleanupFinalizer) 134 } 135 // update the status condition too 136 apiextensions.SetCRDCondition(existingCRD, apiextensions.CustomResourceDefinitionCondition{ 137 Type: apiextensions.Terminating, 138 Status: apiextensions.ConditionTrue, 139 Reason: "InstanceDeletionPending", 140 Message: "CustomResourceDefinition marked for deletion; CustomResource deletion will begin soon", 141 }) 142 return existingCRD, nil 143 }), 144 dryrun.IsDryRun(options.DryRun), 145 ) 146 147 if err != nil { 148 err = storageerr.InterpretGetError(err, apiextensions.Resource("customresourcedefinitions"), name) 149 err = storageerr.InterpretUpdateError(err, apiextensions.Resource("customresourcedefinitions"), name) 150 if _, ok := err.(*apierrors.StatusError); !ok { 151 err = apierrors.NewInternalError(err) 152 } 153 return nil, false, err 154 } 155 156 return out, false, nil 157 } 158 159 return r.Store.Delete(ctx, name, deleteValidation, options) 160} 161 162// NewStatusREST makes a RESTStorage for status that has more limited options. 163// It is based on the original REST so that we can share the same underlying store 164func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST { 165 statusStore := *rest.Store 166 statusStore.CreateStrategy = nil 167 statusStore.DeleteStrategy = nil 168 statusStore.UpdateStrategy = NewStatusStrategy(scheme) 169 return &StatusREST{store: &statusStore} 170} 171 172type StatusREST struct { 173 store *genericregistry.Store 174} 175 176var _ = rest.Patcher(&StatusREST{}) 177 178func (r *StatusREST) New() runtime.Object { 179 return &apiextensions.CustomResourceDefinition{} 180} 181 182// Get retrieves the object from the storage. It is required to support Patch. 183func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { 184 return r.store.Get(ctx, name, options) 185} 186 187// Update alters the status subset of an object. 188func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { 189 // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because 190 // subresources should never allow create on update. 191 return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) 192} 193