Repository: kedacore/http-add-on
Pull request #852 (2023-12-09 13:05:49)

feat: Add support for any resource which implementes `/scale` subresource
This PR enables support for more workload types than just `Deployment`. 
To achieve this, I've changed the logic for detecting if the workload is active or not, using the active endpoints instead of checking deployment replica count. 
As we are routing HTTP traffic, I think that we can assume that scaled workloads always have at least one available endpoint (a service is a requirement, so maybe the endpoints have empty subsets, but the endpoints should be there).

I've added also some e2e tests for new cases

### Checklist

- [x] Commits are signed with Developer Certificate of Origin (DCO)
- [x] Changelog has been updated and is aligned with our [changelog requirements](https://github.com/kedacore/keda/blob/main/CONTRIBUTING.md#Changelog)
- [x] Any necessary documentation is added, such as:
  - [`README.md`](/README.md)
  - [The `docs/` directory](./docs)
  - [The docs repo](https://github.com/kedacore/keda-docs)

Fixes https://github.com/kedacore/http-add-on/issues/438


JorTurFer: PTAL: @tomkerkhove  @t0rr3sp3dr0 

Patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index d92d4a2..c21d050 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,7 +3,7 @@
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 #-------------------------------------------------------------------------------------------------------------
 
-FROM golang:1.20.8
+FROM golang:1.21.5
 
 # Avoid warnings by switching to noninteractive
 ENV DEBIAN_FRONTEND=noninteractive
diff --git a/.github/workflows/build_canary.yml b/.github/workflows/build_canary.yml
index 90468aa..59ebd69 100644
--- a/.github/workflows/build_canary.yml
+++ b/.github/workflows/build_canary.yml
@@ -8,7 +8,7 @@ on:
 jobs:
   build:
     runs-on: ubuntu-20.04
-    container: ghcr.io/kedacore/keda-tools:1.20.8
+    container: ghcr.io/kedacore/keda-tools:1.21.5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
 
diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml
index 38b817e..3c5a502 100644
--- a/.github/workflows/build_release.yml
+++ b/.github/workflows/build_release.yml
@@ -7,7 +7,7 @@ on:
 jobs:
   build:
     runs-on: ubuntu-20.04
-    container: ghcr.io/kedacore/keda-tools:1.20.8
+    container: ghcr.io/kedacore/keda-tools:1.21.5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
 
diff --git a/.github/workflows/images.yaml b/.github/workflows/images.yaml
index 0f3b1b0..6168567 100644
--- a/.github/workflows/images.yaml
+++ b/.github/workflows/images.yaml
@@ -10,7 +10,7 @@ concurrency:
 jobs:
   build_scaler:
     runs-on: ubuntu-latest
-    container: ghcr.io/kedacore/keda-tools:1.20.8
+    container: ghcr.io/kedacore/keda-tools:1.21.5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
       - name: Register workspace path
@@ -22,7 +22,7 @@ jobs:
 
   build_operator:
     runs-on: ubuntu-latest
-    container: ghcr.io/kedacore/keda-tools:1.20.8
+    container: ghcr.io/kedacore/keda-tools:1.21.5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
       - name: Register workspace path
@@ -34,7 +34,7 @@ jobs:
 
   build_interceptor:
     runs-on: ubuntu-latest
-    container: ghcr.io/kedacore/keda-tools:1.20.8
+    container: ghcr.io/kedacore/keda-tools:1.21.5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
       - name: Register workspace path
diff --git a/.golangci.yml b/.golangci.yml
index 70ede47..2093c26 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -49,7 +49,6 @@ issues:
   exclude-rules:
     - path: _test\.go
       linters:
-        - gomnd
         - dupl
         - unparam
   # Exclude gci check for //+kubebuilder:scaffold:imports comments. Waiting to
@@ -60,6 +59,11 @@ issues:
     - path: operator/main.go
       linters:
         - gci
+  # Exlude httpso.Spec.ScaleTargetRef.Deployment until we remove it in v0.9.0
+    - linters:
+       - staticcheck
+      text: "SA1019: httpso.Spec.ScaleTargetRef.Deployment"
+
 linters-settings:
   funlen:
     lines: 80
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f35ef8..5ccd278 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,7 +21,7 @@ This changelog keeps track of work items that have been completed and are ready
 
 ### New
 
-- **General**: TODO ([#TODO](https://github.com/kedacore/http-add-on/issues/TODO))
+- **General**: Support any resource which implements `/scale` subresource ([#438](https://github.com/kedacore/http-add-on/issues/438))
 
 ### Improvements
 
@@ -37,7 +37,7 @@ You can find all deprecations in [this overview](https://github.com/kedacore/htt
 
 New deprecation(s):
 
-- **General**: TODO ([#TODO](https://github.com/kedacore/http-add-on/issues/TODO))
+- **General**: Deprecated `KEDA_HTTP_DEPLOYMENT_CACHE_POLLING_INTERVAL_MS` in favor of `KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS` ([#438](https://github.com/kedacore/http-add-on/issues/438))
 
 Previously announced deprecation(s):
 
@@ -45,7 +45,7 @@ Previously announced deprecation(s):
 
 ### Other
 
-- **General**: TODO ([#TODO](https://github.com/kedacore/http-add-on/issues/TODO))
+- **General**: Bump golang version ([#853](https://github.com/kedacore/http-add-on/pull/853))
 
 ## v0.6.0
 
diff --git a/Makefile b/Makefile
index dd6a2ad..3f3242f 100644
--- a/Makefile
+++ b/Makefile
@@ -140,6 +140,9 @@ KUSTOMIZE = $(shell pwd)/bin/kustomize
 kustomize: ## Download kustomize locally if necessary.
 	GOBIN=$(shell pwd)/bin go install sigs.k8s.io/kustomize/kustomize/v5
 
+install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
+	$(KUSTOMIZE) build config/crd | kubectl apply -f -
+
 deploy: manifests kustomize ## Deploy to the K8s cluster specified in ~/.kube/config.
 	cd config/interceptor && \
 	$(KUSTOMIZE) edit set image ghcr.io/kedacore/http-add-on-interceptor=${IMAGE_INTERCEPTOR_VERSIONED_TAG}
diff --git a/config/crd/bases/http.keda.sh_httpscaledobjects.yaml b/config/crd/bases/http.keda.sh_httpscaledobjects.yaml
index 8939939..85097bb 100644
--- a/config/crd/bases/http.keda.sh_httpscaledobjects.yaml
+++ b/config/crd/bases/http.keda.sh_httpscaledobjects.yaml
@@ -102,9 +102,15 @@ spec:
                 description: The name of the deployment to route HTTP requests to
                   (and to autoscale).
                 properties:
+                  apiVersion:
+                    type: string
                   deployment:
-                    description: The name of the deployment to scale according to
-                      HTTP traffic
+                    description: 'Deprecated: The name of the deployment to scale
+                      according to HTTP traffic'
+                    type: string
+                  kind:
+                    type: string
+                  name:
                     type: string
                   port:
                     description: The port to route to
@@ -114,7 +120,6 @@ spec:
                     description: The name of the service to route to
                     type: string
                 required:
-                - deployment
                 - port
                 - service
                 type: object
diff --git a/config/interceptor/deployment.yaml b/config/interceptor/deployment.yaml
index 3d62e98..cc7c798 100644
--- a/config/interceptor/deployment.yaml
+++ b/config/interceptor/deployment.yaml
@@ -38,7 +38,7 @@ spec:
           value: "500ms"
         - name: KEDA_CONDITION_WAIT_TIMEOUT
           value: "20s"
-        - name: KEDA_HTTP_DEPLOYMENT_CACHE_POLLING_INTERVAL_MS
+        - name: KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS
           value: "1000"
         - name: KEDA_HTTP_FORCE_HTTP2
           value: "false"
diff --git a/config/interceptor/role.yaml b/config/interceptor/role.yaml
index b8d3428..51c0d07 100644
--- a/config/interceptor/role.yaml
+++ b/config/interceptor/role.yaml
@@ -5,9 +5,9 @@ metadata:
   name: interceptor
 rules:
 - apiGroups:
-  - apps
+  - ""
   resources:
-  - deployments
+  - endpoints
   verbs:
   - get
   - list
diff --git a/config/scaler/role.yaml b/config/scaler/role.yaml
index 71a257c..98f75fc 100644
--- a/config/scaler/role.yaml
+++ b/config/scaler/role.yaml
@@ -12,14 +12,6 @@ rules:
   - get
   - list
   - watch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - get
-  - list
-  - watch
 - apiGroups:
   - http.keda.sh
   resources:
diff --git a/docs/design.md b/docs/design.md
index 8b08344..30fa1e4 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -23,7 +23,7 @@ The [operator](../operator) runs inside the Kubernetes namespace to which they'r
 
 - Update an internal routing table that maps incoming HTTP hostnames to internal applications.
 - Furnish this routing table information to interceptors so that they can properly route requests.
-- Create a [`ScaledObject`](https://keda.sh/docs/2.3/concepts/scaling-deployments/#scaledobject-spec) for the `Deployment` specified in the `HTTPScaledObject` resource.
+- Create a [`ScaledObject`](https://keda.sh/docs/latest/concepts/scaling-deployments/#scaledobject-spec) for the `Deployment` specified in the `HTTPScaledObject` resource.
 
 When the `HTTPScaledObject` is deleted, the operator reverses all of the aforementioned actions.
 
diff --git a/docs/faq.md b/docs/faq.md
index 2df34ab..cb42d95 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -2,7 +2,7 @@
 
 ## Why does this project route HTTP requests?
 
-In order to autoscale a `Deployment`, KEDA-HTTP needs to be involved with routing HTTP requests. However, the project is minimally involved with routing and we're working on ways to get out of the "critical path" of an HTTP request as much as possible. For more information, please see our [scope](./scope.md) document.
+In order to autoscale a workload, KEDA-HTTP needs to be involved with routing HTTP requests. However, the project is minimally involved with routing and we're working on ways to get out of the "critical path" of an HTTP request as much as possible. For more information, please see our [scope](./scope.md) document.
 
 ## How is this project similar or different from [Osiris](https://github.com/deislabs/osiris)?
 
@@ -13,7 +13,7 @@ Osiris and KEDA-HTTP have similar features:
 
 However, Osiris and KEDA-HTTP differ in several ways:
 
-- Autoscaling concerns are implemented separately from the application resources - `Service`, `Ingress`, `Deployment` and more in KEDA-HTTP. With Osiris, those concerns are baked into each app resource.
+- Autoscaling concerns are implemented separately from the application resources - `Service`, `Ingress`, `Deployment`, `StatefulSet`, `/scale` and more in KEDA-HTTP. With Osiris, those concerns are baked into each app resource.
 - The KEDA-HTTP operator can automatically deploy and configure networking and compute resources necessary for an HTTP application to autoscale. Osiris does not have this functionality.
 - Osiris is currently archived in GitHub.
 
diff --git a/docs/ref/v0.6.0/http_scaled_object.md b/docs/ref/v0.6.0/http_scaled_object.md
new file mode 100644
index 0000000..1504e66
--- /dev/null
+++ b/docs/ref/v0.6.0/http_scaled_object.md
@@ -0,0 +1,63 @@
+# The `HTTPScaledObject`
+
+>This document reflects the specification of the `HTTPScaledObject` resource for the `v0.7.0` version.
+
+Each `HTTPScaledObject` looks approximately like the below:
+
+```yaml
+kind: HTTPScaledObject
+apiVersion: http.keda.sh/v1alpha1
+metadata:
+    name: xkcd
+spec:
+    hosts:
+    - myhost.com
+    pathPrefixes:
+    - /test
+    scaleTargetRef:
+        deployment: xkcd
+        service: xkcd
+        port: 8080
+    replicas:
+        min: 5
+        max: 10
+
+```
+
+This document is a narrated reference guide for the `HTTPScaledObject`, and we'll focus on the `spec` field.
+
+## `hosts`
+
+These are the hosts to apply this scaling rule to. All incoming requests with one of these values in their `Host` header will be forwarded to the `Service` and port specified in the below `scaleTargetRef`, and that same `scaleTargetRef`'s workload will be scaled accordingly.
+
+## `pathPrefixes`
+
+These are the paths to apply this scaling rule to. All incoming requests with one of these values as path prefix will be forwarded to the `Service` and port specified in the below `scaleTargetRef`, and that same `scaleTargetRef`'s workload will be scaled accordingly.
+
+## `scaleTargetRef`
+
+This is the primary and most important part of the `spec` because it describes:
+
+1. The incoming host to apply this scaling rule to.
+2. What workload to scale.
+3. The service to which to route HTTP traffic.
+
+### `deployment`
+
+This is the name of the `Deployment` to scale. It must exist in the same namespace as this `HTTPScaledObject` and shouldn't be managed by any other autoscaling system. This means that there should not be any `ScaledObject` already created for this `Deployment`. The HTTP Add-on will manage a `ScaledObject` internally.
+
+### `service`
+
+This is the name of the service to route traffic to. The add-on will create autoscaling and routing components that route to this `Service`. It must exist in the same namespace as this `HTTPScaledObject` and should route to the same `Deployment` as you entered in the `deployment` field.
+
+### `port`
+
+This is the port to route to on the service that you specified in the `service` field. It should be exposed on the service and should route to a valid `containerPort` on the `Deployment` you gave in the `deployment` field.
+
+### `targetPendingRequests`
+
+>Default: 100
+
+This is the number of _pending_ (or in-progress) requests that your application needs to have before the HTTP Add-on will scale it. Conversely, if your application has below this number of pending requests, the HTTP add-on will scale it down.
+
+For example, if you set this field to 100, the HTTP Add-on will scale your app up if it sees that there are 200 in-progress requests. On the other hand, it will scale down if it sees that there are only 20 in-progress requests. Note that it will _never_ scale your app to zero replicas unless there are _no_ requests in-progress. Even if you set this value to a very high number and only have a single in-progress request, your app will still have one replica.
diff --git a/docs/ref/v0.7.0/http_scaled_object.md b/docs/ref/v0.7.0/http_scaled_object.md
new file mode 100644
index 0000000..2ae445b
--- /dev/null
+++ b/docs/ref/v0.7.0/http_scaled_object.md
@@ -0,0 +1,77 @@
+# The `HTTPScaledObject`
+
+>This document reflects the specification of the `HTTPScaledObject` resource for the `v0.7.0` version.
+
+Each `HTTPScaledObject` looks approximately like the below:
+
+```yaml
+kind: HTTPScaledObject
+apiVersion: http.keda.sh/v1alpha1
+metadata:
+    name: xkcd
+spec:
+    hosts:
+    - myhost.com
+    pathPrefixes:
+    - /test
+    scaleTargetRef:
+        name: xkcd
+        kind: Deployment
+        apiVersion: apps/v1
+        service: xkcd
+        port: 8080
+    replicas:
+        min: 5
+        max: 10
+
+```
+
+This document is a narrated reference guide for the `HTTPScaledObject`, and we'll focus on the `spec` field.
+
+## `hosts`
+
+These are the hosts to apply this scaling rule to. All incoming requests with one of these values in their `Host` header will be forwarded to the `Service` and port specified in the below `scaleTargetRef`, and that same `scaleTargetRef`'s workload will be scaled accordingly.
+
+## `pathPrefixes`
+
+These are the paths to apply this scaling rule to. All incoming requests with one of these values as path prefix will be forwarded to the `Service` and port specified in the below `scaleTargetRef`, and that same `scaleTargetRef`'s workload will be scaled accordingly.
+
+## `scaleTargetRef`
+
+This is the primary and most important part of the `spec` because it describes:
+
+1. The incoming host to apply this scaling rule to.
+2. What workload to scale.
+3. The service to which to route HTTP traffic.
+
+### `deployment` (DEPRECTATED: removed as part of v0.9.0)
+
+This is the name of the `Deployment` to scale. It must exist in the same namespace as this `HTTPScaledObject` and shouldn't be managed by any other autoscaling system. This means that there should not be any `ScaledObject` already created for this `Deployment`. The HTTP Add-on will manage a `ScaledObject` internally.
+
+### `name`
+
+This is the name of the workload to scale. It must exist in the same namespace as this `HTTPScaledObject` and shouldn't be managed by any other autoscaling system. This means that there should not be any `ScaledObject` already created for this workload. The HTTP Add-on will manage a `ScaledObject` internally.
+
+### `kind`
+
+This is the kind of the workload to scale.
+
+### `apiVersion`
+
+This is the apiVersion of the workload to scale.
+
+### `service`
+
+This is the name of the service to route traffic to. The add-on will create autoscaling and routing components that route to this `Service`. It must exist in the same namespace as this `HTTPScaledObject` and should route to the same `Deployment` as you entered in the `deployment` field.
+
+### `port`
+
+This is the port to route to on the service that you specified in the `service` field. It should be exposed on the service and should route to a valid `containerPort` on the `Deployment` you gave in the `deployment` field.
+
+### `targetPendingRequests`
+
+>Default: 100
+
+This is the number of _pending_ (or in-progress) requests that your application needs to have before the HTTP Add-on will scale it. Conversely, if your application has below this number of pending requests, the HTTP add-on will scale it down.
+
+For example, if you set this field to 100, the HTTP Add-on will scale your app up if it sees that there are 200 in-progress requests. On the other hand, it will scale down if it sees that there are only 20 in-progress requests. Note that it will _never_ scale your app to zero replicas unless there are _no_ requests in-progress. Even if you set this value to a very high number and only have a single in-progress request, your app will still have one replica.
diff --git a/docs/use_cases.md b/docs/use_cases.md
index c35a2b0..b436bb4 100644
--- a/docs/use_cases.md
+++ b/docs/use_cases.md
@@ -19,11 +19,11 @@ Moving this application to Kubernetes may make sense for several reasons, but th
 
 If the application _is_ being moved to Kubernetes, you would follow these steps to get it autoscaling and routing with KEDA-HTTP:
 
-- Create a `Deployment` and `Service`
+- Create a workload and `Service`
 - [Install](./install.md) the HTTP Add-on
-- Create a single `HTTPScaledObject` in the same namespace as the `Deployment` and `Service` you created
+- Create a single `HTTPScaledObject` in the same namespace as the workload and `Service` you created
 
-At that point, the operator will create the proper autoscaling and routing infrastructure behind the scenes and the application will be ready to scale.
+At that point, the operator will create the proper autoscaling and routing infrastructure behind the scenes and the application will be ready to scale. Any request received by the interceptor with the proper host will be routed to the proper backend.
 
 ## Current HTTP Server in Kubernetes
 
@@ -36,6 +36,6 @@ In this case, the reasoning for adding the HTTP Add-on would be clear - adding a
 Getting the HTTP Add-on working can be done transparently and without downtime to the application:
 
 - [Install](./install.md) the add-on. This step will have no effect on the running application.
-- Create a new `HTTPScaledObject`. This step activates autoscaling for the `Deployment` that you specify and the application will immediately start scaling up and down based on incoming traffic through the interceptor that was created.
+- Create a new `HTTPScaledObject`. This step activates autoscaling for the workload that you specify and the application will immediately start scaling up and down based on incoming traffic through the interceptor that was created.
 
 [Go back to landing page](./)
diff --git a/docs/walkthrough.md b/docs/walkthrough.md
index 11c129e..9d956c5 100644
--- a/docs/walkthrough.md
+++ b/docs/walkthrough.md
@@ -14,7 +14,7 @@ You'll need to install a `Deployment` and `Service` first. You'll tell the add-o
 $ helm install xkcd ./examples/xkcd -n ${NAMESPACE}
 ```
 
-You'll need to clone the repository to get access to this chart. If you have your own `Deployment` and `Service` installed, you can go right to creating an `HTTPScaledObject` in the next section.
+You'll need to clone the repository to get access to this chart. If you have your own workload and `Service` installed, you can go right to creating an `HTTPScaledObject` in the next section.
 
 >If you are running KEDA and the HTTP Add-on in cluster-global mode, you can install the XKCD chart in any namespace you choose. If you do so, make sure you add `--set ingressNamespace=${NAMESPACE}` to the above installation command.
 
@@ -25,10 +25,10 @@ You'll need to clone the repository to get access to this chart. If you have you
 You interact with the operator via a CRD called `HTTPScaledObject`. This CRD object instructs interceptors to forward requests for a given host to your app's backing `Service`. To get an example app up and running, read the notes below and then run the subsequent command from the root of this repository.
 
 ```console
-$ kubectl create -n $NAMESPACE -f examples/v0.3.0/httpscaledobject.yaml
+$ kubectl create -n $NAMESPACE -f examples/v0.7.0/httpscaledobject.yaml
 ```
 
->If you'd like to learn more about this object, please see the [`HTTPScaledObject` reference](./ref/v0.3.0/http_scaled_object.md).
+>If you'd like to learn more about this object, please see the [`HTTPScaledObject` reference](./ref/v0.7.0/http_scaled_object.md).
 
 ## Testing Your Installation
 
@@ -40,13 +40,13 @@ $ kubectl port-forward svc/keda-add-ons-http-interceptor-proxy -n ${NAMESPACE} 8
 
 ### Routing to the Right `Service`
 
-As said above, you need to route your HTTP traffic to the `Service` that the add-on has created. If you have existing systems - like an ingress controller - you'll need to anticipate the name of these created `Service`s. Each one will be named consistently like so, in the same namespace as the `HTTPScaledObject` and your application (i.e. `$NAMESPACE`):
+As said above, you need to route your HTTP traffic to the `Service` that the add-on has created during the installation. If you have existing systems - like an ingress controller - you'll need to anticipate the name of these created `Service`s. Each one will be named consistently like so, in the same namespace as the `HTTPScaledObject` and your application (i.e. `$NAMESPACE`):
 
 ```console
 keda-add-ons-http-interceptor-proxy
 ```
 
->This is installed by the [Helm chart](https://github.com/kedacore/charts/tree/master/http-add-on) as a `ClusterIP` `Service` by default.
+> This is installed by the [Helm chart](https://github.com/kedacore/charts/tree/master/http-add-on) as a `ClusterIP` `Service` by default.
 
 #### Installing and Using the [ingress-nginx](https://kubernetes.github.io/ingress-nginx/deploy/#using-helm) Ingress Controller
 
@@ -64,6 +64,8 @@ helm install ingress-nginx ingress-nginx/ingress-nginx -n ${NAMESPACE}
 
 An [`Ingress`](https://kubernetes.io/docs/concepts/services-networking/ingress/) resource was already created as part of the [xkcd chart](../examples/xkcd/templates/ingress.yaml), so the installed NGINX ingress controller will initialize, detect the `Ingress`, and begin routing to the xkcd interceptor `Service`.
 
+>NOTE: You may have to create an external service `type: ExternalName` pointing to the interceptor namespace and use it from `Ingress` manifest.
+
 When you're ready, please run `kubectl get svc -n ${NAMESPACE}`, find the `ingress-nginx-controller` service, and copy and paste its `EXTERNAL-IP`. This is the IP address that your application will be running at on the public internet.
 
 >Note: you should go further and set your DNS records appropriately and set up a TLS certificate for this IP address. Instructions to do that are out of scope of this document, though.
diff --git a/examples/v0.6.0/httpscaledobject.yaml b/examples/v0.6.0/httpscaledobject.yaml
new file mode 100644
index 0000000..b064761
--- /dev/null
+++ b/examples/v0.6.0/httpscaledobject.yaml
@@ -0,0 +1,16 @@
+kind: HTTPScaledObject
+apiVersion: http.keda.sh/v1alpha1
+metadata:
+    name: xkcd
+spec:
+    hosts:
+    - myhost.com
+    pathPrefixes:
+    - /test
+    scaleTargetRef:
+        name: xkcd
+        service: xkcd
+        port: 8080
+    replicas:
+        min: 5
+        max: 10
diff --git a/examples/v0.7.0/httpscaledobject.yaml b/examples/v0.7.0/httpscaledobject.yaml
new file mode 100644
index 0000000..4181e28
--- /dev/null
+++ b/examples/v0.7.0/httpscaledobject.yaml
@@ -0,0 +1,18 @@
+kind: HTTPScaledObject
+apiVersion: http.keda.sh/v1alpha1
+metadata:
+    name: xkcd
+spec:
+    hosts:
+    - myhost.com
+    pathPrefixes:
+    - /test
+    scaleTargetRef:
+        name: xkcd
+        kind: Deployment
+        apiVersion: apps/v1
+        service: xkcd
+        port: 8080
+    replicas:
+        min: 5
+        max: 10
diff --git a/examples/xkcd/templates/externalservice.yaml b/examples/xkcd/templates/externalservice.yaml
new file mode 100644
index 0000000..5e4ec48
--- /dev/null
+++ b/examples/xkcd/templates/externalservice.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "xkcd.fullname" . }}-proxy
+  labels:
+    {{- include "xkcd.labels" . | nindent 4 }}
+spec:
+  type: ExternalName
+  externalName: keda-http-add-on-interceptor-proxy.keda
diff --git a/examples/xkcd/templates/httpscaledobject.yaml b/examples/xkcd/templates/httpscaledobject.yaml
index 8a4ca4e..5d6a00f 100644
--- a/examples/xkcd/templates/httpscaledobject.yaml
+++ b/examples/xkcd/templates/httpscaledobject.yaml
@@ -7,9 +7,15 @@ spec:
     hosts:
         {{- toYaml . | nindent 8 }}
     {{- end }}
+    {{- with .Values.pathPrefixes }}
+    pathPrefixes:
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
     targetPendingRequests: {{ .Values.targetPendingRequests }}
     scaleTargetRef:
-        deployment: {{ include "xkcd.fullname" . }}
+        name: {{ include "xkcd.fullname" . }}
+        kind: Deployment
+        apiVersion: apps/v1
         service: {{ include "xkcd.fullname" . }}
         port: 8080
     replicas:
diff --git a/examples/xkcd/templates/ingress.yaml b/examples/xkcd/templates/ingress.yaml
index 0e6f6a8..d0c5123 100644
--- a/examples/xkcd/templates/ingress.yaml
+++ b/examples/xkcd/templates/ingress.yaml
@@ -2,7 +2,6 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: {{ include "xkcd.fullname" . }}
-  namespace: {{ .Values.ingressNamespace | default .Release.Namespace }}
   annotations:
     nginx.ingress.kubernetes.io/rewrite-target: /
     kubernetes.io/ingress.class: nginx
@@ -16,7 +15,7 @@ spec:
         pathType: Prefix
         backend:
           service:
-            name: keda-add-ons-http-interceptor-proxy
+            name: {{ include "xkcd.fullname" $ }}-proxy
             port:
               number: 8080
   {{- end }}
diff --git a/examples/xkcd/values.yaml b/examples/xkcd/values.yaml
index f8fc650..d362389 100644
--- a/examples/xkcd/values.yaml
+++ b/examples/xkcd/values.yaml
@@ -2,6 +2,9 @@ replicaCount: 1
 hosts:
   - "myhost.com"
   - "myhost2.com"
+pathPrefixes:
+  - "/path1"
+  - "/path2"
 targetPendingRequests: 200
 # This is the namespace that the ingress should be installed
 # into. It should be set to the same namespace as the
diff --git a/go.mod b/go.mod
index 443b115..9330cc5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,40 +1,39 @@
 module github.com/kedacore/http-add-on
 
-go 1.20
+go 1.21
 
 require (
 	github.com/go-logr/logr v1.3.0
-	github.com/go-logr/zapr v1.2.4
+	github.com/go-logr/zapr v1.3.0
 	github.com/golang/mock v1.7.0-rc.1.0.20220812172401-5b455625bd2c
 	github.com/hashicorp/go-immutable-radix/v2 v2.1.0
-	github.com/kedacore/keda/v2 v2.12.0
+	github.com/kedacore/keda/v2 v2.12.1
 	github.com/kelseyhightower/envconfig v1.4.0
-	github.com/onsi/ginkgo/v2 v2.13.1
+	github.com/onsi/ginkgo/v2 v2.13.2
 	github.com/onsi/gomega v1.30.0
 	github.com/stretchr/testify v1.8.4
-	github.com/tj/assert v0.0.3
 	go.uber.org/zap v1.26.0
-	golang.org/x/sync v0.4.0
+	golang.org/x/sync v0.5.0
 	google.golang.org/grpc v1.59.0
 	google.golang.org/protobuf v1.31.0
-	k8s.io/api v0.28.3
-	k8s.io/apimachinery v0.28.3
-	k8s.io/client-go v0.28.3
-	k8s.io/code-generator v0.28.3
-	k8s.io/utils v0.0.0-20230726121419-3b25d923346b
+	k8s.io/api v0.28.4
+	k8s.io/apimachinery v0.28.4
+	k8s.io/client-go v0.28.4
+	k8s.io/code-generator v0.28.4
+	k8s.io/utils v0.0.0-20231127182322-b307cd553661
 	sigs.k8s.io/controller-runtime v0.16.3
-	sigs.k8s.io/kustomize/kustomize/v5 v5.2.1
+	sigs.k8s.io/kustomize/kustomize/v5 v5.3.0
 )
 
 require (
-	github.com/antonmedv/expr v1.15.3 // indirect
+	github.com/antonmedv/expr v1.15.5 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v5.7.0+incompatible // indirect
 	github.com/evanphx/json-patch/v5 v5.7.0 // indirect
-	github.com/fsnotify/fsnotify v1.6.0 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-openapi/jsonpointer v0.20.0 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
@@ -48,14 +47,14 @@ require (
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
-	github.com/google/uuid v1.3.1 // indirect
-	github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect
+	github.com/google/uuid v1.4.0 // indirect
+	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
 	github.com/imdario/mergo v0.3.16 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
 	github.com/moby/spdystream v0.2.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -63,41 +62,41 @@ require (
 	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.0 // indirect
-	github.com/prometheus/client_golang v1.16.0 // indirect
-	github.com/prometheus/client_model v0.4.0 // indirect
-	github.com/prometheus/common v0.44.0 // indirect
-	github.com/prometheus/procfs v0.11.1 // indirect
+	github.com/prometheus/client_golang v1.17.0 // indirect
+	github.com/prometheus/client_model v0.5.0 // indirect
+	github.com/prometheus/common v0.45.0 // indirect
+	github.com/prometheus/procfs v0.12.0 // indirect
 	github.com/spf13/cobra v1.7.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
 	go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
-	golang.org/x/mod v0.13.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
-	golang.org/x/oauth2 v0.12.0 // indirect
-	golang.org/x/sys v0.14.0 // indirect
-	golang.org/x/term v0.13.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
-	golang.org/x/time v0.3.0 // indirect
-	golang.org/x/tools v0.14.0 // indirect
+	golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
+	golang.org/x/mod v0.14.0 // indirect
+	golang.org/x/net v0.19.0 // indirect
+	golang.org/x/oauth2 v0.15.0 // indirect
+	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/term v0.15.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
+	golang.org/x/tools v0.16.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/appengine v1.6.8 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
 	gopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/apiextensions-apiserver v0.28.3 // indirect
-	k8s.io/component-base v0.28.3 // indirect
+	k8s.io/apiextensions-apiserver v0.28.4 // indirect
+	k8s.io/component-base v0.28.4 // indirect
 	k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
-	k8s.io/klog/v2 v2.100.1 // indirect
-	k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d // indirect
-	knative.dev/pkg v0.0.0-20230925085724-0efc1bce35a9 // indirect
+	k8s.io/klog/v2 v2.110.1 // indirect
+	k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 // indirect
+	knative.dev/pkg v0.0.0-20231204120332-9386ad6703ee // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
-	sigs.k8s.io/kustomize/api v0.15.0 // indirect
-	sigs.k8s.io/kustomize/cmd/config v0.12.0 // indirect
-	sigs.k8s.io/kustomize/kyaml v0.15.0 // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
-	sigs.k8s.io/yaml v1.3.0 // indirect
+	sigs.k8s.io/kustomize/api v0.16.0 // indirect
+	sigs.k8s.io/kustomize/cmd/config v0.13.0 // indirect
+	sigs.k8s.io/kustomize/kyaml v0.16.0 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
+	sigs.k8s.io/yaml v1.4.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 540f999..f1e8d91 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
-github.com/antonmedv/expr v1.15.3 h1:q3hOJZNvLvhqE8OHBs1cFRdbXFNKuA+bHmRaI+AmRmI=
-github.com/antonmedv/expr v1.15.3/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
+github.com/antonmedv/expr v1.15.5 h1:y0Iz3cEwmpRz5/r3w4qQR0MfIqJGdGM1zbhD/v0G5Vg=
+github.com/antonmedv/expr v1.15.5/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
-github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -20,17 +20,15 @@ github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ
 github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
 github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+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/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
 github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
 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.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
 github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
-github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
+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.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
 github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
@@ -47,7 +45,6 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.7.0-rc.1.0.20220812172401-5b455625bd2c h1:8AzxBXzXPCzl8EEsgWirPPDA5ru+bm5dVEV/KkpAKnE=
 github.com/golang/mock v1.7.0-rc.1.0.20220812172401-5b455625bd2c/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 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 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
@@ -67,14 +64,15 @@ github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE
 github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
-github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
-github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
 github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
 github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
-github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM=
-github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -83,24 +81,24 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/kedacore/keda/v2 v2.12.0 h1:yvonNclPzmqIW9S9/aVMjeBnCsS6a85MXtXrK9nma9M=
-github.com/kedacore/keda/v2 v2.12.0/go.mod h1:68D+BFRWbvQqv96YEejVyWaZmFPSeYdYttl5dTg3PaI=
+github.com/kedacore/keda/v2 v2.12.1 h1:L400vIMl329NQV/QGPNpZb3UuRJKF0pJTUKv1wBmbUE=
+github.com/kedacore/keda/v2 v2.12.1/go.mod h1:fjNT3rc7AJvZlIj1b2UzAerr4Be5c0uAOZnmlKNKtro=
 github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
 github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
-github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
 github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
 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=
@@ -112,26 +110,27 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/
 github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
 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.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
-github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
+github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
+github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
 github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
 github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
-github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
-github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
-github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
-github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
-github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
-github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
-github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
 github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -148,100 +147,84 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
-github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
 github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
 github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 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=
 go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
 go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
-go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+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=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
+golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
-golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
+golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
 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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
-golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 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-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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-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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
-golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
+golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
 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=
@@ -250,8 +233,8 @@ 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.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
 google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
 google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
 google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
@@ -259,7 +242,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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=
@@ -271,46 +253,45 @@ gopkg.in/yaml.v2 v2.2.8/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-20200605160147-a5ece683394c/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.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
-k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
-k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08=
-k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc=
-k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
-k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
-k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4=
-k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo=
-k8s.io/code-generator v0.28.3 h1:I847QvdpYx7xKiG2KVQeCSyNF/xU9TowaDAg601mvlw=
-k8s.io/code-generator v0.28.3/go.mod h1:A2EAHTRYvCvBrb/MM2zZBNipeCk3f8NtpdNIKawC43M=
-k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI=
-k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8=
+k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=
+k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=
+k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=
+k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=
+k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
+k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
+k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
+k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
+k8s.io/code-generator v0.28.4 h1:tcOSNIZQvuAvXhOwpbuJkKbAABJQeyCcQBCN/3uI18c=
+k8s.io/code-generator v0.28.4/go.mod h1:OQAfl6bZikQ/tK6faJ18Vyzo54rUII2NmjurHyiN1g4=
+k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
+k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
 k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks=
 k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
-k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
-k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d h1:/CFeJBjBrZvHX09rObS2+2iEEDevMWYc1v3aIYAjIYI=
-k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
-k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
-k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-knative.dev/pkg v0.0.0-20230925085724-0efc1bce35a9 h1:0GZJ6ZTAkS59goRuMVc5t9vNMmegbggrB/A7pTm2USE=
-knative.dev/pkg v0.0.0-20230925085724-0efc1bce35a9/go.mod h1:HagK8mQdSVdmm2xSYMpl4iCLjhxPoPJdY1/NuuEvAVw=
+k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
+k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
+k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 h1:vzKzxN5uyJZLY8HL1/OovW7BJefnsBIWt8T7Gjh2boQ=
+k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
+k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI=
+k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+knative.dev/pkg v0.0.0-20231204120332-9386ad6703ee h1:O1bJlEC4pzAEyTt8+f0Qe50QqS2JJxhK269CAGZ68vg=
+knative.dev/pkg v0.0.0-20231204120332-9386ad6703ee/go.mod h1:aJX49KSaKufMCwJgrCbHxXLTQ/j6LGspSZxn9VIv51w=
 sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
 sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
 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/kustomize/api v0.15.0 h1:6Ca88kEOBVotHDw+y2IsIMYtg9Pvv7MKpW9JMyF/OH4=
-sigs.k8s.io/kustomize/api v0.15.0/go.mod h1:p19kb+E14gN7zcIBR/nhByJDAfUa7N8mp6ZdH/mMXbg=
-sigs.k8s.io/kustomize/cmd/config v0.12.0 h1:EXkXg7Bq/wPaEe5sdNbLRI5J6eG+WarKNo5LRD+gCWU=
-sigs.k8s.io/kustomize/cmd/config v0.12.0/go.mod h1:Ct/f74OFOVCO4KQ8PVGUXHQDjZO6yqwxhrvL/b/DkBM=
-sigs.k8s.io/kustomize/kustomize/v5 v5.2.1 h1:bI0UnT+UJiEEl1BaomL71ESl3w5rJo2Aw6CpqxX4aS4=
-sigs.k8s.io/kustomize/kustomize/v5 v5.2.1/go.mod h1:qzRni4VPV6LxTEY5eC5qH3+995Atdi9E46jiwArROik=
-sigs.k8s.io/kustomize/kyaml v0.15.0 h1:ynlLMAxDhrY9otSg5GYE2TcIz31XkGZ2Pkj7SdolD84=
-sigs.k8s.io/kustomize/kyaml v0.15.0/go.mod h1:+uMkBahdU1KNOj78Uta4rrXH+iH7wvg+nW7+GULvREA=
-sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
-sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
+sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g=
+sigs.k8s.io/kustomize/api v0.16.0/go.mod h1:MnFZ7IP2YqVyVwMWoRxPtgl/5hpA+eCCrQR/866cm5c=
+sigs.k8s.io/kustomize/cmd/config v0.13.0 h1:Z/bRyFQupMIqGz1KlRkLimK/VjtE4/Oj/DinJmQqTDc=
+sigs.k8s.io/kustomize/cmd/config v0.13.0/go.mod h1:YlsZ9JysiHN7OjSmIZ17zvq9kl1oN2Osn+3wVyERkcM=
+sigs.k8s.io/kustomize/kustomize/v5 v5.3.0 h1:OUKaQwArd1udTz3ykibOjaUwdfly6FnkQiDSSft6+Fg=
+sigs.k8s.io/kustomize/kustomize/v5 v5.3.0/go.mod h1:qGalrWojwFYaT7KQXLo3kmLyuyr6VaIQYY+BWeRENus=
+sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0=
+sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4=
+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 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
-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/interceptor/Dockerfile b/interceptor/Dockerfile
index 5e0ad0f..0a3fc3e 100644
--- a/interceptor/Dockerfile
+++ b/interceptor/Dockerfile
@@ -1,4 +1,4 @@
-FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/keda-tools:1.20.8 as builder
+FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/keda-tools:1.21.5 as builder
 WORKDIR /workspace
 COPY go.* .
 RUN go mod download
diff --git a/interceptor/config/serving.go b/interceptor/config/serving.go
index a3ff587..50267f5 100644
--- a/interceptor/config/serving.go
+++ b/interceptor/config/serving.go
@@ -24,11 +24,16 @@ type Serving struct {
 	// ConfigMapCacheRsyncPeriod is the time interval
 	// for the configmap informer to rsync the local cache.
 	ConfigMapCacheRsyncPeriod time.Duration `envconfig:"KEDA_HTTP_SCALER_CONFIG_MAP_INFORMER_RSYNC_PERIOD" default:"60m"`
-	// The interceptor has an internal process that periodically fetches the state
+	// Deprecated: The interceptor has an internal process that periodically fetches the state
 	// of deployment that is running the servers it forwards to.
 	//
 	// This is the interval (in milliseconds) representing how often to do a fetch
 	DeploymentCachePollIntervalMS int `envconfig:"KEDA_HTTP_DEPLOYMENT_CACHE_POLLING_INTERVAL_MS" default:"250"`
+	// The interceptor has an internal process that periodically fetches the state
+	// of endpoints that is running the servers it forwards to.
+	//
+	// This is the interval (in milliseconds) representing how often to do a fetch
+	EndpointsCachePollIntervalMS int `envconfig:"KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS" default:"250"`
 }
 
 // Parse parses standard configs using envconfig and returns a pointer to the
diff --git a/interceptor/config/timeouts.go b/interceptor/config/timeouts.go
index 9a0a718..d79f719 100644
--- a/interceptor/config/timeouts.go
+++ b/interceptor/config/timeouts.go
@@ -16,9 +16,9 @@ type Timeouts struct {
 	// ResponseHeaderTimeout is how long to wait between when the HTTP request
 	// is sent to the backing app and when response headers need to arrive
 	ResponseHeader time.Duration `envconfig:"KEDA_RESPONSE_HEADER_TIMEOUT" default:"500ms"`
-	// DeploymentReplicas is how long to wait for the backing deployment
+	// WorkloadReplicas is how long to wait for the backing workload
 	// to have 1 or more replicas before connecting and sending the HTTP request.
-	DeploymentReplicas time.Duration `envconfig:"KEDA_CONDITION_WAIT_TIMEOUT" default:"1500ms"`
+	WorkloadReplicas time.Duration `envconfig:"KEDA_CONDITION_WAIT_TIMEOUT" default:"1500ms"`
 	// ForceHTTP2 toggles whether to try to force HTTP2 for all requests
 	ForceHTTP2 bool `envconfig:"KEDA_HTTP_FORCE_HTTP2" default:"false"`
 	// MaxIdleConns is the max number of connections that can be idle in the
diff --git a/interceptor/config/validate.go b/interceptor/config/validate.go
index c819ae9..738059b 100644
--- a/interceptor/config/validate.go
+++ b/interceptor/config/validate.go
@@ -2,16 +2,36 @@ package config
 
 import (
 	"fmt"
+	"os"
 	"time"
+
+	"github.com/go-logr/logr"
 )
 
-func Validate(srvCfg Serving, timeoutsCfg Timeouts) error {
-	deplCachePollInterval := time.Duration(srvCfg.DeploymentCachePollIntervalMS) * time.Millisecond
-	if timeoutsCfg.DeploymentReplicas < deplCachePollInterval {
+func Validate(srvCfg *Serving, timeoutsCfg Timeouts, lggr logr.Logger) error {
+	// TODO(jorturfer): delete this for v0.9.0
+	_, deploymentEnvExist := os.LookupEnv("KEDA_HTTP_DEPLOYMENT_CACHE_POLLING_INTERVAL_MS")
+	_, endpointsEnvExist := os.LookupEnv("KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS")
+	if deploymentEnvExist && endpointsEnvExist {
+		return fmt.Errorf(
+			"%s and %s are mutual exclusive",
+			"KEDA_HTTP_DEPLOYMENT_CACHE_POLLING_INTERVAL_MS",
+			"KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS",
+		)
+	}
+	if deploymentEnvExist && !endpointsEnvExist {
+		srvCfg.EndpointsCachePollIntervalMS = srvCfg.DeploymentCachePollIntervalMS
+		srvCfg.DeploymentCachePollIntervalMS = 0
+		lggr.Info("WARNING: KEDA_HTTP_DEPLOYMENT_CACHE_POLLING_INTERVAL_MS has been deprecated in favor of KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS and wil be removed for v0.9.0")
+	}
+	// END TODO
+
+	endpointsCachePollInterval := time.Duration(srvCfg.EndpointsCachePollIntervalMS) * time.Millisecond
+	if timeoutsCfg.WorkloadReplicas < endpointsCachePollInterval {
 		return fmt.Errorf(
-			"deployment replicas timeout (%s) should not be less than the Deployment Cache Poll Interval (%s)",
-			timeoutsCfg.DeploymentReplicas,
-			deplCachePollInterval,
+			"workload replicas timeout (%s) should not be less than the Endpoints Cache Poll Interval (%s)",
+			timeoutsCfg.WorkloadReplicas,
+			endpointsCachePollInterval,
 		)
 	}
 	return nil
diff --git a/interceptor/forward_wait_func.go b/interceptor/forward_wait_func.go
index 9ebc69b..6452d97 100644
--- a/interceptor/forward_wait_func.go
+++ b/interceptor/forward_wait_func.go
@@ -5,65 +5,69 @@ import (
 	"fmt"
 
 	"github.com/go-logr/logr"
-	appsv1 "k8s.io/api/apps/v1"
+	v1 "k8s.io/api/core/v1"
 
 	"github.com/kedacore/http-add-on/pkg/k8s"
 )
 
 // forwardWaitFunc is a function that waits for a condition
 // before proceeding to serve the request.
-type forwardWaitFunc func(context.Context, string, string) (int, error)
+type forwardWaitFunc func(context.Context, string, string) (bool, error)
 
-func deploymentCanServe(depl appsv1.Deployment) bool {
-	return depl.Status.ReadyReplicas > 0
+func workloadActiveEndpoints(endpoints v1.Endpoints) int {
+	total := 0
+	for _, subset := range endpoints.Subsets {
+		total += len(subset.Addresses)
+	}
+	return total
 }
 
-func newDeployReplicasForwardWaitFunc(
+func newWorkloadReplicasForwardWaitFunc(
 	lggr logr.Logger,
-	deployCache k8s.DeploymentCache,
+	endpointCache k8s.EndpointsCache,
 ) forwardWaitFunc {
-	return func(ctx context.Context, deployNS, deployName string) (int, error) {
+	return func(ctx context.Context, endpointNS, endpointName string) (bool, error) {
 		// get a watcher & its result channel before querying the
-		// deployment cache, to ensure we don't miss events
-		watcher, err := deployCache.Watch(deployNS, deployName)
+		// endpoints cache, to ensure we don't miss events
+		watcher, err := endpointCache.Watch(endpointNS, endpointName)
 		if err != nil {
-			return 0, err
+			return false, err
 		}
 		eventCh := watcher.ResultChan()
 		defer watcher.Stop()
 
-		deployment, err := deployCache.Get(deployNS, deployName)
+		endpoints, err := endpointCache.Get(endpointNS, endpointName)
 		if err != nil {
-			// if we didn't get the initial deployment state, bail out
-			return 0, fmt.Errorf(
-				"error getting state for deployment %s/%s: %w",
-				deployNS,
-				deployName,
+			// if we didn't get the initial endpoints state, bail out
+			return false, fmt.Errorf(
+				"error getting state for endpoints %s/%s: %w",
+				endpointNS,
+				endpointName,
 				err,
 			)
 		}
-		// if there is 1 or more replica, we're done waiting
-		if deploymentCanServe(deployment) {
-			return int(deployment.Status.ReadyReplicas), nil
+		// if there is 1 or more active endpoints, we're done waiting
+		activeEndpoints := workloadActiveEndpoints(endpoints)
+		if activeEndpoints > 0 {
+			return false, nil
 		}
 
 		for {
 			select {
 			case event := <-eventCh:
-				deployment, ok := event.Object.(*appsv1.Deployment)
+				endpoints, ok := event.Object.(*v1.Endpoints)
 				if !ok {
 					lggr.Info(
-						"Didn't get a deployment back in event",
+						"Didn't get a endpoints back in event",
 					)
-				} else if deploymentCanServe(*deployment) {
-					return 0, nil
+				} else if activeEndpoints := workloadActiveEndpoints(*endpoints); activeEndpoints > 0 {
+					return true, nil
 				}
 			case <-ctx.Done():
 				// otherwise, if the context is marked done before
 				// we're done waiting, fail.
-				return 0, fmt.Errorf(
-					"context marked done while waiting for deployment %s to reach > 0 replicas: %w",
-					deployName,
+				return false, fmt.Errorf(
+					"context marked done while waiting for workload reach > 0 replicas: %w",
 					ctx.Err(),
 				)
 			}
diff --git a/interceptor/main.go b/interceptor/main.go
index c5c0887..fd3734e 100644
--- a/interceptor/main.go
+++ b/interceptor/main.go
@@ -28,8 +28,8 @@ import (
 	"github.com/kedacore/http-add-on/pkg/util"
 )
 
-// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch
 // +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch
+// +kubebuilder:rbac:groups="",resources=endpoints,verbs=get;list;watch
 
 func main() {
 	lggr, err := pkglog.NewZapr()
@@ -40,7 +40,7 @@ func main() {
 
 	timeoutCfg := config.MustParseTimeouts()
 	servingCfg := config.MustParseServing()
-	if err := config.Validate(*servingCfg, *timeoutCfg); err != nil {
+	if err := config.Validate(servingCfg, *timeoutCfg, lggr); err != nil {
 		lggr.Error(err, "invalid configuration")
 		os.Exit(1)
 	}
@@ -63,16 +63,16 @@ func main() {
 		lggr.Error(err, "creating new Kubernetes ClientSet")
 		os.Exit(1)
 	}
-	deployCache := k8s.NewInformerBackedDeploymentCache(
+	endpointsCache := k8s.NewInformerBackedEndpointsCache(
 		lggr,
 		cl,
-		time.Millisecond*time.Duration(servingCfg.DeploymentCachePollIntervalMS),
+		time.Millisecond*time.Duration(servingCfg.EndpointsCachePollIntervalMS),
 	)
 	if err != nil {
-		lggr.Error(err, "creating new deployment cache")
+		lggr.Error(err, "creating new endpoints cache")
 		os.Exit(1)
 	}
-	waitFunc := newDeployReplicasForwardWaitFunc(lggr, deployCache)
+	waitFunc := newWorkloadReplicasForwardWaitFunc(lggr, endpointsCache)
 
 	httpCl, err := clientset.NewForConfig(cfg)
 	if err != nil {
@@ -95,11 +95,11 @@ func main() {
 
 	eg, ctx := errgroup.WithContext(ctx)
 
-	// start the deployment cache updater
+	// start the endpoints cache updater
 	eg.Go(func() error {
-		lggr.Info("starting the deployment cache")
+		lggr.Info("starting the endpoints cache")
 
-		deployCache.Start(ctx)
+		endpointsCache.Start(ctx)
 		return nil
 	})
 
diff --git a/interceptor/proxy_handlers.go b/interceptor/proxy_handlers.go
index badc5d0..4dbf96f 100644
--- a/interceptor/proxy_handlers.go
+++ b/interceptor/proxy_handlers.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net/http"
+	"strconv"
 	"time"
 
 	"github.com/go-logr/logr"
@@ -26,7 +27,7 @@ type forwardingConfig struct {
 
 func newForwardingConfigFromTimeouts(t *config.Timeouts) forwardingConfig {
 	return forwardingConfig{
-		waitTimeout:           t.DeploymentReplicas,
+		waitTimeout:           t.WorkloadReplicas,
 		respHeaderTimeout:     t.ResponseHeader,
 		forceAttemptHTTP2:     t.ForceHTTP2,
 		maxIdleConns:          t.MaxIdleConns,
@@ -64,10 +65,10 @@ func newForwardingHandler(
 
 		waitFuncCtx, done := context.WithTimeout(r.Context(), fwdCfg.waitTimeout)
 		defer done()
-		replicas, err := waitFunc(
+		isColdStart, err := waitFunc(
 			waitFuncCtx,
 			httpso.GetNamespace(),
-			httpso.Spec.ScaleTargetRef.Deployment,
+			httpso.Spec.ScaleTargetRef.Service,
 		)
 		if err != nil {
 			lggr.Error(err, "wait function failed, not forwarding request")
@@ -77,11 +78,7 @@ func newForwardingHandler(
 			}
 			return
 		}
-		isColdStart := "false"
-		if replicas == 0 {
-			isColdStart = "true"
-		}
-		w.Header().Add("X-KEDA-HTTP-Cold-Start", isColdStart)
+		w.Header().Add("X-KEDA-HTTP-Cold-Start", strconv.FormatBool(isColdStart))
 
 		uh := handler.NewUpstream(roundTripper)
 		uh.ServeHTTP(w, r)
diff --git a/operator/Dockerfile b/operator/Dockerfile
index ba9f4b6..f82e252 100644
--- a/operator/Dockerfile
+++ b/operator/Dockerfile
@@ -1,4 +1,4 @@
-FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/keda-tools:1.20.8 as builder
+FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/keda-tools:1.21.5 as builder
 WORKDIR /workspace
 COPY go.* .
 RUN go mod download
diff --git a/operator/apis/http/v1alpha1/httpscaledobject_types.go b/operator/apis/http/v1alpha1/httpscaledobject_types.go
index 138e1fd..0ff6639 100644
--- a/operator/apis/http/v1alpha1/httpscaledobject_types.go
+++ b/operator/apis/http/v1alpha1/httpscaledobject_types.go
@@ -22,8 +22,15 @@ import (
 
 // ScaleTargetRef contains all the details about an HTTP application to scale and route to
 type ScaleTargetRef struct {
-	// The name of the deployment to scale according to HTTP traffic
+	// Deprecated: The name of the deployment to scale according to HTTP traffic
+	// +optional
 	Deployment string `json:"deployment"`
+	// +optional
+	Name string `json:"name"`
+	// +optional
+	APIVersion string `json:"apiVersion,omitempty"`
+	// +optional
+	Kind string `json:"kind,omitempty"`
 	// The name of the service to route to
 	Service string `json:"service"`
 	// The port to route to
diff --git a/operator/controllers/http/config/config.go b/operator/controllers/http/config/config.go
index 2eac129..6671216 100644
--- a/operator/controllers/http/config/config.go
+++ b/operator/controllers/http/config/config.go
@@ -82,7 +82,7 @@ func NewExternalScalerFromEnv() (*ExternalScaler, error) {
 	// image, err := env.Get("KEDAHTTP_OPERATOR_EXTERNAL_SCALER_IMAGE")
 	serviceName, err := env.Get("KEDAHTTP_OPERATOR_EXTERNAL_SCALER_SERVICE")
 	if err != nil {
-		return nil, fmt.Errorf("missing KEDAHTTP_EXTERNAL_SCALER_SERVICE")
+		return nil, fmt.Errorf("missing KEDAHTTP_OPERATOR_EXTERNAL_SCALER_SERVICE")
 	}
 	port := env.GetInt32Or("KEDAHTTP_OPERATOR_EXTERNAL_SCALER_PORT", 8091)
 	return &ExternalScaler{
diff --git a/operator/controllers/http/httpscaledobject_controller.go b/operator/controllers/http/httpscaledobject_controller.go
index e7f72fa..69af278 100644
--- a/operator/controllers/http/httpscaledobject_controller.go
+++ b/operator/controllers/http/httpscaledobject_controller.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"time"
 
+	appsv1 "k8s.io/api/apps/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -32,6 +33,7 @@ import (
 
 	httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
 	"github.com/kedacore/http-add-on/operator/controllers/http/config"
+	"github.com/kedacore/http-add-on/pkg/k8s"
 )
 
 // HTTPScaledObjectReconciler reconciles a HTTPScaledObject object
@@ -89,6 +91,13 @@ func (r *HTTPScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Req
 		logger.Info(".spec.host is deprecated, performing automated migration to .spec.hosts")
 		return ctrl.Result{}, r.migrateHost(ctx, httpso)
 	}
+	// TODO(jorturfer): delete this for v0.9.0
+	if httpso.Spec.ScaleTargetRef.Name == "" ||
+		httpso.Spec.ScaleTargetRef.Kind == "" ||
+		httpso.Spec.ScaleTargetRef.APIVersion == "" {
+		logger.Info(".spec.scaleTargetRef.Deployment is deprecated, performing automated migration")
+		return ctrl.Result{}, r.migrateTargetRef(ctx, httpso)
+	}
 
 	// httpso is updated now
 	logger.Info(
@@ -146,11 +155,30 @@ func (r *HTTPScaledObjectReconciler) migrateHost(ctx context.Context, httpso *ht
 	if (httpso.Spec.Hosts != nil) == (httpso.Spec.Host != nil) {
 		return errors.New("exactly one of .spec.host and .spec.hosts must be set")
 	}
-
 	httpso.Spec.Hosts = []string{
 		*httpso.Spec.Host,
 	}
 	httpso.Spec.Host = nil
+	return r.Client.Update(ctx, httpso)
+}
+
+// TODO(jorturfer): delete this for v0.9.0
+func (r *HTTPScaledObjectReconciler) migrateTargetRef(ctx context.Context, httpso *httpv1alpha1.HTTPScaledObject) error {
+	if (httpso.Spec.ScaleTargetRef.Deployment != "") == (httpso.Spec.ScaleTargetRef.Name != "") {
+		return errors.New("exactly one of .spec.scaleTargetRef.deployment and .spec.scaleTargetRef.name must be set")
+	}
+
+	if httpso.Spec.ScaleTargetRef.Name == "" {
+		httpso.Spec.ScaleTargetRef.Name = httpso.Spec.ScaleTargetRef.Deployment
+	}
+	if httpso.Spec.ScaleTargetRef.Kind == "" {
+		httpso.Spec.ScaleTargetRef.Kind = k8s.ObjectKind(&appsv1.Deployment{})
+	}
+	if httpso.Spec.ScaleTargetRef.APIVersion == "" {
+		httpso.Spec.ScaleTargetRef.APIVersion = appsv1.SchemeGroupVersion.Identifier()
+	}
+
+	httpso.Spec.ScaleTargetRef.Deployment = ""
 
 	return r.Client.Update(ctx, httpso)
 }
diff --git a/operator/controllers/http/scaled_object.go b/operator/controllers/http/scaled_object.go
index 4d56114..05389a1 100644
--- a/operator/controllers/http/scaled_object.go
+++ b/operator/controllers/http/scaled_object.go
@@ -38,7 +38,7 @@ func (r *HTTPScaledObjectReconciler) createOrUpdateScaledObject(
 	appScaledObject := k8s.NewScaledObject(
 		httpso.GetNamespace(),
 		httpso.GetName(), // HTTPScaledObject name is the same as the ScaledObject name
-		httpso.Spec.ScaleTargetRef.Deployment,
+		httpso.Spec.ScaleTargetRef,
 		externalScalerHostName,
 		httpso.Spec.Hosts,
 		httpso.Spec.PathPrefixes,
diff --git a/pkg/k8s/deployment_cache_fake.go b/pkg/k8s/deployment_cache_fake.go
deleted file mode 100644
index 51bf08a..0000000
--- a/pkg/k8s/deployment_cache_fake.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package k8s
-
-import (
-	"encoding/json"
-	"fmt"
-	"sync"
-
-	appsv1 "k8s.io/api/apps/v1"
-	"k8s.io/apimachinery/pkg/watch"
-)
-
-// FakeDeploymentCache is a fake implementation of
-// DeploymentCache, suitable for testing interceptor-level
-// logic, without requiring any real Kubernetes client
-// or API interaction
-type FakeDeploymentCache struct {
-	json.Marshaler
-	mut      *sync.RWMutex
-	current  map[string]appsv1.Deployment
-	watchers map[string]*watch.RaceFreeFakeWatcher
-}
-
-var _ DeploymentCache = &FakeDeploymentCache{}
-
-func NewFakeDeploymentCache() *FakeDeploymentCache {
-	return &FakeDeploymentCache{
-		mut:      &sync.RWMutex{},
-		current:  make(map[string]appsv1.Deployment),
-		watchers: make(map[string]*watch.RaceFreeFakeWatcher),
-	}
-}
-
-// AddDeployment adds a deployment to the current in-memory cache without
-// sending an event to any of the watchers
-func (f *FakeDeploymentCache) AddDeployment(depl appsv1.Deployment) {
-	f.mut.Lock()
-	defer f.mut.Unlock()
-	f.current[key(depl.Namespace, depl.Name)] = depl
-}
-
-// CurrentDeployments returns a map of all the current deployments.
-//
-// The key in the map is a combination of the namespace and name of
-// the corresponding deployment, but the format of the key is not guaranteed
-func (f *FakeDeploymentCache) CurrentDeployments() map[string]appsv1.Deployment {
-	f.mut.RLock()
-	defer f.mut.RUnlock()
-	return f.current
-}
-
-// GetWatcher gets the watcher for the given namespace and name, or
-// nil if there wasn't one registered.
-//
-// Watchers are registered by the .Watch() method
-func (f *FakeDeploymentCache) GetWatcher(ns, name string) *watch.RaceFreeFakeWatcher {
-	f.mut.RLock()
-	defer f.mut.RUnlock()
-	return f.watchers[key(ns, name)]
-}
-
-func (f *FakeDeploymentCache) MarshalJSON() ([]byte, error) {
-	f.mut.RLock()
-	defer f.mut.RUnlock()
-	ret := map[string]int32{}
-	for name, deployment := range f.current {
-		ret[name] = *deployment.Spec.Replicas
-	}
-	return json.Marshal(ret)
-}
-
-func (f *FakeDeploymentCache) Get(ns string, name string) (appsv1.Deployment, error) {
-	f.mut.RLock()
-	defer f.mut.RUnlock()
-	ret, ok := f.current[key(ns, name)]
-	if ok {
-		return ret, nil
-	}
-	return appsv1.Deployment{}, fmt.Errorf("no deployment %s found", name)
-}
-
-func (f *FakeDeploymentCache) Watch(ns, name string) (watch.Interface, error) {
-	f.mut.RLock()
-	defer f.mut.RUnlock()
-	watcher, ok := f.watchers[key(ns, name)]
-	if !ok {
-		watcher = watch.NewRaceFreeFake()
-		f.watchers[key(ns, name)] = watcher
-	}
-	return watcher, nil
-}
-
-func (f *FakeDeploymentCache) Set(ns, name string, deployment appsv1.Deployment) {
-	f.mut.Lock()
-	defer f.mut.Unlock()
-	f.current[key(ns, name)] = deployment
-}
-
-// SetWatcher creates a new race-free fake watcher and sets it into
-// the internal watchers map. After this call, a call to Watch() with
-// the same namespace and name values will return a valid
-// watcher
-func (f *FakeDeploymentCache) SetWatcher(ns, name string) *watch.RaceFreeFakeWatcher {
-	f.mut.Lock()
-	defer f.mut.Unlock()
-	watcher := watch.NewRaceFreeFake()
-	f.watchers[key(ns, name)] = watcher
-	return watcher
-}
-
-func (f *FakeDeploymentCache) SetReplicas(ns, name string, num int32) error {
-	f.mut.Lock()
-	defer f.mut.Unlock()
-	deployment, err := f.Get(ns, name)
-	if err != nil {
-		return fmt.Errorf("no deployment %s found", name)
-	}
-	deployment.Spec.Replicas = &num
-	return nil
-}
-
-func key(ns, name string) string {
-	return fmt.Sprintf("%s/%s", ns, name)
-}
diff --git a/pkg/k8s/deployment_cache_informer.go b/pkg/k8s/deployment_cache_informer.go
deleted file mode 100644
index 7167438..0000000
--- a/pkg/k8s/deployment_cache_informer.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package k8s
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"time"
-
-	"github.com/go-logr/logr"
-	appsv1 "k8s.io/api/apps/v1"
-	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/watch"
-	"k8s.io/client-go/informers"
-	infappsv1 "k8s.io/client-go/informers/apps/v1"
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/tools/cache"
-)
-
-type InformerBackedDeploymentCache struct {
-	lggr         logr.Logger
-	deplInformer infappsv1.DeploymentInformer
-	bcaster      *watch.Broadcaster
-}
-
-func (i *InformerBackedDeploymentCache) MarshalJSON() ([]byte, error) {
-	lst := i.deplInformer.Lister()
-	depls, err := lst.List(labels.Everything())
-	if err != nil {
-		return nil, err
-	}
-	return json.Marshal(&depls)
-}
-
-func (i *InformerBackedDeploymentCache) Start(ctx context.Context) {
-	i.deplInformer.Informer().Run(ctx.Done())
-}
-
-func (i *InformerBackedDeploymentCache) Get(
-	ns,
-	name string,
-) (appsv1.Deployment, error) {
-	depl, err := i.deplInformer.Lister().Deployments(ns).Get(name)
-	if err != nil {
-		return appsv1.Deployment{}, err
-	}
-	return *depl, nil
-}
-
-func (i *InformerBackedDeploymentCache) Watch(
-	ns,
-	name string,
-) (watch.Interface, error) {
-	watched, err := i.bcaster.Watch()
-	if err != nil {
-		return nil, err
-	}
-	return watch.Filter(watched, func(e watch.Event) (watch.Event, bool) {
-		depl := e.Object.(*appsv1.Deployment)
-		if depl.Namespace == ns && depl.Name == name {
-			return e, true
-		}
-		return e, false
-	}), nil
-}
-
-func (i *InformerBackedDeploymentCache) addEvtHandler(obj interface{}) {
-	depl, ok := obj.(*appsv1.Deployment)
-	if !ok {
-		i.lggr.Error(
-			fmt.Errorf("informer expected deployment, got %v", obj),
-			"not forwarding event",
-		)
-		return
-	}
-
-	if err := i.bcaster.Action(watch.Added, depl); err != nil {
-		i.lggr.Error(err, "informer expected deployment")
-	}
-}
-
-func (i *InformerBackedDeploymentCache) updateEvtHandler(_, newObj interface{}) {
-	depl, ok := newObj.(*appsv1.Deployment)
-	if !ok {
-		i.lggr.Error(
-			fmt.Errorf("informer expected deployment, got %v", newObj),
-			"not forwarding event",
-		)
-		return
-	}
-
-	if err := i.bcaster.Action(watch.Modified, depl); err != nil {
-		i.lggr.Error(err, "informer expected deployment")
-	}
-}
-
-func (i *InformerBackedDeploymentCache) deleteEvtHandler(obj interface{}) {
-	depl, ok := obj.(*appsv1.Deployment)
-	if !ok {
-		i.lggr.Error(
-			fmt.Errorf("informer expected deployment, got %v", obj),
-			"not forwarding event",
-		)
-		return
-	}
-
-	if err := i.bcaster.Action(watch.Deleted, depl); err != nil {
-		i.lggr.Error(err, "informer expected deployment")
-	}
-}
-
-func NewInformerBackedDeploymentCache(
-	lggr logr.Logger,
-	cl kubernetes.Interface,
-	defaultResync time.Duration,
-) *InformerBackedDeploymentCache {
-	factory := informers.NewSharedInformerFactory(
-		cl,
-		defaultResync,
-	)
-	deplInformer := factory.Apps().V1().Deployments()
-	ret := &InformerBackedDeploymentCache{
-		lggr:         lggr,
-		bcaster:      watch.NewBroadcaster(0, watch.WaitIfChannelFull),
-		deplInformer: deplInformer,
-	}
-	_, err := ret.deplInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc:    ret.addEvtHandler,
-		UpdateFunc: ret.updateEvtHandler,
-		DeleteFunc: ret.deleteEvtHandler,
-	})
-	if err != nil {
-		lggr.Error(err, "error creating backend informer")
-	}
-	return ret
-}
diff --git a/pkg/k8s/deployment_cache.go b/pkg/k8s/endpoints_cache.go
similarity index 50%
rename from pkg/k8s/deployment_cache.go
rename to pkg/k8s/endpoints_cache.go
index 13ae9c4..9c1202f 100644
--- a/pkg/k8s/deployment_cache.go
+++ b/pkg/k8s/endpoints_cache.go
@@ -3,28 +3,28 @@ package k8s
 import (
 	"encoding/json"
 
-	appsv1 "k8s.io/api/apps/v1"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/watch"
 )
 
-// DeploymentCache is a simple cache of deployments.
-// It allows callers to quickly get a given deployment in a given
-// namespace, or watch for changes to a specific deployment, all
+// EndpointsCache is a simple cache of endpoints.
+// It allows callers to quickly get given endpoints in a given
+// namespace, or watch for changes to specific endpoints, all
 // without incurring the cost of issuing a network request
 // to the Kubernetes API
-type DeploymentCache interface {
+type EndpointsCache interface {
 	json.Marshaler
-	// Get gets the a deployment with the given name
+	// Get gets the endpoints with the given name
 	// in the given namespace from the cache.
 	//
-	// If the deployment doesn't exist in the cache, it
+	// If the endpoints doesn't exist in the cache, it
 	// will be requested from the backing store (most commonly
 	// the Kubernetes API server)
-	Get(namespace, name string) (appsv1.Deployment, error)
-	// Watch opens a watch stream for the deployment with
+	Get(namespace, name string) (v1.Endpoints, error)
+	// Watch opens a watch stream for the endpoints with
 	// the given name in the given namespace from the cache.
 	//
-	// If the deployment doesn't exist in the cache, it
+	// If the endpoints don't exist in the cache, it
 	// will be requested from the backing store (most commonly
 	// the Kubernetes API server)
 	Watch(namespace, name string) (watch.Interface, error)
diff --git a/pkg/k8s/endpoints_cache_fake.go b/pkg/k8s/endpoints_cache_fake.go
new file mode 100644
index 0000000..e122fc0
--- /dev/null
+++ b/pkg/k8s/endpoints_cache_fake.go
@@ -0,0 +1,123 @@
+package k8s
+
+import (
+	"encoding/json"
+	"fmt"
+	"sync"
+
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/watch"
+)
+
+// FakeEndpointsCache is a fake implementation of
+// EndpointsCache, suitable for testing interceptor-level
+// logic, without requiring any real Kubernetes client
+// or API interaction
+type FakeEndpointsCache struct {
+	json.Marshaler
+	mut      *sync.RWMutex
+	current  map[string]v1.Endpoints
+	watchers map[string]*watch.RaceFreeFakeWatcher
+}
+
+var _ EndpointsCache = &FakeEndpointsCache{}
+
+func NewFakeEndpointsCache() *FakeEndpointsCache {
+	return &FakeEndpointsCache{
+		mut:      &sync.RWMutex{},
+		current:  make(map[string]v1.Endpoints),
+		watchers: make(map[string]*watch.RaceFreeFakeWatcher),
+	}
+}
+
+// GetWatcher gets the watcher for the given namespace and name, or
+// nil if there wasn't one registered.
+//
+// Watchers are registered by the .Watch() method
+func (f *FakeEndpointsCache) GetWatcher(ns, name string) *watch.RaceFreeFakeWatcher {
+	f.mut.RLock()
+	defer f.mut.RUnlock()
+	return f.watchers[key(ns, name)]
+}
+
+func (f *FakeEndpointsCache) MarshalJSON() ([]byte, error) {
+	f.mut.RLock()
+	defer f.mut.RUnlock()
+	ret := map[string]int{}
+	for name, endpoints := range f.current {
+		total := 0
+		for _, subset := range endpoints.Subsets {
+			total += len(subset.Addresses)
+		}
+		ret[name] = total
+	}
+	return json.Marshal(ret)
+}
+
+func (f *FakeEndpointsCache) Get(ns string, name string) (v1.Endpoints, error) {
+	f.mut.RLock()
+	defer f.mut.RUnlock()
+	ret, ok := f.current[key(ns, name)]
+	if ok {
+		return ret, nil
+	}
+	return v1.Endpoints{}, fmt.Errorf("no endpoints %s found", name)
+}
+
+// Set adds a endpoints to the current in-memory cache without
+// sending an event to any of the watchers
+func (f *FakeEndpointsCache) Set(endp v1.Endpoints) {
+	f.mut.Lock()
+	defer f.mut.Unlock()
+	f.current[key(endp.Namespace, endp.Name)] = endp
+}
+
+func (f *FakeEndpointsCache) Watch(ns, name string) (watch.Interface, error) {
+	f.mut.RLock()
+	defer f.mut.RUnlock()
+	watcher, ok := f.watchers[key(ns, name)]
+	if !ok {
+		watcher = watch.NewRaceFreeFake()
+		f.watchers[key(ns, name)] = watcher
+	}
+	return watcher, nil
+}
+
+// SetWatcher creates a new race-free fake watcher and sets it into
+// the internal watchers map. After this call, a call to Watch() with
+// the same namespace and name values will return a valid
+// watcher
+func (f *FakeEndpointsCache) SetWatcher(ns, name string) *watch.RaceFreeFakeWatcher {
+	f.mut.Lock()
+	defer f.mut.Unlock()
+	watcher := watch.NewRaceFreeFake()
+	f.watchers[key(ns, name)] = watcher
+	return watcher
+}
+
+func (f *FakeEndpointsCache) SetSubsets(ns, name string, num int) error {
+	endpoints, err := f.Get(ns, name)
+	if err != nil {
+		return fmt.Errorf("no endpoints %s found", name)
+	}
+	subsets := []v1.EndpointSubset{}
+
+	for i := 0; i < num; i++ {
+		subset := v1.EndpointSubset{
+			Addresses: []v1.EndpointAddress{
+				{
+					IP: "1.2.3.4",
+				},
+			},
+		}
+		subsets = append(subsets, subset)
+	}
+
+	endpoints.Subsets = subsets
+	f.Set(endpoints)
+	return nil
+}
+
+func key(ns, name string) string {
+	return fmt.Sprintf("%s/%s", ns, name)
+}
diff --git a/pkg/k8s/endpoints_cache_informer.go b/pkg/k8s/endpoints_cache_informer.go
new file mode 100644
index 0000000..2b1eba4
--- /dev/null
+++ b/pkg/k8s/endpoints_cache_informer.go
@@ -0,0 +1,135 @@
+package k8s
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"time"
+
+	"github.com/go-logr/logr"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/watch"
+	"k8s.io/client-go/informers"
+	infcorev1 "k8s.io/client-go/informers/core/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/cache"
+)
+
+type InformerBackedEndpointsCache struct {
+	lggr              logr.Logger
+	endpointsInformer infcorev1.EndpointsInformer
+	bcaster           *watch.Broadcaster
+}
+
+func (i *InformerBackedEndpointsCache) MarshalJSON() ([]byte, error) {
+	lst := i.endpointsInformer.Lister()
+	depls, err := lst.List(labels.Everything())
+	if err != nil {
+		return nil, err
+	}
+	return json.Marshal(&depls)
+}
+
+func (i *InformerBackedEndpointsCache) Start(ctx context.Context) {
+	i.endpointsInformer.Informer().Run(ctx.Done())
+}
+
+func (i *InformerBackedEndpointsCache) Get(
+	ns,
+	name string,
+) (v1.Endpoints, error) {
+	depl, err := i.endpointsInformer.Lister().Endpoints(ns).Get(name)
+	if err != nil {
+		return v1.Endpoints{}, err
+	}
+	return *depl, nil
+}
+
+func (i *InformerBackedEndpointsCache) Watch(
+	ns,
+	name string,
+) (watch.Interface, error) {
+	watched, err := i.bcaster.Watch()
+	if err != nil {
+		return nil, err
+	}
+	return watch.Filter(watched, func(e watch.Event) (watch.Event, bool) {
+		depl := e.Object.(*v1.Endpoints)
+		if depl.Namespace == ns && depl.Name == name {
+			return e, true
+		}
+		return e, false
+	}), nil
+}
+
+func (i *InformerBackedEndpointsCache) addEvtHandler(obj interface{}) {
+	depl, ok := obj.(*v1.Endpoints)
+	if !ok {
+		i.lggr.Error(
+			fmt.Errorf("informer expected service, got %v", obj),
+			"not forwarding event",
+		)
+		return
+	}
+
+	if err := i.bcaster.Action(watch.Added, depl); err != nil {
+		i.lggr.Error(err, "informer expected service")
+	}
+}
+
+func (i *InformerBackedEndpointsCache) updateEvtHandler(_, newObj interface{}) {
+	depl, ok := newObj.(*v1.Endpoints)
+	if !ok {
+		i.lggr.Error(
+			fmt.Errorf("informer expected service, got %v", newObj),
+			"not forwarding event",
+		)
+		return
+	}
+
+	if err := i.bcaster.Action(watch.Modified, depl); err != nil {
+		i.lggr.Error(err, "informer expected service")
+	}
+}
+
+func (i *InformerBackedEndpointsCache) deleteEvtHandler(obj interface{}) {
+	depl, ok := obj.(*v1.Endpoints)
+	if !ok {
+		i.lggr.Error(
+			fmt.Errorf("informer expected service, got %v", obj),
+			"not forwarding event",
+		)
+		return
+	}
+
+	if err := i.bcaster.Action(watch.Deleted, depl); err != nil {
+		i.lggr.Error(err, "informer expected service")
+	}
+}
+
+func NewInformerBackedEndpointsCache(
+	lggr logr.Logger,
+	cl kubernetes.Interface,
+	defaultResync time.Duration,
+) *InformerBackedEndpointsCache {
+	factory := informers.NewSharedInformerFactory(
+		cl,
+		defaultResync,
+	)
+	endpointsInformer := factory.Core().V1().Endpoints()
+	ret := &InformerBackedEndpointsCache{
+		lggr:              lggr,
+		bcaster:           watch.NewBroadcaster(0, watch.WaitIfChannelFull),
+		endpointsInformer: endpointsInformer,
+	}
+	_, err := ret.endpointsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc:    ret.addEvtHandler,
+		UpdateFunc: ret.updateEvtHandler,
+		DeleteFunc: ret.deleteEvtHandler,
+	})
+	if err != nil {
+		lggr.Error(err, "error creating backend informer")
+	}
+	return ret
+}
diff --git a/pkg/k8s/scaledobject.go b/pkg/k8s/scaledobject.go
index e7bf57d..b5b61d6 100644
--- a/pkg/k8s/scaledobject.go
+++ b/pkg/k8s/scaledobject.go
@@ -4,9 +4,10 @@ import (
 	"strings"
 
 	kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
-	appsv1 "k8s.io/api/apps/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/ptr"
+
+	"github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
 )
 
 const (
@@ -22,7 +23,7 @@ const (
 func NewScaledObject(
 	namespace string,
 	name string,
-	deploymentName string,
+	workloadRef v1alpha1.ScaleTargetRef,
 	scalerAddress string,
 	hosts []string,
 	pathPrefixes []string,
@@ -41,9 +42,9 @@ func NewScaledObject(
 		},
 		Spec: kedav1alpha1.ScaledObjectSpec{
 			ScaleTargetRef: &kedav1alpha1.ScaleTarget{
-				APIVersion: appsv1.SchemeGroupVersion.Identifier(),
-				Kind:       ObjectKind(&appsv1.Deployment{}),
-				Name:       deploymentName,
+				APIVersion: workloadRef.APIVersion,
+				Kind:       workloadRef.Kind,
+				Name:       workloadRef.Name,
 			},
 			PollingInterval: ptr.To[int32](soPollingInterval),
 			CooldownPeriod:  cooldownPeriod,
diff --git a/scaler/Dockerfile b/scaler/Dockerfile
index cca8895..c558075 100644
--- a/scaler/Dockerfile
+++ b/scaler/Dockerfile
@@ -1,4 +1,4 @@
-FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/keda-tools:1.20.8 as builder
+FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/keda-tools:1.21.5 as builder
 WORKDIR /workspace
 COPY go.* .
 RUN go mod download
diff --git a/scaler/main.go b/scaler/main.go
index bc9030d..d41c05a 100644
--- a/scaler/main.go
+++ b/scaler/main.go
@@ -33,7 +33,6 @@ import (
 )
 
 // +kubebuilder:rbac:groups="",resources=endpoints,verbs=get;list;watch
-// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch
 // +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch
 
 func main() {
@@ -74,8 +73,8 @@ func main() {
 		os.Exit(1)
 	}
 
-	// create the deployment informer
-	deployInformer := k8s.NewInformerBackedDeploymentCache(
+	// create the endpoints informer
+	endpInformer := k8s.NewInformerBackedEndpointsCache(
 		lggr,
 		k8sCl,
 		cfg.DeploymentCacheRsyncPeriod,
@@ -94,11 +93,11 @@ func main() {
 
 	eg, ctx := errgroup.WithContext(ctx)
 
-	// start the deployment informer
+	// start the endpoints informer
 	eg.Go(func() error {
-		lggr.Info("starting the deployment informer")
+		lggr.Info("starting the endpoints informer")
 
-		deployInformer.Start(ctx)
+		endpInformer.Start(ctx)
 		return nil
 	})
 
@@ -113,7 +112,7 @@ func main() {
 	eg.Go(func() error {
 		lggr.Info("starting the queue pinger")
 
-		if err := pinger.start(ctx, time.NewTicker(cfg.QueueTickDuration), deployInformer); !util.IsIgnoredErr(err) {
+		if err := pinger.start(ctx, time.NewTicker(cfg.QueueTickDuration), endpInformer); !util.IsIgnoredErr(err) {
 			lggr.Error(err, "queue pinger failed")
 			return err
 		}
diff --git a/scaler/queue_pinger.go b/scaler/queue_pinger.go
index 6f13961..548c8dd 100644
--- a/scaler/queue_pinger.go
+++ b/scaler/queue_pinger.go
@@ -34,16 +34,16 @@ import (
 //	// context
 //	go pinger.start(ctx, ticker)
 type queuePinger struct {
-	getEndpointsFn      k8s.GetEndpointsFunc
-	interceptorNS       string
-	interceptorSvcName  string
-	interceptorDeplName string
-	adminPort           string
-	pingMut             *sync.RWMutex
-	lastPingTime        time.Time
-	allCounts           map[string]int
-	aggregateCount      int
-	lggr                logr.Logger
+	getEndpointsFn         k8s.GetEndpointsFunc
+	interceptorNS          string
+	interceptorSvcName     string
+	interceptorServiceName string
+	adminPort              string
+	pingMut                *sync.RWMutex
+	lastPingTime           time.Time
+	allCounts              map[string]int
+	aggregateCount         int
+	lggr                   logr.Logger
 }
 
 func newQueuePinger(
@@ -57,15 +57,15 @@ func newQueuePinger(
 ) (*queuePinger, error) {
 	pingMut := new(sync.RWMutex)
 	pinger := &queuePinger{
-		getEndpointsFn:      getEndpointsFn,
-		interceptorNS:       ns,
-		interceptorSvcName:  svcName,
-		interceptorDeplName: deplName,
-		adminPort:           adminPort,
-		pingMut:             pingMut,
-		lggr:                lggr,
-		allCounts:           map[string]int{},
-		aggregateCount:      0,
+		getEndpointsFn:         getEndpointsFn,
+		interceptorNS:          ns,
+		interceptorSvcName:     svcName,
+		interceptorServiceName: deplName,
+		adminPort:              adminPort,
+		pingMut:                pingMut,
+		lggr:                   lggr,
+		allCounts:              map[string]int{},
+		aggregateCount:         0,
 	}
 	return pinger, pinger.fetchAndSaveCounts(ctx)
 }
@@ -74,14 +74,14 @@ func newQueuePinger(
 func (q *queuePinger) start(
 	ctx context.Context,
 	ticker *time.Ticker,
-	deplCache k8s.DeploymentCache,
+	endpCache k8s.EndpointsCache,
 ) error {
-	deployWatchIface, err := deplCache.Watch(q.interceptorNS, q.interceptorDeplName)
+	endpoWatchIface, err := endpCache.Watch(q.interceptorNS, q.interceptorServiceName)
 	if err != nil {
 		return err
 	}
-	deployEvtChan := deployWatchIface.ResultChan()
-	defer deployWatchIface.Stop()
+	endpEvtChan := endpoWatchIface.ResultChan()
+	defer endpoWatchIface.Stop()
 
 	lggr := q.lggr.WithName("scaler.queuePinger.start")
 	defer ticker.Stop()
@@ -102,13 +102,13 @@ func (q *queuePinger) start(
 				return fmt.Errorf("error getting request counts: %w", err)
 			}
 		// handle changes to the interceptor fleet
-		// Deployment
-		case <-deployEvtChan:
+		// Endpoints
+		case <-endpEvtChan:
 			err := q.fetchAndSaveCounts(ctx)
 			if err != nil {
 				lggr.Error(
 					err,
-					"getting request counts after interceptor deployment event",
+					"getting request counts after interceptor endpoints event",
 				)
 			}
 		}


Test patch:
diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml
index 3df2875..5114afd 100644
--- a/.github/workflows/e2e-tests.yaml
+++ b/.github/workflows/e2e-tests.yaml
@@ -101,9 +101,9 @@ jobs:
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
 
-      - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1
+      - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v4.1
         with:
-          go-version: "1.20"
+          go-version: "1.21"
 
       - name: Helm install
         uses: Azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index aadaaa2..3019fb3 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -13,7 +13,7 @@ jobs:
   validate:
     name: validate - ${{ matrix.name }}
     runs-on: ${{ matrix.runner }}
-    container: ghcr.io/kedacore/keda-tools:1.20.8
+    container: ghcr.io/kedacore/keda-tools:1.21.5
     strategy:
       matrix:
         include:
@@ -71,12 +71,12 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
-      - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
+      - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
         with:
           python-version: 3.x
-      - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1
+      - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v4.1
         with:
-          go-version: "1.20"
+          go-version: "1.21"
       - name: Get golangci
         run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2
       - uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 # v3.0.0
diff --git a/examples/xkcd/templates/tests/test-connection.yaml b/examples/xkcd/templates/tests/test-connection.yaml
deleted file mode 100644
index 6e3357f..0000000
--- a/examples/xkcd/templates/tests/test-connection.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: v1
-kind: Pod
-metadata:
-  name: "{{ include "xkcd.fullname" . }}-test-connection"
-  labels:
-    {{- include "xkcd.labels" . | nindent 4 }}
-  annotations:
-    "helm.sh/hook": test
-spec:
-  containers:
-    - name: wget
-      image: busybox
-      command: ['wget']
-      args: ['{{ include "xkcd.fullname" . }}:{{ .Values.service.port }}']
-  restartPolicy: Never
diff --git a/interceptor/forward_wait_func_test.go b/interceptor/forward_wait_func_test.go
index 652dea6..3a5f0f0 100644
--- a/interceptor/forward_wait_func_test.go
+++ b/interceptor/forward_wait_func_test.go
@@ -8,8 +8,7 @@ import (
 	"github.com/go-logr/logr"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/sync/errgroup"
-	appsv1 "k8s.io/api/apps/v1"
-	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/watch"
 
@@ -17,70 +16,55 @@ import (
 )
 
 // Test to make sure the wait function returns a nil error if there is immediately
-// one replica on the target deployment
+// one active endpoint on the target deployment
 func TestForwardWaitFuncOneReplica(t *testing.T) {
 	ctx := context.Background()
 
 	const waitFuncWait = 1 * time.Second
 	r := require.New(t)
 	const ns = "testNS"
-	const deployName = "TestForwardingHandlerDeploy"
-	cache := k8s.NewFakeDeploymentCache()
-	cache.AddDeployment(*newDeployment(
-		ns,
-		deployName,
-		"myimage",
-		[]int32{123},
-		nil,
-		map[string]string{},
-		corev1.PullAlways,
-	))
+	const endpointsName = "TestForwardingHandler"
+	endpoints := *newEndpoint(ns, endpointsName)
+	cache := k8s.NewFakeEndpointsCache()
+	cache.Set(endpoints)
+	r.NoError(cache.SetSubsets(ns, endpointsName, 1))
 
 	ctx, done := context.WithTimeout(ctx, waitFuncWait)
 	defer done()
 	group, ctx := errgroup.WithContext(ctx)
 
-	waitFunc := newDeployReplicasForwardWaitFunc(
+	waitFunc := newWorkloadReplicasForwardWaitFunc(
 		logr.Discard(),
 		cache,
 	)
 
 	group.Go(func() error {
-		_, err := waitFunc(ctx, ns, deployName)
+		_, err := waitFunc(ctx, ns, endpointsName)
 		return err
 	})
 	r.NoError(group.Wait(), "wait function failed, but it shouldn't have")
 }
 
-// Test to make sure the wait function returns an error if there are no replicas, and that doesn't change
+// Test to make sure the wait function returns an error if there are active endpoints, and that doesn't change
 // within a timeout
 func TestForwardWaitFuncNoReplicas(t *testing.T) {
 	ctx := context.Background()
 	const waitFuncWait = 1 * time.Second
 	r := require.New(t)
 	const ns = "testNS"
-	const deployName = "TestForwardingHandlerHoldsDeployment"
-	deployment := newDeployment(
-		ns,
-		deployName,
-		"myimage",
-		[]int32{123},
-		nil,
-		map[string]string{},
-		corev1.PullAlways,
-	)
-	deployment.Status.ReadyReplicas = 0
-	cache := k8s.NewFakeDeploymentCache()
-	cache.AddDeployment(*deployment)
+	const endpointsName = "TestForwardWaitFuncNoReplicas"
+	endpoints := *newEndpoint(ns, endpointsName)
+	cache := k8s.NewFakeEndpointsCache()
+	cache.Set(endpoints)
 
 	ctx, done := context.WithTimeout(ctx, waitFuncWait)
 	defer done()
-	waitFunc := newDeployReplicasForwardWaitFunc(
+	waitFunc := newWorkloadReplicasForwardWaitFunc(
 		logr.Discard(),
 		cache,
 	)
 
-	_, err := waitFunc(ctx, ns, deployName)
+	_, err := waitFunc(ctx, ns, endpointsName)
 	r.Error(err)
 }
 
@@ -90,100 +74,58 @@ func TestWaitFuncWaitsUntilReplicas(t *testing.T) {
 	totalWaitDur := 500 * time.Millisecond
 
 	const ns = "testNS"
-	const deployName = "TestForwardingHandlerHoldsDeployment"
-	deployment := newDeployment(
-		ns,
-		deployName,
-		"myimage",
-		[]int32{123},
-		nil,
-		map[string]string{},
-		corev1.PullAlways,
-	)
-	deployment.Spec.Replicas = k8s.Int32P(0)
-	cache := k8s.NewFakeDeploymentCache()
-	cache.AddDeployment(*deployment)
+	const endpointsName = "TestForwardingHandlerHolds"
+
+	endpoints := *newEndpoint(ns, endpointsName)
+	cache := k8s.NewFakeEndpointsCache()
+	cache.Set(endpoints)
 	// create a watcher first so that the goroutine
 	// can later fetch it and send a message on it
-	_, err := cache.Watch(ns, deployName)
+	_, err := cache.Watch(ns, endpointsName)
 	r.NoError(err)
 
 	ctx, done := context.WithTimeout(ctx, totalWaitDur)
-	waitFunc := newDeployReplicasForwardWaitFunc(
+	waitFunc := newWorkloadReplicasForwardWaitFunc(
 		logr.Discard(),
 		cache,
 	)
 
-	// this channel will be closed immediately after the replicas were increased
-	replicasIncreasedCh := make(chan struct{})
+	// this channel will be closed immediately after the active endpoints were increased
+	activeEndpointsIncreasedCh := make(chan struct{})
 	go func() {
 		time.Sleep(totalWaitDur / 2)
-		watcher := cache.GetWatcher(ns, deployName)
+		watcher := cache.GetWatcher(ns, endpointsName)
 		r.NotNil(watcher, "watcher was not found")
-		modifiedDeployment := deployment.DeepCopy()
-		modifiedDeployment.Spec.Replicas = k8s.Int32P(1)
-		watcher.Action(watch.Modified, modifiedDeployment)
-		close(replicasIncreasedCh)
+		modifiedEndpoints := endpoints.DeepCopy()
+		modifiedEndpoints.Subsets = []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{IP: "1.2.3.4"},
+				},
+			},
+		}
+		watcher.Action(watch.Modified, modifiedEndpoints)
+		close(activeEndpointsIncreasedCh)
 	}()
-	_, err = waitFunc(ctx, ns, deployName)
+	_, err = waitFunc(ctx, ns, endpointsName)
 	r.NoError(err)
 	done()
 }
 
-// newDeployment creates a new deployment object
+// newEndpoint creates a new endpoints object
 // with the given name and the given image. This does not actually create
-// the deployment in the cluster, it just creates the deployment object
+// the endpoints in the cluster, it just creates the endpoints object
 // in memory
-func newDeployment(
+func newEndpoint(
 	namespace,
-	name,
-	image string,
-	ports []int32,
-	env []corev1.EnvVar,
-	labels map[string]string,
-	pullPolicy corev1.PullPolicy,
-) *appsv1.Deployment {
-	containerPorts := make([]corev1.ContainerPort, len(ports))
-	for i, port := range ports {
-		containerPorts[i] = corev1.ContainerPort{
-			ContainerPort: port,
-		}
-	}
-	deployment := &appsv1.Deployment{
-		TypeMeta: metav1.TypeMeta{
-			Kind: "Deployment",
-		},
+	name string,
+) *v1.Endpoints {
+	endpoints := &v1.Endpoints{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
 			Namespace: namespace,
-			Labels:    labels,
-		},
-		Spec: appsv1.DeploymentSpec{
-			Selector: &metav1.LabelSelector{
-				MatchLabels: labels,
-			},
-			Replicas: k8s.Int32P(1),
-			Template: corev1.PodTemplateSpec{
-				ObjectMeta: metav1.ObjectMeta{
-					Labels: labels,
-				},
-				Spec: corev1.PodSpec{
-					Containers: []corev1.Container{
-						{
-							Image:           image,
-							Name:            name,
-							ImagePullPolicy: pullPolicy,
-							Ports:           containerPorts,
-							Env:             env,
-						},
-					},
-				},
-			},
-		},
-		Status: appsv1.DeploymentStatus{
-			ReadyReplicas: 1,
 		},
 	}
 
-	return deployment
+	return endpoints
 }
diff --git a/interceptor/handler/upstream_test.go b/interceptor/handler/upstream_test.go
index e7cfe29..d3f9da4 100644
--- a/interceptor/handler/upstream_test.go
+++ b/interceptor/handler/upstream_test.go
@@ -238,10 +238,10 @@ func newRoundTripper(
 
 func defaultTimeouts() config.Timeouts {
 	return config.Timeouts{
-		Connect:            100 * time.Millisecond,
-		KeepAlive:          100 * time.Millisecond,
-		ResponseHeader:     500 * time.Millisecond,
-		DeploymentReplicas: 1 * time.Second,
+		Connect:          100 * time.Millisecond,
+		KeepAlive:        100 * time.Millisecond,
+		ResponseHeader:   500 * time.Millisecond,
+		WorkloadReplicas: 1 * time.Second,
 	}
 }
 
diff --git a/interceptor/main_test.go b/interceptor/main_test.go
index 09910a0..04e2ba7 100644
--- a/interceptor/main_test.go
+++ b/interceptor/main_test.go
@@ -47,6 +47,7 @@ func TestRunProxyServerCountMiddleware(t *testing.T) {
 		originURL,
 		originPort,
 		"testdepl",
+		"testservice",
 	)
 	namespacedName := k8s.NamespacedNameFromObject(httpso).String()
 
@@ -60,9 +61,9 @@ func TestRunProxyServerCountMiddleware(t *testing.T) {
 
 	timeouts := &config.Timeouts{}
 	waiterCh := make(chan struct{})
-	waitFunc := func(_ context.Context, _, _ string) (int, error) {
+	waitFunc := func(_ context.Context, _, _ string) (bool, error) {
 		<-waiterCh
-		return 1, nil
+		return false, nil
 	}
 	g.Go(func() error {
 		return runProxyServer(
diff --git a/interceptor/middleware/counting_test.go b/interceptor/middleware/counting_test.go
index 7f15b40..dab6b24 100644
--- a/interceptor/middleware/counting_test.go
+++ b/interceptor/middleware/counting_test.go
@@ -34,9 +34,9 @@ func TestCountMiddleware(t *testing.T) {
 		},
 		Spec: httpv1alpha1.HTTPScaledObjectSpec{
 			ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-				Deployment: "testdepl",
-				Service:    ":",
-				Port:       8080,
+				Name:    "testdepl",
+				Service: "testservice",
+				Port:    8080,
 			},
 			TargetPendingRequests: ptr.To[int32](123),
 		},
diff --git a/interceptor/proxy_handlers_integration_test.go b/interceptor/proxy_handlers_integration_test.go
index d453053..61b9a01 100644
--- a/interceptor/proxy_handlers_integration_test.go
+++ b/interceptor/proxy_handlers_integration_test.go
@@ -16,7 +16,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/sync/errgroup"
-	appsv1 "k8s.io/api/apps/v1"
+	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 
@@ -29,13 +29,14 @@ import (
 // happy path - deployment is scaled to 1 and host in routing table
 func TestIntegrationHappyPath(t *testing.T) {
 	const (
-		deploymentReplicasTimeout = 200 * time.Millisecond
-		deplName                  = "testdeployment"
+		activeEndpointsTimeout = 200 * time.Millisecond
+		deploymentName         = "testdeployment"
+		serviceName            = "testservice"
 	)
 	r := require.New(t)
 	h, err := newHarness(
 		t,
-		deploymentReplicasTimeout,
+		activeEndpointsTimeout,
 	)
 	r.NoError(err)
 	defer h.close()
@@ -47,23 +48,18 @@ func TestIntegrationHappyPath(t *testing.T) {
 	target := targetFromURL(
 		h.originURL,
 		originPort,
-		deplName,
+		deploymentName,
+		serviceName,
 	)
 	h.routingTable.Memory[hostForTest(t)] = target
 
-	h.deplCache.Set(target.GetNamespace(), deplName, appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{Name: deplName},
-		Spec: appsv1.DeploymentSpec{
-			// note that the forwarding wait function doesn't care about
-			// the replicas field, it only cares about ReadyReplicas in the status.
-			// regardless, we're setting this because in a running cluster,
-			// it's likely that most of the time, this is equal to ReadyReplicas
-			Replicas: i32Ptr(3),
-		},
-		Status: appsv1.DeploymentStatus{
-			ReadyReplicas: 3,
+	h.endpCache.Set(v1.Endpoints{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      serviceName,
+			Namespace: target.GetNamespace(),
 		},
 	})
+	r.NoError(h.endpCache.SetSubsets(target.GetNamespace(), serviceName, 1))
 
 	// happy path
 	res, err := doRequest(
@@ -79,7 +75,7 @@ func TestIntegrationHappyPath(t *testing.T) {
 // deployment scaled to 1 but host not in routing table
 //
 // NOTE: the interceptor needs to check in the routing table
-// _before_ checking the deployment cache, so we don't technically
+// _before_ checking the endpoints cache, so we don't technically
 // need to set the replicas to 1, but we're doing so anyway to
 // isolate the routing table behavior
 func TestIntegrationNoRoutingTableEntry(t *testing.T) {
@@ -103,12 +99,13 @@ func TestIntegrationNoRoutingTableEntry(t *testing.T) {
 // host in the routing table but deployment has no replicas
 func TestIntegrationNoReplicas(t *testing.T) {
 	const (
-		deployTimeout = 100 * time.Millisecond
+		activeEndpointsTimeout = 100 * time.Millisecond
 	)
 	host := hostForTest(t)
-	deployName := "testdeployment"
+	deploymentName := "testdeployment"
+	serviceName := "testservice"
 	r := require.New(t)
-	h, err := newHarness(t, deployTimeout)
+	h, err := newHarness(t, activeEndpointsTimeout)
 	r.NoError(err)
 
 	originPort, err := strconv.Atoi(h.originURL.Port())
@@ -117,15 +114,16 @@ func TestIntegrationNoReplicas(t *testing.T) {
 	target := targetFromURL(
 		h.originURL,
 		originPort,
-		deployName,
+		deploymentName,
+		serviceName,
 	)
 	h.routingTable.Memory[hostForTest(t)] = target
 
 	// 0 replicas
-	h.deplCache.Set(target.GetNamespace(), deployName, appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{Name: deployName},
-		Spec: appsv1.DeploymentSpec{
-			Replicas: i32Ptr(0),
+	h.endpCache.Set(v1.Endpoints{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      serviceName,
+			Namespace: target.GetNamespace(),
 		},
 	})
 
@@ -139,22 +137,23 @@ func TestIntegrationNoReplicas(t *testing.T) {
 	r.Equal(502, res.StatusCode)
 	res.Body.Close()
 	elapsed := time.Since(start)
-	// we should have slept more than the deployment replicas wait timeout
-	r.GreaterOrEqual(elapsed, deployTimeout)
-	r.Less(elapsed, deployTimeout+50*time.Millisecond)
+	// we should have slept more than the active endpoints wait timeout
+	r.GreaterOrEqual(elapsed, activeEndpointsTimeout)
+	r.Less(elapsed, activeEndpointsTimeout+50*time.Millisecond)
 }
 
 // the request comes in while there are no replicas, and one is added
 // while it's pending
 func TestIntegrationWaitReplicas(t *testing.T) {
 	const (
-		deployTimeout   = 2 * time.Second
-		responseTimeout = 1 * time.Second
-		deployName      = "testdeployment"
+		activeEndpointsTimeout = 2 * time.Second
+		responseTimeout        = 1 * time.Second
+		deploymentName         = "testdeployment"
+		serviceName            = "testservice"
 	)
 	ctx := context.Background()
 	r := require.New(t)
-	h, err := newHarness(t, deployTimeout)
+	h, err := newHarness(t, activeEndpointsTimeout)
 	r.NoError(err)
 
 	// add host to routing table
@@ -164,24 +163,25 @@ func TestIntegrationWaitReplicas(t *testing.T) {
 	target := targetFromURL(
 		h.originURL,
 		originPort,
-		deployName,
+		deploymentName,
+		serviceName,
 	)
 	h.routingTable.Memory[hostForTest(t)] = target
 
-	// set up a deployment with zero replicas and create
-	// a watcher we can use later to fake-send a deployment
+	// set up a endpoint with zero replicas and create
+	// a watcher we can use later to fake-send a endpoint
 	// event
-	initialDeployment := appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{Name: deployName},
-		Spec: appsv1.DeploymentSpec{
-			Replicas: i32Ptr(0),
+	h.endpCache.Set(v1.Endpoints{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      serviceName,
+			Namespace: target.GetNamespace(),
 		},
-	}
-	h.deplCache.Set(target.GetNamespace(), deployName, initialDeployment)
-	watcher := h.deplCache.SetWatcher(target.GetNamespace(), deployName)
+	})
+	endpoints, _ := h.endpCache.Get(target.GetNamespace(), serviceName)
+	watcher := h.endpCache.SetWatcher(target.GetNamespace(), serviceName)
 
 	// make the request in one goroutine, and in the other, wait a bit
-	// and then add replicas to the deployment cache
+	// and then add replicas to the endpoints cache
 
 	var response *http.Response
 	grp, _ := errgroup.WithContext(ctx)
@@ -198,20 +198,23 @@ func TestIntegrationWaitReplicas(t *testing.T) {
 		resp.Body.Close()
 		return nil
 	})
-	const sleepDur = deployTimeout / 4
+	const sleepDur = activeEndpointsTimeout / 4
 	grp.Go(func() error {
 		t.Logf("Sleeping for %s", sleepDur)
 		time.Sleep(sleepDur)
 		t.Logf("Woke up, setting replicas to 10")
-		modifiedDeployment := initialDeployment.DeepCopy()
-		// note that the wait function only cares about Status.ReadyReplicas
-		// but we're setting Spec.Replicas to 10 as well because the common
-		// case in the cluster is that they would be equal
-		modifiedDeployment.Spec.Replicas = i32Ptr(10)
-		modifiedDeployment.Status.ReadyReplicas = 10
+
+		modifiedEndpoints := endpoints.DeepCopy()
+		modifiedEndpoints.Subsets = []v1.EndpointSubset{
+			{
+				Addresses: []v1.EndpointAddress{
+					{IP: "1.2.3.4"},
+				},
+			},
+		}
 		// send a watch event (instead of setting replicas) so that the watch
 		// func sees that it can forward the request now
-		watcher.Modify(modifiedDeployment)
+		watcher.Modify(modifiedEndpoints)
 		return nil
 	})
 	start := time.Now()
@@ -256,13 +259,13 @@ type harness struct {
 	originURL    *url.URL
 	routingTable *routingtest.Table
 	dialCtxFunc  kedanet.DialContextFunc
-	deplCache    *k8s.FakeDeploymentCache
+	endpCache    *k8s.FakeEndpointsCache
 	waitFunc     forwardWaitFunc
 }
 
 func newHarness(
 	t *testing.T,
-	deployReplicasTimeout time.Duration,
+	activeEndpointsTimeout time.Duration,
 ) (*harness, error) {
 	t.Helper()
 	lggr := logr.Discard()
@@ -277,10 +280,10 @@ func newHarness(
 		},
 	)
 
-	deplCache := k8s.NewFakeDeploymentCache()
-	waitFunc := newDeployReplicasForwardWaitFunc(
+	endpCache := k8s.NewFakeEndpointsCache()
+	waitFunc := newWorkloadReplicasForwardWaitFunc(
 		logr.Discard(),
-		deplCache,
+		endpCache,
 	)
 
 	originHdl := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -300,7 +303,7 @@ func newHarness(
 		dialContextFunc,
 		waitFunc,
 		forwardingConfig{
-			waitTimeout:       deployReplicasTimeout,
+			waitTimeout:       activeEndpointsTimeout,
 			respHeaderTimeout: time.Second,
 		},
 	))
@@ -320,7 +323,7 @@ func newHarness(
 		originURL:    originSrvURL,
 		routingTable: routingTable,
 		dialCtxFunc:  dialContextFunc,
-		deplCache:    deplCache,
+		endpCache:    endpCache,
 		waitFunc:     waitFunc,
 	}, nil
 }
@@ -338,10 +341,6 @@ func (h *harness) String() string {
 	)
 }
 
-func i32Ptr(i int32) *int32 {
-	return &i
-}
-
 func hostForTest(t *testing.T) string {
 	t.Helper()
 	return fmt.Sprintf("%s.integrationtest.interceptor.kedahttp.dev", t.Name())
diff --git a/interceptor/proxy_handlers_test.go b/interceptor/proxy_handlers_test.go
index 6bbfa21..b282e09 100644
--- a/interceptor/proxy_handlers_test.go
+++ b/interceptor/proxy_handlers_test.go
@@ -43,15 +43,15 @@ func TestImmediatelySuccessfulProxy(t *testing.T) {
 
 	timeouts := defaultTimeouts()
 	dialCtxFunc := retryDialContextFunc(timeouts, timeouts.DefaultBackoff())
-	waitFunc := func(context.Context, string, string) (int, error) {
-		return 1, nil
+	waitFunc := func(context.Context, string, string) (bool, error) {
+		return false, nil
 	}
 	hdl := newForwardingHandler(
 		logr.Discard(),
 		dialCtxFunc,
 		waitFunc,
 		forwardingConfig{
-			waitTimeout:       timeouts.DeploymentReplicas,
+			waitTimeout:       timeouts.WorkloadReplicas,
 			respHeaderTimeout: timeouts.ResponseHeader,
 		},
 	)
@@ -62,6 +62,7 @@ func TestImmediatelySuccessfulProxy(t *testing.T) {
 		originURL,
 		originPort,
 		"testdepl",
+		"testservice",
 	))
 	req = util.RequestWithStream(req, originURL)
 	req.Host = host
@@ -86,15 +87,15 @@ func TestWaitFailedConnection(t *testing.T) {
 		timeouts,
 		backoff,
 	)
-	waitFunc := func(context.Context, string, string) (int, error) {
-		return 1, nil
+	waitFunc := func(context.Context, string, string) (bool, error) {
+		return false, nil
 	}
 	hdl := newForwardingHandler(
 		logr.Discard(),
 		dialCtxFunc,
 		waitFunc,
 		forwardingConfig{
-			waitTimeout:       timeouts.DeploymentReplicas,
+			waitTimeout:       timeouts.WorkloadReplicas,
 			respHeaderTimeout: timeouts.ResponseHeader,
 		},
 	)
@@ -109,9 +110,8 @@ func TestWaitFailedConnection(t *testing.T) {
 		},
 		Spec: httpv1alpha1.HTTPScaledObjectSpec{
 			ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-				Deployment: "nosuchdepl",
-				Service:    "nosuchdepl",
-				Port:       8081,
+				Service: "nosuchdepl",
+				Port:    8081,
 			},
 			TargetPendingRequests: ptr.To[int32](1234),
 		},
@@ -131,7 +131,7 @@ func TestTimesOutOnWaitFunc(t *testing.T) {
 	r := require.New(t)
 
 	timeouts := defaultTimeouts()
-	timeouts.DeploymentReplicas = 25 * time.Millisecond
+	timeouts.WorkloadReplicas = 25 * time.Millisecond
 	timeouts.ResponseHeader = 25 * time.Millisecond
 	dialCtxFunc := retryDialContextFunc(timeouts, timeouts.DefaultBackoff())
 
@@ -144,7 +144,7 @@ func TestTimesOutOnWaitFunc(t *testing.T) {
 		dialCtxFunc,
 		waitFunc,
 		forwardingConfig{
-			waitTimeout:       timeouts.DeploymentReplicas,
+			waitTimeout:       timeouts.WorkloadReplicas,
 			respHeaderTimeout: timeouts.ResponseHeader,
 		},
 	)
@@ -159,9 +159,8 @@ func TestTimesOutOnWaitFunc(t *testing.T) {
 		},
 		Spec: httpv1alpha1.HTTPScaledObjectSpec{
 			ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-				Deployment: "nosuchdepl",
-				Service:    "nosuchsvc",
-				Port:       9091,
+				Service: "nosuchsvc",
+				Port:    9091,
 			},
 			TargetPendingRequests: ptr.To[int32](1234),
 		},
@@ -176,8 +175,8 @@ func TestTimesOutOnWaitFunc(t *testing.T) {
 	t.Logf("elapsed time was %s", elapsed)
 	// serving should take at least timeouts.DeploymentReplicas, but no more than
 	// timeouts.DeploymentReplicas*4
-	r.GreaterOrEqual(elapsed, timeouts.DeploymentReplicas)
-	r.LessOrEqual(elapsed, timeouts.DeploymentReplicas*4)
+	r.GreaterOrEqual(elapsed, timeouts.WorkloadReplicas)
+	r.LessOrEqual(elapsed, timeouts.WorkloadReplicas*4)
 	r.Equal(502, res.Code, "response code was unexpected")
 
 	// we will always return the X-KEDA-HTTP-Cold-Start header
@@ -226,7 +225,7 @@ func TestWaitsForWaitFunc(t *testing.T) {
 		dialCtxFunc,
 		waitFunc,
 		forwardingConfig{
-			waitTimeout:       timeouts.DeploymentReplicas,
+			waitTimeout:       timeouts.WorkloadReplicas,
 			respHeaderTimeout: timeouts.ResponseHeader,
 		},
 	)
@@ -237,6 +236,7 @@ func TestWaitsForWaitFunc(t *testing.T) {
 		testSrvURL,
 		originPort,
 		"nosuchdepl",
+		"noservice",
 	))
 	req = util.RequestWithStream(req, testSrvURL)
 	req.Host = noSuchHost
@@ -257,6 +257,7 @@ func TestWaitsForWaitFunc(t *testing.T) {
 	r.GreaterOrEqual(elapsed, waitDur)
 	r.Less(elapsed, waitDur*4)
 
+	r.Equal("true", res.Header().Get("X-KEDA-HTTP-Cold-Start"), "expected X-KEDA-HTTP-Cold-Start true")
 	r.Equal(
 		originRespCode,
 		res.Code,
@@ -286,15 +287,15 @@ func TestWaitHeaderTimeout(t *testing.T) {
 
 	timeouts := defaultTimeouts()
 	dialCtxFunc := retryDialContextFunc(timeouts, timeouts.DefaultBackoff())
-	waitFunc := func(context.Context, string, string) (int, error) {
-		return 1, nil
+	waitFunc := func(context.Context, string, string) (bool, error) {
+		return false, nil
 	}
 	hdl := newForwardingHandler(
 		logr.Discard(),
 		dialCtxFunc,
 		waitFunc,
 		forwardingConfig{
-			waitTimeout:       timeouts.DeploymentReplicas,
+			waitTimeout:       timeouts.WorkloadReplicas,
 			respHeaderTimeout: timeouts.ResponseHeader,
 		},
 	)
@@ -307,9 +308,8 @@ func TestWaitHeaderTimeout(t *testing.T) {
 		},
 		Spec: httpv1alpha1.HTTPScaledObjectSpec{
 			ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-				Deployment: "nosuchdepl",
-				Service:    "testsvc",
-				Port:       9094,
+				Service: "testsvc",
+				Port:    9094,
 			},
 			TargetPendingRequests: ptr.To[int32](1234),
 		},
@@ -350,13 +350,13 @@ func notifyingFunc() (forwardWaitFunc, <-chan struct{}, func()) {
 	finishFunc := func() {
 		close(finishCh)
 	}
-	return func(ctx context.Context, _, _ string) (int, error) {
+	return func(ctx context.Context, _, _ string) (bool, error) {
 		close(calledCh)
 		select {
 		case <-finishCh:
-			return 0, nil
+			return true, nil
 		case <-ctx.Done():
-			return 0, fmt.Errorf("TEST FUNCTION CONTEXT ERROR: %w", ctx.Err())
+			return true, fmt.Errorf("TEST FUNCTION CONTEXT ERROR: %w", ctx.Err())
 		}
 	}, calledCh, finishFunc
 }
@@ -364,7 +364,8 @@ func notifyingFunc() (forwardWaitFunc, <-chan struct{}, func()) {
 func targetFromURL(
 	u *url.URL,
 	port int,
-	deployment string,
+	workload string,
+	service string,
 ) *httpv1alpha1.HTTPScaledObject {
 	host := strings.Split(u.Host, ":")[0]
 	return &httpv1alpha1.HTTPScaledObject{
@@ -373,9 +374,9 @@ func targetFromURL(
 		},
 		Spec: httpv1alpha1.HTTPScaledObjectSpec{
 			ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-				Deployment: deployment,
-				Service:    ":" + host,
-				Port:       int32(port),
+				Name:    workload,
+				Service: service,
+				Port:    int32(port),
 			},
 			TargetPendingRequests: ptr.To[int32](123),
 		},
@@ -384,10 +385,10 @@ func targetFromURL(
 
 func defaultTimeouts() config.Timeouts {
 	return config.Timeouts{
-		Connect:            100 * time.Millisecond,
-		KeepAlive:          100 * time.Millisecond,
-		ResponseHeader:     500 * time.Millisecond,
-		DeploymentReplicas: 1 * time.Second,
+		Connect:          100 * time.Millisecond,
+		KeepAlive:        100 * time.Millisecond,
+		ResponseHeader:   500 * time.Millisecond,
+		WorkloadReplicas: 1 * time.Second,
 	}
 }
 
diff --git a/operator/controllers/http/suite_test.go b/operator/controllers/http/suite_test.go
index b64d464..17f0928 100644
--- a/operator/controllers/http/suite_test.go
+++ b/operator/controllers/http/suite_test.go
@@ -109,9 +109,8 @@ func newCommonTestInfra(namespace, appName string) *commonTestInfra {
 		},
 		Spec: httpv1alpha1.HTTPScaledObjectSpec{
 			ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-				Deployment: appName,
-				Service:    appName,
-				Port:       8081,
+				Service: appName,
+				Port:    8081,
 			},
 			Hosts: []string{"myhost1.com", "myhost2.com"},
 		},
diff --git a/scaler/handlers_test.go b/scaler/handlers_test.go
index fe1ec82..023ec8a 100644
--- a/scaler/handlers_test.go
+++ b/scaler/handlers_test.go
@@ -346,9 +346,9 @@ func TestGetMetricSpecTable(t *testing.T) {
 					},
 					Spec: httpv1alpha1.HTTPScaledObjectSpec{
 						ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-							Deployment: "testdepl",
-							Service:    "testsrv",
-							Port:       8080,
+							Name:    "testdepl",
+							Service: "testsrv",
+							Port:    8080,
 						},
 						TargetPendingRequests: ptr.To[int32](123),
 					},
@@ -387,9 +387,9 @@ func TestGetMetricSpecTable(t *testing.T) {
 							"validHost2",
 						},
 						ScaleTargetRef: httpv1alpha1.ScaleTargetRef{
-							Deployment: "testdepl",
-							Service:    "testsrv",
-							Port:       8080,
+							Name:    "testdepl",
+							Service: "testsrv",
+							Port:    8080,
 						},
 						TargetPendingRequests: ptr.To[int32](123),
 					},
diff --git a/scaler/queue_pinger_test.go b/scaler/queue_pinger_test.go
index 2159ed7..9eb6624 100644
--- a/scaler/queue_pinger_test.go
+++ b/scaler/queue_pinger_test.go
@@ -73,7 +73,7 @@ func TestCounts(t *testing.T) {
 	r.NoError(q.Resize("host3", 3))
 	r.NoError(q.Resize("host4", 4))
 	ticker := time.NewTicker(tickDur)
-	fakeCache := k8s.NewFakeDeploymentCache()
+	fakeCache := k8s.NewFakeEndpointsCache()
 	go func() {
 		_ = pinger.start(ctx, ticker, fakeCache)
 	}()
diff --git a/tests/checks/ingress_in_app_namespace/ingress_in_app_namespace_test.go b/tests/checks/ingress_in_app_namespace/ingress_in_app_namespace_test.go
index 0f5d7c6..7f886b8 100644
--- a/tests/checks/ingress_in_app_namespace/ingress_in_app_namespace_test.go
+++ b/tests/checks/ingress_in_app_namespace/ingress_in_app_namespace_test.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
diff --git a/tests/checks/ingress_in_keda_namespace/ingress_in_keda_namespace_test.go b/tests/checks/ingress_in_keda_namespace/ingress_in_keda_namespace_test.go
index 0aea4ef..11b7db6 100644
--- a/tests/checks/ingress_in_keda_namespace/ingress_in_keda_namespace_test.go
+++ b/tests/checks/ingress_in_keda_namespace/ingress_in_keda_namespace_test.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
diff --git a/tests/checks/internal_service/internal_service_test.go b/tests/checks/internal_service/internal_service_test.go
index 5142e12..41e224b 100644
--- a/tests/checks/internal_service/internal_service_test.go
+++ b/tests/checks/internal_service/internal_service_test.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
diff --git a/tests/checks/multiple_hosts/multiple_hosts_test.go b/tests/checks/multiple_hosts/multiple_hosts_test.go
index 0b6f977..ed00f5a 100644
--- a/tests/checks/multiple_hosts/multiple_hosts_test.go
+++ b/tests/checks/multiple_hosts/multiple_hosts_test.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
diff --git a/tests/checks/path_prefix/path_prefix_test.go b/tests/checks/path_prefix/path_prefix_test.go
index 1b7b168..17cdc11 100644
--- a/tests/checks/path_prefix/path_prefix_test.go
+++ b/tests/checks/path_prefix/path_prefix_test.go
@@ -8,7 +8,7 @@ import (
 	"sync"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
diff --git a/tests/checks/scaling_phase_custom_resource/scaling_phase_custom_resource_test.go b/tests/checks/scaling_phase_custom_resource/scaling_phase_custom_resource_test.go
new file mode 100644
index 0000000..e2c9896
--- /dev/null
+++ b/tests/checks/scaling_phase_custom_resource/scaling_phase_custom_resource_test.go
@@ -0,0 +1,222 @@
+//go:build e2e
+// +build e2e
+
+package scaling_phase_test
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	. "github.com/kedacore/http-add-on/tests/helper"
+)
+
+const (
+	testName = "scaling-phase-custom-resource-test"
+)
+
+var (
+	testNamespace        = fmt.Sprintf("%s-ns", testName)
+	rolloutName          = fmt.Sprintf("%s-rollout", testName)
+	serviceName          = fmt.Sprintf("%s-service", testName)
+	httpScaledObjectName = fmt.Sprintf("%s-http-so", testName)
+	host                 = testName
+	minReplicaCount      = 0
+	maxReplicaCount      = 4
+)
+
+type templateData struct {
+	TestNamespace        string
+	RolloutName          string
+	ServiceName          string
+	HTTPScaledObjectName string
+	Host                 string
+	MinReplicas          int
+	MaxReplicas          int
+}
+
+const (
+	serviceTemplate = `
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{.ServiceName}}
+  namespace: {{.TestNamespace}}
+  labels:
+    app: {{.RolloutName}}
+spec:
+  ports:
+    - port: 8080
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    app: {{.RolloutName}}
+`
+
+	workloadTemplate = `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: {{.RolloutName}}
+  namespace: {{.TestNamespace}}
+  labels:
+    app: {{.RolloutName}}
+spec:
+  replicas: 0
+  strategy:
+    canary:
+      steps:
+        - setWeight: 50
+        - pause: {duration: 1}
+  selector:
+    matchLabels:
+      app: {{.RolloutName}}
+  template:
+    metadata:
+      labels:
+        app: {{.RolloutName}}
+    spec:
+      containers:
+        - name: {{.RolloutName}}
+          image: registry.k8s.io/e2e-test-images/agnhost:2.45
+          args:
+          - netexec
+          ports:
+            - name: http
+              containerPort: 8080
+              protocol: TCP
+          readinessProbe:
+            httpGet:
+              path: /
+              port: http
+`
+
+	loadJobTemplate = `
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: load-generator
+  namespace: {{.TestNamespace}}
+spec:
+  template:
+    spec:
+      containers:
+      - name: apache-ab
+        image: ghcr.io/kedacore/tests-apache-ab
+        imagePullPolicy: Always
+        args:
+          - "-n"
+          - "2000000"
+          - "-c"
+          - "20"
+          - "-H"
+          - "Host: {{.Host}}"
+          - "http://keda-http-add-on-interceptor-proxy.keda:8080/"
+      restartPolicy: Never
+      terminationGracePeriodSeconds: 5
+  activeDeadlineSeconds: 600
+  backoffLimit: 5
+`
+	httpScaledObjectTemplate = `
+kind: HTTPScaledObject
+apiVersion: http.keda.sh/v1alpha1
+metadata:
+  name: {{.HTTPScaledObjectName}}
+  namespace: {{.TestNamespace}}
+spec:
+  hosts:
+  - {{.Host}}
+  targetPendingRequests: 2
+  scaledownPeriod: 10
+  scaleTargetRef:
+    name: {{.RolloutName}}
+    apiVersion: argoproj.io/v1alpha1
+    kind: Rollout
+    service: {{.ServiceName}}
+    port: 8080
+  replicas:
+    min: {{ .MinReplicas }}
+    max: {{ .MaxReplicas }}
+`
+)
+
+func TestCheck(t *testing.T) {
+	// setup
+	t.Log("--- setting up ---")
+	// Create kubernetes resources
+	kc := GetKubernetesClient(t)
+	data, templates := getTemplateData()
+	CreateKubernetesResources(t, kc, testNamespace, data, templates)
+
+	assert.True(t, waitForArgoRolloutReplicaCount(t, rolloutName, testNamespace, minReplicaCount, 6, 10),
+		"replica count should be %d after 1 minutes", minReplicaCount)
+
+	testScaleOut(t, data)
+	testScaleIn(t)
+
+	// cleanup
+	DeleteKubernetesResources(t, testNamespace, data, templates)
+}
+
+func testScaleOut(t *testing.T, data templateData) {
+	t.Log("--- testing scale out ---")
+
+	KubectlApplyWithTemplate(t, data, "loadJobTemplate", loadJobTemplate)
+
+	assert.True(t, waitForArgoRolloutReplicaCount(t, rolloutName, testNamespace, maxReplicaCount, 18, 10),
+		"replica count should be %d after 3 minutes", maxReplicaCount)
+	KubectlDeleteWithTemplate(t, data, "loadJobTemplate", loadJobTemplate)
+}
+
+func testScaleIn(t *testing.T) {
+	t.Log("--- testing scale out ---")
+
+	assert.True(t, waitForArgoRolloutReplicaCount(t, rolloutName, testNamespace, minReplicaCount, 12, 10),
+		"replica count should be %d after 2 minutes", minReplicaCount)
+}
+
+func getTemplateData() (templateData, []Template) {
+	return templateData{
+			TestNamespace:        testNamespace,
+			RolloutName:          rolloutName,
+			ServiceName:          serviceName,
+			HTTPScaledObjectName: httpScaledObjectName,
+			Host:                 host,
+			MinReplicas:          minReplicaCount,
+			MaxReplicas:          maxReplicaCount,
+		}, []Template{
+			{Name: "workloadTemplate", Config: workloadTemplate},
+			{Name: "serviceNameTemplate", Config: serviceTemplate},
+			{Name: "httpScaledObjectTemplate", Config: httpScaledObjectTemplate},
+		}
+}
+
+func waitForArgoRolloutReplicaCount(t *testing.T, name, namespace string,
+	target, iterations, intervalSeconds int) bool {
+	for i := 0; i < iterations; i++ {
+		kctlGetCmd := fmt.Sprintf(`kubectl get rollouts.argoproj.io/%s -n %s -o jsonpath="{.spec.replicas}"`, name, namespace)
+		output, err := ExecuteCommand(kctlGetCmd)
+
+		assert.NoErrorf(t, err, "cannot get rollout info - %s", err)
+
+		unqoutedOutput := strings.ReplaceAll(string(output), "\"", "")
+		replicas, err := strconv.ParseInt(unqoutedOutput, 10, 64)
+		assert.NoErrorf(t, err, "cannot convert rollout count to int - %s", err)
+
+		t.Logf("Waiting for rollout replicas to hit target. Deployment - %s, Current  - %d, Target - %d",
+			name, replicas, target)
+
+		if replicas == int64(target) {
+			return true
+		}
+
+		time.Sleep(time.Duration(intervalSeconds) * time.Second)
+	}
+
+	return false
+}
diff --git a/tests/checks/scaling_phase/scaling_phase_test.go b/tests/checks/scaling_phase_deployment/scaling_phase_deployment_test.go
similarity index 95%
rename from tests/checks/scaling_phase/scaling_phase_test.go
rename to tests/checks/scaling_phase_deployment/scaling_phase_deployment_test.go
index c9359c6..58ef3bd 100644
--- a/tests/checks/scaling_phase/scaling_phase_test.go
+++ b/tests/checks/scaling_phase_deployment/scaling_phase_deployment_test.go
@@ -7,14 +7,14 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
 )
 
 const (
-	testName = "scaling-phase-test"
+	testName = "scaling-phase-deployment-test"
 )
 
 var (
@@ -56,7 +56,7 @@ spec:
     app: {{.DeploymentName}}
 `
 
-	deploymentTemplate = `
+	workloadTemplate = `
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -127,7 +127,7 @@ spec:
   targetPendingRequests: 2
   scaledownPeriod: 10
   scaleTargetRef:
-    deployment: {{.DeploymentName}}
+    name: {{.DeploymentName}}
     service: {{.ServiceName}}
     port: 8080
   replicas:
@@ -181,7 +181,7 @@ func getTemplateData() (templateData, []Template) {
 			MinReplicas:          minReplicaCount,
 			MaxReplicas:          maxReplicaCount,
 		}, []Template{
-			{Name: "deploymentTemplate", Config: deploymentTemplate},
+			{Name: "workloadTemplate", Config: workloadTemplate},
 			{Name: "serviceNameTemplate", Config: serviceTemplate},
 			{Name: "httpScaledObjectTemplate", Config: httpScaledObjectTemplate},
 		}
diff --git a/tests/checks/scaling_phase_statefulset/scaling_phase_statefulset_test.go b/tests/checks/scaling_phase_statefulset/scaling_phase_statefulset_test.go
new file mode 100644
index 0000000..e62f9e8
--- /dev/null
+++ b/tests/checks/scaling_phase_statefulset/scaling_phase_statefulset_test.go
@@ -0,0 +1,189 @@
+//go:build e2e
+// +build e2e
+
+package scaling_phase_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"k8s.io/client-go/kubernetes"
+
+	. "github.com/kedacore/http-add-on/tests/helper"
+)
+
+const (
+	testName = "scaling-phase-statefulset-test"
+)
+
+var (
+	testNamespace        = fmt.Sprintf("%s-ns", testName)
+	statefulSetName      = fmt.Sprintf("%s-statefulset", testName)
+	serviceName          = fmt.Sprintf("%s-service", testName)
+	httpScaledObjectName = fmt.Sprintf("%s-http-so", testName)
+	host                 = testName
+	minReplicaCount      = 0
+	maxReplicaCount      = 4
+)
+
+type templateData struct {
+	TestNamespace        string
+	StatefulSetName      string
+	ServiceName          string
+	HTTPScaledObjectName string
+	Host                 string
+	MinReplicas          int
+	MaxReplicas          int
+}
+
+const (
+	serviceTemplate = `
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{.ServiceName}}
+  namespace: {{.TestNamespace}}
+  labels:
+    app: {{.StatefulSetName}}
+spec:
+  ports:
+    - port: 8080
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    app: {{.StatefulSetName}}
+`
+
+	workloadTemplate = `
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: {{.StatefulSetName}}
+  namespace: {{.TestNamespace}}
+  labels:
+    app: {{.StatefulSetName}}
+spec:
+  replicas: 0
+  selector:
+    matchLabels:
+      app: {{.StatefulSetName}}
+  template:
+    metadata:
+      labels:
+        app: {{.StatefulSetName}}
+    spec:
+      containers:
+        - name: {{.StatefulSetName}}
+          image: registry.k8s.io/e2e-test-images/agnhost:2.45
+          args:
+          - netexec
+          ports:
+            - name: http
+              containerPort: 8080
+              protocol: TCP
+          readinessProbe:
+            httpGet:
+              path: /
+              port: http
+`
+
+	loadJobTemplate = `
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: load-generator
+  namespace: {{.TestNamespace}}
+spec:
+  template:
+    spec:
+      containers:
+      - name: apache-ab
+        image: ghcr.io/kedacore/tests-apache-ab
+        imagePullPolicy: Always
+        args:
+          - "-n"
+          - "2000000"
+          - "-c"
+          - "20"
+          - "-H"
+          - "Host: {{.Host}}"
+          - "http://keda-http-add-on-interceptor-proxy.keda:8080/"
+      restartPolicy: Never
+      terminationGracePeriodSeconds: 5
+  activeDeadlineSeconds: 600
+  backoffLimit: 5
+`
+	httpScaledObjectTemplate = `
+kind: HTTPScaledObject
+apiVersion: http.keda.sh/v1alpha1
+metadata:
+  name: {{.HTTPScaledObjectName}}
+  namespace: {{.TestNamespace}}
+spec:
+  hosts:
+  - {{.Host}}
+  targetPendingRequests: 2
+  scaledownPeriod: 10
+  scaleTargetRef:
+    name: {{.StatefulSetName}}
+    kind: StatefulSet
+    service: {{.ServiceName}}
+    port: 8080
+  replicas:
+    min: {{ .MinReplicas }}
+    max: {{ .MaxReplicas }}
+`
+)
+
+func TestCheck(t *testing.T) {
+	// setup
+	t.Log("--- setting up ---")
+	// Create kubernetes resources
+	kc := GetKubernetesClient(t)
+	data, templates := getTemplateData()
+	CreateKubernetesResources(t, kc, testNamespace, data, templates)
+
+	assert.True(t, WaitForStatefulsetReplicaReadyCount(t, kc, statefulSetName, testNamespace, minReplicaCount, 6, 10),
+		"replica count should be %d after 1 minutes", minReplicaCount)
+
+	testScaleOut(t, kc, data)
+	testScaleIn(t, kc)
+
+	// cleanup
+	DeleteKubernetesResources(t, testNamespace, data, templates)
+}
+
+func testScaleOut(t *testing.T, kc *kubernetes.Clientset, data templateData) {
+	t.Log("--- testing scale out ---")
+
+	KubectlApplyWithTemplate(t, data, "loadJobTemplate", loadJobTemplate)
+
+	assert.True(t, WaitForStatefulsetReplicaReadyCount(t, kc, statefulSetName, testNamespace, maxReplicaCount, 18, 10),
+		"replica count should be %d after 3 minutes", maxReplicaCount)
+	KubectlDeleteWithTemplate(t, data, "loadJobTemplate", loadJobTemplate)
+}
+
+func testScaleIn(t *testing.T, kc *kubernetes.Clientset) {
+	t.Log("--- testing scale out ---")
+
+	assert.True(t, WaitForStatefulsetReplicaReadyCount(t, kc, statefulSetName, testNamespace, minReplicaCount, 12, 10),
+		"replica count should be %d after 2 minutes", minReplicaCount)
+}
+
+func getTemplateData() (templateData, []Template) {
+	return templateData{
+			TestNamespace:        testNamespace,
+			StatefulSetName:      statefulSetName,
+			ServiceName:          serviceName,
+			HTTPScaledObjectName: httpScaledObjectName,
+			Host:                 host,
+			MinReplicas:          minReplicaCount,
+			MaxReplicas:          maxReplicaCount,
+		}, []Template{
+			{Name: "workloadTemplate", Config: workloadTemplate},
+			{Name: "serviceNameTemplate", Config: serviceTemplate},
+			{Name: "httpScaledObjectTemplate", Config: httpScaledObjectTemplate},
+		}
+}
diff --git a/tests/checks/single_host/single_host_test.go b/tests/checks/single_host/single_host_test.go
index 50178a6..c1eb6d6 100644
--- a/tests/checks/single_host/single_host_test.go
+++ b/tests/checks/single_host/single_host_test.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/tj/assert"
+	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes"
 
 	. "github.com/kedacore/http-add-on/tests/helper"
diff --git a/tests/helper/helper.go b/tests/helper/helper.go
index acafa87..84c5254 100644
--- a/tests/helper/helper.go
+++ b/tests/helper/helper.go
@@ -31,9 +31,11 @@ import (
 )
 
 const (
-	KEDANamespace      = "keda"
-	IngressNamespace   = "ingress"
-	IngressReleaseName = "ingress"
+	KEDANamespace         = "keda"
+	ArgoRolloutsNamespace = "argo-rollouts"
+	ArgoRolloutsName      = "argo-rollouts"
+	IngressNamespace      = "ingress"
+	IngressReleaseName    = "ingress"
 
 	StringFalse = "false"
 	StringTrue  = "true"
diff --git a/tests/utils/cleanup_test.go b/tests/utils/cleanup_test.go
index eeddab3..e644ab6 100644
--- a/tests/utils/cleanup_test.go
+++ b/tests/utils/cleanup_test.go
@@ -31,6 +31,12 @@ func TestRemoveIngress(t *testing.T) {
 	DeleteNamespace(t, IngressNamespace)
 }
 
+func TestRemoveArgoRollouts(t *testing.T) {
+	_, err := ExecuteCommand(fmt.Sprintf("helm uninstall %s --namespace %s", ArgoRolloutsName, ArgoRolloutsNamespace))
+	require.NoErrorf(t, err, "cannot uninstall ingress - %s", err)
+	DeleteNamespace(t, ArgoRolloutsNamespace)
+}
+
 func TestRemoveKEDANamespace(t *testing.T) {
 	DeleteNamespace(t, KEDANamespace)
 }
diff --git a/tests/utils/setup_test.go b/tests/utils/setup_test.go
index 76a8fc7..0ba5b72 100644
--- a/tests/utils/setup_test.go
+++ b/tests/utils/setup_test.go
@@ -56,6 +56,24 @@ func TestCreateKEDANamespace(t *testing.T) {
 	CreateNamespace(t, KubeClient, KEDANamespace)
 }
 
+func TestSetupArgoRollouts(t *testing.T) {
+	KubeClient = GetKubernetesClient(t)
+	CreateNamespace(t, KubeClient, ArgoRolloutsNamespace)
+	_, err := ExecuteCommand("helm version")
+	require.NoErrorf(t, err, "helm is not installed - %s", err)
+
+	_, err = ExecuteCommand("helm repo add argo https://argoproj.github.io/argo-helm")
+	require.NoErrorf(t, err, "cannot add argo helm repo - %s", err)
+
+	_, err = ExecuteCommand("helm repo update argo")
+	require.NoErrorf(t, err, "cannot update argo helm repo - %s", err)
+
+	_, err = ExecuteCommand(fmt.Sprintf("helm upgrade --install %s argo/argo-rollouts --namespace %s --wait",
+		ArgoRolloutsName,
+		ArgoRolloutsNamespace))
+	require.NoErrorf(t, err, "cannot install argo-rollouts - %s", err)
+}
+
 func TestSetupIngress(t *testing.T) {
 	KubeClient = GetKubernetesClient(t)
 	CreateNamespace(t, KubeClient, IngressNamespace)