1
16
17 package customresource
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23
24 autoscalingv1 "k8s.io/api/autoscaling/v1"
25 apierrors "k8s.io/apimachinery/pkg/api/errors"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/util/managedfields"
31 "k8s.io/apiserver/pkg/registry/generic"
32 genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
33 "k8s.io/apiserver/pkg/registry/rest"
34 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
35 )
36
37
38 type CustomResourceStorage struct {
39 CustomResource *REST
40 Status *StatusREST
41 Scale *ScaleREST
42 }
43
44 func NewStorage(resource schema.GroupResource, singularResource schema.GroupResource, kind, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor, replicasPathMapping managedfields.ResourcePathMappings) CustomResourceStorage {
45 var storage CustomResourceStorage
46 store := &genericregistry.Store{
47 NewFunc: func() runtime.Object {
48
49 ret := &unstructured.Unstructured{}
50 ret.SetGroupVersionKind(kind)
51 return ret
52 },
53 NewListFunc: func() runtime.Object {
54
55 ret := &unstructured.UnstructuredList{}
56 ret.SetGroupVersionKind(listKind)
57 return ret
58 },
59 PredicateFunc: strategy.MatchCustomResourceDefinitionStorage,
60 DefaultQualifiedResource: resource,
61 SingularQualifiedResource: singularResource,
62
63 CreateStrategy: strategy,
64 UpdateStrategy: strategy,
65 DeleteStrategy: strategy,
66 ResetFieldsStrategy: strategy,
67
68 TableConvertor: tableConvertor,
69 }
70 options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs}
71 if err := store.CompleteWithOptions(options); err != nil {
72 panic(err)
73 }
74 storage.CustomResource = &REST{store, categories}
75
76 if strategy.status != nil {
77 statusStore := *store
78 statusStrategy := NewStatusStrategy(strategy)
79 statusStore.UpdateStrategy = statusStrategy
80 statusStore.ResetFieldsStrategy = statusStrategy
81 storage.Status = &StatusREST{store: &statusStore}
82 }
83
84 if scale := strategy.scale; scale != nil {
85 var labelSelectorPath string
86 if scale.LabelSelectorPath != nil {
87 labelSelectorPath = *scale.LabelSelectorPath
88 }
89
90 storage.Scale = &ScaleREST{
91 store: store,
92 specReplicasPath: scale.SpecReplicasPath,
93 statusReplicasPath: scale.StatusReplicasPath,
94 labelSelectorPath: labelSelectorPath,
95 parentGV: kind.GroupVersion(),
96 replicasPathMapping: replicasPathMapping,
97 }
98 }
99
100 return storage
101 }
102
103
104 type REST struct {
105 *genericregistry.Store
106 categories []string
107 }
108
109
110 var _ rest.CategoriesProvider = &REST{}
111
112
113 func (r *REST) Categories() []string {
114 return r.categories
115 }
116
117
118 type StatusREST struct {
119 store *genericregistry.Store
120 }
121
122 var _ = rest.Patcher(&StatusREST{})
123
124 func (r *StatusREST) New() runtime.Object {
125 return r.store.New()
126 }
127
128
129 func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
130 o, err := r.store.Get(ctx, name, options)
131 if err != nil {
132 return nil, err
133 }
134 return o, nil
135 }
136
137
138 func (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) {
139
140
141 return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
142 }
143
144
145 func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
146 return r.store.GetResetFields()
147 }
148
149 type ScaleREST struct {
150 store *genericregistry.Store
151 specReplicasPath string
152 statusReplicasPath string
153 labelSelectorPath string
154 parentGV schema.GroupVersion
155 replicasPathMapping managedfields.ResourcePathMappings
156 }
157
158
159 var _ = rest.Patcher(&ScaleREST{})
160 var _ = rest.GroupVersionKindProvider(&ScaleREST{})
161
162 func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
163 return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
164 }
165
166
167 func (r *ScaleREST) New() runtime.Object {
168 return &autoscalingv1.Scale{}
169 }
170
171 func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
172 obj, err := r.store.Get(ctx, name, options)
173 if err != nil {
174 return nil, err
175 }
176 cr := obj.(*unstructured.Unstructured)
177
178 scaleObject, replicasFound, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
179 if err != nil {
180 return nil, err
181 }
182 if !replicasFound {
183 return nil, apierrors.NewInternalError(fmt.Errorf("the spec replicas field %q does not exist", r.specReplicasPath))
184 }
185 return scaleObject, err
186 }
187
188 func (r *ScaleREST) 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 scaleObjInfo := &scaleUpdatedObjectInfo{
190 reqObjInfo: objInfo,
191 specReplicasPath: r.specReplicasPath,
192 labelSelectorPath: r.labelSelectorPath,
193 statusReplicasPath: r.statusReplicasPath,
194 parentGV: r.parentGV,
195 replicasPathMapping: r.replicasPathMapping,
196 }
197
198 obj, _, err := r.store.Update(
199 ctx,
200 name,
201 scaleObjInfo,
202 toScaleCreateValidation(createValidation, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath),
203 toScaleUpdateValidation(updateValidation, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath),
204 false,
205 options,
206 )
207 if err != nil {
208 return nil, false, err
209 }
210 cr := obj.(*unstructured.Unstructured)
211
212 newScale, _, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
213 if err != nil {
214 return nil, false, apierrors.NewBadRequest(err.Error())
215 }
216
217 return newScale, false, err
218 }
219
220 func toScaleCreateValidation(f rest.ValidateObjectFunc, specReplicasPath, statusReplicasPath, labelSelectorPath string) rest.ValidateObjectFunc {
221 return func(ctx context.Context, obj runtime.Object) error {
222 scale, _, err := scaleFromCustomResource(obj.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
223 if err != nil {
224 return err
225 }
226 return f(ctx, scale)
227 }
228 }
229
230 func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc, specReplicasPath, statusReplicasPath, labelSelectorPath string) rest.ValidateObjectUpdateFunc {
231 return func(ctx context.Context, obj, old runtime.Object) error {
232 newScale, _, err := scaleFromCustomResource(obj.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
233 if err != nil {
234 return err
235 }
236 oldScale, _, err := scaleFromCustomResource(old.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
237 if err != nil {
238 return err
239 }
240 return f(ctx, newScale, oldScale)
241 }
242 }
243
244
245 func splitReplicasPath(replicasPath string) []string {
246 return strings.Split(strings.TrimPrefix(replicasPath, "."), ".")
247 }
248
249
250
251 func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, statusReplicasPath, labelSelectorPath string) (*autoscalingv1.Scale, bool, error) {
252 specReplicas, foundSpecReplicas, err := unstructured.NestedInt64(cr.UnstructuredContent(), splitReplicasPath(specReplicasPath)...)
253 if err != nil {
254 return nil, false, err
255 } else if !foundSpecReplicas {
256 specReplicas = 0
257 }
258
259 statusReplicas, found, err := unstructured.NestedInt64(cr.UnstructuredContent(), splitReplicasPath(statusReplicasPath)...)
260 if err != nil {
261 return nil, false, err
262 } else if !found {
263 statusReplicas = 0
264 }
265
266 var labelSelector string
267 if len(labelSelectorPath) > 0 {
268 labelSelector, _, err = unstructured.NestedString(cr.UnstructuredContent(), splitReplicasPath(labelSelectorPath)...)
269 if err != nil {
270 return nil, false, err
271 }
272 }
273
274 scale := &autoscalingv1.Scale{
275
276 TypeMeta: metav1.TypeMeta{
277 APIVersion: "autoscaling/v1",
278 Kind: "Scale",
279 },
280 ObjectMeta: metav1.ObjectMeta{
281 Name: cr.GetName(),
282 Namespace: cr.GetNamespace(),
283 UID: cr.GetUID(),
284 ResourceVersion: cr.GetResourceVersion(),
285 CreationTimestamp: cr.GetCreationTimestamp(),
286 },
287 Spec: autoscalingv1.ScaleSpec{
288 Replicas: int32(specReplicas),
289 },
290 Status: autoscalingv1.ScaleStatus{
291 Replicas: int32(statusReplicas),
292 Selector: labelSelector,
293 },
294 }
295
296 return scale, foundSpecReplicas, nil
297 }
298
299 type scaleUpdatedObjectInfo struct {
300 reqObjInfo rest.UpdatedObjectInfo
301 specReplicasPath string
302 statusReplicasPath string
303 labelSelectorPath string
304 parentGV schema.GroupVersion
305 replicasPathMapping managedfields.ResourcePathMappings
306 }
307
308 func (i *scaleUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
309 return i.reqObjInfo.Preconditions()
310 }
311
312 func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
313 cr := oldObj.DeepCopyObject().(*unstructured.Unstructured)
314 const invalidSpecReplicas = -2147483648
315
316 managedFieldsHandler := managedfields.NewScaleHandler(
317 cr.GetManagedFields(),
318 i.parentGV,
319 i.replicasPathMapping,
320 )
321
322 oldScale, replicasFound, err := scaleFromCustomResource(cr, i.specReplicasPath, i.statusReplicasPath, i.labelSelectorPath)
323 if err != nil {
324 return nil, err
325 }
326 if !replicasFound {
327 oldScale.Spec.Replicas = invalidSpecReplicas
328 }
329
330 scaleManagedFields, err := managedFieldsHandler.ToSubresource()
331 if err != nil {
332 return nil, err
333 }
334 oldScale.ManagedFields = scaleManagedFields
335
336 obj, err := i.reqObjInfo.UpdatedObject(ctx, oldScale)
337 if err != nil {
338 return nil, err
339 }
340 if obj == nil {
341 return nil, apierrors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
342 }
343
344 scale, ok := obj.(*autoscalingv1.Scale)
345 if !ok {
346 return nil, apierrors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
347 }
348
349 if scale.Spec.Replicas == invalidSpecReplicas {
350 return nil, apierrors.NewBadRequest(fmt.Sprintf("the spec replicas field %q cannot be empty", i.specReplicasPath))
351 }
352
353 if err := unstructured.SetNestedField(cr.Object, int64(scale.Spec.Replicas), splitReplicasPath(i.specReplicasPath)...); err != nil {
354 return nil, err
355 }
356 if len(scale.ResourceVersion) != 0 {
357
358
359 cr.SetResourceVersion(scale.ResourceVersion)
360 }
361
362 updatedEntries, err := managedFieldsHandler.ToParent(scale.ManagedFields)
363 if err != nil {
364 return nil, err
365 }
366 cr.SetManagedFields(updatedEntries)
367
368 return cr, nil
369 }
370
View as plain text