From 9ebfd3acc9664315d79314caf7fa4bfa71e921b7 Mon Sep 17 00:00:00 2001 From: Jonathan Mayer Date: Thu, 26 Sep 2024 09:04:08 +0200 Subject: [PATCH] feat/add more workload types (#55) * feat: make argoproj rollouts scalable * feat: add zalando stacks * feat: add tests for new workload types * feat: add prometheuses as workload * refactor: abstract replica-scaled workloads * refactor: simplify types * fix: resource name in permissions * chore: bump go version to 1.23.1 * feat: add some info logs * refactor: sw to suspend scaled resource interface * fix: comments on replicaScaledResource implementations * fix: comment on horizontalpodautoscaler * fix: comment on horizontalpodautoscaler * docs: fix small errors in readme * docs: add troubleshooting information * feat: add more infos and icon to helm chart * refactor: implement pr threads * fix: group version resource in comments of workload types * refactor: naming of downscaleReplicas var * use raw image as icon Co-authored-by: Jonathan Mayer --------- Co-authored-by: Jan Co-authored-by: Jan <157487559+JTaeuber@users.noreply.github.com> --- Dockerfile | 2 +- README.md | 39 ++- cmd/kubedownscaler/main.go | 27 +- cmd/kubedownscaler/main_test.go | 6 +- deployments/chart/.helmignore | 1 + deployments/chart/Chart.yaml | 19 +- deployments/chart/README.md | 4 +- deployments/chart/icon.svg | 19 + deployments/chart/templates/_helpers.tpl | 12 + docs/troubleshooting.md | 24 ++ go.mod | 28 +- go.sum | 327 ++++++++++++++++-- internal/api/kubernetes/client.go | 29 +- internal/pkg/scalable/cronjobs.go | 19 +- internal/pkg/scalable/daemonsets.go | 4 +- internal/pkg/scalable/deployments.go | 62 +--- internal/pkg/scalable/helpers_test.go | 4 +- .../pkg/scalable/horizontalpodautoscalers.go | 72 +--- .../scalable/horizontalpodautoscalers_test.go | 110 ------ internal/pkg/scalable/jobs.go | 19 +- internal/pkg/scalable/jobs_test.go | 78 ----- internal/pkg/scalable/poddisruptionbudgets.go | 53 +-- .../pkg/scalable/poddisruptionbudgets_test.go | 8 +- internal/pkg/scalable/prometheuses.go | 51 +++ .../pkg/scalable/replicaScaledWorkloads.go | 61 ++++ ...test.go => replicaScaledWorkloads_test.go} | 42 +-- internal/pkg/scalable/rollouts.go | 51 +++ internal/pkg/scalable/scaledobjects.go | 66 +--- internal/pkg/scalable/scaledobjects_test.go | 143 -------- internal/pkg/scalable/stacks.go | 51 +++ internal/pkg/scalable/statefulsets.go | 62 +--- internal/pkg/scalable/statefulsets_test.go | 110 ------ .../pkg/scalable/suspendScaledWorkloads.go | 31 ++ ...test.go => suspendScaledWorkloads_test.go} | 10 +- internal/pkg/scalable/util.go | 12 +- internal/pkg/scalable/util_test.go | 53 +-- .../{scalableResource.go => workload.go} | 35 +- .../values/{duration.go => durationValue.go} | 12 +- internal/pkg/values/int32Value.go | 20 ++ internal/pkg/values/layer.go | 24 +- .../{stringlist.go => stringlistValue.go} | 8 +- internal/pkg/values/util.go | 8 +- 42 files changed, 911 insertions(+), 905 deletions(-) create mode 100644 deployments/chart/icon.svg create mode 100644 docs/troubleshooting.md delete mode 100644 internal/pkg/scalable/horizontalpodautoscalers_test.go delete mode 100644 internal/pkg/scalable/jobs_test.go create mode 100644 internal/pkg/scalable/prometheuses.go create mode 100644 internal/pkg/scalable/replicaScaledWorkloads.go rename internal/pkg/scalable/{deployments_test.go => replicaScaledWorkloads_test.go} (69%) create mode 100644 internal/pkg/scalable/rollouts.go delete mode 100644 internal/pkg/scalable/scaledobjects_test.go create mode 100644 internal/pkg/scalable/stacks.go delete mode 100644 internal/pkg/scalable/statefulsets_test.go create mode 100644 internal/pkg/scalable/suspendScaledWorkloads.go rename internal/pkg/scalable/{cronjobs_test.go => suspendScaledWorkloads_test.go} (86%) rename internal/pkg/scalable/{scalableResource.go => workload.go} (65%) rename internal/pkg/values/{duration.go => durationValue.go} (56%) create mode 100644 internal/pkg/values/int32Value.go rename internal/pkg/values/{stringlist.go => stringlistValue.go} (56%) diff --git a/Dockerfile b/Dockerfile index 8dd7e9f..8d26855 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.5 AS build +FROM golang:1.23.1 AS build WORKDIR /tmp/kubedownscaler diff --git a/README.md b/README.md index d7a73fb..0650716 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ This is a golang port of the popular [(py-)kube-downscaler](github.com/caas-team - [PodDisruptionBudgets](#poddisruptionbudgets) - [ScaledObjects](#scaledobjects) - [StatefulSets](#statefulsets) + - [Rollouts](#rollouts) + - [Stacks](#stacks) + - [Prometheuses](#prometheuses) - [Installation](#installation) - [Configuration](#configuration) - [Annotations](#annotations) @@ -41,6 +44,7 @@ This is a golang port of the popular [(py-)kube-downscaler](github.com/caas-team - [Edge Cases](#edge-cases) - [Differences to py-kube-downscaler](#differences-to-py-kube-downscaler) - [Missing Features](#missing-features) +- [Troubleshooting](#troubleshooting) - [Developing](#developing) - [Cloning the Repository](#cloning-the-repository) - [Setting up Pre-Commit](#setting-up-pre-commit) @@ -68,6 +72,12 @@ These are the resources the Downscaler can scale: - sets the paused replicas annotation to the [downscale replicas](#downscale-replicas) - StatefulSets: - sets the replica count to the [downscale replicas](#downscale-replicas) +- Rollouts: + - sets the replica count to the [downscale replicas](#downscale-replicas) +- Stacks: + - sets the replica count to the [downscale replicas](#downscale-replicas) +- Prometheuses: + - sets the replica count to the [downscale replicas](#downscale-replicas) ## Installation @@ -212,12 +222,12 @@ See [RFC3339 Timestamps](https://datatracker.ietf.org/doc/html/rfc3339) for more example: ```text -Mon-Fri 08:00-20:00 Europe/Berlin # From Monday to Friday: from 08:00 to 20:00 -Sat-Sun 00:00-24:00 UTC # On The Weekend: the entire day -Mon-Fri 20:00-08:00 PST # From Monday to Friday: from Midnight to 08:00 and from 20:00 until end of day -Mon-Sun 00:00-00:00 America/New_York # The timespan never matches, this would not do anything -Mon-Tue 20:00-24:00 CEST # On Monday and Tuesday: from 20:00 to midnight -Mon-Tue 20:00-00:00 Europe/Amsterdam # On Monday and Tuesday: from 20:00 to midnight +Mon-Fri 08:00-20:00 Asia/Tokyo # From Monday to Friday: from 08:00 to 20:00 +Sat-Sun 00:00-24:00 UTC # On The Weekend: the entire day +Mon-Fri 20:00-08:00 Australia/Sydney # From Monday to Friday: from Midnight to 08:00 and from 20:00 until end of day +Mon-Sun 00:00-00:00 America/New_York # The timespan never matches, this would not do anything +Mon-Tue 20:00-24:00 Africa/Johannesburg # On Monday and Tuesday: from 20:00 to midnight +Mon-Tue 20:00-00:00 Europe/Amsterdam # On Monday and Tuesday: from 20:00 to midnight ``` Valid Values: @@ -256,6 +266,7 @@ OR with optional spaces: ``` The timespans can be absolute, relative or mixed. + Example: downscale over the weekend and at night: ``` @@ -275,7 +286,7 @@ Or by a duration string: ```text "1h30m" # 1 hour and 30 minutes -"1.5h" # 1 ½ hours (1 hour and 30 minutes) +"1.5h" # 1 hour and 30 minutes "2m" # 2 minutes "10s" # 10 seconds "300s" # 300 seconds @@ -361,7 +372,7 @@ Workload will be scaled according to the downtime schedule on the cli layer --- Layers Workload: uptime="Mon-Fri 08:00-16:00 Europe/Berlin" Namespace: force-downtime=true -CLI: downtime="Mon-Fri 20:00-08:00 PST" +CLI: downtime="Mon-Fri 20:00-08:00 America/Los_Angeles" ENV: (no env vars) --- Process: Exclusion not set on any layer (...) @@ -374,7 +385,7 @@ Workload will be forced into a down-scaled state --- Layers Workload: uptime="Mon-Fri 08:00-16:00 Europe/Berlin" Namespace: force-downtime=true -CLI: downtime="Mon-Fri 20:00-08:00 PST" +CLI: downtime="Mon-Fri 20:00-08:00 America/Los_Angeles" ENV: (no env vars) --- Process: Exclusion not set on any layer (...) @@ -465,7 +476,7 @@ Some cases where this might be needed include: Duration units: -- instead of integers representing seconds you can also use duration strings. See [Duration](#duration) for more information +- instead of integers representing seconds you can also use [duration strings](#duration) - backwards compatible: fully compatible, integer seconds are still supported Layer system: @@ -485,8 +496,8 @@ Some cases where this might be needed include: Uniform timestamp: -- all timestamps are [RFC3339 Timestamps](https://datatracker.ietf.org/doc/html/rfc3339) this is more optimized for golang, more consistent and also used by kubernetes itself -- backwards compatible: mostly, unless you used a short form of ISO 8601 (`2023-08-12`, `2023-233`) or `2023-W34-1` it should be totally fine to not change anything +- all timestamps are [RFC3339 Timestamps](https://datatracker.ietf.org/doc/html/rfc3339) this is more optimized for golang, more consistent and also used by Kubernetes itself +- backwards compatible: mostly, unless you used a short form of ISO 8601 (`2023-08-12`, `2023-233` or `2023-W34-1`) it should be totally fine to not change anything Overlapping [relative timespans](#configuration-of-a-relative-timespan) into next day: @@ -517,6 +528,10 @@ Some cases where this might be needed include: Currently the GoKubeDownscaler is still a WIP. This means that there are still some features missing. You can find a list of the known-missing features [here](/../../labels/missing%20feature). If you think that any other features are missing or you have an idea for a new feature, feel free to open an [Issue](/../../issues/) +## Troubleshooting + +See [troubleshooting](docs/troubleshooting.md) + ## Developing Please read the [contribution manifest](./CONTRIBUTING.md) diff --git a/cmd/kubedownscaler/main.go b/cmd/kubedownscaler/main.go index f1421ce..de10b17 100644 --- a/cmd/kubedownscaler/main.go +++ b/cmd/kubedownscaler/main.go @@ -26,11 +26,11 @@ var ( // if the scan should only run once once = false // how long to wait between scans - interval = values.Duration(30 * time.Second) + interval = 30 * time.Second // list of namespaces to restrict the downscaler to - includeNamespaces values.StringList + includeNamespaces []string // list of resources to restrict the downscaler to - includeResources = values.StringList{"deployments"} + includeResources = []string{"deployments"} // list of namespaces to ignore while downscaling excludeNamespaces = values.RegexList{regexp.MustCompile("kube-system"), regexp.MustCompile("kube-downscaler")} // list of workload names to ignore while downscaling @@ -45,7 +45,8 @@ var ( func init() { // set defaults for layers - layerCli.GracePeriod = values.Duration(15 * time.Minute) + layerCli.GracePeriod = 15 * time.Minute + layerCli.DownscaleReplicas = 0 // cli layer values flag.Var(&layerCli.DownscalePeriod, "downscale-period", "period to scale down in (default: never, incompatible: UpscaleTime, DownscaleTime)") @@ -53,16 +54,16 @@ func init() { flag.Var(&layerCli.UpscalePeriod, "upscale-period", "periods to scale up in (default: never, incompatible: UpscaleTime, DownscaleTime)") flag.Var(&layerCli.UpTime, "default-uptime", "timespans where workloads will be scaled up, outside of them they will be scaled down (default: never, incompatible: UpscalePeriod, DownscalePeriod)") flag.Var(&layerCli.Exclude, "explicit-include", "sets exclude on cli layer to true, makes it so namespaces or deployments have to specify downscaler/exclude=false (default: false)") - flag.IntVar(&layerCli.DownscaleReplicas, "downtime-replicas", 0, "the replicas to scale down to (default: 0)") - flag.Var(&layerCli.GracePeriod, "grace-period", "the grace period between creation of workload until first downscale (default: 15min)") + flag.Var((*values.Int32Value)(&layerCli.DownscaleReplicas), "downtime-replicas", "the replicas to scale down to (default: 0)") + flag.Var((*values.DurationValue)(&layerCli.GracePeriod), "grace-period", "the grace period between creation of workload until first downscale (default: 15min)") // cli runtime configuration flag.BoolVar(&dryRun, "dry-run", false, "print actions instead of doing them. enables debug logs (default: false)") flag.BoolVar(&debug, "debug", false, "print more debug information (default: false)") flag.BoolVar(&once, "once", false, "run scan only once (default: false)") - flag.Var(&interval, "interval", "time between scans (default: 30s)") - flag.Var(&includeNamespaces, "namespace", "restrict the downscaler to the specified namespaces (default: all)") - flag.Var(&includeResources, "include-resources", "restricts the downscaler to the specified resource types (default: deployments)") + flag.Var((*values.DurationValue)(&interval), "interval", "time between scans (default: 30s)") + flag.Var((*values.StringListValue)(&includeNamespaces), "namespace", "restrict the downscaler to the specified namespaces (default: all)") + flag.Var((*values.StringListValue)(&includeResources), "include-resources", "restricts the downscaler to the specified resource types (default: deployments)") flag.Var(&excludeNamespaces, "exclude-namespaces", "exclude namespaces from being scaled (default: kube-system,kube-downscaler)") flag.Var(&excludeWorkloads, "exclude-deployments", "exclude deployments from being scaled (optional)") flag.Var(&includeLabels, "matching-labels", "restricts the downscaler to workloads with these labels (default: all)") @@ -96,14 +97,16 @@ func main() { } ctx := context.Background() + slog.Debug("getting client for kubernetes") client, err := kubernetes.NewClient(kubeconfig, dryRun) if err != nil { - slog.Error("failed to create new kubernetes client", "error", err) + slog.Error("failed to create new Kubernetes client", "error", err) os.Exit(1) } + slog.Info("started downscaler") for { - slog.Debug("scanning workloads") + slog.Info("scanning workloads") workloads, err := client.GetWorkloads(includeNamespaces, includeResources, ctx) if err != nil { @@ -111,6 +114,7 @@ func main() { os.Exit(1) } workloads = scalable.FilterExcluded(workloads, includeLabels, excludeNamespaces, excludeWorkloads) + slog.Info("scanning over workloads matching filters", "amount", len(workloads)) var wg sync.WaitGroup for _, workload := range workloads { @@ -129,6 +133,7 @@ func main() { }() } wg.Wait() + slog.Info("successfully scanned all workloads") if once { slog.Debug("once is set to true, exiting") diff --git a/cmd/kubedownscaler/main_test.go b/cmd/kubedownscaler/main_test.go index 72aed9c..930ebb9 100644 --- a/cmd/kubedownscaler/main_test.go +++ b/cmd/kubedownscaler/main_test.go @@ -23,7 +23,7 @@ func (m *MockClient) GetNamespaceAnnotations(namespace string, ctx context.Conte return args.Get(0).(map[string]string), args.Error(1) } -func (m *MockClient) DownscaleWorkload(replicas int, workload scalable.Workload, ctx context.Context) error { +func (m *MockClient) DownscaleWorkload(replicas int32, workload scalable.Workload, ctx context.Context) error { args := m.Called(replicas, workload, ctx) return args.Error(0) } @@ -65,7 +65,7 @@ func TestScanWorkload(t *testing.T) { layerEnv := values.NewLayer() layerCli.DownscaleReplicas = 0 - layerCli.GracePeriod = values.Duration(15 * time.Minute) + layerCli.GracePeriod = 15 * time.Minute mockClient := new(MockClient) mockWorkload := new(MockWorkload) @@ -78,7 +78,7 @@ func TestScanWorkload(t *testing.T) { }) mockClient.On("GetNamespaceAnnotations", "test-namespace", ctx).Return(map[string]string{}, nil) - mockClient.On("DownscaleWorkload", 0, mockWorkload, ctx).Return(nil) + mockClient.On("DownscaleWorkload", int32(0), mockWorkload, ctx).Return(nil) err := scanWorkload(mockWorkload, mockClient, ctx, layerCli, layerEnv) diff --git a/deployments/chart/.helmignore b/deployments/chart/.helmignore index bb20efb..7410861 100644 --- a/deployments/chart/.helmignore +++ b/deployments/chart/.helmignore @@ -1,2 +1,3 @@ .git/ .gitignore +icon.svg diff --git a/deployments/chart/Chart.yaml b/deployments/chart/Chart.yaml index e8e194b..94c3271 100644 --- a/deployments/chart/Chart.yaml +++ b/deployments/chart/Chart.yaml @@ -1,7 +1,20 @@ apiVersion: v2 name: go-kube-downscaler description: A Helm chart for deploying the go-kube-downscaler - type: application -version: 1.0.3 -appVersion: 1.0.3 +keywords: + - kube-downscaler + - go + - downscaling +version: 1.1.0 +appVersion: 1.1.0 +icon: https://raw.githubusercontent.com/caas-team/GoKubeDownscaler/refs/heads/main/deployments/chart/icon.svg +sources: + - https://github.com/caas-team/GoKubeDownscaler +maintainers: + - name: jonathan-mayer + email: jonathan.mayer@telekom.de + url: https://www.telekom.com + - name: JTaeuber + email: jan.taeuber@telekom.de + url: https://www.telekom.com diff --git a/deployments/chart/README.md b/deployments/chart/README.md index c9274d4..12c95c8 100644 --- a/deployments/chart/README.md +++ b/deployments/chart/README.md @@ -33,7 +33,7 @@ The deployment.yaml file creates the main Deployment of the go-kube-downscaler w ### Serviceaccount -The serviceaccount.yaml file creates a ServiceAccount that will be used by the go-kube-downscaler to interact with kubernetes. +The serviceaccount.yaml file creates a ServiceAccount that will be used by the go-kube-downscaler to interact with Kubernetes. ### Configmap @@ -80,7 +80,7 @@ The default values can be found [here](./values.yaml). ### Prerequisits -In order to get started installing the go-kube-downscaler using our helm chart you only need to have helm installed on your system and have access to a kubernetes cluster in some kind of way. +In order to get started installing the go-kube-downscaler using our helm chart you only need to have helm installed on your system and have access to a Kubernetes cluster in some kind of way. You can find out how to install helm [here](https://helm.sh/docs/intro/install/). diff --git a/deployments/chart/icon.svg b/deployments/chart/icon.svg new file mode 100644 index 0000000..8e17ece --- /dev/null +++ b/deployments/chart/icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/deployments/chart/templates/_helpers.tpl b/deployments/chart/templates/_helpers.tpl index 3751168..058f3a8 100644 --- a/deployments/chart/templates/_helpers.tpl +++ b/deployments/chart/templates/_helpers.tpl @@ -241,6 +241,18 @@ Create defined permissions for roles - update - patch {{- end }} +{{- if eq $resource "prometheuses" }} +- apiGroups: + - monitoring.coreos.com + resources: + - prometheuses + verbs: + - get + - watch + - list + - update + - patch +{{- end }} {{- if eq $resource "poddisruptionbudgets" }} - apiGroups: - policy diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..31c9aaf --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,24 @@ +# Troubleshooting + +- [Synchronous operation](#synchronous-operation) + +## Synchronous operation + +Error: + +``` +Operation cannot be fulfilled on xxxxxxx.xxxxx \"xxxxxxxxxxx\": the object has been modified; please apply your changes to the latest version and try again +``` + +Causes: + +- running multiple downscalers on the same resources +- the resource was modified while the resource was scaled + +Fixes: + +- do not run multiple downscalers on the same resources +- it should just scale in the next scan cycle so there are probably no changes needed + +> [!Note] +> this is a pretty unavoidable issue due to there being no easy way to lock the resource from being edited while the downscaler is scaling it. The py-kube-downscaler solved this by just overwriting the changes made during scaling diff --git a/go.mod b/go.mod index fffa0aa..eca0382 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,14 @@ module github.com/caas-team/gokubedownscaler -go 1.22.5 +go 1.23.1 require ( + github.com/argoproj/argo-rollouts v1.7.2 github.com/kedacore/keda/v2 v2.15.1 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.2 + github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.2 github.com/stretchr/testify v1.9.0 + github.com/zalando-incubator/stackset-controller v1.4.84 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/client-go v0.31.0 @@ -14,7 +18,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/expr-lang/expr v1.16.9 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -26,33 +30,35 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/szuecs/routegroup-client v0.28.2 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -60,7 +66,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.31.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kube-openapi v0.0.0-20240808142205-8e686545bdb8 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect knative.dev/pkg v0.0.0-20240805063731-c88d5dad9653 // indirect sigs.k8s.io/controller-runtime v0.19.0 // indirect diff --git a/go.sum b/go.sum index c597827..8edaf42 100644 --- a/go.sum +++ b/go.sum @@ -1,53 +1,108 @@ +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/argoproj/argo-rollouts v1.7.2 h1:faDUH/qePerYRwsrHfVzNQkhjGBgXIiVYdVK8824kMo= +github.com/argoproj/argo-rollouts v1.7.2/go.mod h1:Te4HrUELxKiBpK8lgk77o4gTa3mv8pXCd8xdPprKrbs= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= -github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.8.1+incompatible h1:2toJaoe7/rNa1zpeQx0UnVEjqk6z2ecyA20V/zg8vTU= -github.com/evanphx/json-patch v5.8.1+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -58,50 +113,117 @@ github.com/kedacore/keda/v2 v2.15.1 h1:Kb3woYuCeCPICH037vTIcUopXgOYpdP2qa+CmHgV3 github.com/kedacore/keda/v2 v2.15.1/go.mod h1:2umVEoNgklKt0+q+7BEEbrSgxqh+KPjyh6vnKXt3sls= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0= -github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= +github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= +github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= +github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.2 h1:BpGDC87A2SaxbKgONsFLEX3kRcRJee2aLQbjXsuz0hA= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.2/go.mod h1:Rd8YnCqz+2FYsiGmE2DMlaLjQRB4v2jFNnzCt9YY4IM= +github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.2 h1:yncs8NglhE3hB+viNsabCAF9TBBDOBljHUyxHC5fSGY= +github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.2/go.mod h1:AfbzyEUFxJmSoTiMcgNHHjDKcorBVd9TIwx0viURgEw= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/szuecs/routegroup-client v0.28.2 h1:Dk9D6VqhtYM0IVRkik0fpZ5IbVrf1mHssYmAyRrwehU= +github.com/szuecs/routegroup-client v0.28.2/go.mod h1:QpI/XGdncIAYIE03Nwjq0w+NXlIfV/n56BI1uR2a2Do= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zalando-incubator/stackset-controller v1.4.84 h1:jyQi1iLOu5TA5G112nNwPAKKZOdWQO/DzxMQq/AE0QI= +github.com/zalando-incubator/stackset-controller v1.4.84/go.mod h1:PDy2PY2eqkUehNVHxh/VtxJbseVcwvYUOuafc2DCZzY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -111,76 +233,220 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.28.7/go.mod h1:y4RbcjCCMff1930SG/TcP3AUKNfaJUgIeUp58e/2vyY= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.28.7/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.28.7/go.mod h1:xIoEaDewZ+EwWOo1/F1t0IOKMPe1rwBZhLu9Es6y0tE= k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/code-generator v0.28.7/go.mod h1:IaYGMqYjgj0zE3L9mnHo7hIL9GkY08GvGyyracaIxTA= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/kube-openapi v0.0.0-20240808142205-8e686545bdb8 h1:1Wof1cGQgA5pqgo8MxKPtf+qN6Sh/0JzznmeGPm1HnE= +k8s.io/kube-openapi v0.0.0-20240808142205-8e686545bdb8/go.mod h1:Os6V6dZwLNii3vxFpxcNaTmH8LJJBkOTg1N0tOA0fvA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20240805063731-c88d5dad9653 h1:VHUW124ZpkDn4EnIzMuGWvGuJte3ISIoHMmEw2kx0zU= @@ -189,7 +455,10 @@ sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/api/kubernetes/client.go b/internal/api/kubernetes/client.go index f0a6a6f..f6b18d7 100644 --- a/internal/api/kubernetes/client.go +++ b/internal/api/kubernetes/client.go @@ -8,8 +8,11 @@ import ( "log/slog" "strings" + argo "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" "github.com/caas-team/gokubedownscaler/internal/pkg/scalable" keda "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" + monitoring "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned" + zalando "github.com/zalando-incubator/stackset-controller/pkg/clientset" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -21,14 +24,14 @@ const ( var errResourceNotSupported = errors.New("error: specified rescource type is not supported") -// Client is a interface representing a high-level client to get and modify kubernetes resources +// Client is an interface representing a high-level client to get and modify Kubernetes resources type Client interface { // GetNamespaceAnnotations gets the annotations of the workload's namespace GetNamespaceAnnotations(namespace string, ctx context.Context) (map[string]string, error) // GetWorkloads gets all workloads of the specified resources for the specified namespaces GetWorkloads(namespaces []string, resourceTypes []string, ctx context.Context) ([]scalable.Workload, error) // DownscaleWorkload downscales the workload to the specified replicas - DownscaleWorkload(replicas int, workload scalable.Workload, ctx context.Context) error + DownscaleWorkload(replicas int32, workload scalable.Workload, ctx context.Context) error // UpscaleWorkload upscales the workload to the original replicas UpscaleWorkload(workload scalable.Workload, ctx context.Context) error // addWorkloadEvent creates a new event on the workload @@ -44,24 +47,36 @@ func NewClient(kubeconfig string, dryRun bool) (client, error) { config, err := getConfig(kubeconfig) if err != nil { - return kubeclient, fmt.Errorf("failed to get config for kubernetes: %w", err) + return kubeclient, fmt.Errorf("failed to get config for Kubernetes: %w", err) } // set qps and burst rate limiting options. See https://kubernetes.io/docs/reference/config-api/apiserver-eventratelimit.v1alpha1/ config.QPS = 500 // available queries per second, when unused will fill the burst buffer config.Burst = 1000 // the max size of the buffer of queries clientsets.Kubernetes, err = kubernetes.NewForConfig(config) if err != nil { - return kubeclient, fmt.Errorf("failed to get clientset for kubernetes resources: %w", err) + return kubeclient, fmt.Errorf("failed to get clientset for Kubernetes resources: %w", err) } clientsets.Keda, err = keda.NewForConfig(config) if err != nil { return kubeclient, fmt.Errorf("failed to get clientset for keda resources: %w", err) } + clientsets.Argo, err = argo.NewForConfig(config) + if err != nil { + return kubeclient, fmt.Errorf("failed to get clientset for argo resources: %w", err) + } + clientsets.Zalando, err = zalando.NewForConfig(config) + if err != nil { + return kubeclient, fmt.Errorf("failed to get clientset for zalando resources: %w", err) + } + clientsets.Monitoring, err = monitoring.NewForConfig(config) + if err != nil { + return kubeclient, fmt.Errorf("failed to get clientset for monitoring resources: %w", err) + } kubeclient.clientsets = &clientsets return kubeclient, nil } -// client is a kubernetes client with downscaling specific functions +// client is a Kubernetes client with downscaling specific functions type client struct { clientsets *scalable.Clientsets dryRun bool @@ -85,7 +100,7 @@ func (c client) GetWorkloads(namespaces []string, resourceTypes []string, ctx co for _, namespace := range namespaces { for _, resourceType := range resourceTypes { slog.Debug("getting workloads from resource type", "resourceType", resourceType) - getWorkloads, ok := scalable.GetResource[strings.ToLower(resourceType)] + getWorkloads, ok := scalable.GetWorkloads[strings.ToLower(resourceType)] if !ok { return nil, errResourceNotSupported } @@ -101,7 +116,7 @@ func (c client) GetWorkloads(namespaces []string, resourceTypes []string, ctx co } // DownscaleWorkload downscales the workload to the specified replicas -func (c client) DownscaleWorkload(replicas int, workload scalable.Workload, ctx context.Context) error { +func (c client) DownscaleWorkload(replicas int32, workload scalable.Workload, ctx context.Context) error { err := workload.ScaleDown(replicas) if err != nil { return fmt.Errorf("failed to set the workload into a scaled down state: %w", err) diff --git a/internal/pkg/scalable/cronjobs.go b/internal/pkg/scalable/cronjobs.go index ad1014e..165ef91 100644 --- a/internal/pkg/scalable/cronjobs.go +++ b/internal/pkg/scalable/cronjobs.go @@ -16,28 +16,19 @@ func getCronJobs(namespace string, clientsets *Clientsets, ctx context.Context) return nil, fmt.Errorf("failed to get cronjobs: %w", err) } for _, item := range cronjobs.Items { - results = append(results, &cronJob{&item}) + results = append(results, &suspendScaledWorkload{&cronJob{&item}}) } return results, nil } -// cronJob is a wrapper for batch/v1.cronJob to implement the Workload interface +// cronJob is a wrapper for batch/v1.CronJob to implement the suspendScaledResource interface type cronJob struct { *batch.CronJob } -// ScaleUp scales the resource up -func (c *cronJob) ScaleUp() error { - newSuspend := false - c.Spec.Suspend = &newSuspend - return nil -} - -// ScaleDown scales the resource down -func (c *cronJob) ScaleDown(_ int) error { - newSuspend := true - c.Spec.Suspend = &newSuspend - return nil +// setSuspend sets the value of the suspend field on the cronJob +func (c *cronJob) setSuspend(suspend bool) { + c.Spec.Suspend = &suspend } // Update updates the resource with all changes made to it. It should only be called once on a resource diff --git a/internal/pkg/scalable/daemonsets.go b/internal/pkg/scalable/daemonsets.go index c42a67d..dd27ac5 100644 --- a/internal/pkg/scalable/daemonsets.go +++ b/internal/pkg/scalable/daemonsets.go @@ -25,7 +25,7 @@ func getDaemonSets(namespace string, clientsets *Clientsets, ctx context.Context return results, nil } -// daemonSet is a wrapper for batch/v1.cronJob to implement the Workload interface +// daemonSet is a wrapper for apps/v1.DeamonSet to implement the Workload interface type daemonSet struct { *appsv1.DaemonSet } @@ -37,7 +37,7 @@ func (d *daemonSet) ScaleUp() error { } // ScaleDown scales the resource down -func (d *daemonSet) ScaleDown(_ int) error { +func (d *daemonSet) ScaleDown(_ int32) error { if d.Spec.Template.Spec.NodeSelector == nil { d.Spec.Template.Spec.NodeSelector = map[string]string{} } diff --git a/internal/pkg/scalable/deployments.go b/internal/pkg/scalable/deployments.go index a88c8ea..c54a08e 100644 --- a/internal/pkg/scalable/deployments.go +++ b/internal/pkg/scalable/deployments.go @@ -3,8 +3,6 @@ package scalable import ( "context" "fmt" - "log/slog" - "math" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,73 +16,29 @@ func getDeployments(namespace string, clientsets *Clientsets, ctx context.Contex return nil, fmt.Errorf("failed to get deployments: %w", err) } for _, item := range deployments.Items { - results = append(results, &deployment{&item}) + results = append(results, &replicaScaledWorkload{&deployment{&item}}) } return results, nil } -// deployment is a wrapper for appsv1.Deployment to implement the Workload interface +// deployment is a wrapper for apps/v1.Deployment to implement the replicaScaledResource interface type deployment struct { *appsv1.Deployment } -// setReplicas sets the amount of replicas on the resource. Changes won't be made on kubernetes until update() is called -func (d *deployment) setReplicas(replicas int) error { - if replicas > math.MaxInt32 || replicas < 0 { - return errBoundOnScalingTargetValue - } - - // #nosec G115 - newReplicas := int32(replicas) - d.Spec.Replicas = &newReplicas +// setReplicas sets the amount of replicas on the resource. Changes won't be made on Kubernetes until update() is called +func (d *deployment) setReplicas(replicas int32) error { + d.Spec.Replicas = &replicas return nil } -// getCurrentReplicas gets the current amount of replicas of the resource -func (d *deployment) getCurrentReplicas() (int, error) { +// getReplicas gets the current amount of replicas of the resource +func (d *deployment) getReplicas() (int32, error) { replicas := d.Spec.Replicas if replicas == nil { return 0, errNoReplicasSpecified } - return int(*d.Spec.Replicas), nil -} - -// ScaleUp scales the resource up -func (d *deployment) ScaleUp() error { - originalReplicas, err := getOriginalReplicas(d) - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == nil { - slog.Debug("original replicas is not set, skipping", "workload", d.GetName(), "namespace", d.GetNamespace()) - return nil - } - - err = d.setReplicas(*originalReplicas) - if err != nil { - return fmt.Errorf("failed to set original replicas for workload: %w", err) - } - removeOriginalReplicas(d) - return nil -} - -// ScaleDown scales the resource down -func (d *deployment) ScaleDown(downscaleReplicas int) error { - originalReplicas, err := d.getCurrentReplicas() - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == downscaleReplicas { - slog.Debug("workload is already scaled down, skipping", "workload", d.GetName(), "namespace", d.GetNamespace()) - return nil - } - - err = d.setReplicas(downscaleReplicas) - if err != nil { - return fmt.Errorf("failed to set replicas for workload: %w", err) - } - setOriginalReplicas(originalReplicas, d) - return nil + return *d.Spec.Replicas, nil } // Update updates the resource with all changes made to it. It should only be called once on a resource diff --git a/internal/pkg/scalable/helpers_test.go b/internal/pkg/scalable/helpers_test.go index 47c0745..c78e4c5 100644 --- a/internal/pkg/scalable/helpers_test.go +++ b/internal/pkg/scalable/helpers_test.go @@ -22,12 +22,12 @@ func assertBoolPointerEqual(t *testing.T, expected, actual *bool) { } } -func intAsPointer(value int) *int { +func intAsPointer(value int32) *int32 { return &value } // assertIntPointerEqual checks if two int pointers equal in state, being nil or pointing to the same integer value -func assertIntPointerEqual(t *testing.T, expected, actual *int) { +func assertIntPointerEqual(t *testing.T, expected, actual *int32) { t.Helper() if expected == nil { assert.Nil(t, actual) diff --git a/internal/pkg/scalable/horizontalpodautoscalers.go b/internal/pkg/scalable/horizontalpodautoscalers.go index 8971687..4daffcd 100644 --- a/internal/pkg/scalable/horizontalpodautoscalers.go +++ b/internal/pkg/scalable/horizontalpodautoscalers.go @@ -2,14 +2,15 @@ package scalable import ( "context" + "errors" "fmt" - "log/slog" - "math" appsv1 "k8s.io/api/autoscaling/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +var errMinReplicasBoundsExceeded = errors.New("error: a HPAs minReplicas can only be set to int32 values larger than 1") + // getHorizontalPodAutoscalers is the getResourceFunc for horizontalPodAutoscalers func getHorizontalPodAutoscalers(namespace string, clientsets *Clientsets, ctx context.Context) ([]Workload, error) { var results []Workload @@ -18,73 +19,32 @@ func getHorizontalPodAutoscalers(namespace string, clientsets *Clientsets, ctx c return nil, fmt.Errorf("failed to get horizontalpodautoscalers: %w", err) } for _, item := range hpas.Items { - results = append(results, &horizontalPodAutoscaler{&item}) + results = append(results, &replicaScaledWorkload{&horizontalPodAutoscaler{&item}}) } return results, nil } -// horizontalPodAutoscaler is a wrapper for autoscaling/v2.horizontalPodAutoscaler to implement the Workload interface +// horizontalPodAutoscaler is a wrapper for autoscaling/v2.HorizontalPodAutoscaler to implement the replicaScaledResource interface type horizontalPodAutoscaler struct { *appsv1.HorizontalPodAutoscaler } -// setMinReplicas set the spec.MinReplicas to a new value -func (h *horizontalPodAutoscaler) setMinReplicas(replicas int) error { - if replicas > math.MaxInt32 || replicas < 1 { - return errBoundOnScalingTargetValue - } - - // #nosec G115 - newReplicas := int32(replicas) - h.Spec.MinReplicas = &newReplicas - return nil -} - -// getMinReplicas gets the spec.MinReplicas from the resource -func (h *horizontalPodAutoscaler) getMinReplicas() (int, error) { - minReplicas := h.Spec.MinReplicas - if minReplicas == nil { - return 0, errNoMinReplicasSpecified - } - return int(*minReplicas), nil -} - -// ScaleUp scales the resource up -func (h *horizontalPodAutoscaler) ScaleUp() error { - originalReplicas, err := getOriginalReplicas(h) - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == nil { - slog.Debug("original replicas is not set, skipping", "workload", h.GetName(), "namespace", h.GetNamespace()) - return nil - } - - err = h.setMinReplicas(*originalReplicas) - if err != nil { - return fmt.Errorf("failed to set minimum replicas for workload: %w", err) +// setReplicas sets the amount of replicas on the resource. Changes won't be made on Kubernetes until update() is called +func (h *horizontalPodAutoscaler) setReplicas(replicas int32) error { + if replicas < 1 { + return errMinReplicasBoundsExceeded } - removeOriginalReplicas(h) + h.Spec.MinReplicas = &replicas return nil } -// ScaleDown scales the resource down -func (h *horizontalPodAutoscaler) ScaleDown(downscaleReplicas int) error { - originalReplicas, err := h.getMinReplicas() - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == downscaleReplicas { - slog.Debug("workload is already scaled down, skipping", "workload", h.GetName(), "namespace", h.GetNamespace()) - return nil - } - - err = h.setMinReplicas(downscaleReplicas) - if err != nil { - return fmt.Errorf("failed to set min replicas for workload: %w", err) +// getReplicas gets the current amount of replicas of the resource +func (h *horizontalPodAutoscaler) getReplicas() (int32, error) { + replicas := h.Spec.MinReplicas + if replicas == nil { + return 0, errNoReplicasSpecified } - setOriginalReplicas(originalReplicas, h) - return nil + return *h.Spec.MinReplicas, nil } // Update updates the resource with all changes made to it. It should only be called once on a resource diff --git a/internal/pkg/scalable/horizontalpodautoscalers_test.go b/internal/pkg/scalable/horizontalpodautoscalers_test.go deleted file mode 100644 index df69e18..0000000 --- a/internal/pkg/scalable/horizontalpodautoscalers_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package scalable - -import ( - "testing" - - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/autoscaling/v2" -) - -func TestHPA_ScaleUp(t *testing.T) { - tests := []struct { - name string - replicas int32 - originalReplicas *int - wantOriginalReplicas *int - wantReplicas int32 - }{ - { - name: "scale up", - replicas: 0, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "already scaled up", - replicas: 5, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "orignal replicas not set", - replicas: 0, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 0, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - hpa := &horizontalPodAutoscaler{&appsv1.HorizontalPodAutoscaler{}} - hpa.Spec.MinReplicas = &test.replicas - if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, hpa) - } - - err := hpa.ScaleUp() - assert.NoError(t, err) - if assert.NotNil(t, hpa.Spec.MinReplicas) { - assert.Equal(t, test.wantReplicas, *hpa.Spec.MinReplicas) - } - oringalReplicas, err := getOriginalReplicas(hpa) - assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value - assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) - }) - } -} - -func TestHPA_ScaleDown(t *testing.T) { - tests := []struct { - name string - replicas int32 - originalReplicas *int - wantOriginalReplicas *int - wantReplicas int32 - }{ - { - name: "scale down", - replicas: 5, - originalReplicas: nil, - wantOriginalReplicas: intAsPointer(5), - wantReplicas: 1, - }, - { - name: "already scaled down", - replicas: 1, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: intAsPointer(5), - wantReplicas: 1, - }, - { - name: "orignal replicas set, but not scaled down", - replicas: 2, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: intAsPointer(2), - wantReplicas: 1, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - hpa := &horizontalPodAutoscaler{&appsv1.HorizontalPodAutoscaler{}} - hpa.Spec.MinReplicas = &test.replicas - if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, hpa) - } - - err := hpa.ScaleDown(1) - assert.NoError(t, err) - if assert.NotNil(t, hpa.Spec.MinReplicas) { - assert.Equal(t, test.wantReplicas, *hpa.Spec.MinReplicas) - } - oringalReplicas, err := getOriginalReplicas(hpa) - assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value - assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) - }) - } -} diff --git a/internal/pkg/scalable/jobs.go b/internal/pkg/scalable/jobs.go index 7325e1d..83518e1 100644 --- a/internal/pkg/scalable/jobs.go +++ b/internal/pkg/scalable/jobs.go @@ -16,28 +16,19 @@ func getJobs(namespace string, clientsets *Clientsets, ctx context.Context) ([]W return nil, fmt.Errorf("failed to get jobs: %w", err) } for _, item := range jobs.Items { - results = append(results, &job{&item}) + results = append(results, &suspendScaledWorkload{&job{&item}}) } return results, nil } -// job is a wrapper for batch/v1.job to implement the Workload interface +// job is a wrapper for batch/v1.Job to implement the suspendScaledResource interface type job struct { *batch.Job } -// ScaleUp scales the resource up -func (j *job) ScaleUp() error { - newSuspend := false - j.Spec.Suspend = &newSuspend - return nil -} - -// ScaleDown scales the resource down -func (j *job) ScaleDown(_ int) error { - newSuspend := true - j.Spec.Suspend = &newSuspend - return nil +// setSuspend sets the value of the suspend field on the job +func (c *job) setSuspend(suspend bool) { + c.Spec.Suspend = &suspend } // Update updates the resource with all changes made to it. It should only be called once on a resource diff --git a/internal/pkg/scalable/jobs_test.go b/internal/pkg/scalable/jobs_test.go deleted file mode 100644 index 66acac0..0000000 --- a/internal/pkg/scalable/jobs_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package scalable - -import ( - "testing" - - "github.com/stretchr/testify/assert" - batch "k8s.io/api/batch/v1" -) - -func TestJob_ScaleUp(t *testing.T) { - tests := []struct { - name string - suspend *bool - wantSuspend *bool - }{ - { - name: "scale up", - suspend: boolAsPointer(true), - wantSuspend: boolAsPointer(false), - }, - { - name: "already scaled up", - suspend: boolAsPointer(false), - wantSuspend: boolAsPointer(false), - }, - { - name: "suspend unset", - suspend: nil, - wantSuspend: boolAsPointer(false), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - j := job{&batch.Job{}} - j.Spec.Suspend = test.suspend - - err := j.ScaleUp() - assert.NoError(t, err) - assertBoolPointerEqual(t, test.wantSuspend, j.Spec.Suspend) - }) - } -} - -func TestJob_ScaleDown(t *testing.T) { - tests := []struct { - name string - suspend *bool - wantSuspend *bool - }{ - { - name: "scale down", - suspend: boolAsPointer(false), - wantSuspend: boolAsPointer(true), - }, - { - name: "already scaled down", - suspend: boolAsPointer(true), - wantSuspend: boolAsPointer(true), - }, - { - name: "suspend unset", - suspend: nil, - wantSuspend: boolAsPointer(true), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - j := job{&batch.Job{}} - j.Spec.Suspend = test.suspend - - err := j.ScaleDown(0) - assert.NoError(t, err) - assertBoolPointerEqual(t, test.wantSuspend, j.Spec.Suspend) - }) - } -} diff --git a/internal/pkg/scalable/poddisruptionbudgets.go b/internal/pkg/scalable/poddisruptionbudgets.go index 87b05d3..93e3714 100644 --- a/internal/pkg/scalable/poddisruptionbudgets.go +++ b/internal/pkg/scalable/poddisruptionbudgets.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "math" "github.com/caas-team/gokubedownscaler/internal/pkg/values" @@ -26,13 +25,13 @@ func getPodDisruptionBudgets(namespace string, clientsets *Clientsets, ctx conte return results, nil } -// podDisruptionBudget is a wrapper for policy/v1.podDisruptionBudget to implement the Workload interface +// podDisruptionBudget is a wrapper for policy/v1.PodDisruptionBudget to implement the Workload interface type podDisruptionBudget struct { *policy.PodDisruptionBudget } // getMinAvailableInt returns the spec.MinAvailable value if it is not a percentage -func (p *podDisruptionBudget) getMinAvailableInt() int { +func (p *podDisruptionBudget) getMinAvailableInt() int32 { minAvailable := p.Spec.MinAvailable if minAvailable == nil { return values.Undefined @@ -40,21 +39,17 @@ func (p *podDisruptionBudget) getMinAvailableInt() int { if minAvailable.Type == intstr.String { return values.Undefined } - return int(minAvailable.IntVal) + return minAvailable.IntVal } // setMinAvailable applies a new value to spec.MinAvailable -func (p *podDisruptionBudget) setMinAvailable(targetMinAvailable int) error { - if targetMinAvailable > math.MaxInt32 || targetMinAvailable < 0 { - return errBoundOnScalingTargetValue - } - // #nosec G115 - p.Spec.MinAvailable = &intstr.IntOrString{IntVal: int32(targetMinAvailable), Type: intstr.Int} - return nil +func (p *podDisruptionBudget) setMinAvailable(targetMinAvailable int32) { + minAvailable := intstr.FromInt32(targetMinAvailable) + p.Spec.MinAvailable = &minAvailable } // getMaxUnavailableInt returns the spec.MaxUnavailable value if it is not a percentage -func (p *podDisruptionBudget) getMaxUnavailableInt() int { +func (p *podDisruptionBudget) getMaxUnavailableInt() int32 { maxUnavailable := p.Spec.MaxUnavailable if maxUnavailable == nil { return values.Undefined @@ -62,17 +57,13 @@ func (p *podDisruptionBudget) getMaxUnavailableInt() int { if maxUnavailable.Type == intstr.String { return values.Undefined } - return int(maxUnavailable.IntVal) + return maxUnavailable.IntVal } // setMaxUnavailable applies a new value to spec.MaxUnavailable -func (p *podDisruptionBudget) setMaxUnavailable(targetMaxUnavailable int) error { - if targetMaxUnavailable > math.MaxInt32 || targetMaxUnavailable < 0 { - return errBoundOnScalingTargetValue - } - // #nosec G115 - p.Spec.MaxUnavailable = &intstr.IntOrString{IntVal: int32(targetMaxUnavailable), Type: intstr.Int} - return nil +func (p *podDisruptionBudget) setMaxUnavailable(targetMaxUnavailable int32) { + maxUnavailable := intstr.FromInt32(targetMaxUnavailable) + p.Spec.MaxUnavailable = &maxUnavailable } // ScaleUp scales the resource up @@ -88,18 +79,12 @@ func (p *podDisruptionBudget) ScaleUp() error { maxUnavailable := p.getMaxUnavailableInt() minAvailable := p.getMinAvailableInt() if maxUnavailable != values.Undefined { - err = p.setMaxUnavailable(*originalReplicas) - if err != nil { - return fmt.Errorf("failed to set maxUnavailable for workload: %w", err) - } + p.setMaxUnavailable(*originalReplicas) removeOriginalReplicas(p) return nil } if minAvailable != values.Undefined { - err = p.setMinAvailable(*originalReplicas) - if err != nil { - return fmt.Errorf("failed to set minAvailable for workload: %w", err) - } + p.setMinAvailable(*originalReplicas) removeOriginalReplicas(p) return nil } @@ -108,7 +93,7 @@ func (p *podDisruptionBudget) ScaleUp() error { } // ScaleDown scales the resource down -func (p *podDisruptionBudget) ScaleDown(downscaleReplicas int) error { +func (p *podDisruptionBudget) ScaleDown(downscaleReplicas int32) error { maxUnavailable := p.getMaxUnavailableInt() minAvailable := p.getMinAvailableInt() if maxUnavailable != values.Undefined { @@ -116,10 +101,7 @@ func (p *podDisruptionBudget) ScaleDown(downscaleReplicas int) error { slog.Debug("workload is already scaled down, skipping", "workload", p.GetName(), "namespace", p.GetNamespace()) return nil } - err := p.setMaxUnavailable(downscaleReplicas) - if err != nil { - return fmt.Errorf("failed to set maxUnavailable for workload: %w", err) - } + p.setMaxUnavailable(downscaleReplicas) setOriginalReplicas(maxUnavailable, p) return nil } @@ -128,10 +110,7 @@ func (p *podDisruptionBudget) ScaleDown(downscaleReplicas int) error { slog.Debug("workload is already scaled down, skipping", "workload", p.GetName(), "namespace", p.GetNamespace()) return nil } - err := p.setMinAvailable(downscaleReplicas) - if err != nil { - return fmt.Errorf("failed to set minAvailable for workload: %w", err) - } + p.setMinAvailable(downscaleReplicas) setOriginalReplicas(minAvailable, p) return nil } diff --git a/internal/pkg/scalable/poddisruptionbudgets_test.go b/internal/pkg/scalable/poddisruptionbudgets_test.go index 78b3e29..514ac76 100644 --- a/internal/pkg/scalable/poddisruptionbudgets_test.go +++ b/internal/pkg/scalable/poddisruptionbudgets_test.go @@ -16,8 +16,8 @@ func TestPodDisruptionBudget_ScaleUp(t *testing.T) { name string minAvailable *intstr.IntOrString maxUnavailable *intstr.IntOrString - originalReplicas *int - wantOriginalReplicas *int + originalReplicas *int32 + wantOriginalReplicas *int32 wantMinAvailable *intstr.IntOrString wantMaxUnavailable *intstr.IntOrString }{ @@ -151,8 +151,8 @@ func TestPodDisruptionBudget_ScaleDown(t *testing.T) { name string minAvailable *intstr.IntOrString maxUnavailable *intstr.IntOrString - originalReplicas *int - wantOriginalReplicas *int + originalReplicas *int32 + wantOriginalReplicas *int32 wantMinAvailable *intstr.IntOrString wantMaxUnavailable *intstr.IntOrString }{ diff --git a/internal/pkg/scalable/prometheuses.go b/internal/pkg/scalable/prometheuses.go new file mode 100644 index 0000000..dc020bd --- /dev/null +++ b/internal/pkg/scalable/prometheuses.go @@ -0,0 +1,51 @@ +package scalable + +import ( + "context" + "fmt" + + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// getPrometheuses is the getResourceFunc for Prometheuses +func getPrometheuses(namespace string, clientsets *Clientsets, ctx context.Context) ([]Workload, error) { + var results []Workload + prometheuses, err := clientsets.Monitoring.MonitoringV1().Prometheuses(namespace).List(ctx, metav1.ListOptions{TimeoutSeconds: &timeout}) + if err != nil { + return nil, fmt.Errorf("failed to get prometheuses: %w", err) + } + for _, item := range prometheuses.Items { + results = append(results, &replicaScaledWorkload{&prometheus{item}}) + } + return results, nil +} + +// prometheus is a wrapper for monitoring.coreos.com/v1.Prometheus to implement the replicaScaledResource interface +type prometheus struct { + *monitoringv1.Prometheus +} + +// setReplicas sets the amount of replicas on the resource. Changes won't be made on Kubernetes until update() is called +func (p *prometheus) setReplicas(replicas int32) error { + p.Spec.Replicas = &replicas + return nil +} + +// getReplicas gets the current amount of replicas of the resource +func (p *prometheus) getReplicas() (int32, error) { + replicas := p.Spec.Replicas + if replicas == nil { + return 0, errNoReplicasSpecified + } + return *p.Spec.Replicas, nil +} + +// Update updates the resource with all changes made to it. It should only be called once on a resource +func (p *prometheus) Update(clientsets *Clientsets, ctx context.Context) error { + _, err := clientsets.Monitoring.MonitoringV1().Prometheuses(p.Namespace).Update(ctx, p.Prometheus, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update prometheus: %w", err) + } + return nil +} diff --git a/internal/pkg/scalable/replicaScaledWorkloads.go b/internal/pkg/scalable/replicaScaledWorkloads.go new file mode 100644 index 0000000..54e596e --- /dev/null +++ b/internal/pkg/scalable/replicaScaledWorkloads.go @@ -0,0 +1,61 @@ +package scalable + +import ( + "context" + "fmt" + "log/slog" +) + +// replicaScaledResource provides all the functions needed to scale a resource which is scaled by setting the replica count +type replicaScaledResource interface { + scalableResource + // Update updates the resource with all changes made to it. It should only be called once on a resource + Update(clientsets *Clientsets, ctx context.Context) error + // setReplicas sets the replicas of the workload + setReplicas(replicas int32) error + // getReplicas gets the replicas of the workload + getReplicas() (int32, error) +} + +// replicaScaledWorkload is a wrapper for all resources which are scaled by setting the replica count +type replicaScaledWorkload struct { + replicaScaledResource +} + +// ScaleUp scales up the underlying replicaScaledResource +func (r *replicaScaledWorkload) ScaleUp() error { + originalReplicas, err := getOriginalReplicas(r) + if err != nil { + return fmt.Errorf("failed to get original replicas for workload: %w", err) + } + if originalReplicas == nil { + slog.Debug("original replicas is not set, skipping", "workload", r.GetName(), "namespace", r.GetNamespace()) + return nil + } + + err = r.setReplicas(*originalReplicas) + if err != nil { + return fmt.Errorf("failed to set original replicas for workload: %w", err) + } + removeOriginalReplicas(r) + return nil +} + +// ScaleDown scales down the underlying replicaScaledResource +func (r *replicaScaledWorkload) ScaleDown(downscaleReplicas int32) error { + originalReplicas, err := r.getReplicas() + if err != nil { + return fmt.Errorf("failed to get original replicas for workload: %w", err) + } + if originalReplicas == downscaleReplicas { + slog.Debug("workload is already scaled down, skipping", "workload", r.GetName(), "namespace", r.GetNamespace()) + return nil + } + + err = r.setReplicas(downscaleReplicas) + if err != nil { + return fmt.Errorf("failed to set replicas for workload: %w", err) + } + setOriginalReplicas(originalReplicas, r) + return nil +} diff --git a/internal/pkg/scalable/deployments_test.go b/internal/pkg/scalable/replicaScaledWorkloads_test.go similarity index 69% rename from internal/pkg/scalable/deployments_test.go rename to internal/pkg/scalable/replicaScaledWorkloads_test.go index 89dec56..45f64d0 100644 --- a/internal/pkg/scalable/deployments_test.go +++ b/internal/pkg/scalable/replicaScaledWorkloads_test.go @@ -7,12 +7,12 @@ import ( appsv1 "k8s.io/api/apps/v1" ) -func TestDeployments_ScaleUp(t *testing.T) { +func TestReplicaScaledWorkload_ScaleUp(t *testing.T) { tests := []struct { name string replicas int32 - originalReplicas *int - wantOriginalReplicas *int + originalReplicas *int32 + wantOriginalReplicas *int32 wantReplicas int32 }{ { @@ -40,30 +40,31 @@ func TestDeployments_ScaleUp(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - d := &deployment{&appsv1.Deployment{}} - d.Spec.Replicas = &test.replicas + r := &replicaScaledWorkload{&deployment{&appsv1.Deployment{}}} + _ = r.setReplicas(test.replicas) if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, d) + setOriginalReplicas(*test.originalReplicas, r) } - err := d.ScaleUp() + err := r.ScaleUp() assert.NoError(t, err) - if assert.NotNil(t, d.Spec.Replicas) { - assert.Equal(t, test.wantReplicas, *d.Spec.Replicas) + replicas, err := r.getReplicas() + if assert.NoError(t, err) { + assert.Equal(t, test.wantReplicas, replicas) } - oringalReplicas, err := getOriginalReplicas(d) + oringalReplicas, err := getOriginalReplicas(r) assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) }) } } -func TestDeployments_ScaleDown(t *testing.T) { +func TestReplicaScaledWorkload_ScaleDown(t *testing.T) { tests := []struct { name string replicas int32 - originalReplicas *int - wantOriginalReplicas *int + originalReplicas *int32 + wantOriginalReplicas *int32 wantReplicas int32 }{ { @@ -91,18 +92,19 @@ func TestDeployments_ScaleDown(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - d := &deployment{&appsv1.Deployment{}} - d.Spec.Replicas = &test.replicas + r := &replicaScaledWorkload{&deployment{&appsv1.Deployment{}}} + _ = r.setReplicas(test.replicas) if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, d) + setOriginalReplicas(*test.originalReplicas, r) } - err := d.ScaleDown(0) + err := r.ScaleDown(0) assert.NoError(t, err) - if assert.NotNil(t, d.Spec.Replicas) { - assert.Equal(t, test.wantReplicas, *d.Spec.Replicas) + replicas, err := r.getReplicas() + if assert.NoError(t, err) { + assert.Equal(t, test.wantReplicas, replicas) } - oringalReplicas, err := getOriginalReplicas(d) + oringalReplicas, err := getOriginalReplicas(r) assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) }) diff --git a/internal/pkg/scalable/rollouts.go b/internal/pkg/scalable/rollouts.go new file mode 100644 index 0000000..f22ea2d --- /dev/null +++ b/internal/pkg/scalable/rollouts.go @@ -0,0 +1,51 @@ +package scalable + +import ( + "context" + "fmt" + + argov1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// getRollouts is the getResourceFunc for Argo Rollouts +func getRollouts(namespace string, clientsets *Clientsets, ctx context.Context) ([]Workload, error) { + var results []Workload + rollouts, err := clientsets.Argo.ArgoprojV1alpha1().Rollouts(namespace).List(ctx, metav1.ListOptions{TimeoutSeconds: &timeout}) + if err != nil { + return nil, fmt.Errorf("failed to get rollouts: %w", err) + } + for _, item := range rollouts.Items { + results = append(results, &replicaScaledWorkload{&rollout{&item}}) + } + return results, nil +} + +// rollout is a wrapper for argoproj.io/v1alpha1.Rollout to implement the replicaScaledResource interface +type rollout struct { + *argov1alpha1.Rollout +} + +// setReplicas sets the amount of replicas on the resource. Changes won't be made on Kubernetes until update() is called +func (r *rollout) setReplicas(replicas int32) error { + r.Spec.Replicas = &replicas + return nil +} + +// getReplicas gets the current amount of replicas of the resource +func (r *rollout) getReplicas() (int32, error) { + replicas := r.Spec.Replicas + if replicas == nil { + return 0, errNoReplicasSpecified + } + return *r.Spec.Replicas, nil +} + +// Update updates the resource with all changes made to it. It should only be called once on a resource +func (r *rollout) Update(clientsets *Clientsets, ctx context.Context) error { + _, err := clientsets.Argo.ArgoprojV1alpha1().Rollouts(r.Namespace).Update(ctx, r.Rollout, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update rollout: %w", err) + } + return nil +} diff --git a/internal/pkg/scalable/scaledobjects.go b/internal/pkg/scalable/scaledobjects.go index 38c10ff..ab1bcda 100644 --- a/internal/pkg/scalable/scaledobjects.go +++ b/internal/pkg/scalable/scaledobjects.go @@ -3,7 +3,6 @@ package scalable import ( "context" "fmt" - "log/slog" "strconv" "github.com/caas-team/gokubedownscaler/internal/pkg/values" @@ -24,70 +23,41 @@ func getScaledObjects(namespace string, clientsets *Clientsets, ctx context.Cont return nil, fmt.Errorf("failed to get scaledobjects: %w", err) } for _, item := range scaledobjects.Items { - results = append(results, &scaledObject{&item}) + results = append(results, &replicaScaledWorkload{&scaledObject{&item}}) } return results, nil } -// scaledObject is a wrapper for keda.sh/v1alpha1.horizontalPodAutoscaler to implement the Workload interface +// scaledObject is a wrapper for keda.sh/v1alpha1.ScaledObject to implement the replicaScaledResource interface type scaledObject struct { *kedav1alpha1.ScaledObject } -// getPauseAnnotation gets the value of keda pause annotations -func (s *scaledObject) getPauseAnnotation() (int, error) { - pausedReplicasAnnotation, ok := s.Annotations[annotationKedaPausedReplicas] - if !ok { - return values.Undefined, nil - } - pausedReplicas, err := strconv.Atoi(pausedReplicasAnnotation) - if err != nil { - return 0, fmt.Errorf("invalid value for annotation %s: %w", annotationKedaPausedReplicas, err) +// setReplicas sets the pausedReplicas annotation to the specified replicas. Changes won't be made on Kubernetes until update() is called +func (s *scaledObject) setReplicas(replicas int32) error { + if replicas == values.Undefined { // pausedAnnotation was not defined before workload was downscaled + delete(s.Annotations, annotationKedaPausedReplicas) + return nil } - return pausedReplicas, nil -} - -// setPauseAnnotation sets the value of keda pause annotations -func (s *scaledObject) setPauseAnnotation(value int) { if s.Annotations == nil { s.Annotations = map[string]string{} } - s.Annotations[annotationKedaPausedReplicas] = strconv.Itoa(value) -} - -// ScaleUp scales the resource up -func (s *scaledObject) ScaleUp() error { - originalReplicas, err := getOriginalReplicas(s) - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == nil { - slog.Debug("original replicas is not set, skipping", "workload", s.GetName(), "namespace", s.GetNamespace()) - return nil - } - if *originalReplicas == values.Undefined { // pausedAnnotation was not defined before workload was downscaled - delete(s.Annotations, annotationKedaPausedReplicas) - removeOriginalReplicas(s) - return nil - } - s.setPauseAnnotation(*originalReplicas) - removeOriginalReplicas(s) + s.Annotations[annotationKedaPausedReplicas] = strconv.Itoa(int(replicas)) return nil } -// ScaleDown scales the workload down -func (s *scaledObject) ScaleDown(downscaleReplicas int) error { - pausedReplicas, err := s.getPauseAnnotation() - if err != nil { - return fmt.Errorf("failed to get pause scaledobject annotation: %w", err) +// getReplicas gets the current value of the pausedReplicas annotation +func (s *scaledObject) getReplicas() (int32, error) { + pausedReplicasAnnotation, ok := s.Annotations[annotationKedaPausedReplicas] + if !ok { + return values.Undefined, nil } - if pausedReplicas == downscaleReplicas { - slog.Debug("workload is already scaled down, skipping", "workload", s.GetName(), "namespace", s.GetNamespace()) - return nil + pausedReplicas, err := strconv.ParseInt(pausedReplicasAnnotation, 10, 32) + if err != nil { + return 0, fmt.Errorf("invalid value for annotation %q: %w", annotationKedaPausedReplicas, err) } - s.setPauseAnnotation(downscaleReplicas) - setOriginalReplicas(pausedReplicas, s) - return nil + // #nosec G115 + return int32(pausedReplicas), nil } // Update updates the resource with all changes made to it. It should only be called once on a resource diff --git a/internal/pkg/scalable/scaledobjects_test.go b/internal/pkg/scalable/scaledobjects_test.go deleted file mode 100644 index ce981bb..0000000 --- a/internal/pkg/scalable/scaledobjects_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package scalable - -import ( - "testing" - - "github.com/caas-team/gokubedownscaler/internal/pkg/values" - kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" - "github.com/stretchr/testify/assert" -) - -func TestScaledObjects_ScaleUp(t *testing.T) { - tests := []struct { - name string - replicas int - originalReplicas *int - wantOriginalReplicas *int - wantReplicas int - }{ - { - name: "scale up", - replicas: 0, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "already scaled up", - replicas: 5, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "orignal replicas not set", - replicas: 0, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 0, - }, - { - name: "scale up replicas undefined", - replicas: values.Undefined, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "already scaled up replicas undefined", - replicas: values.Undefined, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: values.Undefined, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - so := &scaledObject{&kedav1alpha1.ScaledObject{}} - if test.replicas != values.Undefined { - so.setPauseAnnotation(test.replicas) - } - if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, so) - } - - err := so.ScaleUp() - assert.NoError(t, err) - gotReplicas, err := so.getPauseAnnotation() - assert.NoError(t, err) // Scaling set PauseAnnotation to faulty value - assert.Equal(t, test.wantReplicas, gotReplicas) - oringalReplicas, err := getOriginalReplicas(so) - assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value - assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) - }) - } -} - -func TestScaledObjects_ScaleDown(t *testing.T) { - tests := []struct { - name string - replicas int - originalReplicas *int - wantOriginalReplicas *int - wantReplicas int - }{ - { - name: "scale down", - replicas: 5, - originalReplicas: nil, - wantOriginalReplicas: intAsPointer(5), - wantReplicas: 0, - }, - { - name: "already scaled down", - replicas: 0, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 0, - }, - { - name: "orignal replicas set, but not scaled down", - replicas: 2, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: intAsPointer(2), - wantReplicas: 0, - }, - { - name: "scale down replicas undefined", - replicas: values.Undefined, - originalReplicas: nil, - wantOriginalReplicas: intAsPointer(values.Undefined), - wantReplicas: 0, - }, - { - name: "orignal replicas set, but not scaled down replicas undefined", - replicas: values.Undefined, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: intAsPointer(values.Undefined), - wantReplicas: 0, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - so := &scaledObject{&kedav1alpha1.ScaledObject{}} - if test.replicas != values.Undefined { - so.setPauseAnnotation(test.replicas) - } - if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, so) - } - - err := so.ScaleDown(0) - assert.NoError(t, err) - gotReplicas, err := so.getPauseAnnotation() - assert.NoError(t, err) // Scaling set PauseAnnotation to faulty value - assert.Equal(t, test.wantReplicas, gotReplicas) - oringalReplicas, err := getOriginalReplicas(so) - assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value - assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) - }) - } -} diff --git a/internal/pkg/scalable/stacks.go b/internal/pkg/scalable/stacks.go new file mode 100644 index 0000000..b446a92 --- /dev/null +++ b/internal/pkg/scalable/stacks.go @@ -0,0 +1,51 @@ +package scalable + +import ( + "context" + "fmt" + + zalandov1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// getStacks is the getResourceFunc for Zalando Stacks +func getStacks(namespace string, clientsets *Clientsets, ctx context.Context) ([]Workload, error) { + var results []Workload + stacks, err := clientsets.Zalando.ZalandoV1().Stacks(namespace).List(ctx, metav1.ListOptions{TimeoutSeconds: &timeout}) + if err != nil { + return nil, fmt.Errorf("failed to get stacks: %w", err) + } + for _, item := range stacks.Items { + results = append(results, &replicaScaledWorkload{&stack{&item}}) + } + return results, nil +} + +// stack is a wrapper for zalando.org/v1.Stack to implement the replicaScaledResource interface +type stack struct { + *zalandov1.Stack +} + +// setReplicas sets the amount of replicas on the resource. Changes won't be made on Kubernetes until update() is called +func (s *stack) setReplicas(replicas int32) error { + s.Spec.Replicas = &replicas + return nil +} + +// getReplicas gets the current amount of replicas of the resource +func (s *stack) getReplicas() (int32, error) { + replicas := s.Spec.Replicas + if replicas == nil { + return 0, errNoReplicasSpecified + } + return *s.Spec.Replicas, nil +} + +// Update updates the resource with all changes made to it. It should only be called once on a resource +func (s *stack) Update(clientsets *Clientsets, ctx context.Context) error { + _, err := clientsets.Zalando.ZalandoV1().Stacks(s.Namespace).Update(ctx, s.Stack, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update stack: %w", err) + } + return nil +} diff --git a/internal/pkg/scalable/statefulsets.go b/internal/pkg/scalable/statefulsets.go index 0feb1de..ee84d91 100644 --- a/internal/pkg/scalable/statefulsets.go +++ b/internal/pkg/scalable/statefulsets.go @@ -3,8 +3,6 @@ package scalable import ( "context" "fmt" - "log/slog" - "math" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,73 +16,29 @@ func getStatefulSets(namespace string, clientsets *Clientsets, ctx context.Conte return nil, fmt.Errorf("failed to get statefulsets: %w", err) } for _, item := range statefulsets.Items { - results = append(results, &statefulSet{&item}) + results = append(results, &replicaScaledWorkload{&statefulSet{&item}}) } return results, nil } -// statefulset is a wrapper for appsv1.statefulSet to implement the Workload interface +// statefulset is a wrapper for apps/v1.StatefulSet to implement the replicaScaledResource interface type statefulSet struct { *appsv1.StatefulSet } -// setReplicas sets the amount of replicas on the resource. Changes won't be made on kubernetes until update() is called -func (s *statefulSet) setReplicas(replicas int) error { - if replicas > math.MaxInt32 || replicas < 0 { - return errBoundOnScalingTargetValue - } - - // #nosec G115 - newReplicas := int32(replicas) - s.Spec.Replicas = &newReplicas +// setReplicas sets the amount of replicas on the resource. Changes won't be made on Kubernetes until update() is called +func (s *statefulSet) setReplicas(replicas int32) error { + s.Spec.Replicas = &replicas return nil } -// getCurrentReplicas gets the current amount of replicas of the resource -func (s *statefulSet) getCurrentReplicas() (int, error) { +// getReplicas gets the current amount of replicas of the resource +func (s *statefulSet) getReplicas() (int32, error) { replicas := s.Spec.Replicas if replicas == nil { return 0, errNoReplicasSpecified } - return int(*s.Spec.Replicas), nil -} - -// ScaleUp scales the resource up -func (s *statefulSet) ScaleUp() error { - originalReplicas, err := getOriginalReplicas(s) - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == nil { - slog.Debug("original replicas is not set, skipping", "workload", s.GetName(), "namespace", s.GetNamespace()) - return nil - } - - err = s.setReplicas(*originalReplicas) - if err != nil { - return fmt.Errorf("failed to set original replicas for workload: %w", err) - } - removeOriginalReplicas(s) - return nil -} - -// ScaleDown scales the resource down -func (s *statefulSet) ScaleDown(downscaleReplicas int) error { - originalReplicas, err := s.getCurrentReplicas() - if err != nil { - return fmt.Errorf("failed to get original replicas for workload: %w", err) - } - if originalReplicas == downscaleReplicas { - slog.Debug("workload is already scaled down, skipping", "workload", s.GetName(), "namespace", s.GetNamespace()) - return nil - } - - err = s.setReplicas(downscaleReplicas) - if err != nil { - return fmt.Errorf("failed to set replicas for workload: %w", err) - } - setOriginalReplicas(originalReplicas, s) - return nil + return *s.Spec.Replicas, nil } // Update updates the resource with all changes made to it. It should only be called once on a resource diff --git a/internal/pkg/scalable/statefulsets_test.go b/internal/pkg/scalable/statefulsets_test.go deleted file mode 100644 index c4b2e38..0000000 --- a/internal/pkg/scalable/statefulsets_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package scalable - -import ( - "testing" - - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" -) - -func TestStatefulSets_ScaleUp(t *testing.T) { - tests := []struct { - name string - replicas int32 - originalReplicas *int - wantOriginalReplicas *int - wantReplicas int32 - }{ - { - name: "scale up", - replicas: 0, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "already scaled up", - replicas: 5, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 5, - }, - { - name: "orignal replicas not set", - replicas: 0, - originalReplicas: nil, - wantOriginalReplicas: nil, - wantReplicas: 0, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ss := &statefulSet{&appsv1.StatefulSet{}} - ss.Spec.Replicas = &test.replicas - if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, ss) - } - - err := ss.ScaleUp() - assert.NoError(t, err) - if assert.NotNil(t, ss.Spec.Replicas) { - assert.Equal(t, test.wantReplicas, *ss.Spec.Replicas) - } - oringalReplicas, err := getOriginalReplicas(ss) - assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value - assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) - }) - } -} - -func TestStatefulSets_ScaleDown(t *testing.T) { - tests := []struct { - name string - replicas int32 - originalReplicas *int - wantOriginalReplicas *int - wantReplicas int32 - }{ - { - name: "scale down", - replicas: 5, - originalReplicas: nil, - wantOriginalReplicas: intAsPointer(5), - wantReplicas: 0, - }, - { - name: "already scaled down", - replicas: 0, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: intAsPointer(5), - wantReplicas: 0, - }, - { - name: "orignal replicas set, but not scaled down", - replicas: 2, - originalReplicas: intAsPointer(5), - wantOriginalReplicas: intAsPointer(2), - wantReplicas: 0, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ss := &statefulSet{&appsv1.StatefulSet{}} - ss.Spec.Replicas = &test.replicas - if test.originalReplicas != nil { - setOriginalReplicas(*test.originalReplicas, ss) - } - - err := ss.ScaleDown(0) - assert.NoError(t, err) - if assert.NotNil(t, ss.Spec.Replicas) { - assert.Equal(t, test.wantReplicas, *ss.Spec.Replicas) - } - oringalReplicas, err := getOriginalReplicas(ss) - assert.NoError(t, err) // Scaling set OrignialReplicas to faulty value - assertIntPointerEqual(t, test.wantOriginalReplicas, oringalReplicas) - }) - } -} diff --git a/internal/pkg/scalable/suspendScaledWorkloads.go b/internal/pkg/scalable/suspendScaledWorkloads.go new file mode 100644 index 0000000..0452327 --- /dev/null +++ b/internal/pkg/scalable/suspendScaledWorkloads.go @@ -0,0 +1,31 @@ +package scalable + +import ( + "context" +) + +// suspendScaledResource provides all the functions needed to scale a resource which is scaled by setting a suspend field +type suspendScaledResource interface { + scalableResource + // Update updates the resource with all changes made to it. It should only be called once on a resource + Update(clientsets *Clientsets, ctx context.Context) error + // setSuspend sets the value of the suspend field on the workload + setSuspend(suspend bool) +} + +// suspendScaledWorkload is a wrapper for all resources which are scaled by setting a suspend field +type suspendScaledWorkload struct { + suspendScaledResource +} + +// ScaleUp scales up the underlying suspendScaledResource +func (r *suspendScaledWorkload) ScaleUp() error { + r.setSuspend(false) + return nil +} + +// ScaleDown scales down the underlying suspendScaledResource +func (r *suspendScaledWorkload) ScaleDown(_ int32) error { + r.setSuspend(true) + return nil +} diff --git a/internal/pkg/scalable/cronjobs_test.go b/internal/pkg/scalable/suspendScaledWorkloads_test.go similarity index 86% rename from internal/pkg/scalable/cronjobs_test.go rename to internal/pkg/scalable/suspendScaledWorkloads_test.go index 226d21b..89361c4 100644 --- a/internal/pkg/scalable/cronjobs_test.go +++ b/internal/pkg/scalable/suspendScaledWorkloads_test.go @@ -7,7 +7,7 @@ import ( batch "k8s.io/api/batch/v1" ) -func TestCronJob_ScaleUp(t *testing.T) { +func TestSuspendScaledWorkload_ScaleUp(t *testing.T) { tests := []struct { name string suspend *bool @@ -34,15 +34,16 @@ func TestCronJob_ScaleUp(t *testing.T) { t.Run(test.name, func(t *testing.T) { cj := cronJob{&batch.CronJob{}} cj.Spec.Suspend = test.suspend + s := suspendScaledWorkload{&cj} - err := cj.ScaleUp() + err := s.ScaleUp() assert.NoError(t, err) assertBoolPointerEqual(t, test.wantSuspend, cj.Spec.Suspend) }) } } -func TestCronJob_ScaleDown(t *testing.T) { +func TestSuspendScaledWorkload_ScaleDown(t *testing.T) { tests := []struct { name string suspend *bool @@ -69,8 +70,9 @@ func TestCronJob_ScaleDown(t *testing.T) { t.Run(test.name, func(t *testing.T) { cj := cronJob{&batch.CronJob{}} cj.Spec.Suspend = test.suspend + s := suspendScaledWorkload{&cj} - err := cj.ScaleDown(0) + err := s.ScaleDown(0) assert.NoError(t, err) assertBoolPointerEqual(t, test.wantSuspend, cj.Spec.Suspend) }) diff --git a/internal/pkg/scalable/util.go b/internal/pkg/scalable/util.go index d9f6147..f81eedb 100644 --- a/internal/pkg/scalable/util.go +++ b/internal/pkg/scalable/util.go @@ -64,27 +64,29 @@ func isWorkloadExcluded(workload Workload, excludedWorkloads values.RegexList) b } // setOriginalReplicas sets the original replicas annotation on the workload -func setOriginalReplicas(originalReplicas int, workload Workload) { +func setOriginalReplicas(originalReplicas int32, workload Workload) { annotations := workload.GetAnnotations() if annotations == nil { annotations = map[string]string{} } - annotations[annotationOriginalReplicas] = strconv.Itoa(originalReplicas) + annotations[annotationOriginalReplicas] = strconv.Itoa(int(originalReplicas)) workload.SetAnnotations(annotations) } // getOriginalReplicas gets the original replicas annotation on the workload. nil is undefined -func getOriginalReplicas(workload Workload) (*int, error) { +func getOriginalReplicas(workload Workload) (*int32, error) { annotations := workload.GetAnnotations() originalReplicasString, ok := annotations[annotationOriginalReplicas] if !ok { return nil, nil } - originalReplicas, err := strconv.Atoi(originalReplicasString) + originalReplicas, err := strconv.ParseInt(originalReplicasString, 10, 32) if err != nil { return nil, fmt.Errorf("failed to parse original replicas annotation on workload: %w", err) } - return &originalReplicas, nil + // #nosec G115 + result := int32(originalReplicas) + return &result, nil } // removeOriginalReplicas removes the annotationOriginalReplicas from the workload diff --git a/internal/pkg/scalable/util_test.go b/internal/pkg/scalable/util_test.go index 82f6d90..c523e94 100644 --- a/internal/pkg/scalable/util_test.go +++ b/internal/pkg/scalable/util_test.go @@ -13,24 +13,24 @@ import ( func TestFilterExcluded(t *testing.T) { // define some example objects to use type ns struct { - deployment1 deployment - deployment2 deployment - labeledDeployment deployment + deployment1 Workload + deployment2 Workload + labeledDeployment Workload } ns1 := ns{ - deployment1: deployment{Deployment: &appsv1.Deployment{ + deployment1: &replicaScaledWorkload{&deployment{Deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "Deployment1", Namespace: "Namespace1", }, - }}, - deployment2: deployment{Deployment: &appsv1.Deployment{ + }}}, + deployment2: &replicaScaledWorkload{&deployment{Deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "Deployment2", Namespace: "Namespace1", }, - }}, - labeledDeployment: deployment{Deployment: &appsv1.Deployment{ + }}}, + labeledDeployment: &replicaScaledWorkload{&deployment{Deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "LabeledDeployment", Namespace: "Namespace1", @@ -38,30 +38,15 @@ func TestFilterExcluded(t *testing.T) { "label": "value", }, }, - }}, + }}}, } ns2 := ns{ - deployment1: deployment{Deployment: &appsv1.Deployment{ + deployment1: &replicaScaledWorkload{&deployment{Deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "Deployment1", Namespace: "Namespace2", }, - }}, - deployment2: deployment{Deployment: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "Deployment2", - Namespace: "Namespace2", - }, - }}, - labeledDeployment: deployment{Deployment: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "LabeledDeployment", - Namespace: "Namespace2", - Labels: map[string]string{ - "label2": "value", - }, - }, - }}, + }}}, } tests := []struct { name string @@ -73,35 +58,35 @@ func TestFilterExcluded(t *testing.T) { }{ { name: "none set", - workloads: []Workload{&ns1.deployment1, &ns1.deployment2, &ns2.deployment1}, + workloads: []Workload{ns1.deployment1, ns1.deployment2, ns2.deployment1}, includeLabels: nil, excludedNamespaces: nil, excludedWorkloads: nil, - want: []Workload{&ns1.deployment1, &ns1.deployment2, &ns2.deployment1}, + want: []Workload{ns1.deployment1, ns1.deployment2, ns2.deployment1}, }, { name: "includeLabels", - workloads: []Workload{&ns1.deployment1, &ns1.deployment2, &ns1.labeledDeployment}, + workloads: []Workload{ns1.deployment1, ns1.deployment2, ns1.labeledDeployment}, includeLabels: values.RegexList{regexp.MustCompile(".*")}, // match any label excludedNamespaces: nil, excludedWorkloads: nil, - want: []Workload{&ns1.labeledDeployment}, + want: []Workload{ns1.labeledDeployment}, }, { name: "excludeNamespaces", - workloads: []Workload{&ns1.deployment1, &ns1.deployment2, &ns2.deployment1}, + workloads: []Workload{ns1.deployment1, ns1.deployment2, ns2.deployment1}, includeLabels: nil, excludedNamespaces: values.RegexList{regexp.MustCompile("Namespace1")}, // exclude Namespace1 excludedWorkloads: nil, - want: []Workload{&ns2.deployment1}, + want: []Workload{ns2.deployment1}, }, { name: "excludeWorkloads", - workloads: []Workload{&ns1.deployment1, &ns1.deployment2, &ns2.deployment1}, + workloads: []Workload{ns1.deployment1, ns1.deployment2, ns2.deployment1}, includeLabels: nil, excludedNamespaces: nil, excludedWorkloads: values.RegexList{regexp.MustCompile("Deployment1")}, // exclude Deployment1 - want: []Workload{&ns1.deployment2}, + want: []Workload{ns1.deployment2}, }, } for _, test := range tests { diff --git a/internal/pkg/scalable/scalableResource.go b/internal/pkg/scalable/workload.go similarity index 65% rename from internal/pkg/scalable/scalableResource.go rename to internal/pkg/scalable/workload.go index 3d3f5e9..978064e 100644 --- a/internal/pkg/scalable/scalableResource.go +++ b/internal/pkg/scalable/workload.go @@ -4,26 +4,26 @@ import ( "context" "errors" + argo "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" keda "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" - "k8s.io/client-go/kubernetes" - + monitoring "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned" + zalando "github.com/zalando-incubator/stackset-controller/pkg/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" ) var ( - timeout int64 = 30 - errNoReplicasSpecified = errors.New("error: workload has no replicas set") - errNoMinReplicasSpecified = errors.New("error: workload has no minimum replicas set") - errBoundOnScalingTargetValue = errors.New("error: replicas can only be set to a 32-bit integer >= 0 (or >= 1 for HPAs)") + timeout int64 = 30 + errNoReplicasSpecified = errors.New("error: workload has no replicas set") ) // getResourceFunc is a function that gets a specific resource as a Workload type getResourceFunc func(namespace string, clientsets *Clientsets, ctx context.Context) ([]Workload, error) -// GetResource maps the resource name to an implementation specific getResourceFunc -var GetResource = map[string]getResourceFunc{ +// GetWorkloads maps the resource name to an implementation specific getResourceFunc +var GetWorkloads = map[string]getResourceFunc{ "deployments": getDeployments, "statefulsets": getStatefulSets, "cronjobs": getCronJobs, @@ -32,10 +32,13 @@ var GetResource = map[string]getResourceFunc{ "poddisruptionbudgets": getPodDisruptionBudgets, "horizontalpodautoscalers": getHorizontalPodAutoscalers, "scaledobjects": getScaledObjects, + "rollouts": getRollouts, + "stacks": getStacks, + "prometheuses": getPrometheuses, } -// Workload is an interface for a scalable resource. It holds shared resource specific functions -type Workload interface { +// scalableResource provides all functions needed to scale any type of resource +type scalableResource interface { // GetAnnotations gets the annotations of the resource GetAnnotations() map[string]string // GetNamespace gets the namespace of the resource @@ -50,17 +53,25 @@ type Workload interface { GetLabels() map[string]string // GetCreationTimestamp gets the creation timestamp of the workload GetCreationTimestamp() metav1.Time - // SetAnnotations sets the annotations on the resource. Changes won't be made on kubernetes until update() is called + // SetAnnotations sets the annotations on the resource. Changes won't be made on Kubernetes until update() is called SetAnnotations(annotations map[string]string) +} + +// Workload provides all functions needed to scale the workload +type Workload interface { + scalableResource // Update updates the resource with all changes made to it. It should only be called once on a resource Update(clientsets *Clientsets, ctx context.Context) error // ScaleUp scales up the workload ScaleUp() error // ScaleDown scales down the workload - ScaleDown(downscaleReplicas int) error + ScaleDown(downscaleReplicas int32) error } type Clientsets struct { Kubernetes *kubernetes.Clientset Keda *keda.Clientset + Argo *argo.Clientset + Zalando *zalando.Clientset + Monitoring *monitoring.Clientset } diff --git a/internal/pkg/values/duration.go b/internal/pkg/values/durationValue.go similarity index 56% rename from internal/pkg/values/duration.go rename to internal/pkg/values/durationValue.go index da57b3c..cbca185 100644 --- a/internal/pkg/values/duration.go +++ b/internal/pkg/values/durationValue.go @@ -6,15 +6,15 @@ import ( "time" ) -// Duration is an alias for time.Duration with a Set function that allows for durations without a unit -type Duration time.Duration +// DurationValue is an alias for time.DurationValue with a Set function that allows for durations without a unit +type DurationValue time.Duration // Set converts the string value into a duration -func (d *Duration) Set(value string) error { +func (d *DurationValue) Set(value string) error { // try parsing as integer seconds seconds, err := strconv.Atoi(value) if err == nil { - *d = Duration(time.Duration(seconds) * time.Second) + *d = DurationValue(time.Duration(seconds) * time.Second) return nil } @@ -24,10 +24,10 @@ func (d *Duration) Set(value string) error { return fmt.Errorf("failed parsing duration: %w", err) } - *d = Duration(duration) + *d = DurationValue(duration) return nil } -func (d *Duration) String() string { +func (d *DurationValue) String() string { return fmt.Sprint(time.Duration(*d).String()) } diff --git a/internal/pkg/values/int32Value.go b/internal/pkg/values/int32Value.go new file mode 100644 index 0000000..a4c3cc4 --- /dev/null +++ b/internal/pkg/values/int32Value.go @@ -0,0 +1,20 @@ +package values + +import ( + "fmt" + "strconv" +) + +type Int32Value int32 + +func (i *Int32Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 32) + if err != nil { + return fmt.Errorf("failed to parse as int32: %w", err) + } + // #nosec G115 + *i = Int32Value(v) + return nil +} + +func (i *Int32Value) String() string { return strconv.Itoa(int(*i)) } diff --git a/internal/pkg/values/layer.go b/internal/pkg/values/layer.go index 2bb477c..d01f507 100644 --- a/internal/pkg/values/layer.go +++ b/internal/pkg/values/layer.go @@ -38,16 +38,16 @@ func NewLayer() Layer { // Layer represents a value Layer type Layer struct { - DownscalePeriod timeSpans // periods to downscale in - DownTime timeSpans // within these timespans workloads will be scaled down, outside of them they will be scaled up - UpscalePeriod timeSpans // periods to upscale in - UpTime timeSpans // within these timespans workloads will be scaled up, outside of them they will be scaled down - Exclude triStateBool // if workload should be excluded - ExcludeUntil time.Time // until when the workload should be excluded - ForceUptime triStateBool // force workload into a uptime state - ForceDowntime triStateBool // force workload into a downtime state - DownscaleReplicas int // the replicas to scale down to - GracePeriod Duration // grace period until new workloads will be scaled down + DownscalePeriod timeSpans // periods to downscale in + DownTime timeSpans // within these timespans workloads will be scaled down, outside of them they will be scaled up + UpscalePeriod timeSpans // periods to upscale in + UpTime timeSpans // within these timespans workloads will be scaled up, outside of them they will be scaled down + Exclude triStateBool // if workload should be excluded + ExcludeUntil time.Time // until when the workload should be excluded + ForceUptime triStateBool // force workload into a uptime state + ForceDowntime triStateBool // force workload into a downtime state + DownscaleReplicas int32 // the replicas to scale down to + GracePeriod time.Duration // grace period until new workloads will be scaled down } // isScalingExcluded checks if scaling is excluded, nil represents a not set state @@ -152,7 +152,7 @@ func (l Layers) GetCurrentScaling() scaling { } // GetDownscaleReplicas gets the downscale replicas of the first layer that implements downscale replicas -func (l Layers) GetDownscaleReplicas() (int, error) { +func (l Layers) GetDownscaleReplicas() (int32, error) { for _, layer := range l { downscaleReplicas := layer.DownscaleReplicas if downscaleReplicas == Undefined { @@ -179,7 +179,7 @@ func (l Layers) GetExcluded() bool { // IsInGracePeriod gets the grace period of the uppermost layer that has it set func (l Layers) IsInGracePeriod(timeAnnotation string, workloadAnnotations map[string]string, creationTime time.Time, logEvent resourceLogger, ctx context.Context) (bool, error) { - var gracePeriod Duration = Undefined + var gracePeriod time.Duration = Undefined for _, layer := range l { if layer.GracePeriod == Undefined { continue diff --git a/internal/pkg/values/stringlist.go b/internal/pkg/values/stringlistValue.go similarity index 56% rename from internal/pkg/values/stringlist.go rename to internal/pkg/values/stringlistValue.go index db25ec8..56df244 100644 --- a/internal/pkg/values/stringlist.go +++ b/internal/pkg/values/stringlistValue.go @@ -5,10 +5,10 @@ import ( "strings" ) -// StringList is an alias for []string with a Set funciton for the flag package -type StringList []string +// StringListValue is an alias for []string with a Set funciton for the flag package +type StringListValue []string -func (s *StringList) Set(text string) error { +func (s *StringListValue) Set(text string) error { entries := strings.Split(text, ",") var trimmedEntries []string for _, entry := range entries { @@ -18,6 +18,6 @@ func (s *StringList) Set(text string) error { return nil } -func (s *StringList) String() string { +func (s *StringListValue) String() string { return fmt.Sprint(*s) } diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index 8562894..0bb4ca0 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -95,15 +95,17 @@ func GetLayerFromAnnotations(annotations map[string]string, logEvent resourceLog return result, fmt.Errorf("failed to parse %q annotation: %w", annotationForceDowntime, err) } } - if downscaleReplicas, ok := annotations[annotationDownscaleReplicas]; ok { - result.DownscaleReplicas, err = strconv.Atoi(downscaleReplicas) + if downscaleReplicasString, ok := annotations[annotationDownscaleReplicas]; ok { + downscaleReplicas, err := strconv.ParseInt(downscaleReplicasString, 10, 32) if err != nil { logEvent.ErrorInvalidAnnotation(annotationDownscaleReplicas, fmt.Sprintf("failed to parse %q annotation: %s", annotationDownscaleReplicas, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationDownscaleReplicas, err) } + // #nosec G115 + result.DownscaleReplicas = int32(downscaleReplicas) } if gracePeriod, ok := annotations[annotationGracePeriod]; ok { - err = result.GracePeriod.Set(gracePeriod) + err = (*DurationValue)(&result.GracePeriod).Set(gracePeriod) if err != nil { logEvent.ErrorInvalidAnnotation(annotationGracePeriod, fmt.Sprintf("failed to parse %q annotation: %s", annotationGracePeriod, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationGracePeriod, err)