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