@@ -31,6 +31,8 @@ import (
31
31
32
32
// Using v4 to match upstream
33
33
jsonpatch "gopkg.in/evanphx/json-patch.v4"
34
+ appsv1 "k8s.io/api/apps/v1"
35
+ autoscalingv1 "k8s.io/api/autoscaling/v1"
34
36
corev1 "k8s.io/api/core/v1"
35
37
policyv1 "k8s.io/api/policy/v1"
36
38
policyv1beta1 "k8s.io/api/policy/v1beta1"
@@ -50,6 +52,7 @@ import (
50
52
"k8s.io/apimachinery/pkg/watch"
51
53
"k8s.io/client-go/kubernetes/scheme"
52
54
"k8s.io/client-go/testing"
55
+ "k8s.io/utils/ptr"
53
56
54
57
"sigs.k8s.io/controller-runtime/pkg/client"
55
58
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -83,6 +86,8 @@ const (
83
86
maxNameLength = 63
84
87
randomLength = 5
85
88
maxGeneratedNameLength = maxNameLength - randomLength
89
+
90
+ subResourceScale = "scale"
86
91
)
87
92
88
93
// NewFakeClient creates a new fake client for testing.
@@ -1111,7 +1116,26 @@ type fakeSubResourceClient struct {
1111
1116
}
1112
1117
1113
1118
func (sw * fakeSubResourceClient ) Get (ctx context.Context , obj , subResource client.Object , opts ... client.SubResourceGetOption ) error {
1114
- panic ("fakeSubResourceClient does not support get" )
1119
+ switch sw .subResource {
1120
+ case subResourceScale :
1121
+ // Actual client looks up resource, then extracts the scale sub-resource:
1122
+ // https://github.com/kubernetes/kubernetes/blob/fb6bbc9781d11a87688c398778525c4e1dcb0f08/pkg/registry/apps/deployment/storage/storage.go#L307
1123
+ if err := sw .client .Get (ctx , client .ObjectKeyFromObject (obj ), obj ); err != nil {
1124
+ return err
1125
+ }
1126
+ scale , isScale := subResource .(* autoscalingv1.Scale )
1127
+ if ! isScale {
1128
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected Scale, got %t" , subResource ))
1129
+ }
1130
+ scaleOut , err := extractScale (obj )
1131
+ if err != nil {
1132
+ return err
1133
+ }
1134
+ * scale = * scaleOut
1135
+ return nil
1136
+ default :
1137
+ return fmt .Errorf ("fakeSubResourceClient does not support get for %s" , sw .subResource )
1138
+ }
1115
1139
}
1116
1140
1117
1141
func (sw * fakeSubResourceClient ) Create (ctx context.Context , obj client.Object , subResource client.Object , opts ... client.SubResourceCreateOption ) error {
@@ -1138,11 +1162,30 @@ func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object,
1138
1162
updateOptions := client.SubResourceUpdateOptions {}
1139
1163
updateOptions .ApplyOptions (opts )
1140
1164
1141
- body := obj
1142
- if updateOptions .SubResourceBody != nil {
1143
- body = updateOptions .SubResourceBody
1165
+ switch sw .subResource {
1166
+ case subResourceScale :
1167
+ if err := sw .client .Get (ctx , client .ObjectKeyFromObject (obj ), obj ); err != nil {
1168
+ return err
1169
+ }
1170
+ if updateOptions .SubResourceBody == nil {
1171
+ return apierrors .NewBadRequest ("missing SubResourceBody" )
1172
+ }
1173
+
1174
+ scale , isScale := updateOptions .SubResourceBody .(* autoscalingv1.Scale )
1175
+ if ! isScale {
1176
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected Scale, got %t" , updateOptions .SubResourceBody ))
1177
+ }
1178
+ if err := applyScale (obj , scale ); err != nil {
1179
+ return err
1180
+ }
1181
+ return sw .client .update (obj , false , & updateOptions .UpdateOptions )
1182
+ default :
1183
+ body := obj
1184
+ if updateOptions .SubResourceBody != nil {
1185
+ body = updateOptions .SubResourceBody
1186
+ }
1187
+ return sw .client .update (body , true , & updateOptions .UpdateOptions )
1144
1188
}
1145
- return sw .client .update (body , true , & updateOptions .UpdateOptions )
1146
1189
}
1147
1190
1148
1191
func (sw * fakeSubResourceClient ) Patch (ctx context.Context , obj client.Object , patch client.Patch , opts ... client.SubResourcePatchOption ) error {
@@ -1323,3 +1366,124 @@ func getSingleOrZeroOptions[T any](opts []T) (opt T, err error) {
1323
1366
}
1324
1367
return
1325
1368
}
1369
+
1370
+ func extractScale (obj client.Object ) (* autoscalingv1.Scale , error ) {
1371
+ switch obj := obj .(type ) {
1372
+ case * appsv1.Deployment :
1373
+ var replicas int32 = 1
1374
+ if obj .Spec .Replicas != nil {
1375
+ replicas = * obj .Spec .Replicas
1376
+ }
1377
+ var selector string
1378
+ if obj .Spec .Selector != nil {
1379
+ selector = obj .Spec .Selector .String ()
1380
+ }
1381
+ return & autoscalingv1.Scale {
1382
+ ObjectMeta : metav1.ObjectMeta {
1383
+ Namespace : obj .Namespace ,
1384
+ Name : obj .Name ,
1385
+ UID : obj .UID ,
1386
+ ResourceVersion : obj .ResourceVersion ,
1387
+ CreationTimestamp : obj .CreationTimestamp ,
1388
+ },
1389
+ Spec : autoscalingv1.ScaleSpec {
1390
+ Replicas : replicas ,
1391
+ },
1392
+ Status : autoscalingv1.ScaleStatus {
1393
+ Replicas : obj .Status .Replicas ,
1394
+ Selector : selector ,
1395
+ },
1396
+ }, nil
1397
+ case * appsv1.ReplicaSet :
1398
+ var replicas int32 = 1
1399
+ if obj .Spec .Replicas != nil {
1400
+ replicas = * obj .Spec .Replicas
1401
+ }
1402
+ var selector string
1403
+ if obj .Spec .Selector != nil {
1404
+ selector = obj .Spec .Selector .String ()
1405
+ }
1406
+ return & autoscalingv1.Scale {
1407
+ ObjectMeta : metav1.ObjectMeta {
1408
+ Namespace : obj .Namespace ,
1409
+ Name : obj .Name ,
1410
+ UID : obj .UID ,
1411
+ ResourceVersion : obj .ResourceVersion ,
1412
+ CreationTimestamp : obj .CreationTimestamp ,
1413
+ },
1414
+ Spec : autoscalingv1.ScaleSpec {
1415
+ Replicas : replicas ,
1416
+ },
1417
+ Status : autoscalingv1.ScaleStatus {
1418
+ Replicas : obj .Status .Replicas ,
1419
+ Selector : selector ,
1420
+ },
1421
+ }, nil
1422
+ case * corev1.ReplicationController :
1423
+ var replicas int32 = 1
1424
+ if obj .Spec .Replicas != nil {
1425
+ replicas = * obj .Spec .Replicas
1426
+ }
1427
+ return & autoscalingv1.Scale {
1428
+ ObjectMeta : metav1.ObjectMeta {
1429
+ Namespace : obj .Namespace ,
1430
+ Name : obj .Name ,
1431
+ UID : obj .UID ,
1432
+ ResourceVersion : obj .ResourceVersion ,
1433
+ CreationTimestamp : obj .CreationTimestamp ,
1434
+ },
1435
+ Spec : autoscalingv1.ScaleSpec {
1436
+ Replicas : replicas ,
1437
+ },
1438
+ Status : autoscalingv1.ScaleStatus {
1439
+ Replicas : obj .Status .Replicas ,
1440
+ Selector : labels .Set (obj .Spec .Selector ).String (),
1441
+ },
1442
+ }, nil
1443
+ case * appsv1.StatefulSet :
1444
+ var replicas int32 = 1
1445
+ if obj .Spec .Replicas != nil {
1446
+ replicas = * obj .Spec .Replicas
1447
+ }
1448
+ var selector string
1449
+ if obj .Spec .Selector != nil {
1450
+ selector = obj .Spec .Selector .String ()
1451
+ }
1452
+ return & autoscalingv1.Scale {
1453
+ ObjectMeta : metav1.ObjectMeta {
1454
+ Namespace : obj .Namespace ,
1455
+ Name : obj .Name ,
1456
+ UID : obj .UID ,
1457
+ ResourceVersion : obj .ResourceVersion ,
1458
+ CreationTimestamp : obj .CreationTimestamp ,
1459
+ },
1460
+ Spec : autoscalingv1.ScaleSpec {
1461
+ Replicas : replicas ,
1462
+ },
1463
+ Status : autoscalingv1.ScaleStatus {
1464
+ Replicas : obj .Status .Replicas ,
1465
+ Selector : selector ,
1466
+ },
1467
+ }, nil
1468
+ default :
1469
+ // TODO: CRDs https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource
1470
+ return nil , fmt .Errorf ("unimplemented scale subresource for resource %T" , obj )
1471
+ }
1472
+ }
1473
+
1474
+ func applyScale (obj client.Object , scale * autoscalingv1.Scale ) error {
1475
+ switch obj := obj .(type ) {
1476
+ case * appsv1.Deployment :
1477
+ obj .Spec .Replicas = ptr .To (scale .Spec .Replicas )
1478
+ case * appsv1.ReplicaSet :
1479
+ obj .Spec .Replicas = ptr .To (scale .Spec .Replicas )
1480
+ case * corev1.ReplicationController :
1481
+ obj .Spec .Replicas = ptr .To (scale .Spec .Replicas )
1482
+ case * appsv1.StatefulSet :
1483
+ obj .Spec .Replicas = ptr .To (scale .Spec .Replicas )
1484
+ default :
1485
+ // TODO: CRDs https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource
1486
+ return fmt .Errorf ("unimplemented scale subresource for resource %T" , obj )
1487
+ }
1488
+ return nil
1489
+ }
0 commit comments