mirror of
https://github.com/fluxcd/flagger.git
synced 2026-02-15 02:20:22 +00:00
Compare commits
21 Commits
knative-su
...
v1.41.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f165a10de | ||
|
|
89c1ddee79 | ||
|
|
1b8e7653d3 | ||
|
|
98f8514258 | ||
|
|
d9c8a09d3e | ||
|
|
2ac22f831f | ||
|
|
e0de40dcb0 | ||
|
|
8f9bb5b1bc | ||
|
|
f21bc1de3e | ||
|
|
1fc7ac5847 | ||
|
|
1dc270c2e6 | ||
|
|
50d1331ba6 | ||
|
|
bc78156535 | ||
|
|
0df8af8d04 | ||
|
|
633f639383 | ||
|
|
d03cc73386 | ||
|
|
eaf5bb992c | ||
|
|
22618ccb11 | ||
|
|
f5af225ffc | ||
|
|
40a34199fe | ||
|
|
d7357a7377 |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23.x
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
|
||||
2
.github/workflows/push-ld.yml
vendored
2
.github/workflows/push-ld.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: sigstore/cosign-installer@v3.7.0
|
||||
- uses: sigstore/cosign-installer@v3.8.1
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -31,9 +31,9 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23.x
|
||||
go-version: 1.24.x
|
||||
- uses: fluxcd/flux2/action@main
|
||||
- uses: sigstore/cosign-installer@v3.7.0
|
||||
- uses: sigstore/cosign-installer@v3.8.1
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
contents: write # for uploading attestations to GitHub releases.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
provenance-name: "provenance.intoto.jsonl"
|
||||
base64-subjects: "${{ needs.release-flagger.outputs.hashes }}"
|
||||
|
||||
2
.github/workflows/scan.yml
vendored
2
.github/workflows/scan.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23.x
|
||||
go-version: 1.24.x
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -2,6 +2,54 @@
|
||||
|
||||
All notable changes to this project are documented in this file.
|
||||
|
||||
## 1.41.0
|
||||
|
||||
**Release date:** 2025-04-02
|
||||
|
||||
This release comes with major features and minor bug fixes.
|
||||
|
||||
Flagger now supports Knative as a networking provider. This works a bit
|
||||
differently than compared to other service meshes/ingresses. Flagger does not
|
||||
generate any Kubernetes objects. It instead modifies the Knative service itself
|
||||
to configure weighted traffic routing. To learn more, please see the [tutorial](https://docs.flagger.app/tutorials/knative-progressive-delivery).
|
||||
|
||||
The session affinity canary release strategy has also been improved. Flagger can
|
||||
now configure Gateway API HTTPRoutes to also set a cookie for the primary
|
||||
deployment's response. For more info, see the [strategy docs](https://docs.flagger.app/usage/deployment-strategies#canary-release-with-session-affinity).
|
||||
|
||||
Furthermore, there's a new `.spec.service.headless` field which when set to
|
||||
true, tells Flagger to generate headless Kubernetes services. Also, support has
|
||||
been added for adding headers to the request Flagger sends to Prometheus for
|
||||
collecting metrics during an analysis via the `.spec.headers` field in the
|
||||
`MetricTemplate` object.
|
||||
|
||||
Finally, both Flagger and the load tester have been updated to use Go 1.24 and
|
||||
their dependencies have been updated as well.
|
||||
|
||||
#### Improvements
|
||||
- Allow headers to be added to Prometheus requests
|
||||
[#1757](https://github.com/fluxcd/flagger/pull/1757)
|
||||
- feat: Add support for primary backend cookies in session affinity (Gateway API)
|
||||
[#1783](https://github.com/fluxcd/flagger/pull/1783)
|
||||
- Update Go dependencies
|
||||
[#1787](https://github.com/fluxcd/flagger/pull/1787)
|
||||
- Build with Go 1.24
|
||||
[#1784](https://github.com/fluxcd/flagger/pull/1784)
|
||||
- Add support for Knative
|
||||
[#1682](https://github.com/fluxcd/flagger/pull/1682)
|
||||
- chart: add support for deploymentLabels
|
||||
[#1707](https://github.com/fluxcd/flagger/pull/1707)
|
||||
- chart: add support for deploymentLabels
|
||||
[#1707](https://github.com/fluxcd/flagger/pull/1707)
|
||||
- feat: add option to generate headless services
|
||||
[#1755](https://github.com/fluxcd/flagger/pull/1755)
|
||||
|
||||
#### Fixes
|
||||
- Fix: Do not evaluate incomplete samples from Datadog
|
||||
[#1763](https://github.com/fluxcd/flagger/pull/1763)
|
||||
- Prevent primary HPA collision for KEDA scaled objects when migrating from an HPA
|
||||
[#1677](https://github.com/fluxcd/flagger/pull/1677)
|
||||
|
||||
## 1.40.0
|
||||
|
||||
**Release date:** 2024-12-17
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG GO_VERSION=1.23
|
||||
ARG GO_VERSION=1.24
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.23-alpine AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETARCH
|
||||
@@ -6,7 +6,7 @@ ARG REVISION
|
||||
|
||||
RUN apk --no-cache add alpine-sdk perl curl bash tar
|
||||
|
||||
RUN HELM3_VERSION=3.16.3 && \
|
||||
RUN HELM3_VERSION=3.17.2 && \
|
||||
curl -sSL "https://get.helm.sh/helm-v${HELM3_VERSION}-linux-${TARGETARCH}.tar.gz" | tar xvz && \
|
||||
chmod +x linux-${TARGETARCH}/helm && mv linux-${TARGETARCH}/helm /usr/local/bin/helm
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -6,7 +6,7 @@ build:
|
||||
CGO_ENABLED=0 go build -a -o ./bin/flagger ./cmd/flagger
|
||||
|
||||
tidy:
|
||||
rm -f go.sum; go mod tidy -compat=1.23
|
||||
rm -f go.sum; go mod tidy -compat=1.24
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
@@ -1155,6 +1155,9 @@ spec:
|
||||
cookieName:
|
||||
description: CookieName is the key that will be used for the session affinity cookie.
|
||||
type: string
|
||||
primaryCookieName:
|
||||
description: CookieName is the key that will be used for the session affinity cookie.
|
||||
type: string
|
||||
maxAge:
|
||||
description: MaxAge indicates the number of seconds until the session affinity cookie will expire.
|
||||
default: 86400
|
||||
@@ -1310,6 +1313,13 @@ spec:
|
||||
address:
|
||||
description: API address of this provider
|
||||
type: string
|
||||
headers:
|
||||
description: Headers to add to HTTP(S) requests
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Kubernetes secret reference containing the provider credentials
|
||||
type: object
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
serviceAccountName: flagger
|
||||
containers:
|
||||
- name: flagger
|
||||
image: ghcr.io/fluxcd/flagger:1.40.0
|
||||
image: ghcr.io/fluxcd/flagger:1.41.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
name: flagger
|
||||
version: 1.40.0
|
||||
appVersion: 1.40.0
|
||||
version: 1.41.0
|
||||
appVersion: 1.41.0
|
||||
kubeVersion: ">=1.19.0-0"
|
||||
engine: gotpl
|
||||
description: Flagger is a progressive delivery operator for Kubernetes
|
||||
|
||||
@@ -1155,6 +1155,9 @@ spec:
|
||||
cookieName:
|
||||
description: CookieName is the key that will be used for the session affinity cookie.
|
||||
type: string
|
||||
primaryCookieName:
|
||||
description: CookieName is the key that will be used for the session affinity cookie.
|
||||
type: string
|
||||
maxAge:
|
||||
description: MaxAge indicates the number of seconds until the session affinity cookie will expire.
|
||||
default: 86400
|
||||
@@ -1310,6 +1313,13 @@ spec:
|
||||
address:
|
||||
description: API address of this provider
|
||||
type: string
|
||||
headers:
|
||||
description: Headers to add to HTTP(S) requests
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Kubernetes secret reference containing the provider credentials
|
||||
type: object
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
image:
|
||||
repository: ghcr.io/fluxcd/flagger
|
||||
tag: 1.40.0
|
||||
tag: 1.41.0
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecret:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
name: loadtester
|
||||
version: 0.34.0
|
||||
appVersion: 0.34.0
|
||||
version: 0.35.0
|
||||
appVersion: 0.35.0
|
||||
kubeVersion: ">=1.19.0-0"
|
||||
engine: gotpl
|
||||
description: Flagger's load testing services based on rakyll/hey and bojand/ghz that generates traffic during canary analysis when configured as a webhook.
|
||||
|
||||
@@ -2,7 +2,7 @@ replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: ghcr.io/fluxcd/flagger-loadtester
|
||||
tag: 0.34.0
|
||||
tag: 0.35.0
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecret:
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"github.com/fluxcd/flagger/pkg/signals"
|
||||
)
|
||||
|
||||
var VERSION = "0.34.0"
|
||||
var VERSION = "0.35.0"
|
||||
var (
|
||||
logLevel string
|
||||
port string
|
||||
|
||||
@@ -509,6 +509,9 @@ You can load `www.example.com` in your browser and refresh it until you see the
|
||||
All subsequent requests after that will be served by `podinfo:6.0.1` and not `podinfo:6.0.0` because of the session affinity
|
||||
configured by Flagger in the HTTPRoute object.
|
||||
|
||||
To configure stickiness for the Primary deployment to ensure fair weighted traffic routing, please
|
||||
checkout the [deployment strategies docs](../usage/deployment-strategies.md#canary-release-with-session-affinity).
|
||||
|
||||
# A/B Testing
|
||||
|
||||
Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. This is particularly useful for frontend applications that require session affinity.
|
||||
|
||||
@@ -407,7 +407,7 @@ cookie based routing with regular weight based routing. This means once a user i
|
||||
version of our application (based on the traffic weights), they're always routed to that version, i.e.
|
||||
they're never routed back to the old version of our application.
|
||||
|
||||
You can enable this, by specifying `.spec.analsyis.sessionAffinity` in the Canary:
|
||||
You can enable this, by specifying `.spec.analysis.sessionAffinity` in the Canary:
|
||||
|
||||
```yaml
|
||||
analysis:
|
||||
@@ -450,3 +450,47 @@ the Canary deployment:
|
||||
```
|
||||
Set-Cookie: flagger-cookie=McxKdLQoIN; Max-Age=21600
|
||||
```
|
||||
|
||||
### Configuring stickiness for Primary deployment
|
||||
|
||||
The above strategy is helpful because it makes sure that any user that's routed to the Canary deployment
|
||||
once is always routed to that deployment. But, this can results in an imbalance in the traffic shifting,
|
||||
as over time, most of the traffic flows to the Canary deployment. To ensure fair traffic distribution, we
|
||||
can also configure stickiness for the Primary deployment. You can configure this by specifying a
|
||||
`primaryCookieName` field:
|
||||
|
||||
```yaml
|
||||
analysis:
|
||||
# schedule interval (default 60s)
|
||||
interval: 1m
|
||||
sessionAffinity:
|
||||
# name of the cookie used
|
||||
cookieName: flagger-cookie
|
||||
# max age of the cookie (in seconds)
|
||||
# optional; defaults to 86400
|
||||
maxAge: 21600
|
||||
# name of the cookie to use for the primary backend
|
||||
# optional; unset means no primary stickiness
|
||||
primaryCookieName: primary-flagger-cookie
|
||||
```
|
||||
|
||||
> Note: This is only supported for the Gateway API provider for now.
|
||||
|
||||
Let's understand what the above configuration does. All the session affinity stuff in the above section
|
||||
still occurs, but now the response header for requests routed to the primary deployment also include a
|
||||
`Set-Cookie` header:
|
||||
```
|
||||
Set-Cookie: primary-flagger-cookie=ApvLdqCoMF; Max-Age=60
|
||||
```
|
||||
|
||||
Note that the age of the cookie is the same as the Canary analysis's interval. This means that the cookie
|
||||
expires when a new steps of the analysis begins and a new cookie is generated like so:
|
||||
```
|
||||
Set-Cookie: primary-flagger-cookie=BRtlVaQoPC; Max-Age=60
|
||||
```
|
||||
|
||||
This ensures that, if the first request of a user during a particular step is routed to the primary deployment,
|
||||
then all subsequent requests will be routed to the same until the next step starts. During a new step, a new cookie
|
||||
value is generated which is then included in the headers of responses from the primary workload. This allows for
|
||||
weighted traffic routing to happen while ensuring that users don't ever switch back to the primary deployment from
|
||||
the canary deployment during a Canary analysis.
|
||||
|
||||
84
go.mod
84
go.mod
@@ -1,41 +1,41 @@
|
||||
module github.com/fluxcd/flagger
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
cloud.google.com/go/monitoring v1.22.0
|
||||
cloud.google.com/go/monitoring v1.24.1
|
||||
github.com/Masterminds/semver/v3 v3.3.1
|
||||
github.com/aws/aws-sdk-go v1.55.5
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/googleapis/gax-go/v2 v2.14.0
|
||||
github.com/googleapis/gax-go/v2 v2.14.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/signalfx/signalflow-client-go v0.1.0
|
||||
github.com/signalfx/signalfx-go v1.44.0
|
||||
github.com/signalfx/signalfx-go v1.46.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/sync v0.10.0
|
||||
google.golang.org/api v0.211.0
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
|
||||
google.golang.org/grpc v1.69.2
|
||||
google.golang.org/protobuf v1.36.2
|
||||
golang.org/x/sync v0.12.0
|
||||
google.golang.org/api v0.228.0
|
||||
google.golang.org/genproto v0.0.0-20250324211829-b45e905df463
|
||||
google.golang.org/grpc v1.71.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
k8s.io/api v0.31.4
|
||||
k8s.io/apimachinery v0.31.4
|
||||
k8s.io/client-go v0.31.4
|
||||
k8s.io/api v0.32.3
|
||||
k8s.io/apimachinery v0.32.3
|
||||
k8s.io/client-go v0.32.3
|
||||
k8s.io/code-generator v0.31.4
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
knative.dev/serving v0.44.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.12.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blendle/zapdriver v1.3.1 // indirect
|
||||
@@ -49,22 +49,20 @@ require (
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-containerregistry v0.13.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -74,37 +72,37 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.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/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect
|
||||
k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect
|
||||
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
knative.dev/networking v0.0.0-20250117155906-67d1c274ba6a // indirect
|
||||
knative.dev/pkg v0.0.0-20250117084104-c43477f0052b // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
188
go.sum
188
go.sum
@@ -1,11 +1,11 @@
|
||||
cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4=
|
||||
cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI=
|
||||
cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
|
||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/monitoring v1.24.1 h1:vKiypZVFD/5a3BbQMvI4gZdl8445ITzXFh257XBgrS0=
|
||||
cloud.google.com/go/monitoring v1.24.1/go.mod h1:Z05d1/vn9NaujqY2voG6pVQXoJGbp+r3laV+LySt9K0=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY=
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg=
|
||||
@@ -15,8 +15,8 @@ github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
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/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
|
||||
@@ -70,23 +70,23 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k=
|
||||
github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
|
||||
github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 h1:CWyXh/jylQWp2dtiV33mY4iSSp6yf4lmn+c7/tN+ObI=
|
||||
@@ -99,8 +99,6 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
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/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
|
||||
@@ -116,8 +114,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -141,10 +139,10 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
|
||||
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -153,22 +151,22 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0=
|
||||
github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/signalfx/signalflow-client-go v0.1.0 h1:aqyt+st3/y8x8JtuwYRL9pOkOTJb+KeCoRWi0SuY5vw=
|
||||
github.com/signalfx/signalflow-client-go v0.1.0/go.mod h1:mY4DTAZuLHyMNGBjSrNdCg5kUU0hSkYjukAnjsVbsQs=
|
||||
github.com/signalfx/signalfx-go v1.44.0 h1:BkLtohTJkq3mr1Yl1OzCWK+e2DZRqZ0M0zD9Gs+c41Q=
|
||||
github.com/signalfx/signalfx-go v1.44.0/go.mod h1:I30umyhRTu8mPpEtMzEbG0z9wOYjkUKTp9U0gFxFsmk=
|
||||
github.com/signalfx/signalfx-go v1.46.0 h1:bNPUmoOGjFCyVYz6arShOot4AnZe2knqw9N+UyZ+nMw=
|
||||
github.com/signalfx/signalfx-go v1.46.0/go.mod h1:I30umyhRTu8mPpEtMzEbG0z9wOYjkUKTp9U0gFxFsmk=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
@@ -182,20 +180,22 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
@@ -208,8 +208,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
@@ -218,28 +218,28 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -252,18 +252,18 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg=
|
||||
google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0=
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU=
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
|
||||
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
|
||||
google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 h1:qEFnJI6AnfZk0NNe8YTyXQh5i//Zxi4gBHwRgp76qpw=
|
||||
google.golang.org/genproto v0.0.0-20250324211829-b45e905df463/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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=
|
||||
@@ -278,31 +278,31 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM=
|
||||
k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw=
|
||||
k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM=
|
||||
k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ=
|
||||
k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg=
|
||||
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
|
||||
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
|
||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
||||
k8s.io/code-generator v0.31.4 h1:Vu+8fKz+239rKiVDHFVHgjQ162cg5iUQPtTyQbwXeQw=
|
||||
k8s.io/code-generator v0.31.4/go.mod h1:yMDt13Kn7m4MMZ4LxB1KBzdZjEyxzdT4b4qXq+lnI90=
|
||||
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 h1:cErOOTkQ3JW19o4lo91fFurouhP8NcoBvb7CkvhZZpk=
|
||||
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
|
||||
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4=
|
||||
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo=
|
||||
k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA=
|
||||
k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI=
|
||||
k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
knative.dev/networking v0.0.0-20250117155906-67d1c274ba6a h1:FaDPXtv42+AkYh/mE269pttPSZ3fDVAjJiEsYUaM4SM=
|
||||
knative.dev/networking v0.0.0-20250117155906-67d1c274ba6a/go.mod h1:AIKYMfZydhwXR/60c/3KXEnqEnH6aNEEqulifdqJVcQ=
|
||||
knative.dev/pkg v0.0.0-20250117084104-c43477f0052b h1:a+gP7Yzu5NmoX2w1p8nfTgmSKF+aHLKGzqYT82ijJTw=
|
||||
knative.dev/pkg v0.0.0-20250117084104-c43477f0052b/go.mod h1:bedSpkdLybR6JhL1J7XDLpd+JMKM/x8M5Apr80i5TeE=
|
||||
knative.dev/serving v0.44.0 h1:c6TXhoSAI6eXt0/1ET3C69jMWYA4ES9FskSan/fBaac=
|
||||
knative.dev/serving v0.44.0/go.mod h1:9bFONngDZtkdYZkP5ko9LDS9ZelnFY9SaPoHKG0vFxs=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.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/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
||||
@@ -1155,6 +1155,9 @@ spec:
|
||||
cookieName:
|
||||
description: CookieName is the key that will be used for the session affinity cookie.
|
||||
type: string
|
||||
primaryCookieName:
|
||||
description: CookieName is the key that will be used for the session affinity cookie.
|
||||
type: string
|
||||
maxAge:
|
||||
description: MaxAge indicates the number of seconds until the session affinity cookie will expire.
|
||||
default: 86400
|
||||
@@ -1310,6 +1313,13 @@ spec:
|
||||
address:
|
||||
description: API address of this provider
|
||||
type: string
|
||||
headers:
|
||||
description: Headers to add to HTTP(S) requests
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Kubernetes secret reference containing the provider credentials
|
||||
type: object
|
||||
|
||||
@@ -9,4 +9,4 @@ resources:
|
||||
images:
|
||||
- name: ghcr.io/fluxcd/flagger
|
||||
newName: ghcr.io/fluxcd/flagger
|
||||
newTag: 1.40.0
|
||||
newTag: 1.41.0
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: loadtester
|
||||
image: ghcr.io/fluxcd/flagger-loadtester:0.34.0
|
||||
image: ghcr.io/fluxcd/flagger-loadtester:0.35.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -295,6 +295,9 @@ type SessionAffinity struct {
|
||||
// The default value is 86,400 seconds, i.e. a day.
|
||||
// +optional
|
||||
MaxAge int `json:"maxAge,omitempty"`
|
||||
// PrimaryCookieName is the key that will be used for the primary session affinity cookie.
|
||||
// +optional
|
||||
PrimaryCookieName string `json:"primaryCookieName,omitempty"`
|
||||
}
|
||||
|
||||
// CanaryMetric holds the reference to metrics used for canary analysis
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -67,6 +68,10 @@ type MetricTemplateProvider struct {
|
||||
// +optional
|
||||
Address string `json:"address,omitempty"`
|
||||
|
||||
// Headers to be supplied to HTTP(S) request of this provider
|
||||
// +optional
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
|
||||
// Secret reference containing the provider credentials
|
||||
// +optional
|
||||
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
|
||||
@@ -22,6 +22,8 @@ limitations under the License.
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
http "net/http"
|
||||
|
||||
gatewayapiv1beta1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1"
|
||||
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -810,6 +812,21 @@ func (in *MetricTemplateModel) DeepCopy() *MetricTemplateModel {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricTemplateProvider) DeepCopyInto(out *MetricTemplateProvider) {
|
||||
*out = *in
|
||||
if in.Headers != nil {
|
||||
in, out := &in.Headers, &out.Headers
|
||||
*out = make(http.Header, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(v1.LocalObjectReference)
|
||||
|
||||
@@ -337,6 +337,9 @@ func (c *Controller) verifyCanary(canary *flaggerv1.Canary) error {
|
||||
if err := verifyKnativeCanary(canary); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := verifySessionAffinity(canary); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -378,6 +381,16 @@ func verifyKnativeCanary(canary *flaggerv1.Canary) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySessionAffinity(canary *flaggerv1.Canary) error {
|
||||
if canary.Spec.Analysis.SessionAffinity != nil {
|
||||
if canary.Spec.Analysis.SessionAffinity.CookieName == canary.Spec.Analysis.SessionAffinity.PrimaryCookieName {
|
||||
return fmt.Errorf("can't use the same cookie name for both primary and cookie name; please update them to be different")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCustomResourceType(obj interface{}, logger *zap.SugaredLogger) (flaggerv1.Canary, bool) {
|
||||
var roll *flaggerv1.Canary
|
||||
var ok bool
|
||||
|
||||
@@ -26,6 +26,7 @@ func TestController_verifyCanary(t *testing.T) {
|
||||
Name: "upstream",
|
||||
Namespace: "test",
|
||||
},
|
||||
Analysis: &flaggerv1.CanaryAnalysis{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -42,6 +43,7 @@ func TestController_verifyCanary(t *testing.T) {
|
||||
Name: "upstream",
|
||||
Namespace: "default",
|
||||
},
|
||||
Analysis: &flaggerv1.CanaryAnalysis{},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -103,6 +105,7 @@ func TestController_verifyCanary(t *testing.T) {
|
||||
Kind: "Deployment",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Analysis: &flaggerv1.CanaryAnalysis{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -121,6 +124,7 @@ func TestController_verifyCanary(t *testing.T) {
|
||||
APIVersion: "serving.knative.dev/v1",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Analysis: &flaggerv1.CanaryAnalysis{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -140,6 +144,7 @@ func TestController_verifyCanary(t *testing.T) {
|
||||
APIVersion: "serving.knative.dev/v1",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Analysis: &flaggerv1.CanaryAnalysis{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -158,10 +163,29 @@ func TestController_verifyCanary(t *testing.T) {
|
||||
APIVersion: "serving.knative.dev/v1",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Analysis: &flaggerv1.CanaryAnalysis{},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "session affinity with same cookie names should return an error",
|
||||
canary: flaggerv1.Canary{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cd-1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: flaggerv1.CanarySpec{
|
||||
Analysis: &flaggerv1.CanaryAnalysis{
|
||||
SessionAffinity: &flaggerv1.SessionAffinity{
|
||||
CookieName: "smth",
|
||||
PrimaryCookieName: "smth",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctrl := &Controller{
|
||||
|
||||
@@ -168,7 +168,7 @@ func (c *Controller) advanceCanary(name string, namespace string) {
|
||||
msg := "skipping canary run as object is suspended"
|
||||
c.logger.With("canary", fmt.Sprintf("%s.%s", name, namespace)).
|
||||
Debug(msg)
|
||||
c.recordEventInfof(cd, msg)
|
||||
c.recordEventInfof(cd, "%s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ func TestGraphiteProvider_IsOnline(t *testing.T) {
|
||||
}
|
||||
|
||||
w.WriteHeader(test.code)
|
||||
fmt.Fprintf(w, test.body)
|
||||
fmt.Fprintf(w, "%s", test.body)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const prometheusOnlineQuery = "vector(1)"
|
||||
type PrometheusProvider struct {
|
||||
timeout time.Duration
|
||||
url url.URL
|
||||
headers http.Header
|
||||
username string
|
||||
password string
|
||||
token string
|
||||
@@ -69,6 +70,7 @@ func NewPrometheusProvider(provider flaggerv1.MetricTemplateProvider, credential
|
||||
prom := PrometheusProvider{
|
||||
timeout: 5 * time.Second,
|
||||
url: *promURL,
|
||||
headers: provider.Headers,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
|
||||
@@ -116,6 +118,10 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
|
||||
return 0, fmt.Errorf("http.NewRequest failed: %w", err)
|
||||
}
|
||||
|
||||
if p.headers != nil {
|
||||
req.Header = p.headers
|
||||
}
|
||||
|
||||
if p.token != "" {
|
||||
req.Header.Add("Authorization", "Bearer "+p.token)
|
||||
} else if p.username != "" && p.password != "" {
|
||||
|
||||
@@ -307,3 +307,40 @@ func TestPrometheusProvider_IsOnline(t *testing.T) {
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrometheusProvider_RunQueryWithProviderHeaders(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
expected := `sum(envoy_cluster_upstream_rq)`
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
promql := r.URL.Query()["query"][0]
|
||||
assert.Equal(t, expected, promql)
|
||||
|
||||
assert.Equal(t, []string{"tenant1"}, r.Header.Values("X-Scope-Orgid"))
|
||||
|
||||
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}`
|
||||
w.Write([]byte(json))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
clients := prometheusFake()
|
||||
|
||||
template, err := clients.flaggerClient.FlaggerV1beta1().MetricTemplates("default").Get(context.TODO(), "prometheus", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
template.Spec.Provider.Address = ts.URL
|
||||
template.Spec.Provider.Headers = http.Header{
|
||||
"X-Scope-OrgID": []string{"tenant1"},
|
||||
}
|
||||
|
||||
secret, err := clients.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "prometheus", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
|
||||
require.NoError(t, err)
|
||||
|
||||
val, err := prom.RunQuery(template.Spec.Query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, float64(100), val)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
@@ -190,20 +191,26 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error {
|
||||
cmpopts.IgnoreFields(v1.BackendRef{}, "Weight"),
|
||||
cmpopts.EquateEmpty(),
|
||||
}
|
||||
|
||||
if canary.Spec.Analysis.SessionAffinity != nil {
|
||||
ignoreRoute := cmpopts.IgnoreSliceElements(func(r v1.HTTPRouteRule) bool {
|
||||
// Ignore the rule that does sticky routing, i.e. matches against the `Cookie` header.
|
||||
for _, match := range r.Matches {
|
||||
for _, headerMatch := range match.Headers {
|
||||
if *headerMatch.Type == headerMatchRegex && headerMatch.Name == cookieHeader &&
|
||||
strings.Contains(headerMatch.Value, canary.Spec.Analysis.SessionAffinity.CookieName) {
|
||||
return true
|
||||
ignoreCookieRouteFunc := func(name string) func(r v1.HTTPRouteRule) bool {
|
||||
return func(r v1.HTTPRouteRule) bool {
|
||||
// Ignore the rule that does sticky routing, i.e. matches against the `Cookie` header.
|
||||
for _, match := range r.Matches {
|
||||
for _, headerMatch := range match.Headers {
|
||||
if *headerMatch.Type == headerMatchRegex && headerMatch.Name == cookieHeader &&
|
||||
strings.Contains(headerMatch.Value, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
})
|
||||
ignoreCmpOptions = append(ignoreCmpOptions, ignoreRoute)
|
||||
}
|
||||
ignoreCanaryRoute := cmpopts.IgnoreSliceElements(ignoreCookieRouteFunc(canary.Spec.Analysis.SessionAffinity.CookieName))
|
||||
ignorePrimaryRoute := cmpopts.IgnoreSliceElements(ignoreCookieRouteFunc(canary.Spec.Analysis.SessionAffinity.PrimaryCookieName))
|
||||
|
||||
ignoreCmpOptions = append(ignoreCmpOptions, ignoreCanaryRoute, ignorePrimaryRoute)
|
||||
// Ignore backend specific filters, since we use that to insert the `Set-Cookie` header in responses.
|
||||
ignoreCmpOptions = append(ignoreCmpOptions, cmpopts.IgnoreFields(v1.HTTPBackendRef{}, "Filters"))
|
||||
}
|
||||
@@ -439,42 +446,76 @@ func (gwr *GatewayAPIRouter) Finalize(_ *flaggerv1.Canary) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBackendByServiceName(rule *v1.HTTPRouteRule, svcName string) *v1.HTTPBackendRef {
|
||||
for i, backendRef := range rule.BackendRefs {
|
||||
if string(backendRef.BackendObjectReference.Name) == svcName {
|
||||
|
||||
return &rule.BackendRefs[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSessionAffinityRouteRules returns the HTTPRouteRule objects required to perform
|
||||
// session affinity based Canary releases.
|
||||
func (gwr *GatewayAPIRouter) getSessionAffinityRouteRules(canary *flaggerv1.Canary, canaryWeight int,
|
||||
weightedRouteRule *v1.HTTPRouteRule) ([]v1.HTTPRouteRule, error) {
|
||||
_, primarySvcName, canarySvcName := canary.GetServiceNames()
|
||||
stickyRouteRule := *weightedRouteRule
|
||||
stickyCanaryRouteRule := *weightedRouteRule
|
||||
stickyPrimaryRouteRule := *weightedRouteRule
|
||||
|
||||
// If a canary run is active, we want all responses corresponding to requests hitting the canary deployment
|
||||
// (due to weighted routing) to include a `Set-Cookie` header. All requests that have the `Cookie` header
|
||||
// and match the value of the `Set-Cookie` header will be routed to the canary deployment.
|
||||
if canaryWeight != 0 {
|
||||
// if the status doesn't have the canary cookie, then generate a new canary cookie.
|
||||
if canary.Status.SessionAffinityCookie == "" {
|
||||
canary.Status.SessionAffinityCookie = fmt.Sprintf("%s=%s", canary.Spec.Analysis.SessionAffinity.CookieName, randSeq())
|
||||
}
|
||||
primaryCookie := fmt.Sprintf("%s=%s", canary.Spec.Analysis.SessionAffinity.PrimaryCookieName, randSeq())
|
||||
|
||||
// Add `Set-Cookie` header modifier to the primary backend in the weighted routing rule.
|
||||
for i, backendRef := range weightedRouteRule.BackendRefs {
|
||||
if string(backendRef.BackendObjectReference.Name) == canarySvcName {
|
||||
backendRef.Filters = append(backendRef.Filters, v1.HTTPRouteFilter{
|
||||
Type: v1.HTTPRouteFilterResponseHeaderModifier,
|
||||
ResponseHeaderModifier: &v1.HTTPHeaderFilter{
|
||||
Add: []v1.HTTPHeader{
|
||||
{
|
||||
Name: setCookieHeader,
|
||||
Value: fmt.Sprintf("%s; %s=%d", canary.Status.SessionAffinityCookie, maxAgeAttr,
|
||||
canary.Spec.Analysis.SessionAffinity.GetMaxAge(),
|
||||
),
|
||||
},
|
||||
// add response modifier to the canary backend ref in the rule that does weighted routing
|
||||
// to include the canary cookie.
|
||||
canaryBackendRef := getBackendByServiceName(weightedRouteRule, canarySvcName)
|
||||
canaryBackendRef.Filters = append(canaryBackendRef.Filters, v1.HTTPRouteFilter{
|
||||
Type: v1.HTTPRouteFilterResponseHeaderModifier,
|
||||
ResponseHeaderModifier: &v1.HTTPHeaderFilter{
|
||||
Add: []v1.HTTPHeader{
|
||||
{
|
||||
Name: setCookieHeader,
|
||||
Value: fmt.Sprintf("%s; %s=%d", canary.Status.SessionAffinityCookie, maxAgeAttr,
|
||||
canary.Spec.Analysis.SessionAffinity.GetMaxAge(),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// add response modifier to the primary backend ref in the rule that does weighted routing
|
||||
// to include the primary cookie, only if a primary cookie name has been specified.
|
||||
if canary.Spec.Analysis.SessionAffinity.PrimaryCookieName != "" {
|
||||
primaryBackendRef := getBackendByServiceName(weightedRouteRule, primarySvcName)
|
||||
interval, err := time.ParseDuration(canary.Spec.Analysis.Interval)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse canary interval: %w", err)
|
||||
}
|
||||
primaryBackendRef.Filters = append(primaryBackendRef.Filters, v1.HTTPRouteFilter{
|
||||
Type: v1.HTTPRouteFilterResponseHeaderModifier,
|
||||
ResponseHeaderModifier: &v1.HTTPHeaderFilter{
|
||||
Add: []v1.HTTPHeader{
|
||||
{
|
||||
Name: setCookieHeader,
|
||||
Value: fmt.Sprintf("%s; %s=%d", primaryCookie, maxAgeAttr,
|
||||
int(interval.Seconds()),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
weightedRouteRule.BackendRefs[i] = backendRef
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add `Cookie` header matcher to the sticky routing rule.
|
||||
// configure the sticky canary rule to match against requests that match against the
|
||||
// canary cookie and send them to the canary backend.
|
||||
cookieKeyAndVal := strings.Split(canary.Status.SessionAffinityCookie, "=")
|
||||
regexMatchType := v1.HeaderMatchRegularExpression
|
||||
cookieMatch := v1.HTTPRouteMatch{
|
||||
@@ -493,8 +534,8 @@ func (gwr *GatewayAPIRouter) getSessionAffinityRouteRules(canary *flaggerv1.Cana
|
||||
}
|
||||
|
||||
mergedMatches := gwr.mergeMatchConditions([]v1.HTTPRouteMatch{cookieMatch}, svcMatches)
|
||||
stickyRouteRule.Matches = mergedMatches
|
||||
stickyRouteRule.BackendRefs = []v1.HTTPBackendRef{
|
||||
stickyCanaryRouteRule.Matches = mergedMatches
|
||||
stickyCanaryRouteRule.BackendRefs = []v1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(primarySvcName, 0, canary.Spec.Service.Port),
|
||||
},
|
||||
@@ -502,6 +543,42 @@ func (gwr *GatewayAPIRouter) getSessionAffinityRouteRules(canary *flaggerv1.Cana
|
||||
BackendRef: gwr.makeBackendRef(canarySvcName, 100, canary.Spec.Service.Port),
|
||||
},
|
||||
}
|
||||
|
||||
// add a sticky primary rule to match against requests that match against the
|
||||
// primary cookie and send them to the primary backend, only if a primary cookie name has
|
||||
// been specified.
|
||||
if canary.Spec.Analysis.SessionAffinity.PrimaryCookieName != "" {
|
||||
cookieKeyAndVal = strings.Split(primaryCookie, "=")
|
||||
regexMatchType = v1.HeaderMatchRegularExpression
|
||||
primaryCookieMatch := v1.HTTPRouteMatch{
|
||||
Headers: []v1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: ®exMatchType,
|
||||
Name: cookieHeader,
|
||||
Value: fmt.Sprintf(".*%s.*%s.*", cookieKeyAndVal[0], cookieKeyAndVal[1]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svcMatches, err = gwr.mapRouteMatches(canary.Spec.Service.Match)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mergedMatches = gwr.mergeMatchConditions([]v1.HTTPRouteMatch{primaryCookieMatch}, svcMatches)
|
||||
stickyPrimaryRouteRule.Matches = mergedMatches
|
||||
stickyPrimaryRouteRule.BackendRefs = []v1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(primarySvcName, 100, canary.Spec.Service.Port),
|
||||
},
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(canarySvcName, 0, canary.Spec.Service.Port),
|
||||
},
|
||||
}
|
||||
return []v1.HTTPRouteRule{stickyCanaryRouteRule, stickyPrimaryRouteRule, *weightedRouteRule}, nil
|
||||
}
|
||||
|
||||
return []v1.HTTPRouteRule{stickyCanaryRouteRule, *weightedRouteRule}, nil
|
||||
} else {
|
||||
// If canary weight is 0 and SessionAffinityCookie is non-blank, then it belongs to a previous canary run.
|
||||
if canary.Status.SessionAffinityCookie != "" {
|
||||
@@ -524,9 +601,9 @@ func (gwr *GatewayAPIRouter) getSessionAffinityRouteRules(canary *flaggerv1.Cana
|
||||
}
|
||||
svcMatches, _ := gwr.mapRouteMatches(canary.Spec.Service.Match)
|
||||
mergedMatches := gwr.mergeMatchConditions([]v1.HTTPRouteMatch{cookieMatch}, svcMatches)
|
||||
stickyRouteRule.Matches = mergedMatches
|
||||
stickyCanaryRouteRule.Matches = mergedMatches
|
||||
|
||||
stickyRouteRule.Filters = append(stickyRouteRule.Filters, v1.HTTPRouteFilter{
|
||||
stickyCanaryRouteRule.Filters = append(stickyCanaryRouteRule.Filters, v1.HTTPRouteFilter{
|
||||
Type: v1.HTTPRouteFilterResponseHeaderModifier,
|
||||
ResponseHeaderModifier: &v1.HTTPHeaderFilter{
|
||||
Add: []v1.HTTPHeader{
|
||||
@@ -540,9 +617,8 @@ func (gwr *GatewayAPIRouter) getSessionAffinityRouteRules(canary *flaggerv1.Cana
|
||||
}
|
||||
|
||||
canary.Status.SessionAffinityCookie = ""
|
||||
return []v1.HTTPRouteRule{stickyCanaryRouteRule, *weightedRouteRule}, nil
|
||||
}
|
||||
|
||||
return []v1.HTTPRouteRule{stickyRouteRule, *weightedRouteRule}, nil
|
||||
}
|
||||
|
||||
func (gwr *GatewayAPIRouter) mapRouteMatches(requestMatches []istiov1beta1.HTTPMatchRequest) ([]v1.HTTPRouteMatch, error) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
|
||||
v1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1"
|
||||
@@ -104,6 +105,7 @@ func TestGatewayAPIRouter_Routes(t *testing.T) {
|
||||
_, pSvcName, cSvcName := canary.GetServiceNames()
|
||||
|
||||
err := router.SetRoutes(canary, 90, 10, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
hr, err := mocks.meshClient.GatewayapiV1().HTTPRoutes("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
@@ -290,80 +292,192 @@ func TestGatewayAPIRouter_Routes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGatewayAPIRouter_getSessionAffinityRouteRules(t *testing.T) {
|
||||
canary := newTestGatewayAPICanary()
|
||||
mocks := newFixture(canary)
|
||||
cookieKey := "flagger-cookie"
|
||||
canary.Spec.Analysis.SessionAffinity = &flaggerv1.SessionAffinity{
|
||||
CookieName: cookieKey,
|
||||
MaxAge: 300,
|
||||
}
|
||||
t.Run("without primary cookie", func(t *testing.T) {
|
||||
canary := newTestGatewayAPICanary()
|
||||
mocks := newFixture(canary)
|
||||
cookieKey := "flagger-cookie"
|
||||
canary.Spec.Analysis.SessionAffinity = &flaggerv1.SessionAffinity{
|
||||
CookieName: cookieKey,
|
||||
MaxAge: 300,
|
||||
}
|
||||
|
||||
router := &GatewayAPIRouter{
|
||||
gatewayAPIClient: mocks.meshClient,
|
||||
kubeClient: mocks.kubeClient,
|
||||
logger: mocks.logger,
|
||||
}
|
||||
_, pSvcName, cSvcName := canary.GetServiceNames()
|
||||
weightedRouteRule := &v1.HTTPRouteRule{
|
||||
BackendRefs: []v1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: router.makeBackendRef(pSvcName, initialPrimaryWeight, canary.Spec.Service.Port),
|
||||
router := &GatewayAPIRouter{
|
||||
gatewayAPIClient: mocks.meshClient,
|
||||
kubeClient: mocks.kubeClient,
|
||||
logger: mocks.logger,
|
||||
}
|
||||
_, pSvcName, cSvcName := canary.GetServiceNames()
|
||||
weightedRouteRule := &v1.HTTPRouteRule{
|
||||
BackendRefs: []v1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: router.makeBackendRef(pSvcName, initialPrimaryWeight, canary.Spec.Service.Port),
|
||||
},
|
||||
{
|
||||
BackendRef: router.makeBackendRef(cSvcName, initialCanaryWeight, canary.Spec.Service.Port),
|
||||
},
|
||||
},
|
||||
{
|
||||
BackendRef: router.makeBackendRef(cSvcName, initialCanaryWeight, canary.Spec.Service.Port),
|
||||
}
|
||||
rules, err := router.getSessionAffinityRouteRules(canary, 10, weightedRouteRule)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(rules), 2)
|
||||
assert.True(t, strings.HasPrefix(canary.Status.SessionAffinityCookie, cookieKey))
|
||||
|
||||
stickyRule := rules[0]
|
||||
cookieMatch := stickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, cookieKey)
|
||||
|
||||
assert.Equal(t, len(stickyRule.BackendRefs), 2)
|
||||
for _, backendRef := range stickyRule.BackendRefs {
|
||||
if string(backendRef.BackendRef.Name) == pSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(0))
|
||||
}
|
||||
if string(backendRef.BackendRef.Name) == cSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(100))
|
||||
}
|
||||
}
|
||||
|
||||
weightedRule := rules[1]
|
||||
var found bool
|
||||
for _, backendRef := range weightedRule.BackendRefs {
|
||||
if string(backendRef.Name) == cSvcName {
|
||||
found = true
|
||||
filter := backendRef.Filters[0]
|
||||
assert.Equal(t, filter.Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
assert.NotNil(t, filter.ResponseHeaderModifier)
|
||||
assert.Equal(t, string(filter.ResponseHeaderModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Equal(t, filter.ResponseHeaderModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.SessionAffinityCookie, maxAgeAttr, 300))
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
|
||||
rules, err = router.getSessionAffinityRouteRules(canary, 0, weightedRouteRule)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, canary.Status.SessionAffinityCookie)
|
||||
assert.Contains(t, canary.Status.PreviousSessionAffinityCookie, cookieKey)
|
||||
|
||||
stickyRule = rules[0]
|
||||
cookieMatch = stickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, cookieKey)
|
||||
|
||||
assert.Equal(t, stickyRule.Filters[0].Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
headerModifier := stickyRule.Filters[0].ResponseHeaderModifier
|
||||
assert.NotNil(t, headerModifier)
|
||||
assert.Equal(t, string(headerModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Equal(t, headerModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.PreviousSessionAffinityCookie, maxAgeAttr, -1))
|
||||
})
|
||||
|
||||
t.Run("with primary cookie", func(t *testing.T) {
|
||||
canary := newTestGatewayAPICanary()
|
||||
mocks := newFixture(canary)
|
||||
canaryCookieKey := "canary-flagger-cookie"
|
||||
primaryCookieKey := "primary-flagger-cookie"
|
||||
canary.Spec.Analysis.Interval = "15s"
|
||||
canary.Spec.Analysis.SessionAffinity = &flaggerv1.SessionAffinity{
|
||||
CookieName: canaryCookieKey,
|
||||
PrimaryCookieName: primaryCookieKey,
|
||||
MaxAge: 300,
|
||||
}
|
||||
|
||||
router := &GatewayAPIRouter{
|
||||
gatewayAPIClient: mocks.meshClient,
|
||||
kubeClient: mocks.kubeClient,
|
||||
logger: mocks.logger,
|
||||
}
|
||||
_, pSvcName, cSvcName := canary.GetServiceNames()
|
||||
weightedRouteRule := &v1.HTTPRouteRule{
|
||||
BackendRefs: []v1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: router.makeBackendRef(pSvcName, initialPrimaryWeight, canary.Spec.Service.Port),
|
||||
},
|
||||
{
|
||||
BackendRef: router.makeBackendRef(cSvcName, initialCanaryWeight, canary.Spec.Service.Port),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rules, err := router.getSessionAffinityRouteRules(canary, 10, weightedRouteRule)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(rules), 2)
|
||||
assert.True(t, strings.HasPrefix(canary.Status.SessionAffinityCookie, cookieKey))
|
||||
|
||||
stickyRule := rules[0]
|
||||
cookieMatch := stickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, cookieKey)
|
||||
|
||||
assert.Equal(t, len(stickyRule.BackendRefs), 2)
|
||||
for _, backendRef := range stickyRule.BackendRefs {
|
||||
if string(backendRef.BackendRef.Name) == pSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(0))
|
||||
}
|
||||
if string(backendRef.BackendRef.Name) == cSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(100))
|
||||
rules, err := router.getSessionAffinityRouteRules(canary, 10, weightedRouteRule)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(rules), 3)
|
||||
assert.True(t, strings.HasPrefix(canary.Status.SessionAffinityCookie, canaryCookieKey))
|
||||
|
||||
canaryStickyRule := rules[0]
|
||||
cookieMatch := canaryStickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, canaryCookieKey)
|
||||
|
||||
assert.Equal(t, len(canaryStickyRule.BackendRefs), 2)
|
||||
for _, backendRef := range canaryStickyRule.BackendRefs {
|
||||
if string(backendRef.BackendRef.Name) == pSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(0))
|
||||
}
|
||||
if string(backendRef.BackendRef.Name) == cSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(100))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
weightedRule := rules[1]
|
||||
var found bool
|
||||
for _, backendRef := range weightedRule.BackendRefs {
|
||||
if string(backendRef.Name) == cSvcName {
|
||||
found = true
|
||||
filter := backendRef.Filters[0]
|
||||
assert.Equal(t, filter.Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
assert.NotNil(t, filter.ResponseHeaderModifier)
|
||||
assert.Equal(t, string(filter.ResponseHeaderModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Equal(t, filter.ResponseHeaderModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.SessionAffinityCookie, maxAgeAttr, 300))
|
||||
primaryStickyRule := rules[1]
|
||||
cookieMatch = primaryStickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, primaryCookieKey)
|
||||
|
||||
assert.Equal(t, len(primaryStickyRule.BackendRefs), 2)
|
||||
for _, backendRef := range primaryStickyRule.BackendRefs {
|
||||
if string(backendRef.BackendRef.Name) == pSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(100))
|
||||
}
|
||||
if string(backendRef.BackendRef.Name) == cSvcName {
|
||||
assert.Equal(t, *backendRef.BackendRef.Weight, int32(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
|
||||
rules, err = router.getSessionAffinityRouteRules(canary, 0, weightedRouteRule)
|
||||
assert.Empty(t, canary.Status.SessionAffinityCookie)
|
||||
assert.Contains(t, canary.Status.PreviousSessionAffinityCookie, cookieKey)
|
||||
weightedRule := rules[2]
|
||||
var c int
|
||||
for _, backendRef := range weightedRule.BackendRefs {
|
||||
if string(backendRef.Name) == cSvcName {
|
||||
c += 1
|
||||
filter := backendRef.Filters[0]
|
||||
assert.Equal(t, filter.Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
assert.NotNil(t, filter.ResponseHeaderModifier)
|
||||
assert.Equal(t, string(filter.ResponseHeaderModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Equal(t, filter.ResponseHeaderModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.SessionAffinityCookie, maxAgeAttr, 300))
|
||||
}
|
||||
|
||||
stickyRule = rules[0]
|
||||
cookieMatch = stickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, cookieKey)
|
||||
if string(backendRef.Name) == pSvcName {
|
||||
c += 1
|
||||
filter := backendRef.Filters[0]
|
||||
assert.Equal(t, filter.Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
assert.NotNil(t, filter.ResponseHeaderModifier)
|
||||
assert.Equal(t, string(filter.ResponseHeaderModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Contains(t, filter.ResponseHeaderModifier.Add[0].Value, canary.Spec.Analysis.SessionAffinity.PrimaryCookieName)
|
||||
interval, err := time.ParseDuration(canary.Spec.Analysis.Interval)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, filter.ResponseHeaderModifier.Add[0].Value, fmt.Sprintf("%s=%d", maxAgeAttr, int(interval.Seconds())))
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 2, c)
|
||||
|
||||
assert.Equal(t, stickyRule.Filters[0].Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
headerModifier := stickyRule.Filters[0].ResponseHeaderModifier
|
||||
assert.NotNil(t, headerModifier)
|
||||
assert.Equal(t, string(headerModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Equal(t, headerModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.PreviousSessionAffinityCookie, maxAgeAttr, -1))
|
||||
rules, err = router.getSessionAffinityRouteRules(canary, 0, weightedRouteRule)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, canary.Status.SessionAffinityCookie)
|
||||
assert.Contains(t, canary.Status.PreviousSessionAffinityCookie, canaryCookieKey)
|
||||
|
||||
canaryStickyRule = rules[0]
|
||||
cookieMatch = canaryStickyRule.Matches[0].Headers[0]
|
||||
assert.Equal(t, *cookieMatch.Type, v1.HeaderMatchRegularExpression)
|
||||
assert.Equal(t, string(cookieMatch.Name), cookieHeader)
|
||||
assert.Contains(t, cookieMatch.Value, canaryCookieKey)
|
||||
|
||||
assert.Equal(t, canaryStickyRule.Filters[0].Type, v1.HTTPRouteFilterResponseHeaderModifier)
|
||||
headerModifier := canaryStickyRule.Filters[0].ResponseHeaderModifier
|
||||
assert.NotNil(t, headerModifier)
|
||||
assert.Equal(t, string(headerModifier.Add[0].Name), setCookieHeader)
|
||||
assert.Equal(t, headerModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.PreviousSessionAffinityCookie, maxAgeAttr, -1))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGatewayAPIRouter_makeFilters(t *testing.T) {
|
||||
|
||||
@@ -16,5 +16,5 @@ limitations under the License.
|
||||
|
||||
package version
|
||||
|
||||
var VERSION = "1.40.0"
|
||||
var VERSION = "1.41.0"
|
||||
var REVISION = "unknown"
|
||||
|
||||
@@ -44,7 +44,8 @@ spec:
|
||||
maxWeight: 50
|
||||
stepWeight: 10
|
||||
sessionAffinity:
|
||||
cookieName: flagger-cookie
|
||||
cookieName: canary-flagger-cookie
|
||||
primaryCookieName: primary-flagger-cookie
|
||||
metrics:
|
||||
- name: error-rate
|
||||
templateRef:
|
||||
@@ -104,7 +105,8 @@ until ${ok}; do
|
||||
done
|
||||
|
||||
echo '>>> Verifying session affinity'
|
||||
if ! URL=http://localhost:8888 HOST=www.example.com VERSION=6.1.0 COOKIE_NAME=flagger-cookie \
|
||||
if ! URL=http://localhost:8888 HOST=www.example.com CANARY_VERSION=6.1.0 \
|
||||
CANARY_COOKIE_NAME=canary-flagger-cookie PRIMARY_VERSION=6.0.4 PRIMARY_COOKIE_NAME=primary-flagger-cookie \
|
||||
go run ${REPO_ROOT}/test/gatewayapi/verify_session_affinity.go; then
|
||||
echo "failed to verify session affinity"
|
||||
exit $?
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -11,30 +11,39 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var c = make(chan string, 1)
|
||||
var mu sync.Mutex
|
||||
var try = true
|
||||
// channel for canary cookie
|
||||
var cc = make(chan string, 1)
|
||||
|
||||
// channel for primary cookie
|
||||
var pc = make(chan string, 1)
|
||||
var timeout = time.Second * 10
|
||||
|
||||
func main() {
|
||||
url := os.Getenv("URL")
|
||||
host := os.Getenv("HOST")
|
||||
version := os.Getenv("VERSION")
|
||||
cookieName := os.Getenv("COOKIE_NAME")
|
||||
canaryVersion := os.Getenv("CANARY_VERSION")
|
||||
canaryCookieName := os.Getenv("CANARY_COOKIE_NAME")
|
||||
primaryVersion := os.Getenv("PRIMARY_VERSION")
|
||||
primaryCookieName := os.Getenv("PRIMARY_COOKIE_NAME")
|
||||
|
||||
// Generate traffic
|
||||
for i := 0; i < 10; i++ {
|
||||
go tryUntilCanaryIsHit(url, host, version, cookieName)
|
||||
}
|
||||
go tryUntilWorkloadIsHit(url, host, canaryVersion, canaryCookieName, cc)
|
||||
go tryUntilWorkloadIsHit(url, host, primaryVersion, primaryCookieName, pc)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
go verifySessionAffinity(cc, wg, url, host, primaryVersion)
|
||||
go verifySessionAffinity(pc, wg, url, host, canaryVersion)
|
||||
|
||||
wg.Wait()
|
||||
log.Println("✔ successfully verified session affinity")
|
||||
}
|
||||
|
||||
func verifySessionAffinity(cc chan string, wg *sync.WaitGroup, url, host, wrongVersion string) {
|
||||
select {
|
||||
// If we receive a cookie, then try to verify that we are always routed to the
|
||||
// Canary deployment based on the cookie.
|
||||
case cookie := <-c:
|
||||
mu.Lock()
|
||||
try = false
|
||||
mu.Unlock()
|
||||
|
||||
case cookie := <-cc:
|
||||
for i := 0; i < 5; i++ {
|
||||
headers := map[string]string{
|
||||
"Cookie": cookie,
|
||||
@@ -43,15 +52,15 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("failed to send request to verify cookie based routing: %v", err)
|
||||
}
|
||||
if !strings.Contains(body, version) {
|
||||
log.Fatalf("received response from primary deployment instead of canary deployment")
|
||||
if strings.Contains(body, wrongVersion) {
|
||||
log.Fatalf("received response from the wrong deployment")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("✔ successfully verified session affinity")
|
||||
wg.Done()
|
||||
case <-time.After(timeout):
|
||||
log.Fatal("timed out waiting for canary hit")
|
||||
log.Fatal("timed out waiting for workload hit")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// sendRequest sends a request to the URL with the provided host and headers.
|
||||
@@ -74,7 +83,7 @@ func sendRequest(url, host string, headers map[string]string) (string, []*http.C
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -82,17 +91,10 @@ func sendRequest(url, host string, headers map[string]string) (string, []*http.C
|
||||
return string(body), resp.Cookies(), nil
|
||||
}
|
||||
|
||||
// tryUntilCanaryIsHit is a recursive function that tries to send request and
|
||||
// tryUntilWorkloadIsHit is a recursive function that tries to send request and
|
||||
// either sends the cookie back to the main thread (if received) or re-sends
|
||||
// the request.
|
||||
func tryUntilCanaryIsHit(url, host, version, cookieName string) {
|
||||
mu.Lock()
|
||||
if !try {
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
func tryUntilWorkloadIsHit(url, host, version, cookieName string, c chan string) {
|
||||
body, cookies, err := sendRequest(url, host, nil)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to send request: %s", err)
|
||||
@@ -105,6 +107,5 @@ func tryUntilCanaryIsHit(url, host, version, cookieName string) {
|
||||
}
|
||||
}
|
||||
|
||||
tryUntilCanaryIsHit(url, host, version, cookieName)
|
||||
return
|
||||
tryUntilWorkloadIsHit(url, host, version, cookieName, c)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user