Skip to content

Commit 0055031

Browse files
authored
Make plural handling more robust (#202)
1 parent 54308fe commit 0055031

File tree

14 files changed

+505
-354
lines changed

14 files changed

+505
-354
lines changed

CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Fixed
9+
- Handling of plurals is more robust, it no longer simply adds a 's' to the end which failed for plurals such
10+
as 'logstash' -> 'logstashes', now queries using the discovery client and REST mapper.
811

912
## [v2.12.0] - 2025-02-12
1013
### Added
11-
- Added support for Python 3.13 Docker image (`custompodautoscaler/python-3-13`)
14+
- Added support for Python 3.13 Docker image (`custompodautoscaler/python-3-13`).
1215
### Changed
13-
- Upgraded to Go `v1.24`
14-
- Upgraded package dependencies
16+
- Upgraded to Go `v1.24`.
17+
- Upgraded package dependencies.
1518
- Updated `custompodautoscaler/python` to track Python 3.13.
1619
- Updated `custompodautoscaler/python-3-12` to track the latest Debian stable version.
1720
### Removed
18-
- Dropped support for Python 3.8 Docker image (`custompodautoscaler/python-3-8`)
21+
- Dropped support for Python 3.8 Docker image (`custompodautoscaler/python-3-8`).
1922

2023
## [v2.11.0] - 2024-03-22
2124
### Changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ or HTTP request.
4444
- Supports Kubernetes metrics that the Horizontal Pod Autoscaler uses, can be configured using a similar syntax and
4545
used in custom scaling decisions.
4646
- Supports [Argo Rollouts](https://argoproj.github.io/argo-rollouts/).
47+
- Supports targeting any custom resource that implements the scale subresource.
4748

4849
## Why would I use it?
4950

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ or HTTP request.
3030
- Supports Kubernetes metrics that the Horizontal Pod Autoscaler uses, can be configured using a similar syntax and
3131
used in custom scaling decisions.
3232
- Supports [Argo Rollouts](https://argoproj.github.io/argo-rollouts/).
33+
- Supports targeting any custom resource that implements the scale subresource.
3334

3435
## Why would I use it?
3536

docs/reference/docker-images.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
The Custom Pod Autoscaler is bundled with the following Docker base images:
44

5-
- `custompodautoscaler/python` tracks latest stable Python 3.x release (alpine based).
6-
- `custompodautoscaler/python-3-13` tracks latest stable Python 3.13.x release (alpine based).
7-
- `custompodautoscaler/python-3-12` tracks latest stable Python 3.12.x release (debian 12 bookworm based).
5+
- `custompodautoscaler/python` tracks latest stable Python 3.x release (debian based).
6+
- `custompodautoscaler/python-3-13` tracks latest stable Python 3.13.x release (debian based).
7+
- `custompodautoscaler/python-3-12` tracks latest stable Python 3.12.x release (debian based).
88
- `custompodautoscaler/alpine` tracks latest stable Alpine 3.x release.
99

1010
## Deprecated images

docs/reference/scaling-targets.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ scale subresource API. For example this means support for:
1313
* Argo Rollouts
1414
* Other third party resources.
1515

16+
> To see how to target custom resources [see the Custom Resources page](../user-guide/custom-resources.md).
17+
1618
## Scale target reference
1719

1820
To tell a Custom Pod Autoscaler which resource to target, provide a `scaleTargetRef` - a description of the resource to
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Custom Resources
2+
3+
The Custom Pod Autoscaler supports targeting custom resources; however this requires some additional configuration
4+
to make sure the autoscaler has the required permissions to be able to manage the targeted resources.
5+
6+
By default the [Custom Pod Autoscaler
7+
Operator](https://github.com/jthomperoo/custom-pod-autoscaler-operator) will provision a role for your autoscaler
8+
which allows managing the built-in Kubernetes resources (deployments, replicasets, statefulsets,
9+
replicationcontrollers), but if you are targeting a custom resource you need to provide your own role with the
10+
correct permissions.
11+
12+
You need to tell the operator not to provision a role for you, and provide your own with the permissions needed,
13+
for example to target Loadstash:
14+
15+
```yaml
16+
apiVersion: rbac.authorization.k8s.io/v1
17+
kind: Role
18+
metadata:
19+
name: python-custom-autoscaler
20+
rules:
21+
- apiGroups:
22+
- logstash.k8s.elastic.co
23+
resources:
24+
- logstashes
25+
- logstashes/scale
26+
verbs:
27+
- '*'
28+
---
29+
apiVersion: v1
30+
kind: ServiceAccount
31+
metadata:
32+
name: python-custom-autoscaler
33+
---
34+
kind: RoleBinding
35+
apiVersion: rbac.authorization.k8s.io/v1
36+
metadata:
37+
name: python-custom-autoscaler
38+
subjects:
39+
- kind: ServiceAccount
40+
name: python-custom-autoscaler
41+
roleRef:
42+
kind: Role
43+
name: python-custom-autoscaler
44+
apiGroup: rbac.authorization.k8s.io
45+
---
46+
apiVersion: custompodautoscaler.com/v1
47+
kind: CustomPodAutoscaler
48+
metadata:
49+
name: python-custom-autoscaler
50+
spec:
51+
template:
52+
spec:
53+
serviceAccountName: python-custom-autoscaler
54+
containers:
55+
- name: python-custom-autoscaler
56+
image: python-custom-autoscaler:latest
57+
imagePullPolicy: IfNotPresent
58+
scaleTargetRef:
59+
apiVersion: logstash.k8s.elastic.co/v1alpha1
60+
kind: Logstash
61+
name: quickstart
62+
provisionRole: false
63+
provisionRoleBinding: false
64+
provisionServiceAccount: false
65+
config:
66+
- name: interval
67+
value: "10000"
68+
```
69+
70+
This takes over provisioning of the role, the role binding, and the service account from the operator.
71+
72+
For any custom resource the CPO can support scaling it if the resource implements the scale subresource, and if so
73+
the permissions needed are generally:
74+
75+
```yaml
76+
- apiGroups:
77+
- my.api.group
78+
resources:
79+
- mycustomresource
80+
- mycustomresource/scale
81+
verbs:
82+
- '*'
83+
```
84+
85+
There is a special case for [Argo Rollouts](https://argoproj.github.io/rollouts/), which can simply provide the
86+
`roleRequiresArgoRollouts` field. [See more information
87+
here](https://github.com/jthomperoo/custom-pod-autoscaler-operator/blob/v1.4.2/USAGE.md#automatically-provisioning-a-role-that-supports-argo-rollouts).
88+
89+
[See how to skip automatic role/any other resource provision
90+
here](https://github.com/jthomperoo/custom-pod-autoscaler-operator/blob/v1.4.2/USAGE.md#using-custom-resources).

docs/user-guide/troubleshooting.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Troubleshooting
2+
3+
This page explains some of the common issues people may experience.
4+
5+
## Autoscaler is Forbidden From Managing Custom Resources
6+
7+
You may encounter an error like this:
8+
9+
`
10+
E0214 11:30:09.556332 1 main.go:266] Error while autoscaling: failed to get managed resource: logstashes.logstash.k8s.elastic.co "quickstart" is forbidden: User "system:serviceaccount:default:python-custom-autoscaler" cannot get resource "logstashes" in API group "logstash.k8s.elastic.co" in the namespace "default"
11+
`
12+
13+
And see that your autoscaler is not scaling the target resource if you are targeting a custom resource.
14+
15+
This is because your autoscaler does not have the correct permissions to manage the resource you are
16+
targeting.
17+
18+
See [the Custom Resources page for details on how to resolve this](./custom-resources.md).
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
evaluate:
22
type: "shell"
33
timeout: 2500
4-
shell:
4+
shell:
55
entrypoint: "python"
6-
command:
6+
command:
77
- "/evaluate.py"
88
metric:
99
type: "shell"
1010
timeout: 2500
11-
shell:
11+
shell:
1212
entrypoint: "python"
13-
command:
13+
command:
1414
- "/metric.py"
15-
runMode: "per-pod"
15+
runMode: "per-pod"
16+
logVerbosity: 3

internal/resourceclient/resourceclient.go

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Custom Pod Autoscaler Authors.
2+
Copyright 2025 The Custom Pod Autoscaler Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -18,10 +18,8 @@ package resourceclient
1818

1919
import (
2020
"context"
21-
"fmt"
22-
23-
"strings"
2421

22+
meta "k8s.io/apimachinery/pkg/api/meta"
2523
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2624
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2725
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -37,31 +35,21 @@ type Client interface {
3735
// UnstructuredClient is an implementation of the arbitrary resource client that uses a dynamic Kubernetes interface,
3836
// retrieving unstructured k8s objects and converting them to metav1.Object
3937
type UnstructuredClient struct {
40-
Dynamic dynamic.Interface
38+
Dynamic dynamic.Interface
39+
RESTMapper meta.RESTMapper
4140
}
4241

4342
// Get takes descriptors of a Kubernetes object (api version, kind, name, namespace) and fetches the matching object,
4443
// returning it as an unstructured Kubernetes resource
4544
func (u *UnstructuredClient) Get(apiVersion string, kind string, name string, namespace string) (*unstructured.Unstructured, error) {
46-
// TODO: update this to be less hacky
47-
// Convert to plural and lowercase
48-
kindPlural := fmt.Sprintf("%ss", strings.ToLower(kind))
49-
50-
// Parse group version
51-
resourceGV, err := schema.ParseGroupVersion(apiVersion)
45+
resourceGK := schema.FromAPIVersionAndKind(apiVersion, kind)
46+
mapping, err := u.RESTMapper.RESTMapping(resourceGK.GroupKind(), resourceGK.Version)
5247
if err != nil {
5348
return nil, err
5449
}
5550

56-
// Build GVR
57-
resourceGVR := schema.GroupVersionResource{
58-
Group: resourceGV.Group,
59-
Version: resourceGV.Version,
60-
Resource: kindPlural,
61-
}
62-
6351
// Get resource
64-
resource, err := u.Dynamic.Resource(resourceGVR).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
52+
resource, err := u.Dynamic.Resource(mapping.Resource).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
6553
if err != nil {
6654
return nil, err
6755
}

0 commit comments

Comments
 (0)