1
16
17 package storage
18
19 import (
20 "context"
21 "fmt"
22 "net/http"
23
24 "k8s.io/apimachinery/pkg/api/errors"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 "k8s.io/apimachinery/pkg/util/managedfields"
29 genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
30 "k8s.io/apiserver/pkg/registry/generic"
31 genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
32 "k8s.io/apiserver/pkg/registry/rest"
33 "k8s.io/apiserver/pkg/storage"
34 storeerr "k8s.io/apiserver/pkg/storage/errors"
35 "k8s.io/apiserver/pkg/util/dryrun"
36 "k8s.io/klog/v2"
37 "k8s.io/kubernetes/pkg/apis/apps"
38 appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
39 appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
40 appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
41 "k8s.io/kubernetes/pkg/apis/autoscaling"
42 autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
43 autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation"
44 extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
45 "k8s.io/kubernetes/pkg/printers"
46 printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
47 printerstorage "k8s.io/kubernetes/pkg/printers/storage"
48 "k8s.io/kubernetes/pkg/registry/apps/deployment"
49 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
50 )
51
52
53 type DeploymentStorage struct {
54 Deployment *REST
55 Status *StatusREST
56 Scale *ScaleREST
57 Rollback *RollbackREST
58 }
59
60
61 func ReplicasPathMappings() managedfields.ResourcePathMappings {
62 return replicasPathInDeployment
63 }
64
65
66 var replicasPathInDeployment = managedfields.ResourcePathMappings{
67 schema.GroupVersion{Group: "apps", Version: "v1beta1"}.String(): fieldpath.MakePathOrDie("spec", "replicas"),
68 schema.GroupVersion{Group: "apps", Version: "v1beta2"}.String(): fieldpath.MakePathOrDie("spec", "replicas"),
69 schema.GroupVersion{Group: "apps", Version: "v1"}.String(): fieldpath.MakePathOrDie("spec", "replicas"),
70 }
71
72
73 func NewStorage(optsGetter generic.RESTOptionsGetter) (DeploymentStorage, error) {
74 deploymentRest, deploymentStatusRest, deploymentRollbackRest, err := NewREST(optsGetter)
75 if err != nil {
76 return DeploymentStorage{}, err
77 }
78
79 return DeploymentStorage{
80 Deployment: deploymentRest,
81 Status: deploymentStatusRest,
82 Scale: &ScaleREST{store: deploymentRest.Store},
83 Rollback: deploymentRollbackRest,
84 }, nil
85 }
86
87
88 type REST struct {
89 *genericregistry.Store
90 }
91
92
93 func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *RollbackREST, error) {
94 store := &genericregistry.Store{
95 NewFunc: func() runtime.Object { return &apps.Deployment{} },
96 NewListFunc: func() runtime.Object { return &apps.DeploymentList{} },
97 DefaultQualifiedResource: apps.Resource("deployments"),
98 SingularQualifiedResource: apps.Resource("deployment"),
99
100 CreateStrategy: deployment.Strategy,
101 UpdateStrategy: deployment.Strategy,
102 DeleteStrategy: deployment.Strategy,
103 ResetFieldsStrategy: deployment.Strategy,
104
105 TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
106 }
107 options := &generic.StoreOptions{RESTOptions: optsGetter}
108 if err := store.CompleteWithOptions(options); err != nil {
109 return nil, nil, nil, err
110 }
111
112 statusStore := *store
113 statusStore.UpdateStrategy = deployment.StatusStrategy
114 statusStore.ResetFieldsStrategy = deployment.StatusStrategy
115 return &REST{store}, &StatusREST{store: &statusStore}, &RollbackREST{store: store}, nil
116 }
117
118
119 var _ rest.ShortNamesProvider = &REST{}
120
121
122 func (r *REST) ShortNames() []string {
123 return []string{"deploy"}
124 }
125
126
127 var _ rest.CategoriesProvider = &REST{}
128
129
130 func (r *REST) Categories() []string {
131 return []string{"all"}
132 }
133
134
135 type StatusREST struct {
136 store *genericregistry.Store
137 }
138
139
140 func (r *StatusREST) New() runtime.Object {
141 return &apps.Deployment{}
142 }
143
144
145 func (r *StatusREST) Destroy() {
146
147
148 }
149
150
151 func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
152 return r.store.Get(ctx, name, options)
153 }
154
155
156 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) {
157
158
159 return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
160 }
161
162
163 func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
164 return r.store.GetResetFields()
165 }
166
167 func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
168 return r.store.ConvertToTable(ctx, object, tableOptions)
169 }
170
171
172 type RollbackREST struct {
173 store *genericregistry.Store
174 }
175
176
177
178 func (r *RollbackREST) ProducesMIMETypes(verb string) []string {
179 return nil
180 }
181
182
183
184 func (r *RollbackREST) ProducesObject(verb string) interface{} {
185 return metav1.Status{}
186 }
187
188 var _ = rest.StorageMetadata(&RollbackREST{})
189
190
191 func (r *RollbackREST) New() runtime.Object {
192 return &apps.DeploymentRollback{}
193 }
194
195
196 func (r *RollbackREST) Destroy() {
197
198
199 }
200
201 var _ = rest.NamedCreater(&RollbackREST{})
202
203
204 func (r *RollbackREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
205 rollback, ok := obj.(*apps.DeploymentRollback)
206 if !ok {
207 return nil, errors.NewBadRequest(fmt.Sprintf("not a DeploymentRollback: %#v", obj))
208 }
209
210 if errs := appsvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 {
211 return nil, errors.NewInvalid(apps.Kind("DeploymentRollback"), rollback.Name, errs)
212 }
213 if name != rollback.Name {
214 return nil, errors.NewBadRequest("name in URL does not match name in DeploymentRollback object")
215 }
216
217 if createValidation != nil {
218 if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
219 return nil, err
220 }
221 }
222
223
224 err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations, dryrun.IsDryRun(options.DryRun))
225 if err != nil {
226 return nil, err
227 }
228 return &metav1.Status{
229 Status: metav1.StatusSuccess,
230 Message: fmt.Sprintf("rollback request for deployment %q succeeded", rollback.Name),
231 Code: http.StatusOK,
232 }, nil
233 }
234
235 func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID string, config *apps.RollbackConfig, annotations map[string]string, dryRun bool) error {
236 if _, err := r.setDeploymentRollback(ctx, deploymentID, config, annotations, dryRun); err != nil {
237 err = storeerr.InterpretGetError(err, apps.Resource("deployments"), deploymentID)
238 err = storeerr.InterpretUpdateError(err, apps.Resource("deployments"), deploymentID)
239 if _, ok := err.(*errors.StatusError); !ok {
240 err = errors.NewInternalError(err)
241 }
242 return err
243 }
244 return nil
245 }
246
247 func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID string, config *apps.RollbackConfig, annotations map[string]string, dryRun bool) (*apps.Deployment, error) {
248 dKey, err := r.store.KeyFunc(ctx, deploymentID)
249 if err != nil {
250 return nil, err
251 }
252 var finalDeployment *apps.Deployment
253 err = r.store.Storage.GuaranteedUpdate(ctx, dKey, &apps.Deployment{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) {
254 d, ok := obj.(*apps.Deployment)
255 if !ok {
256 return nil, fmt.Errorf("unexpected object: %#v", obj)
257 }
258 if d.Annotations == nil {
259 d.Annotations = make(map[string]string)
260 }
261 for k, v := range annotations {
262 d.Annotations[k] = v
263 }
264 d.Spec.RollbackTo = config
265 finalDeployment = d
266 return d, nil
267 }), dryRun, nil)
268 return finalDeployment, err
269 }
270
271
272 type ScaleREST struct {
273 store *genericregistry.Store
274 }
275
276
277 var _ = rest.Patcher(&ScaleREST{})
278 var _ = rest.GroupVersionKindProvider(&ScaleREST{})
279
280
281 func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
282 switch containingGV {
283 case extensionsv1beta1.SchemeGroupVersion:
284 return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale")
285 case appsv1beta1.SchemeGroupVersion:
286 return appsv1beta1.SchemeGroupVersion.WithKind("Scale")
287 case appsv1beta2.SchemeGroupVersion:
288 return appsv1beta2.SchemeGroupVersion.WithKind("Scale")
289 default:
290 return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
291 }
292 }
293
294
295 func (r *ScaleREST) New() runtime.Object {
296 return &autoscaling.Scale{}
297 }
298
299
300 func (r *ScaleREST) Destroy() {
301
302
303 }
304
305
306 func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
307 obj, err := r.store.Get(ctx, name, options)
308 if err != nil {
309 return nil, errors.NewNotFound(apps.Resource("deployments/scale"), name)
310 }
311 deployment := obj.(*apps.Deployment)
312 scale, err := scaleFromDeployment(deployment)
313 if err != nil {
314 return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
315 }
316 return scale, nil
317 }
318
319
320 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) {
321 obj, _, err := r.store.Update(
322 ctx,
323 name,
324 &scaleUpdatedObjectInfo{name, objInfo},
325 toScaleCreateValidation(createValidation),
326 toScaleUpdateValidation(updateValidation),
327 false,
328 options,
329 )
330 if err != nil {
331 return nil, false, err
332 }
333 deployment := obj.(*apps.Deployment)
334 newScale, err := scaleFromDeployment(deployment)
335 if err != nil {
336 return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
337 }
338 return newScale, false, nil
339 }
340
341 func (r *ScaleREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
342 return r.store.ConvertToTable(ctx, object, tableOptions)
343 }
344
345 func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
346 return func(ctx context.Context, obj runtime.Object) error {
347 scale, err := scaleFromDeployment(obj.(*apps.Deployment))
348 if err != nil {
349 return err
350 }
351 return f(ctx, scale)
352 }
353 }
354
355 func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
356 return func(ctx context.Context, obj, old runtime.Object) error {
357 newScale, err := scaleFromDeployment(obj.(*apps.Deployment))
358 if err != nil {
359 return err
360 }
361 oldScale, err := scaleFromDeployment(old.(*apps.Deployment))
362 if err != nil {
363 return err
364 }
365 return f(ctx, newScale, oldScale)
366 }
367 }
368
369
370 func scaleFromDeployment(deployment *apps.Deployment) (*autoscaling.Scale, error) {
371 selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
372 if err != nil {
373 return nil, err
374 }
375
376 return &autoscaling.Scale{
377
378 ObjectMeta: metav1.ObjectMeta{
379 Name: deployment.Name,
380 Namespace: deployment.Namespace,
381 UID: deployment.UID,
382 ResourceVersion: deployment.ResourceVersion,
383 CreationTimestamp: deployment.CreationTimestamp,
384 },
385 Spec: autoscaling.ScaleSpec{
386 Replicas: deployment.Spec.Replicas,
387 },
388 Status: autoscaling.ScaleStatus{
389 Replicas: deployment.Status.Replicas,
390 Selector: selector.String(),
391 },
392 }, nil
393 }
394
395
396 type scaleUpdatedObjectInfo struct {
397 name string
398 reqObjInfo rest.UpdatedObjectInfo
399 }
400
401 func (i *scaleUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
402 return i.reqObjInfo.Preconditions()
403 }
404
405 func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
406 deployment, ok := oldObj.DeepCopyObject().(*apps.Deployment)
407 if !ok {
408 return nil, errors.NewBadRequest(fmt.Sprintf("expected existing object type to be Deployment, got %T", deployment))
409 }
410
411 if len(deployment.ResourceVersion) == 0 {
412 return nil, errors.NewNotFound(apps.Resource("deployments/scale"), i.name)
413 }
414
415 groupVersion := schema.GroupVersion{Group: "apps", Version: "v1"}
416 if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
417 requestGroupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
418 if _, ok := replicasPathInDeployment[requestGroupVersion.String()]; ok {
419 groupVersion = requestGroupVersion
420 } else {
421 klog.Fatalf("Unrecognized group/version in request info %q", requestGroupVersion.String())
422 }
423 }
424
425 managedFieldsHandler := managedfields.NewScaleHandler(
426 deployment.ManagedFields,
427 groupVersion,
428 replicasPathInDeployment,
429 )
430
431
432 oldScale, err := scaleFromDeployment(deployment)
433 if err != nil {
434 return nil, err
435 }
436 scaleManagedFields, err := managedFieldsHandler.ToSubresource()
437 if err != nil {
438 return nil, err
439 }
440 oldScale.ManagedFields = scaleManagedFields
441
442
443 newScaleObj, err := i.reqObjInfo.UpdatedObject(ctx, oldScale)
444 if err != nil {
445 return nil, err
446 }
447 if newScaleObj == nil {
448 return nil, errors.NewBadRequest("nil update passed to Scale")
449 }
450 scale, ok := newScaleObj.(*autoscaling.Scale)
451 if !ok {
452 return nil, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", newScaleObj))
453 }
454
455
456 if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 {
457 return nil, errors.NewInvalid(autoscaling.Kind("Scale"), deployment.Name, errs)
458 }
459
460
461 if len(scale.UID) > 0 && scale.UID != deployment.UID {
462 return nil, errors.NewConflict(
463 apps.Resource("deployments/scale"),
464 deployment.Name,
465 fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", scale.UID, deployment.UID),
466 )
467 }
468
469
470 deployment.Spec.Replicas = scale.Spec.Replicas
471 deployment.ResourceVersion = scale.ResourceVersion
472
473 updatedEntries, err := managedFieldsHandler.ToParent(scale.ManagedFields)
474 if err != nil {
475 return nil, err
476 }
477 deployment.ManagedFields = updatedEntries
478
479 return deployment, nil
480 }
481
View as plain text