Compare commits

...

93 Commits

Author SHA1 Message Date
Sanskar Jaiswal
d4cc9bf616 Merge pull request #1862 from fluxcd/dependabot/go_modules/golang.org/x/crypto-0.45.0
build(deps): bump golang.org/x/crypto from 0.42.0 to 0.45.0
2025-12-29 18:38:24 +05:30
dependabot[bot]
ca3fd315cd build(deps): bump golang.org/x/crypto from 0.42.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.42.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.42.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 12:14:13 +00:00
Stefan Prodan
d9d910b0cf Merge pull request #1861 from renatovassaomb/rv/istio-primary-cookie-name-support
Feat: Add support for stickiness for primary deployment in Istio
2025-12-18 09:11:10 +02:00
Stefan Prodan
9090802dcc Merge pull request #1858 from erikmiller-gusto/main
fix: datadog metrics should provide http status code on error if non-2xx response
2025-12-18 09:09:51 +02:00
Renato Vassão
70c4c528ed Updates docs with new support for primary cookie name in istio router
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-12-12 10:55:37 -03:00
Stefan Prodan
3ce87af077 Merge pull request #1868 from darkweaver87/chore/update-traefik
chore(ci): update Traefik to 3.6.2 in E2E tests
2025-12-11 13:22:30 +02:00
Rémi BUISSON
1709b076e0 chore(ci): update traefik for E2E tests
Signed-off-by: Rémi BUISSON <remi.buisson@traefik.io>
2025-12-11 11:36:53 +01:00
Renato Vassão
f3b240ca82 Adds integration tests for session affinity in Istio router
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-11-18 15:16:42 -03:00
Renato Vassão
4821a687c1 Adds support for setting primary cookie name in istio router
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-11-18 15:08:25 -03:00
Renato Vassão
931dd7fa6b Adds usage of PrimarySessionAffinityCookie in Gateway API router
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-11-18 15:07:19 -03:00
Renato Vassão
8018353d54 Adds primarySessionAffinityCookie field to sessionAffinity
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-11-18 15:04:04 -03:00
Erik Miller
ffddfd9c24 fix: datadog metrics should provide http status code on error if non-2xx response
currently the log line exposes the error, however that's always going to be nil
based on the check just above it.  This provides better visibility into the failure reason

Signed-off-by: Erik Miller <erik.miller@gusto.com>
2025-11-13 12:59:08 -08:00
Stefan Prodan
3a27fd147f Merge pull request #1847 from fluxcd/release-1.42.0
Release Flagger 1.42.0
2025-10-16 16:56:35 +03:00
Stefan Prodan
1a727d294c Release Flagger 1.42.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-16 15:59:48 +03:00
Stefan Prodan
b170bf479c Merge pull request #1846 from fluxcd/loadtester-0.36.0
Release loadtester 0.36.0
2025-10-16 14:46:03 +03:00
Stefan Prodan
ae5e39bb3d Release loadtester 0.36.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-16 13:34:13 +03:00
Stefan Prodan
7fcba13050 Merge pull request #1845 from fluxcd/update-docs
Update GitOps install docs to latest Flux APIs
2025-10-16 13:23:31 +03:00
Stefan Prodan
8fedc5cdd7 Update documentation
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-16 13:10:55 +03:00
Stefan Prodan
fce1781ebf Merge pull request #1791 from steved/main
fix: gateway router should wait for accepted condition
2025-10-16 12:51:36 +03:00
Stefan Prodan
461abc9e10 Merge pull request #1797 from steved/steved/event-webhook-timeout
fix: honor event webhook timeout
2025-10-16 12:10:36 +03:00
Stefan Prodan
6b89d56074 Merge pull request #1792 from steved/steved/phase-succeeded-webhook
fix: send succeeded webhooks with correct phase
2025-10-16 11:03:49 +03:00
Steven Davidovitz
bb7ad65462 fix: gateway router should wait for accepted condition
It can take some time for changes to propagate for cloud load balancers,
so flagger should ensure the route changes are current before proceeding
with any more.

Signed-off-by: Steven Davidovitz <sdavidovitz@groq.com>
2025-10-15 17:27:12 -07:00
Steven Davidovitz
6fcbe192a7 fix: send succeeded webhooks with correct phase
Signed-off-by: Steven Davidovitz <sdavidovitz@groq.com>
2025-10-15 17:23:02 -07:00
Steven Davidovitz
253123bdff fix: honor event webhook timeout
Signed-off-by: Steven Davidovitz <sdavidovitz@groq.com>
2025-10-15 17:21:14 -07:00
Stefan Prodan
ae72e15049 Merge pull request #1844 from fluxcd/svc-traffic-distribution
Add support for setting traffic distribution
2025-10-15 19:14:05 +03:00
Stefan Prodan
c30e6552d7 Add support for setting traffic distribution
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-15 16:57:06 +03:00
Stefan Prodan
60cb38a773 Merge pull request #1843 from fluxcd/gateway-api-cors
Add support for CORS policy to Gateway API
2025-10-15 16:55:58 +03:00
Stefan Prodan
6031abc3a9 Add support for CORS policy to Gateway API
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-15 16:43:39 +03:00
Stefan Prodan
e4c355e772 Merge pull request #1842 from fluxcd/gateway-api-v1.4
Update Gateway API to v1.4.0
2025-10-15 14:15:47 +03:00
Stefan Prodan
f9fe9a1635 Update Gateway API to v1.4.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-15 12:41:37 +03:00
Stefan Prodan
34830b2448 Merge pull request #1836 from fluxcd/dependabot/github_actions/ci-ebec57696b
build(deps): bump the ci group across 1 directory with 4 updates
2025-10-15 12:25:16 +03:00
dependabot[bot]
9ed5fcdaa3 build(deps): bump the ci group across 1 directory with 4 updates
Bumps the ci group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/setup-go](https://github.com/actions/setup-go), [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `actions/setup-go` from 5 to 6
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

Updates `sigstore/cosign-installer` from 3.8.1 to 3.10.0
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.1...v3.10.0)

Updates `github/codeql-action` from 3 to 4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-15 08:33:49 +00:00
Stefan Prodan
3dc0658a80 Merge pull request #1832 from fluxcd/go1.25
Update dependencies to Kubernetes 1.34
2025-10-15 11:31:34 +03:00
Stefan Prodan
751f52ec25 Regenerate clientset with Kubernetes 1.34 code-gen
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-15 10:20:27 +03:00
Stefan Prodan
e1839fd9c3 Update dependencies to Kubernetes 1.34
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-15 10:10:43 +03:00
Stefan Prodan
b445c8eeaf Build with Go 1.25
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-15 10:09:57 +03:00
Stefan Prodan
3d99d2fddd Merge pull request #1823 from briansonnenberg/main
Add `unmanagedMetadata` to canary service specification
2025-10-15 09:52:08 +03:00
Stefan Prodan
b77230227f Merge pull request #1826 from renatovassaomb/rv/add-cookie-attributes
Feat: Add Support for Cookie Attributes in Session Affinity
2025-10-15 00:33:38 +03:00
Brian Sonnenberg
ea219506a5 Added a new field to canary spec to specify unmanaged metadata
These are labels and annotations that should be ignored by Flagger
(i.e. not overwritten upon reconciliation).

See: github.com/fluxcd/flagger/issues/1573

Signed-off-by: Brian Sonnenberg <bsonnenberg@google.com>
2025-10-14 21:18:47 +00:00
Renato Vassão
fab45404e6 Update docs with new cookie attributes
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-10-14 17:57:09 -03:00
Renato Vassão
0dd9fe9301 Add cookie attributes to Canary CRD
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-10-14 16:00:14 -03:00
Renato Vassão
278e7d31bc Use BuildCookie function when setting cookie in Gateway API routers
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-10-14 16:00:14 -03:00
Renato Vassão
c7dad5b532 Use BuildCookie function when setting cookie in Istio router
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-10-14 16:00:14 -03:00
Renato Vassão
b672363a37 Add cookie attributes to SessionAffinity
Signed-off-by: Renato Vassão <renato.vassao@mindbodyonline.com>
2025-10-14 16:00:07 -03:00
Stefan Prodan
939c32b390 Merge pull request #1835 from fluxcd/traefik-update
Update traefik.containo.us to traefik.io
2025-10-10 23:38:58 +03:00
Sanskar Jaiswal
e08cc1d798 fix: Upgraded traefik helm version for testing
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
Co-authored-by: Ashton Pillay <ashpilzusa@gmail.com>
2025-10-10 23:18:25 +05:30
Sanskar Jaiswal
28291d42d8 fix: update traefik.containo.us to traefik.io
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
Co-authored-by: Ashton Pillay <ashpilzusa@gmail.com>
2025-10-10 23:17:12 +05:30
Stefan Prodan
5c481cd6f9 Merge pull request #1828 from angelbarrera92/issue-1827
fix: default namespace for cross-namespace ref validation
2025-10-08 10:53:20 +03:00
Barrera, Angel
eeeac3543a fix: default namespace for cross-namespace ref validation
When cross-namespace references are disabled, ensure that UpstreamRef,
MetricTemplateRef, and AlertProviderRef default to the canary's namespace
if their namespace field is empty. This aligns the validation logic with
the rest of the controller and prevents false positives when the namespace
is omitted.

Fixes #1827

Signed-off-by: Barrera, Angel <angelbarrerasanchez@protonmail.com>
2025-10-08 09:16:29 +02:00
Stefan Prodan
0743ef72b3 Merge pull request #1812 from cappyzawa/issue-856-count-metrics
Add count metrics for canary successes and failures
2025-10-08 09:49:27 +03:00
cappyzawa
ffbc25efda Add count metrics for canary successes and failures
Implement flagger_canary_successes_total and flagger_canary_failures_total
counter metrics with deployment strategy detection and analysis status
tracking for better observability of canary deployment outcomes.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-10-08 13:53:03 +09:00
cappyzawa
16f54923b2 Add metrics verification to controller tests
Enhance existing scheduler tests for deployments, daemonsets, and
services by adding prometheus metrics verification using testutil.
This ensures that status metrics are correctly recorded during
canary promotion workflows and provides better test coverage for
the metrics recording functionality.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-10-08 13:47:11 +09:00
Stefan Prodan
f9f10e842e Merge pull request #1739 from kahirokunn/fix-typo
fix: correct typo in AutoscalerReference type name
2025-10-07 13:14:17 +03:00
kahirokunn
56200f6d0f fix: correct typo in AutoscalerReference type name
- Fix spelling of AutoscalerReference (was AutoscalerRefernce) in type definition and struct field

Signed-off-by: kahirokunn <okinakahiro@gmail.com>
2025-10-07 18:28:04 +09:00
Stefan Prodan
2cf112841c Merge pull request #1831 from KevinSnyderCodes/fix-apisix-e2e-test
Fix `apisix` E2E test
2025-10-06 21:34:53 +03:00
Kevin Snyder
a67c36ed99 Fix apisix E2E test
`apisix` Helm chart has dependency on `etcd` chart which uses a pinned Bitnami image. These became unavailable on August 28, 2025: https://github.com/bitnami/containers/issues/83267

The image is still available in the `bitnamilegacy` repository.

Signed-off-by: Kevin Snyder <kevin.snyder@gusto.com>
2025-10-06 11:03:03 -07:00
Stefan Prodan
27daa2ca46 Merge pull request #1803 from alex-souslik-hs/main
loadtester: add pod security context
2025-04-21 09:23:30 +02:00
Alex
ed38a79545 add pod security context
Signed-off-by: Alex <alex.souslik@workday.com>
2025-04-20 19:15:37 +03:00
Sanskar Jaiswal
6f165a10de Merge pull request #1788 from fluxcd/release-v1.41.0
Release v1.41.0
2025-04-02 12:40:43 +01:00
Sanskar Jaiswal
89c1ddee79 Release v1.41.0
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-04-02 12:32:23 +01:00
Sanskar Jaiswal
1b8e7653d3 Merge pull request #1787 from fluxcd/update-deps
update Go dependencies
2025-03-31 18:09:11 +05:30
Stefan Prodan
98f8514258 Merge pull request #1786 from fluxcd/fix-session-affinity-e2e
update webhook host in session affinity e2e test
2025-03-30 14:04:02 +01:00
Sanskar Jaiswal
d9c8a09d3e update Go dependencies
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-03-29 18:00:26 +00:00
Sanskar Jaiswal
2ac22f831f update webhook host in session affinity e2e test
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-03-28 12:30:23 +05:30
Sanskar Jaiswal
e0de40dcb0 Merge pull request #1757 from otternq/metric-provider-headers
allow headers to be added to prometheus requests
2025-03-28 12:26:38 +05:30
Nick Otter
8f9bb5b1bc allow headers to be added to prometheus requests
Signed-off-by: Nick Otter <otternq@gmail.com>
Co-authored-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-03-27 01:03:23 +05:30
Sanskar Jaiswal
f21bc1de3e Merge pull request #1783 from fluxcd/session-affinity-primary-cookie
feat: add support for primary backend cookies in session affinity (Gateway API)
2025-03-24 16:02:30 +05:30
Sanskar Jaiswal
1fc7ac5847 add docs for primary stickiness for session affinity
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>

dfk
2025-03-24 13:08:29 +05:30
Sanskar Jaiswal
1dc270c2e6 feat: add support for primary backend cookies in session affinity
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-03-24 13:08:27 +05:30
Stefan Prodan
50d1331ba6 Merge pull request #1785 from fluxcd/loadtester-0.35.0
Release loadtester 0.35.0
2025-03-23 10:14:29 +02:00
Stefan Prodan
bc78156535 Release loadtester 0.35.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-03-23 09:31:44 +02:00
Stefan Prodan
0df8af8d04 Merge pull request #1771 from fluxcd/dependabot/github_actions/ci-8077bd6f50
build(deps): bump the ci group across 1 directory with 2 updates
2025-03-23 09:29:16 +02:00
dependabot[bot]
633f639383 build(deps): bump the ci group across 1 directory with 2 updates
Bumps the ci group with 2 updates in the / directory: [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) and [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator).


Updates `sigstore/cosign-installer` from 3.7.0 to 3.8.1
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.7.0...v3.8.1)

Updates `slsa-framework/slsa-github-generator` from 2.0.0 to 2.1.0
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-23 07:19:45 +00:00
Stefan Prodan
d03cc73386 Merge pull request #1784 from fluxcd/go-1.24
Build with Go 1.24
2025-03-23 09:17:55 +02:00
Stefan Prodan
eaf5bb992c Ensure constant format strings in fmt calls
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-03-23 08:59:45 +02:00
Stefan Prodan
22618ccb11 Build with Go 1.24
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-03-23 08:50:59 +02:00
Stefan Prodan
f5af225ffc Merge pull request #1776 from fluxcd/dependabot/go_modules/golang.org/x/net-0.36.0
build(deps): bump golang.org/x/net from 0.33.0 to 0.36.0
2025-03-23 08:45:20 +02:00
dependabot[bot]
40a34199fe build(deps): bump golang.org/x/net from 0.33.0 to 0.36.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-23 06:40:43 +00:00
Stefan Prodan
d7357a7377 Merge pull request #1682 from tombanksme/knative-support
Add support for Knative
2025-03-23 08:39:29 +02:00
Sanskar Jaiswal
12ee6cbc86 add docs for knative
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-03-22 01:02:32 +05:30
Thomas Banks
f1c8807c0d feat: add knative integration
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
Co-authored-by: Thomas Banks
2025-03-22 01:02:30 +05:30
Stefan Prodan
8276bfa5a5 Merge pull request #1763 from easimon/fix/datadog-provider
fix: do not evaluate incomplete samples from datadog
2025-02-22 10:36:22 +02:00
Markus Dobel
2c4b7a69a2 fix: do not evaluate incomplete samples from datadog
Signed-off-by: Markus Dobel <markus.dobel@epicompany.eu>
2025-02-12 18:12:38 +01:00
Sanskar Jaiswal
660ed7486b Merge pull request #1677 from jdgeisler/keda-scaled-object-hpa-migration
Prevent primary hpa collision for keda scaled objects when migrating from an hpa
2025-02-11 22:49:54 +05:30
James Geisler
21acd7e3d6 If applied, this commit will allow the migration from an hpa to a scaled object
Signed-off-by: James Geisler <geislerjamesd@gmail.com>
2025-02-10 10:24:58 -06:00
Stefan Prodan
40e2802c3d Merge pull request #1707 from quintonm/main
chart: add support for deploymentLabels
2025-01-26 09:44:37 +02:00
Sanskar Jaiswal
d99d37b219 Merge pull request #1755 from fluxcd/headless-svc 2025-01-14 15:02:59 +05:30
Sanskar Jaiswal
45618b90db feat: add option to generate headless services
Add a new field `.spec.service.headless` which if set to true results in
Flagger generating headless Services, i.e. with the Service's
`.spec.clusterIP` set to None.

Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-01-14 14:09:12 +05:30
Sanskar Jaiswal
ff4051f728 Merge pull request #1756 from fluxcd/bump-go-net
chore: bump golang.org/x/net to v0.33.0
2025-01-14 13:18:03 +05:30
Sanskar Jaiswal
2ea13a477b chore: bump golang.org/x/net to v0.33.0
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2025-01-14 00:18:27 +05:30
quintonm
03d4acc77f add support for deploymentLabels
Signed-off-by: quintonm <quinton.mccombs@gmail.com>
2025-01-13 07:56:30 -06:00
Stefan Prodan
16a607549e Merge pull request #1751 from fluxcd/dependabot/github_actions/ci-cba3cefdba
Bump helm/kind-action from 1.11.0 to 1.12.0 in the ci group
2024-12-23 14:12:52 +02:00
dependabot[bot]
b57afd3b0f Bump helm/kind-action from 1.11.0 to 1.12.0 in the ci group
Bumps the ci group with 1 update: [helm/kind-action](https://github.com/helm/kind-action).


Updates `helm/kind-action` from 1.11.0 to 1.12.0
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](https://github.com/helm/kind-action/compare/v1.11.0...v1.12.0)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 11:28:50 +00:00
255 changed files with 8334 additions and 6676 deletions

View File

@@ -5,7 +5,6 @@ redirects:
usage/progressive-delivery: tutorials/istio-progressive-delivery.md
usage/ab-testing: tutorials/istio-ab-testing.md
usage/blue-green: tutorials/kubernetes-blue-green.md
usage/appmesh-progressive-delivery: tutorials/appmesh-progressive-delivery.md
usage/linkerd-progressive-delivery: tutorials/linkerd-progressive-delivery.md
usage/contour-progressive-delivery: tutorials/contour-progressive-delivery.md
usage/gloo-progressive-delivery: tutorials/gloo-progressive-delivery.md
@@ -13,7 +12,7 @@ redirects:
usage/skipper-progressive-delivery: tutorials/skipper-progressive-delivery.md
usage/crossover-progressive-delivery: tutorials/crossover-progressive-delivery.md
usage/traefik-progressive-delivery: tutorials/traefik-progressive-delivery.md
usage/osm-progressive-delivery: tutorials/osm-progressive-delivery.md
usage/kuma-progressive-delivery: tutorials/kuma-progressive-delivery.md
usage/gatewayapi-progressive-delivery: tutorials/gatewayapi-progressive-delivery.md
usage/apisix-progressive-delivery: tutorials/apisix-progressive-delivery.md
usage/knative-progressive-delivery: tutorials/knative-progressive-delivery.md

View File

@@ -19,18 +19,18 @@ jobs:
labels: ubuntu-latest-16-cores
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.23.x
go-version: 1.25.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Download modules
run: |
go mod download
go install golang.org/x/tools/cmd/goimports
go install golang.org/x/tools/cmd/goimports@latest
- name: Run linters
run: make fmt test-codegen
- name: Verify CRDs

View File

@@ -35,18 +35,19 @@ jobs:
- gatewayapi
- keda
- apisix
- knative
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Kubernetes
uses: helm/kind-action@v1.11.0
uses: helm/kind-action@v1.12.0
if: matrix.provider != 'skipper'
with:
version: v0.23.0
cluster_name: kind
node_image: kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e
- name: Setup Kubernetes for skipper
uses: helm/kind-action@v1.11.0
uses: helm/kind-action@v1.12.0
if: matrix.provider == 'skipper'
with:
version: v0.23.0

View File

@@ -12,7 +12,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Publish Helm charts
uses: stefanprodan/helm-gh-pages@v1.7.0
with:

View File

@@ -16,8 +16,8 @@ jobs:
id-token: write
packages: write
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3.7.0
- uses: actions/checkout@v5
- uses: sigstore/cosign-installer@v3.10.0
- name: Prepare
id: prep
run: |

View File

@@ -27,13 +27,13 @@ jobs:
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.23.x
go-version: 1.25.x
- uses: fluxcd/flux2/action@main
- uses: sigstore/cosign-installer@v3.7.0
- uses: sigstore/cosign-installer@v3.10.0
- 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 }}"

View File

@@ -17,7 +17,7 @@ jobs:
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@v3
with:
@@ -30,16 +30,16 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.23.x
go-version: 1.25.x
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4

View File

@@ -2,6 +2,117 @@
All notable changes to this project are documented in this file.
## 1.42.0
**Release date:** 2025-10-16
This release comes with enhancements to Gateway API support, new metrics capabilities, and various bug fixes.
Flagger now supports Gateway API v1.4.0 and adds CORS policy configuration for Gateway API HTTPRoutes.
For more information, please see the [Gateway API tutorial](https://docs.flagger.app/main/tutorials/gatewayapi-progressive-delivery#customising-the-httproute).
Session affinity support has been enhanced with [cookie attributes](https://docs.flagger.app/main/usage/deployment-strategies#configuring-additional-cookie-attributes)
configuration for better control over session management.
A new `.spec.service.unmanagedMetadata` field has been added to the Canary API to allow 3rd-party
controllers to set labels and annotations on the Kubernetes Services created by Flagger.
When running Flagger on Kubernetes 1.33 or later, users can now specify the traffic distribution
using the new `.spec.service.trafficDistribution` Canary field. Depending on the Kubernetes version
the traffic distribution can be set to `PreferClose`, `PreferSameZone` or `PreferSameNode`.
See the [Kubernetes Service docs](https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution) for more details.
This release is built with Go 1.25. The Kubernetes dependencies have been updated to 1.34,
and the Traefik API has been migrated from `traefik.containo.us` to `traefik.io`.
#### Improvements
- Update Gateway API to v1.4.0
[#1842](https://github.com/fluxcd/flagger/pull/1842)
- Add support for CORS policy to Gateway API
[#1843](https://github.com/fluxcd/flagger/pull/1843)
- Add support for setting traffic distribution
[#1844](https://github.com/fluxcd/flagger/pull/1844)
- Add count metrics for canary successes and failures
[#1812](https://github.com/fluxcd/flagger/pull/1812)
- Add support for cookie attributes in session affinity
[#1826](https://github.com/fluxcd/flagger/pull/1826)
- Add `unmanagedMetadata` to canary service specification
[#1823](https://github.com/fluxcd/flagger/pull/1823)
- Update dependencies to Kubernetes 1.34
[#1832](https://github.com/fluxcd/flagger/pull/1832)
- Build with Go 1.25
[#1832](https://github.com/fluxcd/flagger/pull/1832)
- Update Traefik API from traefik.containo.us to traefik.io
[#1835](https://github.com/fluxcd/flagger/pull/1835)
- Update GitOps install docs to latest Flux APIs
[#1845](https://github.com/fluxcd/flagger/pull/1845)
- loadtester: add pod security context
[#1803](https://github.com/fluxcd/flagger/pull/1803)
- Release loadtester 0.36.0
[#1846](https://github.com/fluxcd/flagger/pull/1846)
#### Fixes
- Fix: Gateway router should wait for accepted condition
[#1791](https://github.com/fluxcd/flagger/pull/1791)
- Fix: Send succeeded webhooks with correct phase
[#1792](https://github.com/fluxcd/flagger/pull/1792)
- Fix: Honor event webhook timeout
[#1797](https://github.com/fluxcd/flagger/pull/1797)
- Fix: Default namespace for cross-namespace ref validation
[#1828](https://github.com/fluxcd/flagger/pull/1828)
- Fix: APISIX E2E test
[#1831](https://github.com/fluxcd/flagger/pull/1831)
- Fix: Correct typo in AutoscalerReference type name
[#1739](https://github.com/fluxcd/flagger/pull/1739)
## 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

View File

@@ -1,4 +1,4 @@
ARG GO_VERSION=1.23
ARG GO_VERSION=1.25
ARG XX_VERSION=1.6.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
@@ -29,7 +29,7 @@ RUN xx-go build \
-ldflags "-s -w -X github.com/fluxcd/flagger/pkg/version.REVISION=${REVISON}" \
-a -o flagger ./cmd/flagger
FROM alpine:3.21
FROM alpine:3.22
RUN apk --no-cache add ca-certificates

View File

@@ -1,4 +1,4 @@
FROM golang:1.23-alpine AS builder
FROM golang:1.25-alpine AS builder
ARG TARGETPLATFORM
ARG TARGETARCH
@@ -6,11 +6,11 @@ ARG REVISION
RUN apk --no-cache add alpine-sdk perl curl bash tar
RUN HELM3_VERSION=3.16.3 && \
RUN HELM3_VERSION=3.19.0 && \
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
RUN KUBECTL_VERSION=v1.31.3 && \
RUN KUBECTL_VERSION=v1.34.1 && \
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" && \
chmod +x kubectl && mv kubectl /usr/local/bin/kubectl

View File

@@ -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.25
vet:
go vet ./...

View File

@@ -1,4 +1,4 @@
# flagger
# Flagger
[![release](https://img.shields.io/github/release/fluxcd/flagger/all.svg)](https://github.com/fluxcd/flagger/releases)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4783/badge)](https://bestpractices.coreinfrastructure.org/projects/4783)
@@ -16,39 +16,26 @@ by gradually shifting traffic to the new version while measuring metrics and run
Flagger implements several deployment strategies (Canary releases, A/B testing, Blue/Green mirroring)
and integrates with various Kubernetes ingress controllers, service mesh, and monitoring solutions.
Flagger is a [Cloud Native Computing Foundation](https://cncf.io/) project
Flagger is a [Cloud Native Computing Foundation](https://cncf.io/) graduated project
and part of the [Flux](https://fluxcd.io) family of GitOps tools.
### Documentation
Flagger documentation can be found at [fluxcd.io/flagger](https://fluxcd.io/flagger/).
The Flagger documentation can be found at [docs.flagger.app](https://docs.flagger.app/main).
* Install
* [Flagger install on Kubernetes](https://fluxcd.io/flagger/install/flagger-install-on-kubernetes)
* [Flagger Install with Flux](https://docs.flagger.app/main/install/flagger-install-with-flux)
* Usage
* [How it works](https://fluxcd.io/flagger/usage/how-it-works)
* [Deployment strategies](https://fluxcd.io/flagger/usage/deployment-strategies)
* [Metrics analysis](https://fluxcd.io/flagger/usage/metrics)
* [Webhooks](https://fluxcd.io/flagger/usage/webhooks)
* [Alerting](https://fluxcd.io/flagger/usage/alerting)
* [Monitoring](https://fluxcd.io/flagger/usage/monitoring)
* Tutorials
* [App Mesh](https://fluxcd.io/flagger/tutorials/appmesh-progressive-delivery)
* [Istio](https://fluxcd.io/flagger/tutorials/istio-progressive-delivery)
* [Linkerd](https://fluxcd.io/flagger/tutorials/linkerd-progressive-delivery)
* [Open Service Mesh (OSM)](https://dfluxcd.io/flagger/tutorials/osm-progressive-delivery)
* [Kuma Service Mesh](https://fluxcd.io/flagger/tutorials/kuma-progressive-delivery)
* [Contour](https://fluxcd.io/flagger/tutorials/contour-progressive-delivery)
* [Gloo](https://fluxcd.io/flagger/tutorials/gloo-progressive-delivery)
* [NGINX Ingress](https://fluxcd.io/flagger/tutorials/nginx-progressive-delivery)
* [Skipper](https://fluxcd.io/flagger/tutorials/skipper-progressive-delivery)
* [Traefik](https://fluxcd.io/flagger/tutorials/traefik-progressive-delivery)
* [Gateway API](https://fluxcd.io/flagger/tutorials/gatewayapi-progressive-delivery/)
* [Kubernetes Blue/Green](https://fluxcd.io/flagger/tutorials/kubernetes-blue-green)
* [How it works](https://docs.flagger.app/main/usage/how-it-works)
* [Deployment strategies](https://docs.flagger.app/main/usage/deployment-strategies)
* [Metrics analysis](https://docs.flagger.app/main/usage/metrics)
* [Webhooks](https://docs.flagger.app/main/usage/webhooks)
* [Alerting](https://docs.flagger.app/main/usage/alerting)
* [Monitoring](https://docs.flagger.app/main/usage/monitoring)
### Adopters
**Our list of production users has moved to <https://fluxcd.io/adopters/#flagger>**.
The list of production users can be found at [fluxcd.io/adopters/#flagger](https://fluxcd.io/adopters/#flagger).
If you are using Flagger, please
[submit a PR to add your organization](https://github.com/fluxcd/website/blob/main/data/adopters/2-flagger.yaml) to the list!
@@ -72,8 +59,8 @@ metadata:
namespace: test
spec:
# service mesh provider (optional)
# can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, contour, gloo, supergloo, traefik, osm
# for SMI TrafficSplit can be: smi:v1alpha1, smi:v1alpha2, smi:v1alpha3
# can be: kubernetes, istio, linkerd, kuma, knative, nginx, contour, gloo, traefik, skipper
# for Gateway API implementations: gatewayapi:v1 and gatewayapi:v1beta1
provider: istio
# deployment reference
targetRef:
@@ -178,23 +165,23 @@ spec:
name: on-call-msteams
```
For more details on how the canary analysis and promotion works please [read the docs](https://fluxcd.io/flagger/usage/how-it-works).
For more details on how the canary analysis and promotion works please [read the docs](https://docs.flagger.app/usage/how-it-works).
### Features
**Service Mesh**
| Feature | App Mesh | Istio | Linkerd | Kuma | OSM | Kubernetes CNI |
|--------------------------------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|
| Canary deployments (weighted traffic) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
| A/B testing (headers and cookies routing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Blue/Green deployments (traffic switch) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Blue/Green deployments (traffic mirroring) | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Webhooks (acceptance/load testing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Manual gating (approve/pause/resume) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Request success rate check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
| Request duration check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
| Custom metric checks | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Feature | Istio | Linkerd | Kuma | Knative | Kubernetes CNI |
|--------------------------------------------|--------------------|--------------------|--------------------|--------------------|--------------------|
| Canary deployments (weighted traffic) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
| A/B testing (headers and cookies routing) | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Blue/Green deployments (traffic switch) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: |
| Blue/Green deployments (traffic mirroring) | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Webhooks (acceptance/load testing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Manual gating (approve/pause/resume) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Request success rate check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: |
| Request duration check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: |
| Custom metric checks | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
**Ingress**
@@ -211,29 +198,26 @@ For more details on how the canary analysis and promotion works please [read the
**Networking Interface**
| Feature | Gateway API | SMI |
|-----------------------------------------------|--------------------|--------------------|
| Canary deployments (weighted traffic) | :heavy_check_mark: | :heavy_check_mark: |
| A/B testing (headers and cookies routing) | :heavy_check_mark: | :heavy_minus_sign: |
| Blue/Green deployments (traffic switch) | :heavy_check_mark: | :heavy_check_mark: |
| Blue/Green deployments (traffic mirrroring) | :heavy_minus_sign: | :heavy_minus_sign: |
| Webhooks (acceptance/load testing) | :heavy_check_mark: | :heavy_check_mark: |
| Manual gating (approve/pause/resume) | :heavy_check_mark: | :heavy_check_mark: |
| Request success rate check (L7 metric) | :heavy_minus_sign: | :heavy_minus_sign: |
| Request duration check (L7 metric) | :heavy_minus_sign: | :heavy_minus_sign: |
| Custom metric checks | :heavy_check_mark: | :heavy_check_mark: |
| Feature | Gateway API | SMI |
|--------------------------------------------|--------------------|--------------------|
| Canary deployments (weighted traffic) | :heavy_check_mark: | :heavy_check_mark: |
| Canary deployments with session affinity | :heavy_check_mark: | :heavy_minus_sign: |
| A/B testing (headers and cookies routing) | :heavy_check_mark: | :heavy_minus_sign: |
| Blue/Green deployments (traffic switch) | :heavy_check_mark: | :heavy_check_mark: |
| Blue/Green deployments (traffic mirroring) | :heavy_minus_sign: | :heavy_minus_sign: |
| Webhooks (acceptance/load testing) | :heavy_check_mark: | :heavy_check_mark: |
| Manual gating (approve/pause/resume) | :heavy_check_mark: | :heavy_check_mark: |
| Request success rate check (L7 metric) | :heavy_minus_sign: | :heavy_minus_sign: |
| Request duration check (L7 metric) | :heavy_minus_sign: | :heavy_minus_sign: |
| Custom metric checks | :heavy_check_mark: | :heavy_check_mark: |
For all [Gateway API](https://gateway-api.sigs.k8s.io/) implementations like
[Contour](https://projectcontour.io/guides/gateway-api/) or
[Istio](https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/)
and [SMI](https://smi-spec.io) compatible service mesh solutions like
[Nginx Service Mesh](https://docs.nginx.com/nginx-service-mesh/),
[Prometheus MetricTemplates](https://docs.flagger.app/usage/metrics#prometheus)
For all the [Gateway API](https://gateway-api.sigs.k8s.io/) compatible ingress controllers and service meshes,
the [Prometheus MetricTemplates](https://docs.flagger.app/usage/metrics#prometheus)
can be used to implement the request success rate and request duration checks.
### Roadmap
#### [GitOps Toolkit](https://github.com/fluxcd/flux2) compatibility
#### [GitOps Toolkit](https://fluxcd.io/flux/components/) compatibility
- Migrate Flagger to Kubernetes controller-runtime and [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
- Make the Canary status compatible with [kstatus](https://github.com/kubernetes-sigs/cli-utils)
@@ -242,8 +226,7 @@ can be used to implement the request success rate and request duration checks.
#### Integrations
- Add support for ingress controllers like HAProxy, ALB, and Apache APISIX
- Add support for Knative Serving
- Migrate Linkerd, Kuma and other service mesh integrations to Gateway API
### Contributing

View File

@@ -1,62 +0,0 @@
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: appmesh
progressDeadlineSeconds: 600
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
service:
port: 80
targetPort: 9898
meshName: global
retries:
attempts: 3
perTryTimeout: 5s
retryOn: "gateway-error,client-error,stream-error"
timeout: 35s
match:
- uri:
prefix: /
rewrite:
uri: /
analysis:
interval: 15s
threshold: 10
iterations: 10
match:
- headers:
x-canary:
exact: "insider"
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 30s
webhooks:
- name: conformance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 15s
metadata:
type: "bash"
cmd: "curl -sd 'test' http://podinfo-canary.test/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 1m -q 10 -c 2 -H 'X-Canary: insider' http://podinfo-canary.test/"

View File

@@ -1,59 +0,0 @@
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: appmesh
progressDeadlineSeconds: 600
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
service:
port: 80
targetPort: http
meshName: global
retries:
attempts: 3
perTryTimeout: 5s
retryOn: "gateway-error,client-error,stream-error"
timeout: 35s
match:
- uri:
prefix: /
rewrite:
uri: /
analysis:
interval: 15s
threshold: 10
maxWeight: 50
stepWeight: 5
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 30s
webhooks:
- name: conformance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 15s
metadata:
type: "bash"
cmd: "curl -sd 'test' http://podinfo-canary.test/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test/"

View File

@@ -1,42 +0,0 @@
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: osm
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 600
service:
port: 9898
targetPort: 9898
analysis:
interval: 15s
threshold: 10
stepWeights: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 30s
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 15s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"

View File

@@ -1,43 +0,0 @@
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: osm
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 600
service:
port: 9898
targetPort: 9898
analysis:
interval: 15s
threshold: 10
maxWeight: 50
stepWeight: 5
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 30s
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 15s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"

View File

@@ -80,7 +80,6 @@ spec:
type: object
required:
- targetRef
- service
- analysis
properties:
provider:
@@ -192,11 +191,21 @@ spec:
appProtocol:
description: Application protocol of the port
type: string
trafficDistribution:
description: Traffic distribution of the service
type: string
enum:
- PreferClose
- PreferSameZone
- PreferSameNode
targetPort:
description: Container target port name
x-kubernetes-int-or-string: true
portDiscovery:
description: Enable port dicovery
description: Enable port discovery
type: boolean
headless:
description: Headless if set to true, generates headless Kubernetes services.
type: boolean
timeout:
description: HTTP or gRPC request timeout
@@ -907,6 +916,18 @@ spec:
type: object
additionalProperties:
type: string
unmanagedMetadata:
description: UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
type: object
properties:
annotations:
type: array
items:
type: string
labels:
type: array
items:
type: string
skipAnalysis:
description: Skip analysis and promote canary
type: boolean
@@ -1153,10 +1174,35 @@ 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
domain:
description: Domain defines the host to which the cookie will be sent.
type: string
httpOnly:
description: HttpOnly forbids JavaScript from accessing the cookie, for example, through the Document.cookie property.
type: boolean
maxAge:
description: MaxAge indicates the number of seconds until the session affinity cookie will expire.
default: 86400
type: number
partitioned:
description: Partitioned indicates that the cookie should be stored using partitioned storage.
type: boolean
path:
description: Path indicates the path that must exist in the requested URL for the browser to send the Cookie header.
type: string
sameSite:
description: SameSite controls whether or not a cookie is sent with cross-site requests.
type: string
enum:
- Strict
- Lax
- None
secure:
description: "Secure indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost)"
type: boolean
status:
description: CanaryStatus defines the observed state of a canary.
type: object
@@ -1204,6 +1250,9 @@ spec:
sessionAffinityCookie:
description: Session affinity cookie of the current canary run
type: string
primarySessionAffinityCookie:
description: Primary session affinity cookie of the current canary run
type: string
previousSessionAffinityCookie:
description: Session affinity cookie of the previous canary run
type: string
@@ -1308,6 +1357,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

View File

@@ -22,7 +22,7 @@ spec:
serviceAccountName: flagger
containers:
- name: flagger
image: ghcr.io/fluxcd/flagger:1.40.0
image: ghcr.io/fluxcd/flagger:1.42.0
imagePullPolicy: IfNotPresent
ports:
- name: http

View File

@@ -1,7 +1,7 @@
apiVersion: v1
name: flagger
version: 1.40.0
appVersion: 1.40.0
version: 1.42.0
appVersion: 1.42.0
kubeVersion: ">=1.19.0-0"
engine: gotpl
description: Flagger is a progressive delivery operator for Kubernetes
@@ -19,7 +19,6 @@ keywords:
- appmesh
- linkerd
- kuma
- osm
- smi
- gloo
- contour

View File

@@ -59,15 +59,6 @@ $ helm upgrade -i flagger flagger/flagger \
```
To install Flagger for **Open Service Mesh** (requires OSM to have been installed with Prometheus):
```console
$ helm upgrade -i flagger flagger/flagger \
--namespace=osm-system \
--set meshProvider=osm \
--set metricsServer=http://osm-prometheus.osm-system.svc:7070
```
To install Flagger for **Kuma Service Mesh** (requires Kuma to have been installed with Prometheus):
```console
@@ -114,6 +105,15 @@ $ helm upgrade -i flagger flagger/flagger \
--set meshProvider=traefik
```
If you need to add labels to the flagger deployment or pods, you can pass the labels as parameters as shown below.
```console
helm upgrade -i flagger flagger/flagger \
<other parameters> \
--set podLabels.<labelName>=<labelValue> \
--set deploymentLabels.<labelName>=<labelValue>
```
The [configuration](#configuration) section lists the parameters that can be configured during installation.
## Uninstalling the Chart
@@ -186,6 +186,8 @@ The following tables lists the configurable parameters of the Flagger chart and
| `podDisruptionBudget.minAvailable` | The minimal number of available replicas that will be set in the PodDisruptionBudget | `1` |
| `noCrossNamespaceRefs` | If `true`, cross namespace references to custom resources will be disabled | `false` |
| `namespace` | When specified, Flagger will restrict itself to watching Canary objects from that namespace | `""` |
| `deploymentLabels` | Labels to add to Flagger deployment | `{}` |
| `podLabels` | Labels to add to pods of Flagger deployment | `{}` |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm upgrade`. For example,

View File

@@ -80,7 +80,6 @@ spec:
type: object
required:
- targetRef
- service
- analysis
properties:
provider:
@@ -192,11 +191,21 @@ spec:
appProtocol:
description: Application protocol of the port
type: string
trafficDistribution:
description: Traffic distribution of the service
type: string
enum:
- PreferClose
- PreferSameZone
- PreferSameNode
targetPort:
description: Container target port name
x-kubernetes-int-or-string: true
portDiscovery:
description: Enable port dicovery
description: Enable port discovery
type: boolean
headless:
description: Headless if set to true, generates headless Kubernetes services.
type: boolean
timeout:
description: HTTP or gRPC request timeout
@@ -907,6 +916,18 @@ spec:
type: object
additionalProperties:
type: string
unmanagedMetadata:
description: UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
type: object
properties:
annotations:
type: array
items:
type: string
labels:
type: array
items:
type: string
skipAnalysis:
description: Skip analysis and promote canary
type: boolean
@@ -1153,10 +1174,35 @@ 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
domain:
description: Domain defines the host to which the cookie will be sent.
type: string
httpOnly:
description: HttpOnly forbids JavaScript from accessing the cookie, for example, through the Document.cookie property.
type: boolean
maxAge:
description: MaxAge indicates the number of seconds until the session affinity cookie will expire.
default: 86400
type: number
partitioned:
description: Partitioned indicates that the cookie should be stored using partitioned storage.
type: boolean
path:
description: Path indicates the path that must exist in the requested URL for the browser to send the Cookie header.
type: string
sameSite:
description: SameSite controls whether or not a cookie is sent with cross-site requests.
type: string
enum:
- Strict
- Lax
- None
secure:
description: "Secure indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost)"
type: boolean
status:
description: CanaryStatus defines the observed state of a canary.
type: object
@@ -1204,6 +1250,9 @@ spec:
sessionAffinityCookie:
description: Session affinity cookie of the current canary run
type: string
primarySessionAffinityCookie:
description: Primary session affinity cookie of the current canary run
type: string
previousSessionAffinityCookie:
description: Session affinity cookie of the previous canary run
type: string
@@ -1308,6 +1357,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

View File

@@ -9,6 +9,11 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
{{- if .Values.deploymentLabels }}
{{- range $key, $value := .Values.deploymentLabels }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- with .Values.annotations }}
annotations:
{{- toYaml . | nindent 4 }}

View File

@@ -197,7 +197,7 @@ rules:
- patch
- delete
- apiGroups:
- traefik.containo.us
- traefik.io
resources:
- traefikservices
verbs:
@@ -276,6 +276,19 @@ rules:
- /version
verbs:
- get
- apiGroups:
- serving.knative.dev
resources:
- services
verbs:
- get
- update
- apiGroups:
- serving.knative.dev
resources:
- revisions
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding

View File

@@ -5,7 +5,7 @@
image:
repository: ghcr.io/fluxcd/flagger
tag: 1.40.0
tag: 1.42.0
pullPolicy: IfNotPresent
pullSecret:
@@ -32,7 +32,7 @@ serviceMonitor:
# Set labels for the ServiceMonitor, use this to define your scrape label for Prometheus Operator
# labels:
# accepted values are kubernetes, istio, linkerd, appmesh, contour, nginx, gloo, skipper, traefik, apisix, osm
# accepted values are kubernetes, istio, linkerd, appmesh, contour, nginx, gloo, skipper, traefik, apisix
meshProvider: ""
# single namespace restriction
@@ -195,8 +195,12 @@ podDisruptionBudget:
enabled: false
minAvailable: 1
# Additional labels to be added to pods
podLabels: {}
# Additional labels to be added to deployments
deploymentLabels: { }
noCrossNamespaceRefs: false
#Placeholder to supply additional volumes to the flagger pod

View File

@@ -1,7 +1,7 @@
apiVersion: v1
name: loadtester
version: 0.34.0
appVersion: 0.34.0
version: 0.36.0
appVersion: 0.36.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.
@@ -19,7 +19,6 @@ keywords:
- appmesh
- linkerd
- gloo
- osm
- smi
- gitops
- load testing

View File

@@ -69,8 +69,10 @@ The following tables lists the configurable parameters of the load tester chart
| `istio.tls.enabled` | Enable TLS in gateway ( TLS secrets should be in namespace ) | `false` |
| `istio.tls.httpsRedirect` | Redirect traffic to TLS port | `false` |
| `podPriorityClassName` | PriorityClass name for pod priority configuration | "" |
| `securityContext.enabled` | Add securityContext to container | "" |
| `securityContext.context` | securityContext to add | "" |
| `securityContext.enabled` | Add securityContext to container | `false` |
| `SecurityContext.context` | securityContext to add | "" |
| `podSecurityContext.enabled` | Add securityContext to pod | `false` |
| `podSecurityContext.context` | securityContext to add | "" |
| `podDisruptionBudget.enabled` | A PodDisruptionBudget will be created if `true` | `false` |
| `podDisruptionBudget.minAvailable` | The minimal number of available replicas that will be set in the PodDisruptionBudget | `1` |

View File

@@ -24,7 +24,7 @@ spec:
appmesh.k8s.aws/ports: "444"
openservicemesh.io/inbound-port-exclusion-list: "80, 8080"
{{- if .Values.podAnnotations }}
{{ toYaml .Values.podAnnotations | indent 8 }}
{{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
spec:
{{- if .Values.serviceAccountName }}
@@ -39,7 +39,7 @@ spec:
- name: {{ .Chart.Name }}
{{- if .Values.securityContext.enabled }}
securityContext:
{{ toYaml .Values.securityContext.context | indent 12 }}
{{- toYaml .Values.securityContext.context | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
@@ -102,3 +102,7 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.podSecurityContext.enabled }}
securityContext:
{{- toYaml .Values.podSecurityContext.context | nindent 12 }}
{{- end }}

View File

@@ -2,7 +2,7 @@ replicaCount: 1
image:
repository: ghcr.io/fluxcd/flagger-loadtester
tag: 0.34.0
tag: 0.36.0
pullPolicy: IfNotPresent
pullSecret:
@@ -91,6 +91,12 @@ securityContext:
runAsUser: 100
runAsGroup: 101
podSecurityContext:
enabled: false
context:
fsGroup: 101
fsGroupChangePolicy: "OnRootMismatch"
podDisruptionBudget:
enabled: false
minAvailable: 1

View File

@@ -51,6 +51,8 @@ import (
"github.com/fluxcd/flagger/pkg/server"
"github.com/fluxcd/flagger/pkg/signals"
"github.com/fluxcd/flagger/pkg/version"
knative "knative.dev/serving/pkg/client/clientset/versioned"
)
var (
@@ -110,7 +112,7 @@ func init() {
flag.BoolVar(&zapReplaceGlobals, "zap-replace-globals", false, "Whether to change the logging level of the global zap logger.")
flag.StringVar(&zapEncoding, "zap-encoding", "json", "Zap logger encoding.")
flag.StringVar(&namespace, "namespace", "", "Namespace that flagger would watch canary object.")
flag.StringVar(&meshProvider, "mesh-provider", "istio", "Service mesh provider, can be istio, linkerd, appmesh, contour, gloo, nginx, skipper, traefik, apisix, osm or kuma.")
flag.StringVar(&meshProvider, "mesh-provider", "istio", "Service mesh provider, can be istio, linkerd, appmesh, contour, knative, gloo, nginx, skipper, traefik, apisix, osm or kuma.")
flag.StringVar(&selectorLabels, "selector-labels", "app,name,app.kubernetes.io/name", "List of pod labels that Flagger uses to create pod selectors.")
flag.StringVar(&ingressAnnotationsPrefix, "ingress-annotations-prefix", "nginx.ingress.kubernetes.io", "Annotations prefix for NGINX ingresses.")
flag.StringVar(&ingressClass, "ingress-class", "", "Ingress class used for annotating HTTPProxy objects.")
@@ -166,6 +168,11 @@ func main() {
logger.Fatalf("Error building flagger clientset: %s", err.Error())
}
knativeClient, err := knative.NewForConfig(cfg)
if err != nil {
logger.Fatalf("Error building knative clientset: %s", err.Error())
}
// use a remote cluster for routing if a service mesh kubeconfig is specified
if kubeconfigServiceMesh == "" {
kubeconfigServiceMesh = kubeconfig
@@ -221,7 +228,7 @@ func main() {
setOwnerRefs = false
}
routerFactory := router.NewFactory(cfg, kubeClient, flaggerClient, ingressAnnotationsPrefix, ingressClass, logger, meshClient, setOwnerRefs)
routerFactory := router.NewFactory(cfg, kubeClient, flaggerClient, knativeClient, ingressAnnotationsPrefix, ingressClass, logger, meshClient, setOwnerRefs)
var configTracker canary.Tracker
if enableConfigTracking {
@@ -236,10 +243,11 @@ func main() {
includeLabelPrefixArray := strings.Split(includeLabelPrefix, ",")
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, labels, includeLabelPrefixArray, logger)
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, knativeClient, configTracker, labels, includeLabelPrefixArray, logger)
c := controller.NewController(
kubeClient,
knativeClient,
flaggerClient,
infos,
controlLoopInterval,

View File

@@ -29,7 +29,7 @@ import (
"github.com/fluxcd/flagger/pkg/signals"
)
var VERSION = "0.34.0"
var VERSION = "0.36.0"
var (
logLevel string
port string

View File

@@ -10,8 +10,7 @@ version in production by gradually shifting traffic to the new version while mea
and running conformance tests.
Flagger implements several deployment strategies (Canary releases, A/B testing, Blue/Green mirroring)
using a service mesh (App Mesh, Istio, Linkerd, Kuma, Open Service Mesh)
or an ingress controller (Contour, Gloo, NGINX, Skipper, Traefik, APISIX) for traffic routing.
using a service mesh or an ingress controller for traffic routing.
For release analysis, Flagger can query Prometheus, InfluxDB, Datadog, New Relic, CloudWatch, Stackdriver
or Graphite and for alerting it uses Slack, MS Teams, Discord and Rocket.
@@ -19,26 +18,23 @@ or Graphite and for alerting it uses Slack, MS Teams, Discord and Rocket.
Flagger can be configured with Kubernetes custom resources and is compatible with
any CI/CD solutions made for Kubernetes. Since Flagger is declarative and reacts to Kubernetes events,
it can be used in **GitOps** pipelines together with tools like [Flux](install/flagger-install-with-flux.md),
JenkinsX, Carvel, Argo, etc.
it can be used in **GitOps** pipelines together with tools like [Flux CD](install/flagger-install-with-flux.md).
Flagger is a [Cloud Native Computing Foundation](https://cncf.io/) project
Flagger is a [Cloud Native Computing Foundation](https://cncf.io/) graduated project
and part of [Flux](https://fluxcd.io) family of GitOps tools.
## Getting started
To get started with Flagger, choose one of the supported routing providers and
[install](install/flagger-install-on-kubernetes.md) Flagger with Helm or Kustomize.
[install](install/flagger-install-with-flux.md) Flagger with Flux CD.
After installing Flagger, you can follow one of these tutorials to get started:
**Service mesh tutorials**
* [Gateway API](tutorials/gatewayapi-progressive-delivery.md)
* [Istio](tutorials/istio-progressive-delivery.md)
* [Linkerd](tutorials/linkerd-progressive-delivery.md)
* [AWS App Mesh](tutorials/appmesh-progressive-delivery.md)
* [AWS App Mesh: Canary Deployment Using Flagger](https://www.eksworkshop.com/advanced/340_appmesh_flagger/)
* [Open Service Mesh](tutorials/osm-progressive-delivery.md)
* [Kuma](tutorials/kuma-progressive-delivery.md)
**Ingress controller tutorials**
@@ -50,11 +46,5 @@ After installing Flagger, you can follow one of these tutorials to get started:
* [Traefik](tutorials/traefik-progressive-delivery.md)
* [Apache APISIX](tutorials/apisix-progressive-delivery.md)
**Hands-on GitOps workshops**
* [Istio](https://github.com/stefanprodan/gitops-istio)
* [Linkerd](https://helm.workshop.flagger.dev)
* [AWS App Mesh](https://eks.handson.flagger.dev)
The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation,
please see our [Trademark Usage page](https://www.linuxfoundation.org/legal/trademark-usage).

View File

@@ -7,9 +7,6 @@
* [Flagger Install on Kubernetes](install/flagger-install-on-kubernetes.md)
* [Flagger Install with Flux](install/flagger-install-with-flux.md)
* [Flagger Install on GKE Istio](install/flagger-install-on-google-cloud.md)
* [Flagger Install on EKS App Mesh](install/flagger-install-on-eks-appmesh.md)
* [Flagger Install on Alibaba ServiceMesh](install/flagger-install-on-alibaba-servicemesh.md)
## Usage
@@ -22,19 +19,18 @@
## Tutorials
* [Gateway API Canary Deployments](tutorials/gatewayapi-progressive-delivery.md)
* [Istio Canary Deployments](tutorials/istio-progressive-delivery.md)
* [Istio A/B Testing](tutorials/istio-ab-testing.md)
* [Linkerd Canary Deployments](tutorials/linkerd-progressive-delivery.md)
* [App Mesh Canary Deployments](tutorials/appmesh-progressive-delivery.md)
* [Kuma Canary Deployments](tutorials/kuma-progressive-delivery.md)
* [Knative Canary Deployments](tutorials/knative-progressive-delivery.md)
* [Contour Canary Deployments](tutorials/contour-progressive-delivery.md)
* [Gloo Canary Deployments](tutorials/gloo-progressive-delivery.md)
* [NGINX Canary Deployments](tutorials/nginx-progressive-delivery.md)
* [Skipper Canary Deployments](tutorials/skipper-progressive-delivery.md)
* [Traefik Canary Deployments](tutorials/traefik-progressive-delivery.md)
* [Apache APISIX Canary Deployments](tutorials/apisix-progressive-delivery.md)
* [Open Service Mesh Deployments](tutorials/osm-progressive-delivery.md)
* [Kuma Canary Deployments](tutorials/kuma-progressive-delivery.md)
* [Gateway API Canary Deployments](tutorials/gatewayapi-progressive-delivery.md)
* [Blue/Green Deployments](tutorials/kubernetes-blue-green.md)
* [Canary analysis with Prometheus Operator](tutorials/prometheus-operator.md)
* [Canary analysis with KEDA ScaledObjects](tutorials/keda-scaledobject.md)

View File

@@ -8,17 +8,13 @@ Flagger is written in Go and uses Go modules for dependency management.
On your dev machine install the following tools:
* go >= 1.19
* git >;= 2.20
* bash >= 5.0
* make >= 3.81
* kubectl >= 1.22
* kustomize >= 4.4
* go >= 1.25
* kubectl >= 1.30
* kustomize >= 5.0
* helm >= 3.0
* docker >= 19.03
You'll also need a Kubernetes cluster for testing Flagger.
You can use Minikube, Kind, Docker desktop or any remote cluster (AKS/EKS/GKE/etc) Kubernetes version 1.22 or newer.
You can use Minikube, Kind, Docker desktop or any remote cluster (AKS/EKS/GKE/etc).
To start contributing to Flagger, fork the [repository](https://github.com/fluxcd/flagger) on GitHub.
@@ -195,7 +191,6 @@ docker build -t test/flagger:latest .
kind load docker-image test/flagger:latest
```
Run the Istio e2e tests:
```bash

View File

@@ -1,57 +0,0 @@
# Flagger Install on Alibaba ServiceMesh
This guide walks you through setting up Flagger on Alibaba ServiceMesh.
## Prerequisites
- Created an ACK([Alibabacloud Container Service for Kubernetes](https://cs.console.aliyun.com)) cluster instance.
- Create an ASM([Alibaba ServiceMesh](https://servicemesh.console.aliyun.com)) enterprise instance and add ACK cluster.
### Variables declaration
- `$ACK_CONFIG`: the kubeconfig file path of ACK, which be treated as`$HOME/.kube/config` in the rest of guide.
- `$MESH_CONFIG`: the kubeconfig file path of ASM.
### Enable Data-plane KubeAPI access in ASM
In the Alibaba Cloud Service Mesh (ASM) console, on the basic information page, make sure Data-plane KubeAPI access is enabled. When enabled, the Istio resources of the control plane can be managed through the Kubeconfig of the data plane cluster.
## Enable Prometheus
In the Alibaba Cloud Service Mesh (ASM) console, click Settings to enable the collection of Prometheus monitoring metrics. You can use the self-built Prometheus monitoring, or you can use the Alibaba Cloud ARMS Prometheus monitoring plug-in that has joined the ACK cluster, and use ARMS Prometheus to collect monitoring indicators.
## Install Flagger
Add Flagger Helm repository:
```bash
helm repo add flagger https://flagger.app
```
Install Flagger's Canary CRD:
```bash
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/v1.21.0/artifacts/flagger/crd.yaml
```
## Deploy Flagger for Istio
### Add data plane cluster to Alibaba Cloud Service Mesh (ASM)
In the Alibaba Cloud Service Mesh (ASM) console, click Cluster & Workload Management, select the Kubernetes cluster, select the target ACK cluster, and add it to ASM.
### Prometheus address
If you are using Alibaba Cloud Container Service for Kubernetes (ACK) ARMS Prometheus monitoring, replace {Region-ID} in the link below with your region ID, such as cn-hangzhou. {ACKID} is the ACK ID of the data plane cluster that you added to Alibaba Cloud Service Mesh (ASM). Visit the following links to query the public and intranet addresses monitored by ACK's ARMS Prometheus:
[https://arms.console.aliyun.com/#/promDetail/{Region-ID}/{ACK-ID}/setting](https://arms.console.aliyun.com/)
An example of an intranet address is as follows:
[http://{Region-ID}-intranet.arms.aliyuncs.com:9090/api/v1/prometheus/{Prometheus-ID}/{u-id}/{ACK-ID}/{Region-ID}](https://arms.console.aliyun.com/)
## Deploy Flagger
Replace the value of metricsServer with your Prometheus address.
```bash
helm upgrade -i flagger flagger/flagger \
--namespace=istio-system \
--set crd.create=false \
--set meshProvider=istio \
--set metricsServer=http://prometheus:9090
```

View File

@@ -1,151 +0,0 @@
# Flagger Install on EKS App Mesh
This guide walks you through setting up Flagger and AWS App Mesh on EKS.
## App Mesh
The App Mesh integration with EKS is made out of the following components:
* Kubernetes custom resources
* `mesh.appmesh.k8s.aws` defines a logical boundary for network traffic between the services
* `virtualnode.appmesh.k8s.aws` defines a logical pointer to a Kubernetes workload
* `virtualservice.appmesh.k8s.aws` defines the routing rules for a workload inside the mesh
* CRD controller - keeps the custom resources in sync with the App Mesh control plane
* Admission controller - injects the Envoy sidecar and assigns Kubernetes pods to App Mesh virtual nodes
* Telemetry service - Prometheus instance that collects and stores Envoy's metrics
## Create a Kubernetes cluster
In order to create an EKS cluster you can use [eksctl](https://eksctl.io).
Eksctl is an open source command-line utility made by Weaveworks in collaboration with Amazon.
On MacOS you can install eksctl with Homebrew:
```bash
brew tap weaveworks/tap
brew install weaveworks/tap/eksctl
```
Create an EKS cluster with:
```bash
eksctl create cluster --name=appmesh \
--region=us-west-2 \
--nodes 3 \
--node-volume-size=120 \
--appmesh-access
```
The above command will create a two nodes cluster with
App Mesh [IAM policy](https://docs.aws.amazon.com/app-mesh/latest/userguide/MESH_IAM_user_policies.html)
attached to the EKS node instance role.
Verify the install with:
```bash
kubectl get nodes
```
## Install Helm
Install the [Helm](https://docs.helm.sh/using_helm/#installing-helm) v3 command-line tool:
```text
brew install helm
```
Add the EKS repository to Helm:
```bash
helm repo add eks https://aws.github.io/eks-charts
```
## Enable horizontal pod auto-scaling
Install the Horizontal Pod Autoscaler (HPA) metrics provider:
```bash
helm upgrade -i metrics-server stable/metrics-server \
--namespace kube-system \
--set args[0]=--kubelet-preferred-address-types=InternalIP
```
After a minute, the metrics API should report CPU and memory usage for pods. You can very the metrics API with:
```bash
kubectl -n kube-system top pods
```
## Install the App Mesh components
Install the App Mesh CRDs:
```bash
kubectl apply -k github.com/aws/eks-charts/stable/appmesh-controller//crds?ref=master
```
Create the `appmesh-system` namespace:
```bash
kubectl create ns appmesh-system
```
Install the App Mesh controller:
```bash
helm upgrade -i appmesh-controller eks/appmesh-controller \
--wait --namespace appmesh-system
```
In order to collect the App Mesh metrics that Flagger needs to run the canary analysis,
you'll need to setup a Prometheus instance to scrape the Envoy sidecars.
Install the App Mesh Prometheus:
```bash
helm upgrade -i appmesh-prometheus eks/appmesh-prometheus \
--wait --namespace appmesh-system
```
## Install Flagger
Add Flagger Helm repository:
```bash
helm repo add flagger https://flagger.app
```
Install Flagger's Canary CRD:
```yaml
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
```
Deploy Flagger in the _**appmesh-system**_ namespace:
```bash
helm upgrade -i flagger flagger/flagger \
--namespace=appmesh-system \
--set crd.create=false \
--set meshProvider=appmesh:v1beta2 \
--set metricsServer=http://appmesh-prometheus:9090
```
## Install Grafana
Deploy App Mesh Grafana that comes with a dashboard for monitoring Flagger's canary releases:
```bash
helm upgrade -i appmesh-grafana eks/appmesh-grafana \
--namespace appmesh-system
```
You can access Grafana using port forwarding:
```bash
kubectl -n appmesh-system port-forward svc/appmesh-grafana 3000:3000
```
Now that you have Flagger running, you can try the
[App Mesh canary deployments tutorial](https://docs.flagger.app/usage/appmesh-progressive-delivery).

View File

@@ -1,400 +0,0 @@
# Flagger Install on GKE Istio
This guide walks you through setting up Flagger and Istio on Google Kubernetes Engine.
![GKE Cluster Overview](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-gke-istio.png)
## Prerequisites
You will be creating a cluster on Googles Kubernetes Engine \(GKE\), if you dont have an account you can sign up [here](https://cloud.google.com/free/) for free credits.
Login into Google Cloud, create a project and enable billing for it.
Install the [gcloud](https://cloud.google.com/sdk/) command line utility and configure your project with `gcloud init`.
Set the default project \(replace `PROJECT_ID` with your own project\):
```text
gcloud config set project PROJECT_ID
```
Set the default compute region and zone:
```text
gcloud config set compute/region us-central1
gcloud config set compute/zone us-central1-a
```
Enable the Kubernetes and Cloud DNS services for your project:
```text
gcloud services enable container.googleapis.com
gcloud services enable dns.googleapis.com
```
Install the kubectl command-line tool:
```text
gcloud components install kubectl
```
## GKE cluster setup
Create a cluster with the Istio add-on:
```bash
K8S_VERSION=$(gcloud container get-server-config --format=json \
| jq -r '.validMasterVersions[0]')
gcloud beta container clusters create istio \
--cluster-version=${K8S_VERSION} \
--zone=us-central1-a \
--num-nodes=2 \
--machine-type=n1-highcpu-4 \
--preemptible \
--no-enable-cloud-logging \
--no-enable-cloud-monitoring \
--disk-size=30 \
--enable-autorepair \
--addons=HorizontalPodAutoscaling,Istio \
--istio-config=auth=MTLS_PERMISSIVE
```
The above command will create a default node pool consisting of two `n1-highcpu-4` \(vCPU: 4, RAM 3.60GB, DISK: 30GB\) preemptible VMs. Preemptible VMs are up to 80% cheaper than regular instances and are terminated and replaced after a maximum of 24 hours.
Set up credentials for `kubectl`:
```bash
gcloud container clusters get-credentials istio
```
Create a cluster admin role binding:
```bash
kubectl create clusterrolebinding "cluster-admin-$(whoami)" \
--clusterrole=cluster-admin \
--user="$(gcloud config get-value core/account)"
```
Validate your setup with:
```bash
kubectl -n istio-system get svc
```
In a couple of seconds GCP should allocate an external IP to the `istio-ingressgateway` service.
## Cloud DNS setup
You will need an internet domain and access to the registrar to change the name servers to Google Cloud DNS.
Create a managed zone named `istio` in Cloud DNS \(replace `example.com` with your domain\):
```bash
gcloud dns managed-zones create \
--dns-name="example.com." \
--description="Istio zone" "istio"
```
Look up your zone's name servers:
```bash
gcloud dns managed-zones describe istio
```
Update your registrar's name server records with the records returned by the above command.
Wait for the name servers to change \(replace `example.com` with your domain\):
```bash
watch dig +short NS example.com
```
Create a static IP address named `istio-gateway` using the Istio ingress IP:
```bash
export GATEWAY_IP=$(kubectl -n istio-system get svc/istio-ingressgateway -ojson \
| jq -r .status.loadBalancer.ingress[0].ip)
gcloud compute addresses create istio-gateway --addresses ${GATEWAY_IP} --region us-central1
```
Create the following DNS records \(replace `example.com` with your domain\):
```bash
DOMAIN="example.com"
gcloud dns record-sets transaction start --zone=istio
gcloud dns record-sets transaction add --zone=istio \
--name="${DOMAIN}" --ttl=300 --type=A ${GATEWAY_IP}
gcloud dns record-sets transaction add --zone=istio \
--name="www.${DOMAIN}" --ttl=300 --type=A ${GATEWAY_IP}
gcloud dns record-sets transaction add --zone=istio \
--name="*.${DOMAIN}" --ttl=300 --type=A ${GATEWAY_IP}
gcloud dns record-sets transaction execute --zone istio
```
Verify that the wildcard DNS is working \(replace `example.com` with your domain\):
```bash
watch host test.example.com
```
## Install Helm
Install the [Helm](https://docs.helm.sh/using_helm/#installing-helm) command-line tool:
```text
brew install kubernetes-helm
```
Create a service account and a cluster role binding for Tiller:
```bash
kubectl -n kube-system create sa tiller
kubectl create clusterrolebinding tiller-cluster-rule \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:tiller
```
Deploy Tiller in the `kube-system` namespace:
```bash
helm init --service-account tiller
```
You should consider using SSL between Helm and Tiller, for more information on securing your Helm installation see [docs.helm.sh](https://docs.helm.sh/using_helm/#securing-your-helm-installation).
## Install cert-manager
Jetstack's [cert-manager](https://github.com/jetstack/cert-manager) is a Kubernetes operator that automatically creates and manages TLS certs issued by Lets Encrypt.
You'll be using cert-manager to provision a wildcard certificate for the Istio ingress gateway.
Install cert-manager's CRDs:
```bash
CERT_REPO=https://raw.githubusercontent.com/jetstack/cert-manager
kubectl apply -f ${CERT_REPO}/release-0.10/deploy/manifests/00-crds.yaml
```
Create the cert-manager namespace and disable resource validation:
```bash
kubectl create namespace cert-manager
kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true
```
Install cert-manager with Helm:
```bash
helm repo add jetstack https://charts.jetstack.io && \
helm repo update && \
helm upgrade -i cert-manager \
--namespace cert-manager \
--version v0.10.0 \
jetstack/cert-manager
```
## Istio Gateway TLS setup
![Istio Let&apos;s Encrypt](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/istio-cert-manager-gke.png)
Create a generic Istio Gateway to expose services outside the mesh on HTTPS:
```bash
REPO=https://raw.githubusercontent.com/fluxcd/flagger/main
kubectl apply -f ${REPO}/artifacts/gke/istio-gateway.yaml
```
Create a service account with Cloud DNS admin role \(replace `my-gcp-project` with your project ID\):
```bash
GCP_PROJECT=my-gcp-project
gcloud iam service-accounts create dns-admin \
--display-name=dns-admin \
--project=${GCP_PROJECT}
gcloud iam service-accounts keys create ./gcp-dns-admin.json \
--iam-account=dns-admin@${GCP_PROJECT}.iam.gserviceaccount.com \
--project=${GCP_PROJECT}
gcloud projects add-iam-policy-binding ${GCP_PROJECT} \
--member=serviceAccount:dns-admin@${GCP_PROJECT}.iam.gserviceaccount.com \
--role=roles/dns.admin
```
Create a Kubernetes secret with the GCP Cloud DNS admin key:
```bash
kubectl create secret generic cert-manager-credentials \
--from-file=./gcp-dns-admin.json \
--namespace=istio-system
```
Create a letsencrypt issuer for CloudDNS \(replace `email@example.com` with a valid email address and `my-gcp-project`with your project ID\):
```yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
name: letsencrypt-prod
namespace: istio-system
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: email@example.com
privateKeySecretRef:
name: letsencrypt-prod
dns01:
providers:
- name: cloud-dns
clouddns:
serviceAccountSecretRef:
name: cert-manager-credentials
key: gcp-dns-admin.json
project: my-gcp-project
```
Save the above resource as letsencrypt-issuer.yaml and then apply it:
```text
kubectl apply -f ./letsencrypt-issuer.yaml
```
Create a wildcard certificate \(replace `example.com` with your domain\):
```yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: istio-gateway
namespace: istio-system
spec:
secretName: istio-ingressgateway-certs
issuerRef:
name: letsencrypt-prod
commonName: "*.example.com"
acme:
config:
- dns01:
provider: cloud-dns
domains:
- "*.example.com"
- "example.com"
```
Save the above resource as istio-gateway-cert.yaml and then apply it:
```text
kubectl apply -f ./istio-gateway-cert.yaml
```
In a couple of seconds cert-manager should fetch a wildcard certificate from letsencrypt.org:
```text
kubectl -n istio-system describe certificate istio-gateway
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CertIssued 1m52s cert-manager Certificate issued successfully
```
Recreate Istio ingress gateway pods:
```bash
kubectl -n istio-system get pods -l istio=ingressgateway
```
Note that Istio gateway doesn't reload the certificates from the TLS secret on cert-manager renewal. Since the GKE cluster is made out of preemptible VMs the gateway pods will be replaced once every 24h, if your not using preemptible nodes then you need to manually delete the gateway pods every two months before the certificate expires.
## Install Prometheus
The GKE Istio add-on does not include a Prometheus instance that scrapes the Istio telemetry service. Because Flagger uses the Istio HTTP metrics to run the canary analysis you have to deploy the following Prometheus configuration that's similar to the one that comes with the official Istio Helm chart.
Find the GKE Istio version with:
```bash
kubectl -n istio-system get deploy istio-pilot -oyaml | grep image:
```
Install Prometheus in istio-system namespace:
```bash
kubectl -n istio-system apply -f \
https://storage.googleapis.com/gke-release/istio/release/1.0.6-gke.3/patches/install-prometheus.yaml
```
## Install Flagger and Grafana
Add Flagger Helm repository:
```bash
helm repo add flagger https://flagger.app
```
Install Flagger's Canary CRD:
```yaml
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
```
Deploy Flagger in the `istio-system` namespace with Slack notifications enabled:
```bash
helm upgrade -i flagger flagger/flagger \
--namespace=istio-system \
--set crd.create=false \
--set metricsServer=http://prometheus.istio-system:9090 \
--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
--set slack.channel=general \
--set slack.user=flagger
```
Deploy Grafana in the `istio-system` namespace:
```bash
helm upgrade -i flagger-grafana flagger/grafana \
--namespace=istio-system \
--set url=http://prometheus.istio-system:9090 \
--set user=admin \
--set password=replace-me
```
Expose Grafana through the public gateway by creating a virtual service \(replace `example.com` with your domain\):
```yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: grafana
namespace: istio-system
spec:
hosts:
- "grafana.example.com"
gateways:
- istio-system/public-gateway
http:
- route:
- destination:
host: flagger-grafana
```
Save the above resource as grafana-virtual-service.yaml and then apply it:
```bash
kubectl apply -f ./grafana-virtual-service.yaml
```
Navigate to `http://grafana.example.com` in your browser and you should be redirected to the HTTPS version.

View File

@@ -1,10 +1,8 @@
# Flagger Install on Kubernetes
This guide walks you through setting up Flagger on a Kubernetes cluster with Helm v3 or Kustomize.
This guide walks you through setting up Flagger on a Kubernetes cluster with Helm or Kubectl.
## Prerequisites
Flagger requires a Kubernetes cluster **v1.16** or newer.
See the [Flux install guide](flagger-install-with-flux.md) for installing Flagger and keeping it up to date the GitOps way.
## Install Flagger with Helm
@@ -61,29 +59,18 @@ helm upgrade -i flagger flagger/flagger \
--set metricsServer=http://linkerd-prometheus:9090
```
Deploy Flagger for App Mesh:
```bash
helm upgrade -i flagger flagger/flagger \
--namespace=appmesh-system \
--set crd.create=false \
--set meshProvider=appmesh \
--set metricsServer=http://appmesh-prometheus:9090
```
Deploy Flagger for **Open Service Mesh (OSM)** (requires OSM to have been installed with Prometheus):
If you need to add labels to the flagger deployment or pods, you can pass the labels as parameters as shown below.
```console
$ helm upgrade -i flagger flagger/flagger \
--namespace=osm-system \
--set crd.create=false \
--set meshProvider=osm \
--set metricsServer=http://osm-prometheus.osm-system.svc:7070
helm upgrade -i flagger flagger/flagger \
<other parameters> \
--set podLabels.<labelName>=<labelValue> \
--set deploymentLabels.<labelName>=<labelValue>
```
You can install Flagger in any namespace as long as it can talk to the Prometheus service on port 9090.
For ingress controllers, the install instructions are:
For ingress controllers, the installation instructions are:
* [Contour](https://docs.flagger.app/tutorials/contour-progressive-delivery)
* [Gloo](https://docs.flagger.app/tutorials/gloo-progressive-delivery)
@@ -92,20 +79,6 @@ For ingress controllers, the install instructions are:
* [Traefik](https://docs.flagger.app/tutorials/traefik-progressive-delivery)
* [APISIX](https://docs.flagger.app/tutorials/apisix-progressive-delivery)
You can use the helm template command and apply the generated yaml with kubectl:
```bash
# generate
helm fetch --untar --untardir . flagger/flagger &&
helm template flagger ./flagger \
--namespace=istio-system \
--set metricsServer=http://prometheus.istio-system:9090 \
> flagger.yaml
# apply
kubectl apply -f flagger.yaml
```
To uninstall the Flagger release with Helm run:
```text
@@ -117,7 +90,7 @@ The command removes all the Kubernetes components associated with the chart and
> **Note** that on uninstall the Canary CRD will not be removed. Deleting the CRD will make Kubernetes
> remove all the objects owned by Flagger like Istio virtual services, Kubernetes deployments and ClusterIP services.
If you want to remove all the objects created by Flagger you have delete the Canary CRD with kubectl:
If you want to remove all the objects created by Flagger you have to delete the Canary CRD with kubectl:
```text
kubectl delete crd canaries.flagger.app
@@ -137,73 +110,18 @@ helm upgrade -i flagger-grafana flagger/grafana \
--set password=change-me
```
Or use helm template command and apply the generated yaml with kubectl:
```bash
# generate
helm fetch --untar --untardir . flagger/grafana &&
helm template flagger-grafana ./grafana \
--namespace=istio-system \
> flagger-grafana.yaml
# apply
kubectl apply -f flagger-grafana.yaml
```
You can access Grafana using port forwarding:
```bash
kubectl -n istio-system port-forward svc/flagger-grafana 3000:80
```
## Install Flagger with Kustomize
## Install Flagger with Kubectl
As an alternative to Helm, Flagger can be installed with Kustomize **3.5.0** or newer.
**Service mesh specific installers**
Install Flagger for Istio:
Install Flagger and Prometheus using the Kustomize overlay from the GitHub repository:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/istio?ref=main | kubectl apply -f -
```
Install Flagger for AWS App Mesh:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/appmesh?ref=main | kubectl apply -f -
```
This deploys Flagger and sets the metrics server URL to App Mesh's Prometheus instance.
Install Flagger for Linkerd:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/linkerd?ref=main | kubectl apply -f -
```
This deploys Flagger in the `linkerd` namespace and sets the metrics server URL to Linkerd's Prometheus instance.
Install Flagger for Open Service Mesh:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/osm?ref=main | kubectl apply -f -
```
This deploys Flagger in the `osm-system` namespace and sets the metrics server URL to OSM's Prometheus instance.
If you want to install a specific Flagger release, add the version number to the URL:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/linkerd?ref=v1.0.0 | kubectl apply -f -
```
**Generic installer**
Install Flagger and Prometheus for Contour, Gloo, NGINX, Skipper, APISIX or Traefik ingress:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/kubernetes?ref=main | kubectl apply -f -
kubectl apply -k https://github.com/fluxcd/flagger/kustomize/kubernetes?ref=main
```
This deploys Flagger and Prometheus in the `flagger-system` namespace,
@@ -212,20 +130,6 @@ sets the metrics server URL to `http://flagger-prometheus.flagger-system:9090` a
The Prometheus instance has a two hours data retention and is configured to scrape all pods in your cluster
that have the `prometheus.io/scrape: "true"` annotation.
To target a different provider you can specify it in the canary custom resource:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: app
namespace: test
spec:
# can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, gloo, traefik, osm, apisix
# use the kubernetes provider for Blue/Green style deployments
provider: nginx
```
**Customized installer**
Create a kustomization file using Flagger as base and patch the container args:

View File

@@ -35,46 +35,43 @@ metadata:
toolkit.fluxcd.io/tenant: sre-team
```
Define a Flux `HelmRepository` that points to where the Flagger Helm charts are stored:
Define a Flux `OCIRepository` that points to where the Flagger Helm charts are stored:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: flagger
namespace: flagger-system
spec:
interval: 1h
url: oci://ghcr.io/fluxcd/charts
type: oci
url: oci://ghcr.io/fluxcd/charts/flagger
layerSelector:
mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
operation: copy
ref:
semver: "1.x" # update to the latest version
```
Define a Flux `HelmRelease` that verifies and installs Flagger's latest version on the cluster:
```yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: flagger
namespace: flagger-system
spec:
interval: 1h
interval: 12h
releaseName: flagger
install: # override existing Flagger CRDs
crds: CreateReplace
upgrade: # update Flagger CRDs
crds: CreateReplace
chart:
spec:
chart: flagger
version: 1.x # update Flagger to the latest minor version
interval: 6h # scan for new versions every six hours
sourceRef:
kind: HelmRepository
name: flagger
verify: # verify the chart signature with Cosign keyless
provider: cosign
chartRef:
kind: OCIRepository
name: flagger
values:
nodeSelector:
kubernetes.io/os: linux
@@ -88,7 +85,7 @@ After Flux reconciles the changes on your cluster, you can check if Flagger got
```console
$ helm list -n flagger-system
NAME NAMESPACE REVISION STATUS CHART APP VERSION
flagger flagger-system 1 deployed flagger-1.23.0 1.23.0
flagger flagger-system 1 deployed flagger-1.42.0 1.42.0
```
To uninstall Flagger, delete the `flagger.yaml` from your repository, then Flux will uninstall
@@ -108,7 +105,7 @@ Define a Flux `OCIRepository` that points to where the Flagger Kustomize overlay
```yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: flagger-loadtester
@@ -117,21 +114,20 @@ spec:
interval: 6h # scan for new versions every six hours
url: oci://ghcr.io/fluxcd/flagger-manifests
ref:
semver: 1.x # update to the latest version
verify: # verify the artifact signature with Cosign keyless
provider: cosign
semver: "*" # update to the latest version
```
Define a Flux `Kustomization` that deploys the Flagger load tester to the `apps` namespace:
```yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flagger-loadtester
namespace: apps
spec:
targetNamespace: apps
interval: 6h
wait: true
timeout: 5m
@@ -140,7 +136,6 @@ spec:
kind: OCIRepository
name: flagger-loadtester
path: ./tester
targetNamespace: apps
```
Copy the above manifests into a file called `flagger-loadtester.yaml`, place the YAML file

View File

@@ -1,434 +0,0 @@
# App Mesh Canary Deployments
This guide shows you how to use App Mesh and Flagger to automate canary deployments.
You'll need an EKS cluster (Kubernetes >= 1.16) configured with App Mesh,
you can find the installation guide [here](https://docs.flagger.app/install/flagger-install-on-eks-appmesh).
## Bootstrap
Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),
then creates a series of objects (Kubernetes deployments, ClusterIP services,
App Mesh virtual nodes and services).
These objects expose the application on the mesh and drive the canary analysis and promotion.
The only App Mesh object you need to create by yourself is the mesh resource.
Create a mesh called `global`:
```bash
cat << EOF | kubectl apply -f -
apiVersion: appmesh.k8s.aws/v1beta2
kind: Mesh
metadata:
name: global
spec:
namespaceSelector:
matchLabels:
appmesh.k8s.aws/sidecarInjectorWebhook: enabled
EOF
```
Create a test namespace with App Mesh sidecar injection enabled:
```bash
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
appmesh.k8s.aws/sidecarInjectorWebhook: enabled
EOF
```
Create a deployment and a horizontal pod autoscaler:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/podinfo?ref=main
```
Deploy the load testing service to generate traffic during the canary analysis:
```bash
helm upgrade -i flagger-loadtester flagger/loadtester \
--namespace=test \
--set appmesh.enabled=true \
--set "appmesh.backends[0]=podinfo" \
--set "appmesh.backends[1]=podinfo-canary"
```
Create a canary definition:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
annotations:
# Enable Envoy access logging to stdout.
appmesh.flagger.app/accesslog: enabled
name: podinfo
namespace: test
spec:
# App Mesh API reference
provider: appmesh:v1beta2
# deployment reference
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
service:
# container port
port: 9898
# App Mesh ingress timeout (optional)
timeout: 15s
# App Mesh retry policy (optional)
retries:
attempts: 3
perTryTimeout: 5s
retryOn: "gateway-error,client-error,stream-error"
# App Mesh URI settings
match:
- uri:
prefix: /
rewrite:
uri: /
# define the canary analysis timing and KPIs
analysis:
# schedule interval (default 60s)
interval: 1m
# max number of failed metric checks before rollback
threshold: 5
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 5
# App Mesh Prometheus checks
metrics:
- name: request-success-rate
# minimum req success rate (non 5xx responses)
# percentage (0-100)
thresholdRange:
min: 99
interval: 1m
- name: request-duration
# maximum req duration P99
# milliseconds
thresholdRange:
max: 500
interval: 30s
# testing (optional)
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
```
Save the above resource as podinfo-canary.yaml and then apply it:
```bash
kubectl apply -f ./podinfo-canary.yaml
```
After a couple of seconds Flagger will create the canary objects:
```bash
# applied
deployment.apps/podinfo
horizontalpodautoscaler.autoscaling/podinfo
canary.flagger.app/podinfo
# generated Kubernetes objects
deployment.apps/podinfo-primary
horizontalpodautoscaler.autoscaling/podinfo-primary
service/podinfo
service/podinfo-canary
service/podinfo-primary
# generated App Mesh objects
virtualnode.appmesh.k8s.aws/podinfo-canary
virtualnode.appmesh.k8s.aws/podinfo-primary
virtualrouter.appmesh.k8s.aws/podinfo
virtualrouter.appmesh.k8s.aws/podinfo-canary
virtualservice.appmesh.k8s.aws/podinfo
virtualservice.appmesh.k8s.aws/podinfo-canary
```
After the bootstrap, the podinfo deployment will be scaled to zero and the traffic to `podinfo.test`
will be routed to the primary pods.
During the canary analysis, the `podinfo-canary.test` address can be used to target directly the canary pods.
App Mesh blocks all egress traffic by default.
If your application needs to call another service, you have to create an App Mesh virtual service for it
and add the virtual service name to the backend list.
```yaml
service:
port: 9898
backends:
- backend1
- arn:aws:appmesh:eu-west-1:12345678910:mesh/my-mesh/virtualService/backend2
```
## Setup App Mesh Gateway (optional)
In order to expose the podinfo app outside the mesh you can use the App Mesh Gateway.
Deploy the App Mesh Gateway behind an AWS NLB:
```bash
helm upgrade -i appmesh-gateway eks/appmesh-gateway \
--namespace test
```
Find the gateway public address:
```bash
export URL="http://$(kubectl -n test get svc/appmesh-gateway -ojson | jq -r ".status.loadBalancer.ingress[].hostname")"
echo $URL
```
Wait for the NLB to become active:
```bash
watch curl -sS $URL
```
Create a gateway route that points to the podinfo virtual service:
```yaml
cat << EOF | kubectl apply -f -
apiVersion: appmesh.k8s.aws/v1beta2
kind: GatewayRoute
metadata:
name: podinfo
namespace: test
spec:
httpRoute:
match:
prefix: "/"
action:
target:
virtualService:
virtualServiceRef:
name: podinfo
EOF
```
Open your browser and navigate to the ingress address to access podinfo UI.
## Automated canary promotion
A canary deployment is triggered by changes in any of the following objects:
* Deployment PodSpec (container image, command, ports, env, resources, etc)
* ConfigMaps and Secrets mounted as volumes or mapped to environment variables
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 0
Phase: Succeeded
Events:
New revision detected! Scaling up podinfo.test
Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Advance podinfo.test canary weight 20
Advance podinfo.test canary weight 25
Advance podinfo.test canary weight 30
Advance podinfo.test canary weight 35
Advance podinfo.test canary weight 40
Advance podinfo.test canary weight 45
Advance podinfo.test canary weight 50
Copying podinfo.test template spec to podinfo-primary.test
Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Routing all traffic to primary
Promotion completed! Scaling down podinfo.test
```
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.
**Note** that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.
During the analysis the canarys progress can be monitored with Grafana.
The App Mesh dashboard URL is
[http://localhost:3000/d/flagger-appmesh/appmesh-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo](http://localhost:3000/d/flagger-appmesh/appmesh-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo).
![App Mesh Canary Dashboard](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/flagger-grafana-appmesh.png)
You can monitor all canaries with:
```bash
watch kubectl get canaries --all-namespaces
NAMESPACE NAME STATUS WEIGHT
test podinfo Progressing 15
prod frontend Succeeded 0
prod backend Failed 0
```
If youve enabled the Slack notifications, you should receive the following messages:
![Flagger Slack Notifications](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/slack-canary-notifications.png)
## Automated rollback
During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.
Trigger a canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.2
```
Exec into the load tester pod with:
```bash
kubectl -n test exec -it deploy/flagger-loadtester bash
```
Generate HTTP 500 errors:
```bash
hey -z 1m -c 5 -q 5 http://podinfo-canary.test:9898/status/500
```
Generate latency:
```bash
watch -n 1 curl http://podinfo-canary.test:9898/delay/1
```
When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,
the canary is scaled to zero and the rollout is marked as failed.
```text
kubectl -n appmesh-system logs deploy/flagger -f | jq .msg
New revision detected! progressing canary analysis for podinfo.test
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Halt podinfo.test advancement success rate 69.17% < 99%
Halt podinfo.test advancement success rate 61.39% < 99%
Halt podinfo.test advancement success rate 55.06% < 99%
Halt podinfo.test advancement request duration 1.20s > 0.5s
Halt podinfo.test advancement request duration 1.45s > 0.5s
Rolling back podinfo.test failed checks threshold reached 5
Canary failed! Scaling down podinfo.test
```
If youve enabled the Slack notifications, youll receive a message if the progress deadline is exceeded,
or if the analysis reached the maximum number of failed checks:
![Flagger Slack Notifications](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/slack-canary-failed.png)
## 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.
![Flagger A/B Testing Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-abtest-steps.png)
Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:
```yaml
analysis:
interval: 1m
threshold: 5
iterations: 10
match:
- headers:
x-canary:
exact: "insider"
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
metadata:
cmd: "hey -z 1m -q 10 -c 2 -H 'X-Canary: insider' http://podinfo.test:9898/"
```
The above configuration will run an analysis for ten minutes targeting users that have a `X-Canary: insider` header.
You can also use a HTTP cookie, to target all users with a `canary` cookie set to `insider` the match condition should be:
```yaml
match:
- headers:
cookie:
regex: "^(.*?;)?(canary=insider)(;.*)?$"
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
metadata:
cmd: "hey -z 1m -q 10 -c 2 -H 'Cookie: canary=insider' http://podinfo.test:9898/"
```
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.3
```
Flagger detects that the deployment revision changed and starts the A/B test:
```text
kubectl -n appmesh-system logs deploy/flagger -f | jq .msg
New revision detected! progressing canary analysis for podinfo.test
Advance podinfo.test canary iteration 1/10
Advance podinfo.test canary iteration 2/10
Advance podinfo.test canary iteration 3/10
Advance podinfo.test canary iteration 4/10
Advance podinfo.test canary iteration 5/10
Advance podinfo.test canary iteration 6/10
Advance podinfo.test canary iteration 7/10
Advance podinfo.test canary iteration 8/10
Advance podinfo.test canary iteration 9/10
Advance podinfo.test canary iteration 10/10
Copying podinfo.test template spec to podinfo-primary.test
Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Routing all traffic to primary
Promotion completed! Scaling down podinfo.test
```
The above procedure can be extended with
[custom metrics](../usage/metrics.md) checks,
[webhooks](../usage/webhooks.md),
[manual promotion](../usage/webhooks.md#manual-gating) approval and
[Slack or MS Teams](../usage/alerting.md) notifications.

View File

@@ -1,347 +0,0 @@
# Canaries with Helm charts and GitOps
This guide shows you how to package a web app into a Helm chart, trigger canary deployments on Helm upgrade and automate the chart release process with Weave Flux.
## Packaging
You'll be using the [podinfo](https://github.com/stefanprodan/k8s-podinfo) chart. This chart packages a web app made with Go, it's configuration, a horizontal pod autoscaler \(HPA\) and the canary configuration file.
```text
├── Chart.yaml
├── README.md
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── canary.yaml
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── service.yaml
│ └── tests
│ ├── test-config.yaml
│ └── test-pod.yaml
└── values.yaml
```
You can find the chart source [here](https://github.com/stefanprodan/flagger/tree/master/charts/podinfo).
## Install
Create a test namespace with Istio sidecar injection enabled:
```bash
export REPO=https://raw.githubusercontent.com/fluxcd/flagger/main
kubectl apply -f ${REPO}/artifacts/namespaces/test.yaml
```
Add Flagger Helm repository:
```bash
helm repo add flagger https://flagger.app
```
Install podinfo with the release name `frontend` \(replace `example.com` with your own domain\):
```bash
helm upgrade -i frontend flagger/podinfo \
--namespace test \
--set nameOverride=frontend \
--set backend=http://backend.test:9898/echo \
--set canary.enabled=true \
--set canary.istioIngress.enabled=true \
--set canary.istioIngress.gateway=istio-system/public-gateway \
--set canary.istioIngress.host=frontend.istio.example.com
```
Flagger takes a Kubernetes deployment and a horizontal pod autoscaler \(HPA\), then creates a series of objects \(Kubernetes deployments, ClusterIP services and Istio virtual services\). These objects expose the application on the mesh and drive the canary analysis and promotion.
```bash
# generated by Helm
configmap/frontend
deployment.apps/frontend
horizontalpodautoscaler.autoscaling/frontend
canary.flagger.app/frontend
# generated by Flagger
configmap/frontend-primary
deployment.apps/frontend-primary
horizontalpodautoscaler.autoscaling/frontend-primary
service/frontend
service/frontend-canary
service/frontend-primary
virtualservice.networking.istio.io/frontend
```
When the `frontend-primary` deployment comes online, Flagger will route all traffic to the primary pods and scale to zero the `frontend` deployment.
Open your browser and navigate to the frontend URL:
![Podinfo Frontend](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/demo-frontend.png)
Now let's install the `backend` release without exposing it outside the mesh:
```bash
helm upgrade -i backend flagger/podinfo \
--namespace test \
--set nameOverride=backend \
--set canary.enabled=true \
--set canary.istioIngress.enabled=false
```
Check if Flagger has successfully deployed the canaries:
```text
kubectl -n test get canaries
NAME STATUS WEIGHT LASTTRANSITIONTIME
backend Initialized 0 2019-02-12T18:53:18Z
frontend Initialized 0 2019-02-12T17:50:50Z
```
Click on the ping button in the `frontend` UI to trigger a HTTP POST request that will reach the `backend` app:
![Jaeger Tracing](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/demo-frontend-jaeger.png)
We'll use the `/echo` endpoint \(same as the one the ping button calls\) to generate load on both apps during a canary deployment.
## Upgrade
First let's install a load testing service that will generate traffic during analysis:
```bash
helm upgrade -i flagger-loadtester flagger/loadtester \
--namespace=test
```
Install Flagger's helm test runner in the `kube-system` using `tiller` service account:
```bash
helm upgrade -i flagger-helmtester flagger/loadtester \
--namespace=kube-system \
--set serviceAccountName=tiller
```
Enable the load and helm tester and deploy a new `frontend` version:
```bash
helm upgrade -i frontend flagger/podinfo/ \
--namespace test \
--reuse-values \
--set canary.loadtest.enabled=true \
--set canary.helmtest.enabled=true \
--set image.tag=3.1.1
```
Flagger detects that the deployment revision changed and starts the canary analysis:
```text
kubectl -n istio-system logs deployment/flagger -f | jq .msg
New revision detected! Scaling up frontend.test
Halt advancement frontend.test waiting for rollout to finish: 0 of 2 updated replicas are available
Starting canary analysis for frontend.test
Pre-rollout check helm test passed
Advance frontend.test canary weight 5
Advance frontend.test canary weight 10
Advance frontend.test canary weight 15
Advance frontend.test canary weight 20
Advance frontend.test canary weight 25
Advance frontend.test canary weight 30
Advance frontend.test canary weight 35
Advance frontend.test canary weight 40
Advance frontend.test canary weight 45
Advance frontend.test canary weight 50
Copying frontend.test template spec to frontend-primary.test
Halt advancement frontend-primary.test waiting for rollout to finish: 1 old replicas are pending termination
Promotion completed! Scaling down frontend.test
```
You can monitor the canary deployment with Grafana. Open the Flagger dashboard, select `test` from the namespace dropdown, `frontend-primary` from the primary dropdown and `frontend` from the canary dropdown.
![Flagger Grafana Dashboard](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/demo-frontend-dashboard.png)
Now trigger a canary deployment for the `backend` app, but this time you'll change a value in the configmap:
```bash
helm upgrade -i backend flagger/podinfo/ \
--namespace test \
--reuse-values \
--set canary.loadtest.enabled=true \
--set canary.helmtest.enabled=true \
--set httpServer.timeout=25s
```
Generate HTTP 500 errors:
```bash
kubectl -n test exec -it flagger-loadtester-xxx-yyy sh
watch curl http://backend-canary:9898/status/500
```
Generate latency:
```bash
kubectl -n test exec -it flagger-loadtester-xxx-yyy sh
watch curl http://backend-canary:9898/delay/1
```
Flagger detects the config map change and starts a canary analysis. Flagger will pause the advancement when the HTTP success rate drops under 99% or when the average request duration in the last minute is over 500ms:
```text
kubectl -n test describe canary backend
Events:
ConfigMap backend has changed
New revision detected! Scaling up backend.test
Starting canary analysis for backend.test
Advance backend.test canary weight 5
Advance backend.test canary weight 10
Advance backend.test canary weight 15
Advance backend.test canary weight 20
Advance backend.test canary weight 25
Advance backend.test canary weight 30
Advance backend.test canary weight 35
Halt backend.test advancement success rate 62.50% < 99%
Halt backend.test advancement success rate 88.24% < 99%
Advance backend.test canary weight 40
Advance backend.test canary weight 45
Halt backend.test advancement request duration 2.415s > 500ms
Halt backend.test advancement request duration 2.42s > 500ms
Advance backend.test canary weight 50
ConfigMap backend-primary synced
Copying backend.test template spec to backend-primary.test
Promotion completed! Scaling down backend.test
```
![Flagger Grafana Dashboard](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/demo-backend-dashboard.png)
If the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.
```bash
kubectl -n test get canary
NAME STATUS WEIGHT LASTTRANSITIONTIME
backend Succeeded 0 2019-02-12T19:33:11Z
frontend Failed 0 2019-02-12T19:47:20Z
```
If you've enabled the Slack notifications, you'll receive an alert with the reason why the `backend` promotion failed.
## GitOps automation
Instead of using Helm CLI from a CI tool to perform the install and upgrade, you could use a Git based approach. GitOps is a way to do Continuous Delivery, it works by using Git as a source of truth for declarative infrastructure and workloads. In the [GitOps model](https://www.weave.works/technologies/gitops/), any change to production must be committed in source control prior to being applied on the cluster. This way rollback and audit logs are provided by Git.
![Helm GitOps Canary Deployment](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-flux-gitops.png)
In order to apply the GitOps pipeline model to Flagger canary deployments you'll need a Git repository with your workloads definitions in YAML format, a container registry where your CI system pushes immutable images and an operator that synchronizes the Git repo with the cluster state.
Create a git repository with the following content:
```text
├── namespaces
│ └── test.yaml
└── releases
└── test
├── backend.yaml
├── frontend.yaml
├── loadtester.yaml
└── helmtester.yaml
```
Define the `frontend` release using Flux `HelmRelease` custom resource:
```yaml
apiVersion: flux.weave.works/v1beta1
kind: HelmRelease
metadata:
name: frontend
namespace: test
annotations:
fluxcd.io/automated: "true"
filter.fluxcd.io/chart-image: semver:~3.1
spec:
releaseName: frontend
chart:
git: https://github.com/weaveowrks/flagger
ref: master
path: charts/podinfo
values:
image:
repository: stefanprodan/podinfo
tag: 3.1.0
backend: http://backend-podinfo:9898/echo
canary:
enabled: true
istioIngress:
enabled: true
gateway: istio-system/public-gateway
host: frontend.istio.example.com
loadtest:
enabled: true
helmtest:
enabled: true
```
In the `chart` section I've defined the release source by specifying the Helm repository \(hosted on GitHub Pages\), chart name and version. In the `values` section I've overwritten the defaults set in values.yaml.
With the `fluxcd.io` annotations I instruct Flux to automate this release. When an image tag in the sem ver range of `3.1.0 - 3.1.99` is pushed to Docker Hub, Flux will upgrade the Helm release and from there Flagger will pick up the change and start a canary deployment.
Install [Flux](https://github.com/fluxcd/flux) and its [Helm Operator](https://github.com/fluxcd/helm-operator) by specifying your Git repo URL:
```bash
helm repo add fluxcd https://charts.fluxcd.io
helm install --name flux \
--set git.url=git@github.com:<USERNAME>/<REPOSITORY> \
--namespace fluxcd \
fluxcd/flux
helm upgrade -i helm-operator fluxcd/helm-operator \
--namespace fluxcd \
--set git.ssh.secretName=flux-git-deploy
```
At startup Flux generates a SSH key and logs the public key. Find the SSH public key with:
```bash
kubectl -n fluxcd logs deployment/flux | grep identity.pub | cut -d '"' -f2
```
In order to sync your cluster state with Git you need to copy the public key and create a deploy key with write access on your GitHub repository.
Open GitHub, navigate to your fork, go to _Setting &gt; Deploy keys_ click on _Add deploy key_, check _Allow write access_, paste the Flux public key and click _Add key_.
After a couple of seconds Flux will apply the Kubernetes resources from Git and Flagger will launch the `frontend` and `backend` apps.
A CI/CD pipeline for the `frontend` release could look like this:
* cut a release from the master branch of the podinfo code repo with the git tag `3.1.1`
* CI builds the image and pushes the `podinfo:6.0.1` image to the container registry
* Flux scans the registry and updates the Helm release `image.tag` to `3.1.1`
* Flux commits and push the change to the cluster repo
* Flux applies the updated Helm release on the cluster
* Flux Helm Operator picks up the change and calls Tiller to upgrade the release
* Flagger detects a revision change and scales up the `frontend` deployment
* Flagger runs the helm test before routing traffic to the canary service
* Flagger starts the load test and runs the canary analysis
* Based on the analysis result the canary deployment is promoted to production or rolled back
* Flagger sends a Slack or MS Teams notification with the canary result
If the canary fails, fix the bug, do another patch release eg `3.1.2` and the whole process will run again.
A canary deployment can fail due to any of the following reasons:
* the container image can't be downloaded
* the deployment replica set is stuck for more then ten minutes \(eg. due to a container crash loop\)
* the webhooks \(acceptance tests, helm tests, load tests, etc\) are returning a non 2xx response
* the HTTP success rate \(non 5xx responses\) metric drops under the threshold
* the HTTP average duration metric goes over the threshold
* the Istio telemetry service is unable to collect traffic metrics
* the metrics server \(Prometheus\) can't be reached
If you want to find out more about managing Helm releases with Flux here are two in-depth guides: [flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example) and [gitops-istio](https://github.com/stefanprodan/gitops-istio).

View File

@@ -2,17 +2,19 @@
This guide shows you how to use [Gateway API](https://gateway-api.sigs.k8s.io/) and Flagger to automate canary deployments and A/B testing.
![Flagger Canary Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-gatewayapi-canary.png)
![Flagger Gateway API Integration](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-gatewayapi-canary.png)
## Prerequisites
Flagger requires a Kubernetes cluster **v1.19** or newer and any mesh/ingress that implements the `v1beta1` or the `v1` version of Gateway API.
Flagger requires an ingress controller or service mesh that implements the Gateway API **HTTPRoute** (`v1` or `v1beta1`).
We'll be using Istio for the sake of this tutorial, but you can use any other implementation.
Install the Gateway API CRDs
Install the Gateway API CRDs:
```bash
kubectl apply -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.0.0"
# Suggestion: Change v1.4.0 in to the latest Gateway API version
kubectl apply --server-side -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.4.0"
```
Install Istio:
@@ -20,8 +22,8 @@ Install Istio:
```bash
istioctl install --set profile=minimal -y
# Suggestion: Please change release-1.20 in below command, to your real istio version.
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/prometheus.yaml
# Suggestion: Change release-1.27 in to the latest Istio version
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.27/samples/addons/prometheus.yaml
```
Install Flagger in the `flagger-system` namespace:
@@ -37,9 +39,6 @@ helm upgrade -i flagger flagger/flagger \
--set metricsServer=http://prometheus.istio-system:9090
```
> Note: The above installation sets the mesh provider to be `gatewayapi:v1`. If your Gateway API implementation uses the `v1beta1` CRDs, then
set the `--meshProvider` value to `gatewayapi:v1beta1`.
Create a namespace for the `Gateway`:
```bash
@@ -49,7 +48,7 @@ kubectl create ns istio-ingress
Create a `Gateway` that configures load balancing, traffic ACL, etc:
```yaml
apiVersion: gateway.networking.k8s.io/v1beta1
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
@@ -68,7 +67,9 @@ spec:
## Bootstrap
Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler \(HPA\), then creates a series of objects \(Kubernetes deployments, ClusterIP services, HTTPRoutes for the Gateway\). These objects expose the application inside the mesh and drive the canary analysis and promotion.
Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler \(HPA\),
then creates a series of objects \(Kubernetes deployments, ClusterIP services, HTTPRoutes for the Gateway\).
These objects expose the application inside the mesh and drive the canary analysis and promotion.
Create a test namespace:
@@ -88,7 +89,9 @@ Deploy the load testing service to generate traffic during the canary analysis:
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref=main
```
Create metric templates targeting the Prometheus server in the `flagger-system` namespace. The PromQL queries below are meant for `Envoy`, but you can [change it to your ingress/mesh provider](https://docs.flagger.app/faq#metrics) accordingly.
Create metric templates targeting the Prometheus server in the `flagger-system` namespace.
The PromQL queries below are meant for `Envoy`,
but you can [change it to your ingress/mesh provider](https://docs.flagger.app/faq#metrics) accordingly.
```yaml
apiVersion: flagger.app/v1beta1
@@ -152,7 +155,7 @@ Save the above resource as metric-templates.yaml and then apply it:
kubectl apply -f metric-templates.yaml
```
Create a canary custom resource \(replace "www.example.com" with your own domain\):
Create a Canary custom resource \(replace "www.example.com" with your own domain\):
```yaml
apiVersion: flagger.app/v1beta1
@@ -237,7 +240,8 @@ Save the above resource as podinfo-canary.yaml and then apply it:
kubectl apply -f ./podinfo-canary.yaml
```
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary. The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every minute.
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.
The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every minute.
After a couple of seconds Flagger will create the canary objects:
@@ -266,22 +270,27 @@ export ADDRESS="$(kubectl -n istio-ingress get svc/gateway-istio -ojson \
echo $ADDRESS
```
Configure your DNS server with a CNAME record \(AWS\) or A record \(GKE/AKS/DOKS\) and point a domain e.g. `www.example.com` to the LB address.
Configure your DNS server with a CNAME record \(AWS\) or A record \(GKE/AKS/DOKS\) and
point a domain e.g. `www.example.com` to the LB address.
Now you can access the podinfo UI using your domain address.
Note that you should be using HTTPS when exposing production workloads on internet. You can obtain free TLS certs from Let's Encrypt, read this
[guide](https://github.com/stefanprodan/istio-gke) on how to configure cert-manager to secure Istio with TLS certificates.
Note that you should be using HTTPS when exposing production workloads on internet.
If you're using a local cluster you can port forward to the Envoy LoadBalancer service:
If you're using a local cluster via kind/k3s you can port forward the Envoy LoadBalancer service:
```bash
kubectl port-forward -n istio-ingress svc/gateway-istio 8080:80
```
Now you can access podinfo via `curl -H "Host: www.example.com" localhost:8080`
Now you can access podinfo via `curl -H "Host: www.example.com" localhost:8080`.
## Automated canary promotion
With the application bootstrapped, Flagger will continuously monitor the deployment for changes.
When a new revision is detected, Flagger will start a canary analysis and gradually shift traffic to the new version.
![Flagger Canary Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-canary-steps.png)
Trigger a canary deployment by updating the container image:
```bash
@@ -319,7 +328,8 @@ Events:
Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test
```
**Note** that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.
**Note** that if you apply new changes to the deployment during the canary analysis,
Flagger will restart the analysis.
A canary deployment is triggered by changes in any of the following objects:
@@ -327,7 +337,8 @@ A canary deployment is triggered by changes in any of the following objects:
* ConfigMaps mounted as volumes or mapped to environment variables
* Secrets mounted as volumes or mapped to environment variables
You can monitor how Flagger progressively changes the weights of the HTTPRoute object that is attahed to the Gateway with:
You can monitor how Flagger progressively changes the weights of
the HTTPRoute object that is attached to the Gateway with:
```bash
watch kubectl get httproute -n test podinfo -o=jsonpath='{.spec.rules}'
@@ -339,9 +350,9 @@ You can monitor all canaries with:
watch kubectl get canaries --all-namespaces
NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME
test podinfo Progressing 15 2022-01-16T14:05:07Z
prod frontend Succeeded 0 2022-01-15T16:15:07Z
prod backend Failed 0 2022-01-14T17:05:07Z
test podinfo Progressing 15 2025-10-16T14:05:07Z
prod frontend Succeeded 0 2025-10-15T16:15:07Z
prod backend Failed 0 2025-10-14T17:05:07Z
```
## Automated rollback
@@ -373,7 +384,8 @@ Generate latency:
watch curl http://podinfo-canary:9898/delay/1
```
When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.
When the number of failed checks reaches the canary analysis threshold,
the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.
```text
kubectl -n test describe canary/podinfo
@@ -398,13 +410,142 @@ Events:
Warning Synced 1m flagger Canary failed! Scaling down podinfo.test
```
## 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 and cookies to target a certain segment of your users.
![Flagger A/B Testing Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-abtest-steps.png)
Create a canary custom resource \(replace "www.example.com" with your own domain\):
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
# deployment reference
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
service:
# service port number
port: 9898
# container port number or name (optional)
targetPort: 9898
# Gateway API HTTPRoute host names
hosts:
- www.example.com
# Reference to the Gateway that the generated HTTPRoute would attach to.
gatewayRefs:
- name: gateway
namespace: istio-ingress
analysis:
# schedule interval (default 60s)
interval: 1m
# total number of iterations
iterations: 10
# max number of failed iterations before rollback
threshold: 2
# canary match condition
match:
- headers:
user-agent:
regex: ".*Firefox.*"
- headers:
cookie:
regex: "^(.*?;)?(type=insider)(;.*)?$"
metrics:
- name: error-rate
# max error rate (5xx responses)
# percentage (0-100)
templateRef:
name: error-rate
namespace: flagger-system
thresholdRange:
max: 1
interval: 1m
- name: latency
templateRef:
name: latency
namespace: flagger-system
# seconds
thresholdRange:
max: 0.5
interval: 30s
# testing (optional)
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 2m -q 10 -c 2 -host www.example.com -H 'Cookie: type=insider' http://gateway-istio.istio-ingress/"
```
The above configuration will run an analysis for ten minutes targeting those users that
have an insider cookie or are using Firefox as a browser.
Save the above resource as podinfo-ab-canary.yaml and then apply it:
```bash
kubectl apply -f ./podinfo-ab-canary.yaml
```
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:6.0.3
```
Flagger detects that the deployment revision changed and starts a new rollout:
```text
kubectl -n test describe canary/podinfo
Status:
Failed Checks: 0
Phase: Succeeded
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger New revision detected podinfo.test
Normal Synced 3m flagger Scaling up podinfo.test
Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10
Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10
Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10
Normal Synced 2m flagger Advance podinfo.test canary iteration 4/10
Normal Synced 2m flagger Advance podinfo.test canary iteration 5/10
Normal Synced 1m flagger Advance podinfo.test canary iteration 6/10
Normal Synced 1m flagger Advance podinfo.test canary iteration 7/10
Normal Synced 55s flagger Advance podinfo.test canary iteration 8/10
Normal Synced 45s flagger Advance podinfo.test canary iteration 9/10
Normal Synced 35s flagger Advance podinfo.test canary iteration 10/10
Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test
Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test
```
## Session Affinity
While Flagger can perform weighted routing and A/B testing individually, with Gateway API it can combine the two leading to a Canary
release with session affinity.
While Flagger can perform weighted routing and A/B testing individually,
with Gateway API it can combine the two leading to a Canary release with session affinity.
For more information you can read the [deployment strategies docs](../usage/deployment-strategies.md#canary-release-with-session-affinity).
> **Note:** The implementation must have support for the [`ResponseHeaderModifier`](https://github.com/kubernetes-sigs/gateway-api/blob/3d22aa5a08413222cb79e6b2e245870360434614/apis/v1beta1/httproute_types.go#L651) API.
> **Note:** Session Affinity requires a Gateway API implementation that supports
> the [`ResponseHeaderModifier`](https://gateway-api.sigs.k8s.io/guides/http-header-modifier/) API.
Create a canary custom resource \(replace www.example.com with your own domain\):
@@ -478,13 +619,6 @@ spec:
interval: 30s
# testing (optional)
webhooks:
- name: smoke-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 15s
metadata:
type: bash
cmd: "curl -sd 'anon' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
@@ -509,145 +643,17 @@ 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.
# 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.
![Flagger A/B Testing Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-abtest-steps.png)
Create a canary custom resource \(replace "www.example.com" with your own domain\):
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
# deployment reference
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
service:
# service port number
port: 9898
# container port number or name (optional)
targetPort: 9898
# Gateway API HTTPRoute host names
hosts:
- www.example.com
# Reference to the Gateway that the generated HTTPRoute would attach to.
gatewayRefs:
- name: gateway
namespace: istio-ingress
analysis:
# schedule interval (default 60s)
interval: 1m
# max number of failed metric checks before rollback
threshold: 5
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 10
metrics:
- name: error-rate
# max error rate (5xx responses)
# percentage (0-100)
templateRef:
name: error-rate
namespace: flagger-system
thresholdRange:
max: 1
interval: 1m
- name: latency
templateRef:
name: latency
namespace: flagger-system
# seconds
thresholdRange:
max: 0.5
interval: 30s
# testing (optional)
webhooks:
- name: smoke-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 15s
metadata:
type: bash
cmd: "curl -sd 'anon' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 2m -q 10 -c 2 -host www.example.com -H 'X-Canary: insider' http://gateway-istio.istio-ingress/"
```
The above configuration will run an analysis for ten minutes targeting those users that have an insider cookie.
Save the above resource as podinfo-ab-canary.yaml and then apply it:
```bash
kubectl apply -f ./podinfo-ab-canary.yaml
```
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:6.0.3
```
Flagger detects that the deployment revision changed and starts a new rollout:
```text
kubectl -n test describe canary/podinfo
Status:
Failed Checks: 0
Phase: Succeeded
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger New revision detected podinfo.test
Normal Synced 3m flagger Scaling up podinfo.test
Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10
Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10
Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10
Normal Synced 2m flagger Advance podinfo.test canary iteration 4/10
Normal Synced 2m flagger Advance podinfo.test canary iteration 5/10
Normal Synced 1m flagger Advance podinfo.test canary iteration 6/10
Normal Synced 1m flagger Advance podinfo.test canary iteration 7/10
Normal Synced 55s flagger Advance podinfo.test canary iteration 8/10
Normal Synced 45s flagger Advance podinfo.test canary iteration 9/10
Normal Synced 35s flagger Advance podinfo.test canary iteration 10/10
Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test
Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test
```
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).
## Traffic mirroring
![Flagger Canary Traffic Shadowing](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-canary-traffic-mirroring.png)
For applications that perform read operations, Flagger can be configured to do B/G tests with traffic mirroring.
Gateway API traffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service.
The response from the primary is sent back to the user and the response from the canary is discarded.
Metrics are collected on both requests so that the deployment will only proceed if the canary metrics are within the threshold values.
Note that mirroring should be used for requests that are **idempotent** or capable of being processed twice \(once by the primary and once by the canary\).
> **Note:** Traffic mirroring requires a Gateway API implementation that supports
> the [`RequestMirror`](https://gateway-api.sigs.k8s.io/guides/http-request-mirroring/) filter.
You can enable mirroring by replacing `stepWeight` with `iterations` and by setting `analysis.mirror` to `true`:
@@ -702,26 +708,139 @@ spec:
cmd: "hey -z 2m -q 10 -c 2 -host www.example.com http://gateway-istio.istio-ingress/"
```
With the above configuration, Flagger will run a canary release with the following steps:
Gateway API traffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service.
The response from the primary is sent back to the user and the response from the canary is discarded.
* detect new revision \(deployment spec, secrets or configmaps changes\)
* scale from zero the canary deployment
* wait for the HPA to set the canary minimum replicas
* check canary pods health
* run the acceptance tests
* abort the canary release if tests fail
* start the load tests
* mirror 100% of the traffic from primary to canary
* check request success rate and request duration every minute
* abort the canary release if the metrics check failure threshold is reached
* stop traffic mirroring after the number of iterations is reached
* route live traffic to the canary pods
* promote the canary \(update the primary secrets, configmaps and deployment spec\)
* wait for the primary deployment rollout to finish
* wait for the HPA to set the primary minimum replicas
* check primary pods health
* switch live traffic back to primary
* scale to zero the canary
* send notification with the canary analysis result
Metrics are collected on both requests so that the deployment will only proceed if the canary metrics are within the threshold values.
The above procedures can be extended with [custom metrics](../usage/metrics.md) checks, [webhooks](../usage/webhooks.md), [manual promotion](../usage/webhooks.md#manual-gating) approval and [Slack or MS Teams](../usage/alerting.md) notifications.
## Customising the HTTPRoute
Besides the `hosts` and `gatewayRefs` fields, you can customize the generated HTTPRoute with various options
exposed under the `spec.service` field of the Canary.
### Header Manipulation
You can configure request and response header manipulation using the `spec.service.headers` field of the Canary.
> **Note:** Header manipulation requires a Gateway API implementation that supports
> the [`RequestHeaderModifier`](https://gateway-api.sigs.k8s.io/guides/http-header-modifier/) and [`ResponseHeaderModifier`](https://gateway-api.sigs.k8s.io/guides/http-header-modifier/) filters.
Example configuration:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
service:
headers:
request:
add:
x-custom-header: "custom-value"
set:
x-api-version: "v1"
remove:
- x-debug-header
response:
add:
x-frame-options: "DENY"
x-content-type-options: "nosniff"
set:
cache-control: "no-cache"
remove:
- x-powered-by
```
### URL Rewriting
You can configure URL rewriting using the `spec.service.rewrite` field of the Canary to modify the path or hostname of requests.
> **Note:** URL rewriting requires a Gateway API implementation that supports
> the [`URLRewrite`](https://gateway-api.sigs.k8s.io/guides/http-redirect-rewrite/?h=urlrewrite#rewrites) filter.
Example configuration:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
service:
rewrite:
# Rewrite the URI path
uri: "/v2/api"
# Optionally specify the rewrite type: "ReplaceFullPath" or "ReplacePrefixMatch"
# Defaults to "ReplaceFullPath" if not specified
type: "ReplaceFullPath"
# Rewrite the hostname/authority header
authority: "api.example.com"
```
The `type` field determines how the URI rewriting is performed:
- **ReplaceFullPath**: Replaces the entire request path with the specified `uri` value
- **ReplacePrefixMatch**: Replaces only the prefix portion of the path that was matched
Example with prefix replacement:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
service:
rewrite:
uri: "/api/v2"
type: "ReplacePrefixMatch"
```
When using `ReplacePrefixMatch`, if a request comes to `/old/path`, and the HTTPRoute matches the prefix `/old`,
the request will be rewritten to `/api/v2/path`.
### CORS Policy
The cross-origin resource sharing policy can be configured the `spec.service.corsPolicy` field of the Canary.
> **Note:** Cross-origin resource sharing requires a Gateway API implementation that supports
> the [`CORS`](https://gateway-api.sigs.k8s.io/geps/gep-1767/) filter.
Example configuration:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
service:
corsPolicy:
allowOrigin:
- https://foo.example
- http://foo.example
allowMethods:
- GET
- PUT
- POST
- DELETE
- PATCH
- OPTIONS
allowCredentials: true
allowHeaders:
- Keep-Alive
- User-Agent
- X-Requested-With
- If-Modified-Since
- Cache-Control
- Content-Type
- Authorization
maxAge: 24h
```

View File

@@ -404,6 +404,9 @@ You can load `app.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 with Istio.
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).
## Traffic mirroring
![Flagger Canary Traffic Shadowing](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-canary-traffic-mirroring.png)

View File

@@ -0,0 +1,249 @@
# Knative Canary Deployments
This guide shows you how to use [Knative](https://knative.dev/) and Flagger to automate canary deployments.
![Flagger Canary Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-gatewayapi-canary.png)
## Prerequisites
Flagger requires a Kubernetes cluster **v1.19** or newer and a Knative Serving installation that supports
the resources with `serving.knative.dev/v1` as their API version.
Install Knative v1.17.0:
```bash
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.17.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.17.0/serving-core.yaml
kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.17.0/kourier.yaml
kubectl patch configmap/config-network \
--namespace knative-serving \
--type merge \
--patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'
```
Install Flagger in the `flagger-system` namespace:
```bash
kubectl apply -k github.com/fluxcd/flagger//kustomize/knative
```
Create a namespace for your Kntive Service:
```bash
kubectl create namespace test
```
Create a Knative Service that deploys podinfo:
```yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: podinfo
namespace: test
spec:
template:
spec:
containers:
- image: ghcr.io/stefanprodan/podinfo:6.0.0
ports:
- containerPort: 9898
protocol: TCP
command:
- ./podinfo
- --port=9898
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false
```
Deploy the load testing service to generate traffic during the canary analysis:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref=main
```
Create a Canary custom resource:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: knative
# knative service ref
targetRef:
apiVersion: serving.knative.dev/v1
kind: Service
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
analysis:
# schedule interval (default 60s)
interval: 15s
# max number of failed metric checks before rollback
threshold: 15
# max traffic percentage routed to canary
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 10
metrics:
- name: request-success-rate
# min success rate (non-5xx responses)
# percentage (0-100)
thresholdRange:
min: 99
interval: 1m
- name: request-duration
# milliseconds
thresholdRange:
max: 500
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 1m -q 5 -c 2 http://podinfo.test"
logCmdOutput: "true"
```
> Note: Please note that for a Canary resource with `.spec.provider` set to `knative`, the resource is only valid if the
`.spec.targetRef.kind` is `Service` and `.spec.targetRef.apiVersion` is `serving.knative.dev/v1`.
Save the above resource as podinfo-canary.yaml and then apply it:
```bash
kubectl apply -f ./podinfo-canary.yaml
```
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.
The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every minute.
After a couple of seconds Flagger will make the following changes the Knative Service `podinfo`:
* Add an annotation to the object with the name `flagger.app/primary-revision`.
* Modify the `.spec.traffic` section of the object such that it can manipulate the traffic spread between
the primary and canary Knative Revision.
## Automated canary promotion
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test patch services.serving podinfo --type=json \
-p '[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "ghcr.io/stefanprodan/podinfo:6.0.1"}]'
```
Flagger detects that the deployment revision changed and starts a new rollout:
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 0
Phase: Succeeded
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger New revision detected podinfo.test
Normal Synced 3m flagger Scaling up podinfo.test
Normal Synced 3m flagger Advance podinfo.test canary weight 5
Normal Synced 3m flagger Advance podinfo.test canary weight 10
Normal Synced 3m flagger Advance podinfo.test canary weight 15
Normal Synced 2m flagger Advance podinfo.test canary weight 20
Normal Synced 2m flagger Advance podinfo.test canary weight 25
Normal Synced 1m flagger Advance podinfo.test canary weight 30
Normal Synced 1m flagger Advance podinfo.test canary weight 35
Normal Synced 55s flagger Advance podinfo.test canary weight 40
Normal Synced 45s flagger Advance podinfo.test canary weight 45
Normal Synced 35s flagger Advance podinfo.test canary weight 50
Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test
Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test
```
A canary deployment is triggered everytime a new Knative Revision is created.
**Note** that if you apply new changes to the Knative Service during the canary analysis, Flagger will restart the analysis.
You can monitor how Flagger progressively changes the Knative Service object to spread traffic between Knative Revisions:
```bash
watch kubectl get httproute -n test podinfo -o=jsonpath='{.spec.traffic}'
```
You can monitor all canaries with:
```bash
watch kubectl get canaries --all-namespaces
NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME
test podinfo Progressing 15 2025-03-16T14:05:07Z
prod frontend Succeeded 0 2025-03-16T16:15:07Z
prod backend Failed 0 2025-03-16T17:05:07Z
```
## Automated rollback
During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses the rollout.
Trigger another canary deployment:
```bash
kubectl -n test patch services.serving podinfo --type=json \
-p '[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "ghcr.io/stefanprodan/podinfo:6.0.2"}]'
```
Exec into the load tester pod with:
```bash
kubectl -n test exec -it flagger-loadtester-xx-xx sh
```
Generate HTTP 500 errors:
```bash
watch curl http://podinfo-canary:9898/status/500
```
Generate latency:
```bash
watch curl http://podinfo-canary:9898/delay/1
```
When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary
Knative Revision and the rollout is marked as failed.
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 10
Phase: Failed
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger Starting canary deployment for podinfo.test
Normal Synced 3m flagger Advance podinfo.test canary weight 5
Normal Synced 3m flagger Advance podinfo.test canary weight 10
Normal Synced 3m flagger Advance podinfo.test canary weight 15
Normal Synced 3m flagger Halt podinfo.test advancement error rate 69.17% > 1%
Normal Synced 2m flagger Halt podinfo.test advancement error rate 61.39% > 1%
Normal Synced 2m flagger Halt podinfo.test advancement error rate 55.06% > 1%
Normal Synced 2m flagger Halt podinfo.test advancement error rate 47.00% > 1%
Normal Synced 2m flagger (combined from similar events): Halt podinfo.test advancement error rate 38.08% > 1%
Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10
Warning Synced 1m flagger Canary failed! Scaling down podinfo.test
```

View File

@@ -72,7 +72,6 @@ metadata:
name: podinfo
namespace: test
spec:
# service mesh provider can be: kubernetes, istio, appmesh, nginx, gloo
provider: kubernetes
# deployment reference
targetRef:

View File

@@ -1,363 +0,0 @@
# Open Service Mesh Canary Deployments
This guide shows you how to use Open Service Mesh (OSM) and Flagger to automate canary deployments.
![Flagger OSM Traffic Split](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-osm-traffic-split.png)
## Prerequisites
Flagger requires a Kubernetes cluster **v1.16** or newer and Open Service Mesh **0.9.1** or newer.
OSM must have permissive traffic policy enabled and have an instance of Prometheus for metrics.
- If the OSM CLI is being used for installation, install OSM using the following command:
```bash
osm install \
--set=OpenServiceMesh.deployPrometheus=true \
--set=OpenServiceMesh.enablePermissiveTrafficPolicy=true
```
- If a managed instance of OSM is being used:
- [Bring your own instance](docs.openservicemesh.io/docs/guides/observability/metrics/#byo-prometheus) of Prometheus,
setting the namespace to match the managed OSM controller namespace
- Enable permissive traffic policy after installation by updating the OSM MeshConfig resource:
```bash
# Replace <osm-namespace> with OSM controller's namespace
kubectl patch meshconfig osm-mesh-config -n <osm-namespace> -p '{"spec":{"traffic":{"enablePermissiveTrafficPolicyMode":true}}}' --type=merge
```
To install Flagger in the default `osm-system` namespace, use:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/osm?ref=main
```
Alternatively, if a non-default namespace or managed instance of OSM is in use, install Flagger with Helm, replacing the <osm-namespace>
values as appropriate. If a custom instance of Prometheus is being used, replace `osm-prometheus` with the relevant Prometheus service name.
```bash
helm upgrade -i flagger flagger/flagger \
--namespace=<osm-namespace> \
--set meshProvider=osm \
--set metricsServer=http://osm-prometheus.<osm-namespace>.svc:7070
```
## Bootstrap
Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),
then creates a series of objects (Kubernetes deployments, ClusterIP services and SMI traffic split).
These objects expose the application inside the mesh and drive the canary analysis and promotion.
Create a `test` namespace and enable OSM namespace monitoring and metrics scraping for the namespace.
```bash
kubectl create namespace test
osm namespace add test
osm metrics enable --namespace test
```
Create a `podinfo` deployment and a horizontal pod autoscaler:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/podinfo?ref=main
```
Install the load testing service to generate traffic during the canary analysis:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref=main
```
Create a canary custom resource for the `podinfo` deployment.
The following `podinfo` canary custom resource instructs Flagger to:
1. monitor any changes to the `podinfo` deployment created earlier,
2. detect `podinfo` deployment revision changes, and
3. start a Flagger canary analysis, rollout, and promotion if there were deployment revision changes.
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: osm
# deployment reference
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rolled back (default 600s)
progressDeadlineSeconds: 60
service:
# ClusterIP port number
port: 9898
# container port number or name (optional)
targetPort: 9898
analysis:
# schedule interval (default 60s)
interval: 30s
# max number of failed metric checks before rollback
threshold: 5
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 5
# OSM Prometheus checks
metrics:
- name: request-success-rate
# minimum req success rate (non 5xx responses)
# percentage (0-100)
thresholdRange:
min: 99
interval: 1m
- name: request-duration
# maximum req duration P99
# milliseconds
thresholdRange:
max: 500
interval: 30s
# testing (optional)
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"
```
Save the above resource as podinfo-canary.yaml and then apply it:
```bash
kubectl apply -f ./podinfo-canary.yaml
```
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.
The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every half a minute.
After a couple of seconds Flagger will create the canary objects.
```bash
# applied
deployment.apps/podinfo
horizontalpodautoscaler.autoscaling/podinfo
ingresses.extensions/podinfo
canary.flagger.app/podinfo
# generated
deployment.apps/podinfo-primary
horizontalpodautoscaler.autoscaling/podinfo-primary
service/podinfo
service/podinfo-canary
service/podinfo-primary
trafficsplits.split.smi-spec.io/podinfo
```
After the bootstrap, the `podinfo` deployment will be scaled to zero and the traffic to `podinfo.test` will be routed to the primary pods.
During the canary analysis, the `podinfo-canary.test` address can be used to target directly the canary pods.
## Automated Canary Promotion
Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance indicators like HTTP requests success rate, requests average duration and pod health.
Based on analysis of the KPIs a canary is promoted or aborted.
![Flagger Canary Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-canary-steps.png)
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout.
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 0
Phase: Succeeded
Events:
New revision detected! Scaling up podinfo.test
Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Advance podinfo.test canary weight 20
Advance podinfo.test canary weight 25
Waiting for podinfo.test rollout to finish: 1 of 2 updated replicas are available
Advance podinfo.test canary weight 30
Advance podinfo.test canary weight 35
Advance podinfo.test canary weight 40
Advance podinfo.test canary weight 45
Advance podinfo.test canary weight 50
Copying podinfo.test template spec to podinfo-primary.test
Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Promotion completed! Scaling down podinfo.test
```
**Note** that if you apply any new changes to the `podinfo` deployment during the canary analysis, Flagger will restart the analysis.
A canary deployment is triggered by changes in any of the following objects:
* Deployment PodSpec \(container image, command, ports, env, resources, etc\)
* ConfigMaps mounted as volumes or mapped to environment variables
* Secrets mounted as volumes or mapped to environment variables
You can monitor all canaries with:
```bash
watch kubectl get canaries --all-namespaces
NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME
test podinfo Progressing 15 2019-06-30T14:05:07Z
prod frontend Succeeded 0 2019-06-30T16:15:07Z
prod backend Failed 0 2019-06-30T17:05:07Z
```
## Automated Rollback
During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses and rolls back the faulted version.
Trigger another canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.2
```
Exec into the load tester pod with:
```bash
kubectl -n test exec -it flagger-loadtester-xx-xx sh
```
Repeatedly generate HTTP 500 errors until the `kubectl describe` output below shows canary rollout failure:
```bash
watch -n 0.1 curl http://podinfo-canary.test:9898/status/500
```
Repeatedly generate latency until canary rollout fails:
```bash
watch -n 0.1 curl http://podinfo-canary.test:9898/delay/1
```
When the number of failed checks reaches the canary analysis thresholds defined in the `podinfo` canary custom resource earlier, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 10
Phase: Failed
Events:
Starting canary analysis for podinfo.test
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Halt podinfo.test advancement success rate 69.17% < 99%
Halt podinfo.test advancement success rate 61.39% < 99%
Halt podinfo.test advancement success rate 55.06% < 99%
Halt podinfo.test advancement request duration 1.20s > 0.5s
Halt podinfo.test advancement request duration 1.45s > 0.5s
Rolling back podinfo.test failed checks threshold reached 5
Canary failed! Scaling down podinfo.test
```
## Custom Metrics
The canary analysis can be extended with Prometheus queries.
Let's define a check for 404 not found errors.
Edit the canary analysis (`podinfo-canary.yaml` file) and add the following metric.
For more information on creating additional custom metrics using OSM metrics, please check the [metrics available in OSM](https://docs.openservicemesh.io/docs/guides/observability/metrics/#available-metrics).
```yaml
analysis:
metrics:
- name: "404s percentage"
threshold: 3
query: |
100 - (
sum(
rate(
osm_request_total{
destination_namespace="test",
destination_kind="Deployment",
destination_name="podinfo",
response_code!="404"
}[1m]
)
)
/
sum(
rate(
osm_request_total{
destination_namespace="test",
destination_kind="Deployment",
destination_name="podinfo"
}[1m]
)
) * 100
)
```
The above configuration validates the canary version by checking if the HTTP 404 req/sec percentage is below three percent of the total traffic.
If the 404s rate reaches the 3% threshold, then the analysis is aborted and the canary is marked as failed.
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.3
```
Exec into the load tester pod with:
```bash
kubectl -n test exec -it flagger-loadtester-xx-xx sh
```
Repeatedly generate 404s until canary rollout fails:
```bash
watch -n 0.1 curl http://podinfo-canary.test:9898/status/404
```
Watch Flagger logs to confirm successful canary rollback.
```text
kubectl -n osm-system logs deployment/flagger -f | jq .msg
Starting canary deployment for podinfo.test
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Halt podinfo.test advancement 404s percentage 6.20 > 3
Halt podinfo.test advancement 404s percentage 6.45 > 3
Halt podinfo.test advancement 404s percentage 7.22 > 3
Halt podinfo.test advancement 404s percentage 6.50 > 3
Halt podinfo.test advancement 404s percentage 6.34 > 3
Rolling back podinfo.test failed checks threshold reached 5
Canary failed! Scaling down podinfo.test
```

View File

@@ -63,7 +63,7 @@ helm upgrade -i flagger-loadtester flagger/loadtester \
Create Traefik IngressRoute that references TraefikService generated by Flagger \(replace `app.example.com` with your own domain\):
```yaml
apiVersion: traefik.containo.us/v1alpha1
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: podinfo
@@ -177,7 +177,7 @@ horizontalpodautoscaler.autoscaling/podinfo-primary
service/podinfo
service/podinfo-canary
service/podinfo-primary
traefikservice.traefik.containo.us/podinfo
traefikservice.traefik.io/podinfo
```
## Automated canary promotion

View File

@@ -3,11 +3,11 @@
Flagger can run automated application analysis, promotion and rollback for the following deployment strategies:
* **Canary Release** \(progressive traffic shifting\)
* Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo Edge, Traefik, Open Service Mesh, Kuma, Gateway API, Apache APISIX
* Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo Edge, Traefik, Kuma, Gateway API, Apache APISIX, Knative
* **A/B Testing** \(HTTP headers and cookies traffic routing\)
* Istio, App Mesh, NGINX, Contour, Gloo Edge, Gateway API
* **Blue/Green** \(traffic switching\)
* Kubernetes CNI, Istio, Linkerd, App Mesh, NGINX, Contour, Gloo Edge, Open Service Mesh, Gateway API
* Kubernetes CNI, Istio, Linkerd, App Mesh, NGINX, Contour, Gloo Edge, Gateway API
* **Blue/Green Mirroring** \(traffic shadowing\)
* Istio, Gateway API
* **Canary Release with Session Affinity** \(progressive traffic shifting combined with cookie based routing\)
@@ -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,82 @@ 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 and Istio providers 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.
### Configuring additional cookie attributes
Depending on your use case, you may neet to set additional [cookie attributes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes) in order for your application to route requests correctly.
You may set the following attributes:
```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
# defines the host to which the cookie will be sent.
# optional
domain: fluxcd.io
# forbids JavaScript from accessing the cookie, for example, through the Document.cookie property.
# optional
httpOnly: true
# indicates that the cookie should be stored using partitioned storage.
# optional
partitioned: true
# indicates the path that must exist in the requested URL for the browser to send the Cookie header.
# optional
path: /flagger
# controls whether or not a cookie is sent with cross-site requests.
# optional; valid values are Strict, Lax or None
sameSite: Strict
# indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost)
# optional
secure: true
```

View File

@@ -147,6 +147,8 @@ spec:
appProtocol: http
targetPort: 9898
portDiscovery: true
headless: false
trafficDistribution: PreferClose
```
The container port from the target workload should match the `service.port` or `service.targetPort`.
@@ -154,6 +156,7 @@ The `service.name` is optional, defaults to `spec.targetRef.name`.
The `service.targetPort` can be a container port number or name.
The `service.portName` is optional (defaults to `http`), if your workload uses gRPC then set the port name to `grpc`.
The `service.appProtocol` is optional, more details can be found [here](https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol).
The `service.trafficDistribution` is optional, more details can be found [here](https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution).
If port discovery is enabled, Flagger scans the target workload and extracts the containers ports
excluding the port specified in the canary service and service mesh sidecar ports.
@@ -204,6 +207,13 @@ Note that the `apex` annotations are added to both the generated Kubernetes Serv
generated service mesh/ingress object. This allows using external-dns with Istio `VirtualServices`
and `TraefikServices`. Beware of configuration conflicts [here](../faq.md#ExternalDNS).
Note that if any annotations or labels are added that are not specified here,
Flagger will remove them during reconciliation. To specify metadata
that should be ignored by Flagger, configure `unmanagedMetadata`.
If you want for the generated Kubernetes ClusterIP services to be [headless](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services),
then set `service.headless` to true.
Besides port mapping and metadata, the service specification can
contain URI match and rewrite rules, timeout and retry polices:

View File

@@ -121,4 +121,10 @@ flagger_canary_duration_seconds_count{name="podinfo",namespace="test"} 6
# Last canary metric analysis result per different metrics
flagger_canary_metric_analysis{metric="podinfo-http-successful-rate",name="podinfo",namespace="test"} 1
flagger_canary_metric_analysis{metric="podinfo-custom-metric",name="podinfo",namespace="test"} 0.918223108974359
# Canary successes total counter
flagger_canary_successes_total{name="podinfo",namespace="test",deployment_strategy="canary",analysis_status="completed"} 5
# Canary failures total counter
flagger_canary_failures_total{name="podinfo",namespace="test",deployment_strategy="canary",analysis_status="completed"} 1
```

139
go.mod
View File

@@ -1,102 +1,109 @@
module github.com/fluxcd/flagger
go 1.23.0
go 1.25.0
require (
cloud.google.com/go/monitoring v1.22.0
github.com/Masterminds/semver/v3 v3.3.1
github.com/aws/aws-sdk-go v1.55.5
cloud.google.com/go/monitoring v1.24.2
github.com/Masterminds/semver/v3 v3.4.0
github.com/aws/aws-sdk-go v1.55.8
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/hashicorp/go-retryablehttp v0.7.7
github.com/googleapis/gax-go/v2 v2.15.0
github.com/hashicorp/go-retryablehttp v0.7.8
github.com/influxdata/influxdb-client-go/v2 v2.14.0
github.com/prometheus/client_golang v1.20.5
github.com/prometheus/client_golang v1.23.2
github.com/signalfx/signalflow-client-go v0.1.0
github.com/signalfx/signalfx-go v1.44.0
github.com/stretchr/testify v1.10.0
github.com/signalfx/signalfx-go v1.53.0
github.com/stretchr/testify v1.11.1
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.67.1
google.golang.org/protobuf v1.35.2
golang.org/x/sync v0.18.0
google.golang.org/api v0.252.0
google.golang.org/genproto v0.0.0-20250603155806-513f23925822
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
gopkg.in/h2non/gock.v1 v1.1.2
k8s.io/api v0.31.3
k8s.io/apimachinery v0.31.3
k8s.io/client-go v0.31.3
k8s.io/code-generator v0.31.3
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
k8s.io/code-generator v0.34.1
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
knative.dev/serving v0.46.6
)
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.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.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
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
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/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/gorilla/websocket v1.5.1 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // 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.4-0.20250319132907-e064f32e3674 // 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.15 // indirect
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
github.com/jmespath/go-jmespath v0.4.0 // 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/mailru/easyjson v0.7.7 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/spf13/pflag v1.0.6 // 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.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // 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
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.38.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // 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-20240228010128-51d4e06bde70 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // 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/yaml v1.4.0 // indirect
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
knative.dev/networking v0.0.0-20250902160145-7dad473f6351 // indirect
knative.dev/pkg v0.0.0-20250909011231-077dcf0d00e8 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

324
go.sum
View File

@@ -1,94 +1,100 @@
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=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
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.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
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.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
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=
github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
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 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/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/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
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.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
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.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
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.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
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=
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -98,19 +104,16 @@ 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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -118,126 +121,146 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
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=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
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_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/signalfx/signalfx-go v1.53.0 h1:TMQDuj/Kyom8Xtb7NFuvV7URfCuTRjTrLyD0BroUDmM=
github.com/signalfx/signalfx-go v1.53.0/go.mod h1:CHt+/W1qd62tXxNqG7ZUB9pEsEAOD6tuvdlyDNIOO1s=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
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.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
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=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
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.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
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=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI=
google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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=
@@ -250,28 +273,35 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8=
k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE=
k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4=
k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4=
k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs=
k8s.io/code-generator v0.31.3 h1:Pj0fYOBms+ZrsulLi4DMsCEx1jG8fWKRLy44onHsLBI=
k8s.io/code-generator v0.31.3/go.mod h1:/umCIlT84g1+Yu5ZXtP1KGSRTnGiIzzX5AzUAxsNlts=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc=
k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg=
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q=
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
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/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
knative.dev/networking v0.0.0-20250902160145-7dad473f6351 h1:Gv/UqbN0AK+ORoT5e2Kg+3+uMW/y9CCdhpXKxYaVV6k=
knative.dev/networking v0.0.0-20250902160145-7dad473f6351/go.mod h1:P/fAhhVDgmLt1ugFX8vBvdSDiUOw2P4SGcjbzoZ02Xw=
knative.dev/pkg v0.0.0-20250909011231-077dcf0d00e8 h1:n0BMHXIem9MyDkK4vfA4Vzdxaf1e+EeLJ6k+8exCjjI=
knative.dev/pkg v0.0.0-20250909011231-077dcf0d00e8/go.mod h1:a1amDzo4YIUNuGeDgEz/uDHs5MQVYI1DXnRnEpWCAts=
knative.dev/serving v0.46.6 h1:jmVF560qnepNBG69VEbNRtknGFwZtGEyR1QSlNuoKmk=
knative.dev/serving v0.46.6/go.mod h1:mY7uXQo49PnxxM6UJWfnSAi6OqFEPM03dpWc8xsH3+I=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -34,14 +34,6 @@ kustomize build https://github.com/fluxcd/flagger/kustomize/linkerd?ref=main | k
This deploys Flagger in the `linkerd` namespace and sets the metrics server URL to linkerd-viz extension's Prometheus instance
which lives under `linkerd-viz` namespace by default.
Install Flagger for Open Service Mesh:
```bash
kustomize build https://github.com/fluxcd/flagger/kustomize/osm?ref=main | kubectl apply -f -
```
This deploys Flagger in the `osm-system` namespace and sets the metrics server URL to OSM's Prometheus instance.
If you want to install a specific Flagger release, add the version number to the URL:
```bash
@@ -76,7 +68,7 @@ metadata:
name: app
namespace: test
spec:
# can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, gloo, osm
# can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, gloo
# use the kubernetes provider for Blue/Green style deployments
provider: nginx
```

View File

@@ -80,7 +80,6 @@ spec:
type: object
required:
- targetRef
- service
- analysis
properties:
provider:
@@ -192,11 +191,21 @@ spec:
appProtocol:
description: Application protocol of the port
type: string
trafficDistribution:
description: Traffic distribution of the service
type: string
enum:
- PreferClose
- PreferSameZone
- PreferSameNode
targetPort:
description: Container target port name
x-kubernetes-int-or-string: true
portDiscovery:
description: Enable port dicovery
description: Enable port discovery
type: boolean
headless:
description: Headless if set to true, generates headless Kubernetes services.
type: boolean
timeout:
description: HTTP or gRPC request timeout
@@ -907,6 +916,18 @@ spec:
type: object
additionalProperties:
type: string
unmanagedMetadata:
description: UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
type: object
properties:
annotations:
type: array
items:
type: string
labels:
type: array
items:
type: string
skipAnalysis:
description: Skip analysis and promote canary
type: boolean
@@ -1153,10 +1174,35 @@ 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
domain:
description: Domain defines the host to which the cookie will be sent.
type: string
httpOnly:
description: HttpOnly forbids JavaScript from accessing the cookie, for example, through the Document.cookie property.
type: boolean
maxAge:
description: MaxAge indicates the number of seconds until the session affinity cookie will expire.
default: 86400
type: number
partitioned:
description: Partitioned indicates that the cookie should be stored using partitioned storage.
type: boolean
path:
description: Path indicates the path that must exist in the requested URL for the browser to send the Cookie header.
type: string
sameSite:
description: SameSite controls whether or not a cookie is sent with cross-site requests.
type: string
enum:
- Strict
- Lax
- None
secure:
description: "Secure indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost)"
type: boolean
status:
description: CanaryStatus defines the observed state of a canary.
type: object
@@ -1204,6 +1250,9 @@ spec:
sessionAffinityCookie:
description: Session affinity cookie of the current canary run
type: string
primarySessionAffinityCookie:
description: Primary session affinity cookie of the current canary run
type: string
previousSessionAffinityCookie:
description: Session affinity cookie of the previous canary run
type: string
@@ -1308,6 +1357,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

View File

@@ -9,4 +9,4 @@ resources:
images:
- name: ghcr.io/fluxcd/flagger
newName: ghcr.io/fluxcd/flagger
newTag: 1.40.0
newTag: 1.42.0

View File

@@ -254,10 +254,24 @@ rules:
- update
- patch
- delete
- apiGroups:
- serving.knative.dev
resources:
- services
verbs:
- get
- update
- apiGroups:
- serving.knative.dev
resources:
- revisions
verbs:
- get
- nonResourceURLs:
- /version
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding

View File

@@ -0,0 +1,8 @@
namespace: flagger-system
resources:
- namespace.yaml
bases:
- ../base/flagger/
- ../base/prometheus/
patchesStrategicMerge:
- patch.yaml

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
name: flagger-system
annotations:
linkerd.io/inject: disabled
labels:
istio-injection: disabled
appmesh.k8s.aws/sidecarInjectorWebhook: disabled

View File

@@ -0,0 +1,14 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flagger
spec:
template:
spec:
containers:
- name: flagger
args:
- -log-level=info
- -include-label-prefix=app.kubernetes.io
- -mesh-provider=knative
- -metrics-server=http://flagger-prometheus:9090

View File

@@ -1,5 +0,0 @@
namespace: osm-system
bases:
- ../base/flagger/
patchesStrategicMerge:
- patch.yaml

View File

@@ -1,27 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flagger
spec:
template:
spec:
containers:
- name: flagger
args:
- -log-level=info
- -include-label-prefix=app.kubernetes.io
- -mesh-provider=osm
- -metrics-server=http://osm-prometheus.osm-system.svc:7070
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flagger
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flagger
subjects:
- kind: ServiceAccount
name: flagger
namespace: osm-system

View File

@@ -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.36.0
imagePullPolicy: IfNotPresent
ports:
- name: http

View File

@@ -20,10 +20,11 @@ import (
"fmt"
"time"
"github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1"
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1"
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
)
const (
@@ -35,6 +36,13 @@ const (
MetricInterval = "1m"
)
// Deployment strategies
const (
DeploymentStrategyCanary = "canary"
DeploymentStrategyBlueGreen = "blue-green"
DeploymentStrategyABTesting = "ab-testing"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -74,7 +82,7 @@ type CanarySpec struct {
// AutoscalerRef references an autoscaling resource
// +optional
AutoscalerRef *AutoscalerRefernce `json:"autoscalerRef,omitempty"`
AutoscalerRef *AutoscalerReference `json:"autoscalerRef,omitempty"`
// Reference to NGINX ingress resource
// +optional
@@ -143,9 +151,19 @@ type CanaryService struct {
// +optional
AppProtocol string `json:"appProtocol,omitempty"`
// TrafficDistribution of the service
// https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution
// +optional
TrafficDistribution string `json:"trafficDistribution,omitempty"`
// PortDiscovery adds all container ports to the generated Kubernetes service
PortDiscovery bool `json:"portDiscovery"`
// Headless if set to true, generates headless Kubernetes services.
// ref: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
// +optional
Headless bool `json:"headless,omitempty"`
// Timeout of the HTTP or gRPC request
// +optional
Timeout string `json:"timeout,omitempty"`
@@ -218,6 +236,17 @@ type CanaryService struct {
// Canary is the metadata to add to the canary service
// +optional
Canary *CustomMetadata `json:"canary,omitempty"`
// UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
// Flagger will not add, remove or change the value of these annotations.
// +optional
UnmanagedMetadata *UnmanagedMetadata `json:"unmanagedMetadata,omitempty"`
}
// UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
type UnmanagedMetadata struct {
Annotations []string `json:"annotations,omitempty"`
Labels []string `json:"labels,omitempty"`
}
// CanaryAnalysis is used to describe how the analysis should be done
@@ -285,11 +314,33 @@ type CanaryAnalysis struct {
type SessionAffinity struct {
// CookieName is the key that will be used for the session affinity cookie.
CookieName string `json:"cookieName,omitempty"`
// MaxAge indicates the number of seconds until the session affinity cookie will expire.
// ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes
// Domain defines the host to which the cookie will be sent.
// +optional
Domain string `json:"domain,omitempty"`
// HttpOnly forbids JavaScript from accessing the cookie, for example, through the Document.cookie property.
// +optional
HttpOnly bool `json:"httpOnly,omitempty"`
// MaxAge indicates the number of seconds until the session affinity cookie will expire.
// The default value is 86,400 seconds, i.e. a day.
// +optional
MaxAge int `json:"maxAge,omitempty"`
// Partitioned indicates that the cookie should be stored using partitioned storage.
// +optional
Partitioned bool `json:"partitioned,omitempty"`
// Path indicates the path that must exist in the requested URL for the browser to send the Cookie header.
// +optional
Path string `json:"path,omitempty"`
// SameSite controls whether or not a cookie is sent with cross-site requests.
// +optional
// +kubebuilder:validation:Enum=Strict;Lax;None
SameSite string `json:"sameSite,omitempty"`
// Secure indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost)
// +optional
Secure bool `json:"secure,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
@@ -458,7 +509,15 @@ type LocalObjectReference struct {
Name string `json:"name"`
}
type AutoscalerRefernce struct {
func (l *LocalObjectReference) IsKnativeService() bool {
if l.Kind == "Service" && l.APIVersion == "serving.knative.dev/v1" {
return true
}
return false
}
type AutoscalerReference struct {
// API version of the scaler
// +required
APIVersion string `json:"apiVersion,omitempty"`
@@ -624,3 +683,57 @@ func (c *Canary) SkipAnalysis() bool {
}
return c.Spec.SkipAnalysis
}
// DeploymentStrategy returns the deployment strategy based on canary analysis configuration
func (c *Canary) DeploymentStrategy() string {
analysis := c.GetAnalysis()
if analysis == nil {
return DeploymentStrategyCanary
}
// A/B Testing: has match conditions and iterations
if len(analysis.Match) > 0 && analysis.Iterations > 0 {
return DeploymentStrategyABTesting
}
// Blue/Green: has iterations but no match conditions
if analysis.Iterations > 0 {
return DeploymentStrategyBlueGreen
}
// Canary Release: default (has maxWeight, stepWeight, or stepWeights)
return DeploymentStrategyCanary
}
// BuildCookie returns the cookie that should be used as the value of a Set-Cookie header
func (s *SessionAffinity) BuildCookie(cookieName string, maxAge int) string {
cookie := fmt.Sprintf("%s; %s=%d", cookieName, "Max-Age",
maxAge,
)
if s.Domain != "" {
cookie += fmt.Sprintf("; %s=%s", "Domain", s.Domain)
}
if s.HttpOnly {
cookie += fmt.Sprintf("; %s", "HttpOnly")
}
if s.Partitioned {
cookie += fmt.Sprintf("; %s", "Partitioned")
}
if s.Path != "" {
cookie += fmt.Sprintf("; %s=%s", "Path", s.Path)
}
if s.SameSite != "" {
cookie += fmt.Sprintf("; %s=%s", "SameSite", s.SameSite)
}
if s.Secure {
cookie += fmt.Sprintf("; %s", "Secure")
}
return cookie
}

View File

@@ -0,0 +1,94 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
"testing"
istiov1alpha1 "github.com/fluxcd/flagger/pkg/apis/istio/common/v1alpha1"
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
"github.com/stretchr/testify/assert"
)
func TestCanary_GetDeploymentStrategy(t *testing.T) {
tests := []struct {
name string
analysis *CanaryAnalysis
expected string
}{
{
name: "canary strategy with maxWeight",
analysis: &CanaryAnalysis{
MaxWeight: 30,
StepWeight: 10,
},
expected: DeploymentStrategyCanary,
},
{
name: "canary strategy with stepWeights",
analysis: &CanaryAnalysis{
StepWeights: []int{10, 20, 30},
},
expected: DeploymentStrategyCanary,
},
{
name: "blue-green strategy with iterations",
analysis: &CanaryAnalysis{
Iterations: 5,
},
expected: DeploymentStrategyBlueGreen,
},
{
name: "ab-testing strategy with iterations and match",
analysis: &CanaryAnalysis{
Iterations: 10,
Match: []istiov1beta1.HTTPMatchRequest{
{
Headers: map[string]istiov1alpha1.StringMatch{
"x-canary": {
Exact: "insider",
},
},
},
},
},
expected: DeploymentStrategyABTesting,
},
{
name: "default to canary when analysis is nil",
analysis: nil,
expected: DeploymentStrategyCanary,
},
{
name: "default to canary when analysis is empty",
analysis: &CanaryAnalysis{},
expected: DeploymentStrategyCanary,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
canary := &Canary{
Spec: CanarySpec{
Analysis: tt.analysis,
},
}
result := canary.DeploymentStrategy()
assert.Equal(t, tt.expected, result)
})
}
}

View File

@@ -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"`

View File

@@ -7,6 +7,7 @@ const (
IstioProvider string = "istio"
SMIProvider string = "smi"
ContourProvider string = "contour"
KnativeProvider string = "knative"
GlooProvider string = "gloo"
NGINXProvider string = "nginx"
KubernetesProvider string = "kubernetes"

View File

@@ -78,6 +78,8 @@ type CanaryStatus struct {
// +optional
SessionAffinityCookie string `json:"sessionAffinityCookie,omitempty"`
// +optional
PrimarySessionAffinityCookie string `json:"primarySessionAffinityCookie,omitempty"`
// +optional
TrackedConfigs *map[string]string `json:"trackedConfigs,omitempty"`
// +optional
LastAppliedSpec string `json:"lastAppliedSpec,omitempty"`

View File

@@ -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"
@@ -152,7 +154,7 @@ func (in *AlertProviderStatus) DeepCopy() *AlertProviderStatus {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AutoscalerRefernce) DeepCopyInto(out *AutoscalerRefernce) {
func (in *AutoscalerReference) DeepCopyInto(out *AutoscalerReference) {
*out = *in
if in.PrimaryScalerQueries != nil {
in, out := &in.PrimaryScalerQueries, &out.PrimaryScalerQueries
@@ -169,12 +171,12 @@ func (in *AutoscalerRefernce) DeepCopyInto(out *AutoscalerRefernce) {
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerRefernce.
func (in *AutoscalerRefernce) DeepCopy() *AutoscalerRefernce {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerReference.
func (in *AutoscalerReference) DeepCopy() *AutoscalerReference {
if in == nil {
return nil
}
out := new(AutoscalerRefernce)
out := new(AutoscalerReference)
in.DeepCopyInto(out)
return out
}
@@ -450,6 +452,11 @@ func (in *CanaryService) DeepCopyInto(out *CanaryService) {
*out = new(CustomMetadata)
(*in).DeepCopyInto(*out)
}
if in.UnmanagedMetadata != nil {
in, out := &in.UnmanagedMetadata, &out.UnmanagedMetadata
*out = new(UnmanagedMetadata)
(*in).DeepCopyInto(*out)
}
return
}
@@ -469,7 +476,7 @@ func (in *CanarySpec) DeepCopyInto(out *CanarySpec) {
out.TargetRef = in.TargetRef
if in.AutoscalerRef != nil {
in, out := &in.AutoscalerRef, &out.AutoscalerRef
*out = new(AutoscalerRefernce)
*out = new(AutoscalerReference)
(*in).DeepCopyInto(*out)
}
if in.IngressRef != nil {
@@ -810,6 +817,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)
@@ -909,3 +931,29 @@ func (in *SessionAffinity) DeepCopy() *SessionAffinity {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UnmanagedMetadata) DeepCopyInto(out *UnmanagedMetadata) {
*out = *in
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnmanagedMetadata.
func (in *UnmanagedMetadata) DeepCopy() *UnmanagedMetadata {
if in == nil {
return nil
}
out := new(UnmanagedMetadata)
in.DeepCopyInto(out)
return out
}

View File

@@ -20,11 +20,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories=gateway-api
// +kubebuilder:subresource:status
// +kubebuilder:storageversion
// +kubebuilder:printcolumn:name="Hostnames",type=string,JSONPath=`.spec.hostnames`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
@@ -33,13 +34,16 @@ import (
// used to specify additional processing steps. Backends specify where matching
// requests should be routed.
type HTTPRoute struct {
metav1.TypeMeta `json:",inline"`
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec defines the desired state of HTTPRoute.
// +required
Spec HTTPRouteSpec `json:"spec"`
// Status defines the current state of HTTPRoute.
// +optional
Status HTTPRouteStatus `json:"status,omitempty"`
}
@@ -112,14 +116,18 @@ type HTTPRouteSpec struct {
// Support: Core
//
// +optional
// +listType=atomic
// +kubebuilder:validation:MaxItems=16
Hostnames []Hostname `json:"hostnames,omitempty"`
// Rules are a list of HTTP matchers, filters and actions.
//
// +optional
// +listType=atomic
// <gateway:experimental:validation:XValidation:message="Rule name must be unique within the route",rule="self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name))">
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:default={{matches: {{path: {type: "PathPrefix", value: "/"}}}}}
// +kubebuilder:validation:XValidation:message="While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less than 128",rule="(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128"
Rules []HTTPRouteRule `json:"rules,omitempty"`
}
@@ -133,6 +141,12 @@ type HTTPRouteSpec struct {
// +kubebuilder:validation:XValidation:message="Within backendRefs, when using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified",rule="(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == 'ReplacePrefixMatch' && has(f.requestRedirect.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != 'PathPrefix') ? false : true) : true"
// +kubebuilder:validation:XValidation:message="Within backendRefs, When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified",rule="(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == 'ReplacePrefixMatch' && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != 'PathPrefix') ? false : true) : true"
type HTTPRouteRule struct {
// Name is the name of the route rule. This name MUST be unique within a Route if it is set.
//
// Support: Extended
// +optional
Name *SectionName `json:"name,omitempty"`
// Matches define conditions used for matching the rule against incoming
// HTTP requests. Each match is independent, i.e. this rule will be matched
// if **any** one of the matches is satisfied.
@@ -191,15 +205,27 @@ type HTTPRouteRule struct {
// parent a request is coming from, a HTTP 404 status code MUST be returned.
//
// +optional
// +kubebuilder:validation:MaxItems=8
// +listType=atomic
// +kubebuilder:validation:MaxItems=64
// +kubebuilder:default={{path:{ type: "PathPrefix", value: "/"}}}
Matches []HTTPRouteMatch `json:"matches,omitempty"`
// Filters define the filters that are applied to requests that match
// this rule.
//
// The effects of ordering of multiple behaviors are currently unspecified.
// This can change in the future based on feedback during the alpha stage.
// Wherever possible, implementations SHOULD implement filters in the order
// they are specified.
//
// Implementations MAY choose to implement this ordering strictly, rejecting
// any combination or order of filters that cannot be supported. If implementations
// choose a strict interpretation of filter ordering, they MUST clearly document
// that behavior.
//
// To reject an invalid combination or order of filters, implementations SHOULD
// consider the Route Rules with this configuration invalid. If all Route Rules
// in a Route are invalid, the entire Route would be considered invalid. If only
// a portion of Route Rules are invalid, implementations MUST set the
// "PartiallyInvalid" condition for the Route.
//
// Conformance-levels at this level are defined based on the type of filter:
//
@@ -213,7 +239,7 @@ type HTTPRouteRule struct {
//
// All filters are expected to be compatible with each other except for the
// URLRewrite and RequestRedirect filters, which may not be combined. If an
// implementation can not support other combinations of filters, they must clearly
// implementation cannot support other combinations of filters, they must clearly
// document that limitation. In cases where incompatible or unsupported
// filters are specified and cause the `Accepted` condition to be set to status
// `False`, implementations may use the `IncompatibleFilters` reason to specify
@@ -222,6 +248,7 @@ type HTTPRouteRule struct {
// Support: Core
//
// +optional
// +listType=atomic
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:validation:XValidation:message="May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both",rule="!(self.exists(f, f.type == 'RequestRedirect') && self.exists(f, f.type == 'URLRewrite'))"
// +kubebuilder:validation:XValidation:message="RequestHeaderModifier filter cannot be repeated",rule="self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1"
@@ -253,6 +280,11 @@ type HTTPRouteRule struct {
// invalid, 50 percent of traffic must receive a 500. Implementations may
// choose how that 50 percent is determined.
//
// When a HTTPBackendRef refers to a Service that has no ready endpoints,
// implementations SHOULD return a 503 for requests to that backend instead.
// If an implementation chooses to do this, all of the above rules for 500 responses
// MUST also apply for responses that return a 503.
//
// Support: Core for Kubernetes Service
//
// Support: Extended for Kubernetes ServiceImport
@@ -262,6 +294,7 @@ type HTTPRouteRule struct {
// Support for weight: Core
//
// +optional
// +listType=atomic
// +kubebuilder:validation:MaxItems=16
BackendRefs []HTTPBackendRef `json:"backendRefs,omitempty"`
@@ -270,13 +303,28 @@ type HTTPRouteRule struct {
// Support: Extended
//
// +optional
// <gateway:experimental>
Timeouts *HTTPRouteTimeouts `json:"timeouts,omitempty"`
// Retry defines the configuration for when to retry an HTTP request.
//
// Support: Extended
//
// +optional
// <gateway:experimental>
Retry *HTTPRouteRetry `json:"retry,omitempty"`
// SessionPersistence defines and configures session persistence
// for the route rule.
//
// Support: Extended
//
// +optional
// <gateway:experimental>
SessionPersistence *SessionPersistence `json:"sessionPersistence,omitempty"`
}
// HTTPRouteTimeouts defines timeouts that can be configured for an HTTPRoute.
// Timeout values are represented with Gateway API Duration formatting.
// Specifying a zero value such as "0s" is interpreted as no timeout.
//
// +kubebuilder:validation:XValidation:message="backendRequest timeout cannot be longer than request timeout",rule="!(has(self.request) && has(self.backendRequest) && duration(self.request) != duration('0s') && duration(self.backendRequest) > duration(self.request))"
type HTTPRouteTimeouts struct {
@@ -288,12 +336,18 @@ type HTTPRouteTimeouts struct {
// `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds
// to complete.
//
// Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
// completely. Implementations that cannot completely disable the timeout MUST
// instead interpret the zero duration as the longest possible value to which
// the timeout can be set.
//
// This timeout is intended to cover as close to the whole request-response transaction
// as possible although an implementation MAY choose to start the timeout after the entire
// request stream has been received instead of immediately after the transaction is
// initiated by the client.
//
// When this field is unspecified, request timeout behavior is implementation-specific.
// The value of Request is a Gateway API Duration string as defined by GEP-2257. When this
// field is unspecified, request timeout behavior is implementation-specific.
//
// Support: Extended
//
@@ -304,12 +358,19 @@ type HTTPRouteTimeouts struct {
// to a backend. This covers the time from when the request first starts being
// sent from the gateway to when the full response has been received from the backend.
//
// Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
// completely. Implementations that cannot completely disable the timeout MUST
// instead interpret the zero duration as the longest possible value to which
// the timeout can be set.
//
// An entire client HTTP transaction with a gateway, covered by the Request timeout,
// may result in more than one call from the gateway to the destination backend,
// for example, if automatic retries are supported.
//
// Because the Request timeout encompasses the BackendRequest timeout, the value of
// BackendRequest must be <= the value of Request timeout.
// The value of BackendRequest must be a Gateway API Duration string as defined by
// GEP-2257. When this field is unspecified, its behavior is implementation-specific;
// when specified, the value of BackendRequest must be no more than the value of the
// Request timeout (since the Request timeout encompasses the BackendRequest timeout).
//
// Support: Extended
//
@@ -317,6 +378,96 @@ type HTTPRouteTimeouts struct {
BackendRequest *Duration `json:"backendRequest,omitempty"`
}
// HTTPRouteRetry defines retry configuration for an HTTPRoute.
//
// Implementations SHOULD retry on connection errors (disconnect, reset, timeout,
// TCP failure) if a retry stanza is configured.
type HTTPRouteRetry struct {
// Codes defines the HTTP response status codes for which a backend request
// should be retried.
//
// Support: Extended
//
// +optional
// +listType=atomic
Codes []HTTPRouteRetryStatusCode `json:"codes,omitempty"`
// Attempts specifies the maximum number of times an individual request
// from the gateway to a backend should be retried.
//
// If the maximum number of retries has been attempted without a successful
// response from the backend, the Gateway MUST return an error.
//
// When this field is unspecified, the number of times to attempt to retry
// a backend request is implementation-specific.
//
// Support: Extended
//
// +optional
Attempts *int `json:"attempts,omitempty"`
// Backoff specifies the minimum duration a Gateway should wait between
// retry attempts and is represented in Gateway API Duration formatting.
//
// For example, setting the `rules[].retry.backoff` field to the value
// `100ms` will cause a backend request to first be retried approximately
// 100 milliseconds after timing out or receiving a response code configured
// to be retryable.
//
// An implementation MAY use an exponential or alternative backoff strategy
// for subsequent retry attempts, MAY cap the maximum backoff duration to
// some amount greater than the specified minimum, and MAY add arbitrary
// jitter to stagger requests, as long as unsuccessful backend requests are
// not retried before the configured minimum duration.
//
// If a Request timeout (`rules[].timeouts.request`) is configured on the
// route, the entire duration of the initial request and any retry attempts
// MUST not exceed the Request timeout duration. If any retry attempts are
// still in progress when the Request timeout duration has been reached,
// these SHOULD be canceled if possible and the Gateway MUST immediately
// return a timeout error.
//
// If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is
// configured on the route, any retry attempts which reach the configured
// BackendRequest timeout duration without a response SHOULD be canceled if
// possible and the Gateway should wait for at least the specified backoff
// duration before attempting to retry the backend request again.
//
// If a BackendRequest timeout is _not_ configured on the route, retry
// attempts MAY time out after an implementation default duration, or MAY
// remain pending until a configured Request timeout or implementation
// default duration for total request time is reached.
//
// When this field is unspecified, the time to wait between retry attempts
// is implementation-specific.
//
// Support: Extended
//
// +optional
Backoff *Duration `json:"backoff,omitempty"`
}
// HTTPRouteRetryStatusCode defines an HTTP response status code for
// which a backend request should be retried.
//
// Implementations MUST support the following status codes as retryable:
//
// * 500
// * 502
// * 503
// * 504
//
// Implementations MAY support specifying additional discrete values in the
// 500-599 range.
//
// Implementations MAY support specifying discrete values in the 400-499 range,
// which are often inadvisable to retry.
//
// +kubebuilder:validation:Minimum:=400
// +kubebuilder:validation:Maximum:=599
// <gateway:experimental>
type HTTPRouteRetryStatusCode int
// PathMatchType specifies the semantics of how HTTP paths should be compared.
// Valid PathMatchType values, along with their support levels, are:
//
@@ -346,7 +497,7 @@ const (
PathMatchExact PathMatchType = "Exact"
// Matches based on a URL path prefix split by `/`. Matching is
// case sensitive and done on a path element by element basis. A
// case-sensitive and done on a path element by element basis. A
// path element refers to the list of labels in the path split by
// the `/` separator. When specified, a trailing `/` is ignored.
//
@@ -455,7 +606,7 @@ type HTTPHeaderMatch struct {
Type *HeaderMatchType `json:"type,omitempty"`
// Name is the name of the HTTP Header to be matched. Name matching MUST be
// case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
// case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
//
// If multiple entries specify equivalent header names, only the first
// entry with an equivalent name MUST be considered for a match. Subsequent
@@ -468,12 +619,14 @@ type HTTPHeaderMatch struct {
// Generally, proxies should follow the guidance from the RFC:
// https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding
// processing a repeated header, with special handling for "Set-Cookie".
// +required
Name HTTPHeaderName `json:"name"`
// Value is the value of HTTP Header to be matched.
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=4096
// +required
Value string `json:"value"`
}
@@ -535,12 +688,14 @@ type HTTPQueryParamMatch struct {
//
// Users SHOULD NOT route traffic based on repeated query params to guard
// themselves against potential differences in the implementations.
// +required
Name HTTPHeaderName `json:"name"`
// Value is the value of HTTP query param to be matched.
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=1024
// +required
Value string `json:"value"`
}
@@ -560,6 +715,9 @@ type HTTPQueryParamMatch struct {
// +kubebuilder:validation:Enum=GET;HEAD;POST;PUT;DELETE;CONNECT;OPTIONS;TRACE;PATCH
type HTTPMethod string
// +kubebuilder:validation:Enum=GET;HEAD;POST;PUT;DELETE;CONNECT;OPTIONS;TRACE;PATCH;*
type HTTPMethodWithWildcard string
const (
HTTPMethodGet HTTPMethod = "GET"
HTTPMethodHead HTTPMethod = "HEAD"
@@ -646,6 +804,10 @@ type HTTPRouteMatch struct {
// +kubebuilder:validation:XValidation:message="filter.requestRedirect must be specified for RequestRedirect filter.type",rule="!(!has(self.requestRedirect) && self.type == 'RequestRedirect')"
// +kubebuilder:validation:XValidation:message="filter.urlRewrite must be nil if the filter.type is not URLRewrite",rule="!(has(self.urlRewrite) && self.type != 'URLRewrite')"
// +kubebuilder:validation:XValidation:message="filter.urlRewrite must be specified for URLRewrite filter.type",rule="!(!has(self.urlRewrite) && self.type == 'URLRewrite')"
// <gateway:experimental:validation:XValidation:message="filter.cors must be nil if the filter.type is not CORS",rule="!(has(self.cors) && self.type != 'CORS')">
// <gateway:experimental:validation:XValidation:message="filter.cors must be specified for CORS filter.type",rule="!(!has(self.cors) && self.type == 'CORS')">
// <gateway:experimental:validation:XValidation:message="filter.externalAuth must be nil if the filter.type is not ExternalAuth",rule="!(has(self.externalAuth) && self.type != 'ExternalAuth')">
// <gateway:experimental:validation:XValidation:message="filter.externalAuth must be specified for ExternalAuth filter.type",rule="!(!has(self.externalAuth) && self.type == 'ExternalAuth')">
// +kubebuilder:validation:XValidation:message="filter.extensionRef must be nil if the filter.type is not ExtensionRef",rule="!(has(self.extensionRef) && self.type != 'ExtensionRef')"
// +kubebuilder:validation:XValidation:message="filter.extensionRef must be specified for ExtensionRef filter.type",rule="!(!has(self.extensionRef) && self.type == 'ExtensionRef')"
type HTTPRouteFilter struct {
@@ -684,6 +846,8 @@ type HTTPRouteFilter struct {
//
// +unionDiscriminator
// +kubebuilder:validation:Enum=RequestHeaderModifier;ResponseHeaderModifier;RequestMirror;RequestRedirect;URLRewrite;ExtensionRef
// <gateway:experimental:validation:Enum=RequestHeaderModifier;ResponseHeaderModifier;RequestMirror;RequestRedirect;URLRewrite;ExtensionRef;CORS;ExternalAuth>
// +required
Type HTTPRouteFilterType `json:"type"`
// RequestHeaderModifier defines a schema for a filter that modifies request
@@ -713,6 +877,8 @@ type HTTPRouteFilter struct {
// Support: Extended
//
// +optional
//
// +kubebuilder:validation:XValidation:message="Only one of percent or fraction may be specified in HTTPRequestMirrorFilter",rule="!(has(self.percent) && has(self.fraction))"
RequestMirror *HTTPRequestMirrorFilter `json:"requestMirror,omitempty"`
// RequestRedirect defines a schema for a filter that responds to the
@@ -730,6 +896,28 @@ type HTTPRouteFilter struct {
// +optional
URLRewrite *HTTPURLRewriteFilter `json:"urlRewrite,omitempty"`
// CORS defines a schema for a filter that responds to the
// cross-origin request based on HTTP response header.
//
// Support: Extended
//
// +optional
// <gateway:experimental>
CORS *HTTPCORSFilter `json:"cors,omitempty"`
// ExternalAuth configures settings related to sending request details
// to an external auth service. The external service MUST authenticate
// the request, and MAY authorize the request as well.
//
// If there is any problem communicating with the external service,
// this filter MUST fail closed.
//
// Support: Extended
//
// +optional
// <gateway:experimental>
ExternalAuth *HTTPExternalAuthFilter `json:"externalAuth,omitempty"`
// ExtensionRef is an optional, implementation-specific extension to the
// "filter" behavior. For example, resource "myroutefilter" in group
// "networking.example.net"). ExtensionRef MUST NOT be used for core and
@@ -792,6 +980,27 @@ const (
// Support in HTTPBackendRef: Extended
HTTPRouteFilterRequestMirror HTTPRouteFilterType = "RequestMirror"
// HTTPRouteFilterCORS can be used to add CORS headers to an
// HTTP response before it is sent to the client.
//
// Support in HTTPRouteRule: Extended
//
// Support in HTTPBackendRef: Extended
// <gateway:experimental>
HTTPRouteFilterCORS HTTPRouteFilterType = "CORS"
// HTTPRouteFilterExternalAuth can be used to configure a Gateway implementation
// to call out to an external Auth server, which MUST perform Authentication
// and MAY perform Authorization on the matched request before the request
// is forwarded to the backend.
//
// Support in HTTPRouteRule: Extended
//
// Feature Name: HTTPRouteExternalAuth
//
// <gateway:experimental>
HTTPRouteFilterExternalAuth HTTPRouteFilterType = "ExternalAuth"
// HTTPRouteFilterExtensionRef should be used for configuring custom
// HTTP filters.
//
@@ -804,28 +1013,30 @@ const (
// HTTPHeader represents an HTTP Header name and value as defined by RFC 7230.
type HTTPHeader struct {
// Name is the name of the HTTP Header to be matched. Name matching MUST be
// case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
// case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
//
// If multiple entries specify equivalent header names, the first entry with
// an equivalent name MUST be considered for a match. Subsequent entries
// with an equivalent header name MUST be ignored. Due to the
// case-insensitivity of header names, "foo" and "Foo" are considered
// equivalent.
// +required
Name HTTPHeaderName `json:"name"`
// Value is the value of HTTP Header to be matched.
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=4096
// +required
Value string `json:"value"`
}
// HTTPHeaderFilter defines a filter that modifies the headers of an HTTP
// request or response. Only one action for a given header name is permitted.
// Filters specifying multiple actions of the same or different type for any one
// header name are invalid and will be rejected by the webhook if installed.
// Configuration to set or add multiple values for a header must use RFC 7230
// header value formatting, separating each value with a comma.
// request or response. Only one action for a given header name is
// permitted. Filters specifying multiple actions of the same or different
// type for any one header name are invalid. Configuration to set or add
// multiple values for a header must use RFC 7230 header value formatting,
// separating each value with a comma.
type HTTPHeaderFilter struct {
// Set overwrites the request with the given header (name, value)
// before the action.
@@ -935,6 +1146,7 @@ type HTTPPathModifier struct {
// Reason of `UnsupportedValue`.
//
// +kubebuilder:validation:Enum=ReplaceFullPath;ReplacePrefixMatch
// +required
Type HTTPPathModifierType `json:"type"`
// ReplaceFullPath specifies the value with which to replace the full path
@@ -1042,6 +1254,9 @@ type HTTPRequestRedirectFilter struct {
// Support: Extended
//
// +optional
//
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
Port *PortNumber `json:"port,omitempty"`
// StatusCode is the HTTP status code to be used in response.
@@ -1108,7 +1323,438 @@ type HTTPRequestMirrorFilter struct {
// Support: Extended for Kubernetes Service
//
// Support: Implementation-specific for any other resource
// +required
BackendRef BackendObjectReference `json:"backendRef"`
// Percent represents the percentage of requests that should be
// mirrored to BackendRef. Its minimum value is 0 (indicating 0% of
// requests) and its maximum value is 100 (indicating 100% of requests).
//
// Only one of Fraction or Percent may be specified. If neither field
// is specified, 100% of requests will be mirrored.
//
// +optional
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=100
Percent *int32 `json:"percent,omitempty"`
// Fraction represents the fraction of requests that should be
// mirrored to BackendRef.
//
// Only one of Fraction or Percent may be specified. If neither field
// is specified, 100% of requests will be mirrored.
//
// +optional
Fraction *Fraction `json:"fraction,omitempty"`
}
// HTTPCORSFilter defines a filter that that configures Cross-Origin Request
// Sharing (CORS).
type HTTPCORSFilter struct {
// AllowOrigins indicates whether the response can be shared with requested
// resource from the given `Origin`.
//
// The `Origin` consists of a scheme and a host, with an optional port, and
// takes the form `<scheme>://<host>(:<port>)`.
//
// Valid values for scheme are: `http` and `https`.
//
// Valid values for port are any integer between 1 and 65535 (the list of
// available TCP/UDP ports). Note that, if not included, port `80` is
// assumed for `http` scheme origins, and port `443` is assumed for `https`
// origins. This may affect origin matching.
//
// The host part of the origin may contain the wildcard character `*`. These
// wildcard characters behave as follows:
//
// * `*` is a greedy match to the _left_, including any number of
// DNS labels to the left of its position. This also means that
// `*` will include any number of period `.` characters to the
// left of its position.
// * A wildcard by itself matches all hosts.
//
// An origin value that includes _only_ the `*` character indicates requests
// from all `Origin`s are allowed.
//
// When the `AllowOrigins` field is configured with multiple origins, it
// means the server supports clients from multiple origins. If the request
// `Origin` matches the configured allowed origins, the gateway must return
// the given `Origin` and sets value of the header
// `Access-Control-Allow-Origin` same as the `Origin` header provided by the
// client.
//
// The status code of a successful response to a "preflight" request is
// always an OK status (i.e., 204 or 200).
//
// If the request `Origin` does not match the configured allowed origins,
// the gateway returns 204/200 response but doesn't set the relevant
// cross-origin response headers. Alternatively, the gateway responds with
// 403 status to the "preflight" request is denied, coupled with omitting
// the CORS headers. The cross-origin request fails on the client side.
// Therefore, the client doesn't attempt the actual cross-origin request.
//
// The `Access-Control-Allow-Origin` response header can only use `*`
// wildcard as value when the `AllowCredentials` field is false or omitted.
//
// When the `AllowCredentials` field is true and `AllowOrigins` field
// specified with the `*` wildcard, the gateway must return a single origin
// in the value of the `Access-Control-Allow-Origin` response header,
// instead of specifying the `*` wildcard. The value of the header
// `Access-Control-Allow-Origin` is same as the `Origin` header provided by
// the client.
//
// Support: Extended
// +listType=set
// +kubebuilder:validation:MaxItems=64
// +kubebuilder:validation:XValidation:message="AllowOrigins cannot contain '*' alongside other origins",rule="!('*' in self && self.size() > 1)"
// +optional
AllowOrigins []CORSOrigin `json:"allowOrigins,omitempty"`
// AllowCredentials indicates whether the actual cross-origin request allows
// to include credentials.
//
// When set to true, the gateway will include the `Access-Control-Allow-Credentials`
// response header with value true (case-sensitive).
//
// When set to false or omitted the gateway will omit the header
// `Access-Control-Allow-Credentials` entirely (this is the standard CORS
// behavior).
//
// Support: Extended
//
// +optional
AllowCredentials *bool `json:"allowCredentials,omitempty"`
// AllowMethods indicates which HTTP methods are supported for accessing the
// requested resource.
//
// Valid values are any method defined by RFC9110, along with the special
// value `*`, which represents all HTTP methods are allowed.
//
// Method names are case sensitive, so these values are also case-sensitive.
// (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1)
//
// Multiple method names in the value of the `Access-Control-Allow-Methods`
// response header are separated by a comma (",").
//
// A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`.
// (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The
// CORS-safelisted methods are always allowed, regardless of whether they
// are specified in the `AllowMethods` field.
//
// When the `AllowMethods` field is configured with one or more methods, the
// gateway must return the `Access-Control-Allow-Methods` response header
// which value is present in the `AllowMethods` field.
//
// If the HTTP method of the `Access-Control-Request-Method` request header
// is not included in the list of methods specified by the response header
// `Access-Control-Allow-Methods`, it will present an error on the client
// side.
//
// The `Access-Control-Allow-Methods` response header can only use `*`
// wildcard as value when the `AllowCredentials` field is false or omitted.
//
// When the `AllowCredentials` field is true and `AllowMethods` field
// specified with the `*` wildcard, the gateway must specify one HTTP method
// in the value of the Access-Control-Allow-Methods response header. The
// value of the header `Access-Control-Allow-Methods` is same as the
// `Access-Control-Request-Method` header provided by the client. If the
// header `Access-Control-Request-Method` is not included in the request,
// the gateway will omit the `Access-Control-Allow-Methods` response header,
// instead of specifying the `*` wildcard. A Gateway implementation may
// choose to add implementation-specific default methods.
//
// Support: Extended
//
// +listType=set
// +kubebuilder:validation:MaxItems=9
// +kubebuilder:validation:XValidation:message="AllowMethods cannot contain '*' alongside other methods",rule="!('*' in self && self.size() > 1)"
// +optional
AllowMethods []HTTPMethodWithWildcard `json:"allowMethods,omitempty"`
// AllowHeaders indicates which HTTP request headers are supported for
// accessing the requested resource.
//
// Header names are not case sensitive.
//
// Multiple header names in the value of the `Access-Control-Allow-Headers`
// response header are separated by a comma (",").
//
// When the `AllowHeaders` field is configured with one or more headers, the
// gateway must return the `Access-Control-Allow-Headers` response header
// which value is present in the `AllowHeaders` field.
//
// If any header name in the `Access-Control-Request-Headers` request header
// is not included in the list of header names specified by the response
// header `Access-Control-Allow-Headers`, it will present an error on the
// client side.
//
// If any header name in the `Access-Control-Allow-Headers` response header
// does not recognize by the client, it will also occur an error on the
// client side.
//
// A wildcard indicates that the requests with all HTTP headers are allowed.
// The `Access-Control-Allow-Headers` response header can only use `*`
// wildcard as value when the `AllowCredentials` field is false or omitted.
//
// When the `AllowCredentials` field is true and `AllowHeaders` field
// specified with the `*` wildcard, the gateway must specify one or more
// HTTP headers in the value of the `Access-Control-Allow-Headers` response
// header. The value of the header `Access-Control-Allow-Headers` is same as
// the `Access-Control-Request-Headers` header provided by the client. If
// the header `Access-Control-Request-Headers` is not included in the
// request, the gateway will omit the `Access-Control-Allow-Headers`
// response header, instead of specifying the `*` wildcard. A Gateway
// implementation may choose to add implementation-specific default headers.
//
// Support: Extended
//
// +listType=set
// +kubebuilder:validation:MaxItems=64
// +optional
AllowHeaders []HTTPHeaderName `json:"allowHeaders,omitempty"`
// ExposeHeaders indicates which HTTP response headers can be exposed
// to client-side scripts in response to a cross-origin request.
//
// A CORS-safelisted response header is an HTTP header in a CORS response
// that it is considered safe to expose to the client scripts.
// The CORS-safelisted response headers include the following headers:
// `Cache-Control`
// `Content-Language`
// `Content-Length`
// `Content-Type`
// `Expires`
// `Last-Modified`
// `Pragma`
// (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name)
// The CORS-safelisted response headers are exposed to client by default.
//
// When an HTTP header name is specified using the `ExposeHeaders` field,
// this additional header will be exposed as part of the response to the
// client.
//
// Header names are not case sensitive.
//
// Multiple header names in the value of the `Access-Control-Expose-Headers`
// response header are separated by a comma (",").
//
// A wildcard indicates that the responses with all HTTP headers are exposed
// to clients. The `Access-Control-Expose-Headers` response header can only
// use `*` wildcard as value when the `AllowCredentials` field is false or omitted.
//
// Support: Extended
//
// +optional
// +listType=set
// +kubebuilder:validation:MaxItems=64
ExposeHeaders []HTTPHeaderName `json:"exposeHeaders,omitempty"`
// MaxAge indicates the duration (in seconds) for the client to cache the
// results of a "preflight" request.
//
// The information provided by the `Access-Control-Allow-Methods` and
// `Access-Control-Allow-Headers` response headers can be cached by the
// client until the time specified by `Access-Control-Max-Age` elapses.
//
// The default value of `Access-Control-Max-Age` response header is 5
// (seconds).
//
// +optional
// +kubebuilder:default=5
// +kubebuilder:validation:Minimum=1
MaxAge int32 `json:"maxAge,omitempty"`
}
// HTTPRouteExternalAuthProtcol specifies what protocol should be used
// for communicating with an external authorization server.
//
// Valid values are supplied as constants below.
type HTTPRouteExternalAuthProtocol string
const (
HTTPRouteExternalAuthGRPCProtocol HTTPRouteExternalAuthProtocol = "GRPC"
HTTPRouteExternalAuthHTTPProtocol HTTPRouteExternalAuthProtocol = "HTTP"
)
// HTTPExternalAuthFilter defines a filter that modifies requests by sending
// request details to an external authorization server.
//
// Support: Extended
// Feature Name: HTTPRouteExternalAuth
// +kubebuilder:validation:XValidation:message="grpc must be specified when protocol is set to 'GRPC'",rule="self.protocol == 'GRPC' ? has(self.grpc) : true"
// +kubebuilder:validation:XValidation:message="protocol must be 'GRPC' when grpc is set",rule="has(self.grpc) ? self.protocol == 'GRPC' : true"
// +kubebuilder:validation:XValidation:message="http must be specified when protocol is set to 'HTTP'",rule="self.protocol == 'HTTP' ? has(self.http) : true"
// +kubebuilder:validation:XValidation:message="protocol must be 'HTTP' when http is set",rule="has(self.http) ? self.protocol == 'HTTP' : true"
type HTTPExternalAuthFilter struct {
// ExternalAuthProtocol describes which protocol to use when communicating with an
// ext_authz authorization server.
//
// When this is set to GRPC, each backend must use the Envoy ext_authz protocol
// on the port specified in `backendRefs`. Requests and responses are defined
// in the protobufs explained at:
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto
//
// When this is set to HTTP, each backend must respond with a `200` status
// code in on a successful authorization. Any other code is considered
// an authorization failure.
//
// Feature Names:
// GRPC Support - HTTPRouteExternalAuthGRPC
// HTTP Support - HTTPRouteExternalAuthHTTP
//
// +unionDiscriminator
// +required
// +kubebuilder:validation:Enum=HTTP;GRPC
ExternalAuthProtocol HTTPRouteExternalAuthProtocol `json:"protocol,omitempty"`
// BackendRef is a reference to a backend to send authorization
// requests to.
//
// The backend must speak the selected protocol (GRPC or HTTP) on the
// referenced port.
//
// If the backend service requires TLS, use BackendTLSPolicy to tell the
// implementation to supply the TLS details to be used to connect to that
// backend.
//
// +required
BackendRef BackendObjectReference `json:"backendRef,omitempty"`
// GRPCAuthConfig contains configuration for communication with ext_authz
// protocol-speaking backends.
//
// If unset, implementations must assume the default behavior for each
// included field is intended.
//
// +optional
GRPCAuthConfig *GRPCAuthConfig `json:"grpc,omitempty"`
// HTTPAuthConfig contains configuration for communication with HTTP-speaking
// backends.
//
// If unset, implementations must assume the default behavior for each
// included field is intended.
//
// +optional
HTTPAuthConfig *HTTPAuthConfig `json:"http,omitempty"`
// ForwardBody controls if requests to the authorization server should include
// the body of the client request; and if so, how big that body is allowed
// to be.
//
// It is expected that implementations will buffer the request body up to
// `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a
// 4xx series error (413 or 403 are common examples), and fail processing
// of the filter.
//
// If unset, or `forwardBody.maxSize` is set to `0`, then the body will not
// be forwarded.
//
// Feature Name: HTTPRouteExternalAuthForwardBody
//
//
// +optional
ForwardBody *ForwardBodyConfig `json:"forwardBody,omitempty"`
}
// GRPCAuthConfig contains configuration for communication with Auth server
// backends that speak Envoy's ext_authz gRPC protocol.
//
// Requests and responses are defined in the protobufs explained at:
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto
type GRPCAuthConfig struct {
// AllowedRequestHeaders specifies what headers from the client request
// will be sent to the authorization server.
//
// If this list is empty, then all headers must be sent.
//
// If the list has entries, only those entries must be sent.
//
// +optional
// +listType=set
// +kubebuilder:validation:MaxLength=64
AllowedRequestHeaders []string `json:"allowedHeaders,omitempty"`
}
// HTTPAuthConfig contains configuration for communication with HTTP-speaking
// backends.
type HTTPAuthConfig struct {
// Path sets the prefix that paths from the client request will have added
// when forwarded to the authorization server.
//
// When empty or unspecified, no prefix is added.
//
// Valid values are the same as the "value" regex for path values in the `match`
// stanza, and the validation regex will screen out invalid paths in the same way.
// Even with the validation, implementations MUST sanitize this input before using it
// directly.
//
// +optional
// +kubebuilder:validation:MaxLength=1024
// +kubebuilder:validation:Pattern="^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$"
Path string `json:"path,omitempty"`
// AllowedRequestHeaders specifies what additional headers from the client request
// will be sent to the authorization server.
//
// The following headers must always be sent to the authorization server,
// regardless of this setting:
//
// * `Host`
// * `Method`
// * `Path`
// * `Content-Length`
// * `Authorization`
//
// If this list is empty, then only those headers must be sent.
//
// Note that `Content-Length` has a special behavior, in that the length
// sent must be correct for the actual request to the external authorization
// server - that is, it must reflect the actual number of bytes sent in the
// body of the request to the authorization server.
//
// So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set
// to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set
// to anything other than `0`, then the `Content-Length` of the authorization
// request must be set to the actual number of bytes forwarded.
//
// +optional
// +listType=set
// +kubebuilder:validation:MaxLength=64
AllowedRequestHeaders []string `json:"allowedHeaders,omitempty"`
// AllowedResponseHeaders specifies what headers from the authorization response
// will be copied into the request to the backend.
//
// If this list is empty, then all headers from the authorization server
// except Authority or Host must be copied.
//
// +optional
// +listType=set
// +kubebuilder:validation:MaxLength=64
AllowedResponseHeaders []string `json:"allowedResponseHeaders,omitempty"`
}
// ForwardBody configures if requests to the authorization server should include
// the body of the client request; and if so, how big that body is allowed
// to be.
//
// If empty or unset, do not forward the body.
type ForwardBodyConfig struct {
// MaxSize specifies how large in bytes the largest body that will be buffered
// and sent to the authorization server. If the body size is larger than
// `maxSize`, then the body sent to the authorization server must be
// truncated to `maxSize` bytes.
//
// Experimental note: This behavior needs to be checked against
// various dataplanes; it may need to be changed.
// See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746
// for more.
//
// If 0, the body will not be sent to the authorization server.
// +optional
MaxSize uint16 `json:"maxSize,omitempty"`
}
// HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.
@@ -1187,9 +1833,9 @@ type HTTPBackendRef struct {
// Filters field in HTTPRouteRule.)
//
// +optional
// +listType=atomic
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:validation:XValidation:message="May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both",rule="!(self.exists(f, f.type == 'RequestRedirect') && self.exists(f, f.type == 'URLRewrite'))"
// +kubebuilder:validation:XValidation:message="May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both",rule="!(self.exists(f, f.type == 'RequestRedirect') && self.exists(f, f.type == 'URLRewrite'))"
// +kubebuilder:validation:XValidation:message="RequestHeaderModifier filter cannot be repeated",rule="self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1"
// +kubebuilder:validation:XValidation:message="ResponseHeaderModifier filter cannot be repeated",rule="self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1"
// +kubebuilder:validation:XValidation:message="RequestRedirect filter cannot be repeated",rule="self.filter(f, f.type == 'RequestRedirect').size() <= 1"

View File

@@ -1,9 +1,12 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,12 +27,15 @@ package v1
type LocalObjectReference struct {
// Group is the group of the referent. For example, "gateway.networking.k8s.io".
// When unspecified or empty string, core API group is inferred.
// +required
Group Group `json:"group"`
// Kind is kind of the referent. For example "HTTPRoute" or "Service".
// +required
Kind Kind `json:"kind"`
// Name is the name of the referent.
// +required
Name ObjectName `json:"name"`
}
@@ -50,22 +56,23 @@ type SecretObjectReference struct {
// +kubebuilder:default=""
Group *Group `json:"group"`
// Kind is kind of the referent. For example "HTTPRoute" or "Service".
// Kind is kind of the referent. For example "Secret".
//
// +optional
// +kubebuilder:default=Secret
Kind *Kind `json:"kind"`
// Name is the name of the referent.
// +required
Name ObjectName `json:"name"`
// Namespace is the namespace of the backend. When unspecified, the local
// Namespace is the namespace of the referenced object. When unspecified, the local
// namespace is inferred.
//
// Note that when a namespace is specified, a ReferenceGrant object
// is required in the referent namespace to allow that namespace's
// owner to accept the reference. See the ReferenceGrant documentation
// for details.
// Note that when a namespace different than the local namespace is specified,
// a ReferenceGrant object is required in the referent namespace to allow that
// namespace's owner to accept the reference. See the ReferenceGrant
// documentation for details.
//
// Support: Core
//
@@ -77,10 +84,10 @@ type SecretObjectReference struct {
// specific to BackendRef. It includes a few additional fields and features
// than a regular ObjectReference.
//
// Note that when a namespace is specified, a ReferenceGrant object
// is required in the referent namespace to allow that namespace's
// owner to accept the reference. See the ReferenceGrant documentation
// for details.
// Note that when a namespace different than the local namespace is specified, a
// ReferenceGrant object is required in the referent namespace to allow that
// namespace's owner to accept the reference. See the ReferenceGrant
// documentation for details.
//
// The API object must be valid in the cluster; the Group and Kind must
// be registered in the cluster for this reference to be valid.
@@ -88,6 +95,8 @@ type SecretObjectReference struct {
// References to objects with invalid Group and Kind are not valid, and must
// be rejected by the implementation, with appropriate Conditions set
// on the containing object.
//
// +kubebuilder:validation:XValidation:message="Must have port for Service reference",rule="(size(self.group) == 0 && self.kind == 'Service') ? has(self.port) : true"
type BackendObjectReference struct {
// Group is the group of the referent. For example, "gateway.networking.k8s.io".
// When unspecified or empty string, core API group is inferred.
@@ -96,23 +105,36 @@ type BackendObjectReference struct {
// +kubebuilder:default=""
Group *Group `json:"group,omitempty"`
// Kind is kind of the referent. For example "HTTPRoute" or "Service".
// Kind is the Kubernetes resource kind of the referent. For example
// "Service".
//
// Defaults to "Service" when not specified.
//
// ExternalName services can refer to CNAME DNS records that may live
// outside of the cluster and as such are difficult to reason about in
// terms of conformance. They also may not be safe to forward to (see
// CVE-2021-25740 for more information). Implementations SHOULD NOT
// support ExternalName Services.
//
// Support: Core (Services with a type other than ExternalName)
//
// Support: Implementation-specific (Services with type ExternalName)
//
// +optional
// +kubebuilder:default=Service
Kind *Kind `json:"kind,omitempty"`
// Name is the name of the referent.
// +required
Name ObjectName `json:"name"`
// Namespace is the namespace of the backend. When unspecified, the local
// namespace is inferred.
//
// Note that when a namespace is specified, a ReferenceGrant object
// is required in the referent namespace to allow that namespace's
// owner to accept the reference. See the ReferenceGrant documentation
// for details.
// Note that when a namespace different than the local namespace is specified,
// a ReferenceGrant object is required in the referent namespace to allow that
// namespace's owner to accept the reference. See the ReferenceGrant
// documentation for details.
//
// Support: Core
//
@@ -126,5 +148,43 @@ type BackendObjectReference struct {
// resource or this field.
//
// +optional
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
Port *PortNumber `json:"port,omitempty"`
}
// ObjectReference identifies an API object including its namespace.
//
// The API object must be valid in the cluster; the Group and Kind must
// be registered in the cluster for this reference to be valid.
//
// References to objects with invalid Group and Kind are not valid, and must
// be rejected by the implementation, with appropriate Conditions set
// on the containing object.
type ObjectReference struct {
// Group is the group of the referent. For example, "gateway.networking.k8s.io".
// When set to the empty string, core API group is inferred.
// +required
Group Group `json:"group"`
// Kind is kind of the referent. For example "ConfigMap" or "Service".
// +required
Kind Kind `json:"kind"`
// Name is the name of the referent.
// +required
Name ObjectName `json:"name"`
// Namespace is the namespace of the referenced object. When unspecified, the local
// namespace is inferred.
//
// Note that when a namespace different than the local namespace is specified,
// a ReferenceGrant object is required in the referent namespace to allow that
// namespace's owner to accept the reference. See the ReferenceGrant
// documentation for details.
//
// Support: Core
//
// +optional
Namespace *Namespace `json:"namespace,omitempty"`
}

View File

@@ -25,7 +25,7 @@ import (
// with "Core" support:
//
// * Gateway (Gateway conformance profile)
// * Service (Mesh conformance profile, experimental, ClusterIP Services only)
// * Service (Mesh conformance profile, ClusterIP Services only)
//
// This API may be extended in the future to support additional kinds of parent
// resources.
@@ -49,7 +49,7 @@ type ParentReference struct {
// There are two kinds of parent resources with "Core" support:
//
// * Gateway (Gateway conformance profile)
// * Service (Mesh conformance profile, experimental, ClusterIP Services only)
// * Service (Mesh conformance profile, ClusterIP Services only)
//
// Support for other resources is Implementation-Specific.
//
@@ -86,19 +86,18 @@ type ParentReference struct {
// Name is the name of the referent.
//
// Support: Core
// +required
Name ObjectName `json:"name"`
// SectionName is the name of a section within the target resource. In the
// following resources, SectionName is interpreted as the following:
//
// * Gateway: Listener Name. When both Port (experimental) and SectionName
// * Gateway: Listener name. When both Port (experimental) and SectionName
// are specified, the name and port of the selected listener must match
// both specified values.
// * Service: Port Name. When both Port (experimental) and SectionName
// * Service: Port name. When both Port (experimental) and SectionName
// are specified, the name and port of the selected listener must match
// both specified values. Note that attaching Routes to Services as Parents
// is part of experimental Mesh support and is not supported for any other
// purpose.
// both specified values.
//
// Implementations MAY choose to support attaching Routes to other resources.
// If that is the case, they MUST clearly document how SectionName is
@@ -150,10 +149,31 @@ type ParentReference struct {
// Support: Extended
//
// +optional
// <gateway:experimental>
//
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
Port *PortNumber `json:"port,omitempty"`
}
// GatewayDefaultScope defines the set of default scopes that a Gateway
// can claim, for use in any Route type. At present the only supported
// scopes are "All" and "None". "None" is a special scope which
// explicitly means that the Route MUST NOT attached to any default
// Gateway.
//
// +kubebuilder:validation:Enum=All;None
type GatewayDefaultScope string
const (
// GatewayDefaultScopeAll indicates that a Gateway can claim absolutely
// any Route asking for a default Gateway.
GatewayDefaultScopeAll GatewayDefaultScope = "All"
// GatewayDefaultScopeNone indicates that a Gateway MUST NOT claim
// any Route asking for a default Gateway.
GatewayDefaultScopeNone GatewayDefaultScope = "None"
)
// CommonRouteSpec defines the common attributes that all Routes MUST include
// within their spec.
type CommonRouteSpec struct {
@@ -171,9 +191,8 @@ type CommonRouteSpec struct {
// There are two kinds of parent resources with "Core" support:
//
// * Gateway (Gateway conformance profile)
// <gateway:experimental:description>
// * Service (Mesh conformance profile, experimental, ClusterIP Services only)
// </gateway:experimental:description>
// * Service (Mesh conformance profile, ClusterIP Services only)
//
// This API may be extended in the future to support additional kinds of parent
// resources.
//
@@ -222,19 +241,34 @@ type CommonRouteSpec struct {
// </gateway:experimental:description>
//
// +optional
// +listType=atomic
// +kubebuilder:validation:MaxItems=32
// <gateway:standard:validation:XValidation:message="sectionName must be specified when parentRefs includes 2 or more references to the same parent",rule="self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) || p1.sectionName == '') == (!has(p2.sectionName) || p2.sectionName == '')) : true))">
// <gateway:standard:validation:XValidation:message="sectionName must be unique when parentRefs includes 2 or more references to the same parent",rule="self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName))))">
// <gateway:experimental:validation:XValidation:message="sectionName or port must be specified when parentRefs includes 2 or more references to the same parent",rule="self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '') == (!has(p2.sectionName) || p2.sectionName == '') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))">
// <gateway:experimental:validation:XValidation:message="sectionName or port must be unique when parentRefs includes 2 or more references to the same parent",rule="self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port))))">
ParentRefs []ParentReference `json:"parentRefs,omitempty"`
// UseDefaultGateways indicates the default Gateway scope to use for this
// Route. If unset (the default) or set to None, the Route will not be
// attached to any default Gateway; if set, it will be attached to any
// default Gateway supporting the named scope, subject to the usual rules
// about which Routes a Gateway is allowed to claim.
//
// Think carefully before using this functionality! The set of default
// Gateways supporting the requested scope can change over time without
// any notice to the Route author, and in many situations it will not be
// appropriate to request a default Gateway for a given Route -- for
// example, a Route with specific security requirements should almost
// certainly not use a default Gateway.
//
// +optional
// <gateway:experimental>
UseDefaultGateways GatewayDefaultScope `json:"useDefaultGateways,omitempty"`
}
// PortNumber defines a network port.
//
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
type PortNumber int32
type PortNumber = int32
// BackendRef defines how a Route should forward a request to a Kubernetes
// resource.
@@ -440,6 +474,7 @@ const (
type RouteParentStatus struct {
// ParentRef corresponds with a ParentRef in the spec that this
// RouteParentStatus struct describes the status of.
// +required
ParentRef ParentReference `json:"parentRef"`
// ControllerName is a domain/path string that indicates the name of the
@@ -455,6 +490,7 @@ type RouteParentStatus struct {
// Controllers MUST populate this field when writing status. Controllers should ensure that
// entries to status populated with their ControllerName are cleaned up when they are no
// longer necessary.
// +required
ControllerName GatewayController `json:"controllerName"`
// Conditions describes the status of the route with respect to the Gateway.
@@ -473,14 +509,45 @@ type RouteParentStatus struct {
// There are a number of cases where the "Accepted" condition may not be set
// due to lack of controller visibility, that includes when:
//
// * The Route refers to a non-existent parent.
// * The Route refers to a nonexistent parent.
// * The Route is of a type that the controller does not support.
// * The Route is in a namespace the controller does not have access to.
//
// <gateway:util:excludeFromCRD>
//
// Notes for implementors:
//
// Conditions are a listType `map`, which means that they function like a
// map with a key of the `type` field _in the k8s apiserver_.
//
// This means that implementations must obey some rules when updating this
// section.
//
// * Implementations MUST perform a read-modify-write cycle on this field
// before modifying it. That is, when modifying this field, implementations
// must be confident they have fetched the most recent version of this field,
// and ensure that changes they make are on that recent version.
// * Implementations MUST NOT remove or reorder Conditions that they are not
// directly responsible for. For example, if an implementation sees a Condition
// with type `special.io/SomeField`, it MUST NOT remove, change or update that
// Condition.
// * Implementations MUST always _merge_ changes into Conditions of the same Type,
// rather than creating more than one Condition of the same Type.
// * Implementations MUST always update the `observedGeneration` field of the
// Condition to the `metadata.generation` of the Gateway at the time of update creation.
// * If the `observedGeneration` of a Condition is _greater than_ the value the
// implementation knows about, then it MUST NOT perform the update on that Condition,
// but must wait for a future reconciliation and status update. (The assumption is that
// the implementation's copy of the object is stale and an update will be re-triggered
// if relevant.)
//
// </gateway:util:excludeFromCRD>
//
// +listType=map
// +listMapKey=type
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=8
// +required
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
@@ -502,6 +569,31 @@ type RouteStatus struct {
// A maximum of 32 Gateways will be represented in this list. An empty list
// means the route has not been attached to any Gateway.
//
// <gateway:util:excludeFromCRD>
// Notes for implementors:
//
// While parents is not a listType `map`, this is due to the fact that the
// list key is not scalar, and Kubernetes is unable to represent this.
//
// Parent status MUST be considered to be namespaced by the combination of
// the parentRef and controllerName fields, and implementations should keep
// the following rules in mind when updating this status:
//
// * Implementations MUST update only entries that have a matching value of
// `controllerName` for that implementation.
// * Implementations MUST NOT update entries with non-matching `controllerName`
// fields.
// * Implementations MUST treat each `parentRef`` in the Route separately and
// update its status based on the relationship with that parent.
// * Implementations MUST perform a read-modify-write cycle on this field
// before modifying it. That is, when modifying this field, implementations
// must be confident they have fetched the most recent version of this field,
// and ensure that changes they make are on that recent version.
//
// </gateway:util:excludeFromCRD>
//
// +required
// +listType=atomic
// +kubebuilder:validation:MaxItems=32
Parents []RouteParentStatus `json:"parents"`
}
@@ -539,6 +631,30 @@ type Hostname string
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
type PreciseHostname string
// AbsoluteURI represents a Uniform Resource Identifier (URI) as defined by RFC3986.
// The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and
// encoding rules specified in RFC3986. The AbsoluteURI MUST include both a
// scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that
// include an authority MUST include a fully qualified domain name or
// IP address as the host.
// <gateway:util:excludeFromCRD> The below regex is taken from the regex section in RFC 3986 with a slight modification to enforce a full URI and not relative. </gateway:util:excludeFromCRD>
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))?`
type AbsoluteURI string
// The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and
// encoding rules specified in RFC3986. The CORSOrigin MUST include both a
// scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character.
// URIs that include an authority MUST include a fully qualified domain name or
// IP address as the host.
// <gateway:util:excludeFromCRD> The below regex was generated to simplify the assertion of scheme://host:<port> being port optional </gateway:util:excludeFromCRD>
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`(^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$)`
type CORSOrigin string
// Group refers to a Kubernetes Group. It must either be an empty string or a
// RFC 1123 subdomain.
//
@@ -576,7 +692,7 @@ type Group string
type Kind string
// ObjectName refers to the name of a Kubernetes object.
// Object names can have a variety of forms, including RFC1123 subdomains,
// Object names can have a variety of forms, including RFC 1123 subdomains,
// RFC 1123 labels, or RFC 1035 labels.
//
// +kubebuilder:validation:MinLength=1
@@ -606,11 +722,22 @@ type Namespace string
// SectionName is the name of a section in a Kubernetes resource.
//
// In the following resources, SectionName is interpreted as the following:
//
// * Gateway: Listener name
// * HTTPRoute: HTTPRouteRule name
// * Service: Port name
//
// Section names can have a variety of forms, including RFC 1123 subdomains,
// RFC 1123 labels, or RFC 1035 labels.
//
// This validation is based off of the corresponding Kubernetes validation:
// https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go#L208
//
// Valid values include:
//
// * "example"
// * "foo-example"
// * "example.com"
// * "foo.example.com"
//
@@ -655,11 +782,11 @@ type GatewayController string
// Invalid values include:
//
// * example~ - "~" is an invalid character
// * example.com. - can not start or end with "."
// * example.com. - cannot start or end with "."
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]/?)*$`
// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$`
type AnnotationKey string
// AnnotationValue is the value of an annotation in Gateway API. This is used
@@ -671,6 +798,45 @@ type AnnotationKey string
// +kubebuilder:validation:MaxLength=4096
type AnnotationValue string
// LabelKey is the key of a label in the Gateway API. This is used for validation
// of maps such as Gateway infrastructure labels. This matches the Kubernetes
// "qualified name" validation that is used for labels.
//
// Valid values include:
//
// * example
// * example.com
// * example.com/path
// * example.com/path.html
//
// Invalid values include:
//
// * example~ - "~" is an invalid character
// * example.com. - cannot start or end with "."
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$`
type LabelKey string
// LabelValue is the value of a label in the Gateway API. This is used for validation
// of maps such as Gateway infrastructure labels. This matches the Kubernetes
// label validation rules:
// * must be 63 characters or less (can be empty),
// * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]),
// * could contain dashes (-), underscores (_), dots (.), and alphanumerics between.
//
// Valid values include:
//
// * MyValue
// * my.name
// * 123-my-value
//
// +kubebuilder:validation:MinLength=0
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$`
type LabelValue string
// AddressType defines how a network address is represented as a text string.
// This may take two possible forms:
//
@@ -712,7 +878,7 @@ const (
// (see [RFC 5952](https://tools.ietf.org/html/rfc5952)).
//
// This type is intended for specific addresses. Address ranges are not
// supported (e.g. you can not use a CIDR range like 127.0.0.0/24 as an
// supported (e.g. you cannot use a CIDR range like 127.0.0.0/24 as an
// IPAddress).
//
// Support: Extended
@@ -736,3 +902,129 @@ const (
// Support: Implementation-specific
NamedAddressType AddressType = "NamedAddress"
)
// SessionPersistence defines the desired state of SessionPersistence.
// +kubebuilder:validation:XValidation:message="AbsoluteTimeout must be specified when cookie lifetimeType is Permanent",rule="!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) || self.cookieConfig.lifetimeType != 'Permanent' || has(self.absoluteTimeout)"
type SessionPersistence struct {
// SessionName defines the name of the persistent session token
// which may be reflected in the cookie or the header. Users
// should avoid reusing session names to prevent unintended
// consequences, such as rejection or unpredictable behavior.
//
// Support: Implementation-specific
//
// +optional
// +kubebuilder:validation:MaxLength=128
SessionName *string `json:"sessionName,omitempty"`
// AbsoluteTimeout defines the absolute timeout of the persistent
// session. Once the AbsoluteTimeout duration has elapsed, the
// session becomes invalid.
//
// Support: Extended
//
// +optional
AbsoluteTimeout *Duration `json:"absoluteTimeout,omitempty"`
// IdleTimeout defines the idle timeout of the persistent session.
// Once the session has been idle for more than the specified
// IdleTimeout duration, the session becomes invalid.
//
// Support: Extended
//
// +optional
IdleTimeout *Duration `json:"idleTimeout,omitempty"`
// Type defines the type of session persistence such as through
// the use a header or cookie. Defaults to cookie based session
// persistence.
//
// Support: Core for "Cookie" type
//
// Support: Extended for "Header" type
//
// +optional
// +kubebuilder:default=Cookie
Type *SessionPersistenceType `json:"type,omitempty"`
// CookieConfig provides configuration settings that are specific
// to cookie-based session persistence.
//
// Support: Core
//
// +optional
CookieConfig *CookieConfig `json:"cookieConfig,omitempty"`
}
// +kubebuilder:validation:Enum=Cookie;Header
type SessionPersistenceType string
const (
// CookieBasedSessionPersistence specifies cookie-based session
// persistence.
//
// Support: Core
CookieBasedSessionPersistence SessionPersistenceType = "Cookie"
// HeaderBasedSessionPersistence specifies header-based session
// persistence.
//
// Support: Extended
HeaderBasedSessionPersistence SessionPersistenceType = "Header"
)
// CookieConfig defines the configuration for cookie-based session persistence.
type CookieConfig struct {
// LifetimeType specifies whether the cookie has a permanent or
// session-based lifetime. A permanent cookie persists until its
// specified expiry time, defined by the Expires or Max-Age cookie
// attributes, while a session cookie is deleted when the current
// session ends.
//
// When set to "Permanent", AbsoluteTimeout indicates the
// cookie's lifetime via the Expires or Max-Age cookie attributes
// and is required.
//
// When set to "Session", AbsoluteTimeout indicates the
// absolute lifetime of the cookie tracked by the gateway and
// is optional.
//
// Defaults to "Session".
//
// Support: Core for "Session" type
//
// Support: Extended for "Permanent" type
//
// +optional
// +kubebuilder:default=Session
LifetimeType *CookieLifetimeType `json:"lifetimeType,omitempty"`
}
// +kubebuilder:validation:Enum=Permanent;Session
type CookieLifetimeType string
const (
// SessionCookieLifetimeType specifies the type for a session
// cookie.
//
// Support: Core
SessionCookieLifetimeType CookieLifetimeType = "Session"
// PermanentCookieLifetimeType specifies the type for a permanent
// cookie.
//
// Support: Extended
PermanentCookieLifetimeType CookieLifetimeType = "Permanent"
)
// +kubebuilder:validation:XValidation:message="numerator must be less than or equal to denominator",rule="self.numerator <= self.denominator"
type Fraction struct {
// +kubebuilder:validation:Minimum=0
// +required
Numerator int32 `json:"numerator"`
// +optional
// +kubebuilder:default=100
// +kubebuilder:validation:Minimum=1
Denominator *int32 `json:"denominator,omitempty"`
}

View File

@@ -46,7 +46,7 @@ func (in *BackendObjectReference) DeepCopyInto(out *BackendObjectReference) {
}
if in.Port != nil {
in, out := &in.Port, &out.Port
*out = new(PortNumber)
*out = new(int32)
**out = **in
}
return
@@ -107,6 +107,111 @@ func (in *CommonRouteSpec) DeepCopy() *CommonRouteSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CookieConfig) DeepCopyInto(out *CookieConfig) {
*out = *in
if in.LifetimeType != nil {
in, out := &in.LifetimeType, &out.LifetimeType
*out = new(CookieLifetimeType)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookieConfig.
func (in *CookieConfig) DeepCopy() *CookieConfig {
if in == nil {
return nil
}
out := new(CookieConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForwardBodyConfig) DeepCopyInto(out *ForwardBodyConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForwardBodyConfig.
func (in *ForwardBodyConfig) DeepCopy() *ForwardBodyConfig {
if in == nil {
return nil
}
out := new(ForwardBodyConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Fraction) DeepCopyInto(out *Fraction) {
*out = *in
if in.Denominator != nil {
in, out := &in.Denominator, &out.Denominator
*out = new(int32)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Fraction.
func (in *Fraction) DeepCopy() *Fraction {
if in == nil {
return nil
}
out := new(Fraction)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GRPCAuthConfig) DeepCopyInto(out *GRPCAuthConfig) {
*out = *in
if in.AllowedRequestHeaders != nil {
in, out := &in.AllowedRequestHeaders, &out.AllowedRequestHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCAuthConfig.
func (in *GRPCAuthConfig) DeepCopy() *GRPCAuthConfig {
if in == nil {
return nil
}
out := new(GRPCAuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPAuthConfig) DeepCopyInto(out *HTTPAuthConfig) {
*out = *in
if in.AllowedRequestHeaders != nil {
in, out := &in.AllowedRequestHeaders, &out.AllowedRequestHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllowedResponseHeaders != nil {
in, out := &in.AllowedResponseHeaders, &out.AllowedResponseHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPAuthConfig.
func (in *HTTPAuthConfig) DeepCopy() *HTTPAuthConfig {
if in == nil {
return nil
}
out := new(HTTPAuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPBackendRef) DeepCopyInto(out *HTTPBackendRef) {
*out = *in
@@ -131,6 +236,79 @@ func (in *HTTPBackendRef) DeepCopy() *HTTPBackendRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPCORSFilter) DeepCopyInto(out *HTTPCORSFilter) {
*out = *in
if in.AllowOrigins != nil {
in, out := &in.AllowOrigins, &out.AllowOrigins
*out = make([]CORSOrigin, len(*in))
copy(*out, *in)
}
if in.AllowCredentials != nil {
in, out := &in.AllowCredentials, &out.AllowCredentials
*out = new(bool)
**out = **in
}
if in.AllowMethods != nil {
in, out := &in.AllowMethods, &out.AllowMethods
*out = make([]HTTPMethodWithWildcard, len(*in))
copy(*out, *in)
}
if in.AllowHeaders != nil {
in, out := &in.AllowHeaders, &out.AllowHeaders
*out = make([]HTTPHeaderName, len(*in))
copy(*out, *in)
}
if in.ExposeHeaders != nil {
in, out := &in.ExposeHeaders, &out.ExposeHeaders
*out = make([]HTTPHeaderName, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPCORSFilter.
func (in *HTTPCORSFilter) DeepCopy() *HTTPCORSFilter {
if in == nil {
return nil
}
out := new(HTTPCORSFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPExternalAuthFilter) DeepCopyInto(out *HTTPExternalAuthFilter) {
*out = *in
in.BackendRef.DeepCopyInto(&out.BackendRef)
if in.GRPCAuthConfig != nil {
in, out := &in.GRPCAuthConfig, &out.GRPCAuthConfig
*out = new(GRPCAuthConfig)
(*in).DeepCopyInto(*out)
}
if in.HTTPAuthConfig != nil {
in, out := &in.HTTPAuthConfig, &out.HTTPAuthConfig
*out = new(HTTPAuthConfig)
(*in).DeepCopyInto(*out)
}
if in.ForwardBody != nil {
in, out := &in.ForwardBody, &out.ForwardBody
*out = new(ForwardBodyConfig)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPExternalAuthFilter.
func (in *HTTPExternalAuthFilter) DeepCopy() *HTTPExternalAuthFilter {
if in == nil {
return nil
}
out := new(HTTPExternalAuthFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) {
*out = *in
@@ -276,6 +454,16 @@ func (in *HTTPQueryParamMatch) DeepCopy() *HTTPQueryParamMatch {
func (in *HTTPRequestMirrorFilter) DeepCopyInto(out *HTTPRequestMirrorFilter) {
*out = *in
in.BackendRef.DeepCopyInto(&out.BackendRef)
if in.Percent != nil {
in, out := &in.Percent, &out.Percent
*out = new(int32)
**out = **in
}
if in.Fraction != nil {
in, out := &in.Fraction, &out.Fraction
*out = new(Fraction)
(*in).DeepCopyInto(*out)
}
return
}
@@ -309,7 +497,7 @@ func (in *HTTPRequestRedirectFilter) DeepCopyInto(out *HTTPRequestRedirectFilter
}
if in.Port != nil {
in, out := &in.Port, &out.Port
*out = new(PortNumber)
*out = new(int32)
**out = **in
}
if in.StatusCode != nil {
@@ -386,6 +574,16 @@ func (in *HTTPRouteFilter) DeepCopyInto(out *HTTPRouteFilter) {
*out = new(HTTPURLRewriteFilter)
(*in).DeepCopyInto(*out)
}
if in.CORS != nil {
in, out := &in.CORS, &out.CORS
*out = new(HTTPCORSFilter)
(*in).DeepCopyInto(*out)
}
if in.ExternalAuth != nil {
in, out := &in.ExternalAuth, &out.ExternalAuth
*out = new(HTTPExternalAuthFilter)
(*in).DeepCopyInto(*out)
}
if in.ExtensionRef != nil {
in, out := &in.ExtensionRef, &out.ExtensionRef
*out = new(LocalObjectReference)
@@ -477,9 +675,45 @@ func (in *HTTPRouteMatch) DeepCopy() *HTTPRouteMatch {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteRetry) DeepCopyInto(out *HTTPRouteRetry) {
*out = *in
if in.Codes != nil {
in, out := &in.Codes, &out.Codes
*out = make([]HTTPRouteRetryStatusCode, len(*in))
copy(*out, *in)
}
if in.Attempts != nil {
in, out := &in.Attempts, &out.Attempts
*out = new(int)
**out = **in
}
if in.Backoff != nil {
in, out := &in.Backoff, &out.Backoff
*out = new(Duration)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteRetry.
func (in *HTTPRouteRetry) DeepCopy() *HTTPRouteRetry {
if in == nil {
return nil
}
out := new(HTTPRouteRetry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteRule) DeepCopyInto(out *HTTPRouteRule) {
*out = *in
if in.Name != nil {
in, out := &in.Name, &out.Name
*out = new(SectionName)
**out = **in
}
if in.Matches != nil {
in, out := &in.Matches, &out.Matches
*out = make([]HTTPRouteMatch, len(*in))
@@ -506,6 +740,16 @@ func (in *HTTPRouteRule) DeepCopyInto(out *HTTPRouteRule) {
*out = new(HTTPRouteTimeouts)
(*in).DeepCopyInto(*out)
}
if in.Retry != nil {
in, out := &in.Retry, &out.Retry
*out = new(HTTPRouteRetry)
(*in).DeepCopyInto(*out)
}
if in.SessionPersistence != nil {
in, out := &in.SessionPersistence, &out.SessionPersistence
*out = new(SessionPersistence)
(*in).DeepCopyInto(*out)
}
return
}
@@ -633,6 +877,27 @@ func (in *LocalObjectReference) DeepCopy() *LocalObjectReference {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectReference) DeepCopyInto(out *ObjectReference) {
*out = *in
if in.Namespace != nil {
in, out := &in.Namespace, &out.Namespace
*out = new(Namespace)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference.
func (in *ObjectReference) DeepCopy() *ObjectReference {
if in == nil {
return nil
}
out := new(ObjectReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ParentReference) DeepCopyInto(out *ParentReference) {
*out = *in
@@ -658,7 +923,7 @@ func (in *ParentReference) DeepCopyInto(out *ParentReference) {
}
if in.Port != nil {
in, out := &in.Port, &out.Port
*out = new(PortNumber)
*out = new(int32)
**out = **in
}
return
@@ -751,3 +1016,44 @@ func (in *SecretObjectReference) DeepCopy() *SecretObjectReference {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SessionPersistence) DeepCopyInto(out *SessionPersistence) {
*out = *in
if in.SessionName != nil {
in, out := &in.SessionName, &out.SessionName
*out = new(string)
**out = **in
}
if in.AbsoluteTimeout != nil {
in, out := &in.AbsoluteTimeout, &out.AbsoluteTimeout
*out = new(Duration)
**out = **in
}
if in.IdleTimeout != nil {
in, out := &in.IdleTimeout, &out.IdleTimeout
*out = new(Duration)
**out = **in
}
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(SessionPersistenceType)
**out = **in
}
if in.CookieConfig != nil {
in, out := &in.CookieConfig, &out.CookieConfig
*out = new(CookieConfig)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionPersistence.
func (in *SessionPersistence) DeepCopy() *SessionPersistence {
if in == nil {
return nil
}
out := new(SessionPersistence)
in.DeepCopyInto(out)
return out
}

View File

@@ -103,6 +103,8 @@ type AdvancedConfig struct {
// HorizontalPodAutoscalerConfig specifies horizontal scale config
type HorizontalPodAutoscalerConfig struct {
// +optional
Name string `json:"name,omitempty"`
// +optional
Behavior *autoscalingv2beta2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"`
}

View File

@@ -1,5 +1,5 @@
package traefik
const (
GroupName = "traefik.containo.us"
GroupName = "traefik.io"
)

View File

@@ -1,5 +1,5 @@
// +k8s:deepcopy-gen=package
// Package v1alpha1 is the v1alpha1 version of the API.
// +groupName=traefik.containo.us
// +groupName=traefik.io
package v1alpha1

View File

@@ -396,7 +396,7 @@ func newDeploymentControllerTestCanary(cc canaryConfigs) *flaggerv1.Canary {
APIVersion: "apps/v1",
Kind: "Deployment",
},
AutoscalerRef: &flaggerv1.AutoscalerRefernce{
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
APIVersion: "autoscaling/v2beta2",
Kind: "HorizontalPodAutoscaler",

View File

@@ -20,12 +20,15 @@ import (
"go.uber.org/zap"
"k8s.io/client-go/kubernetes"
"github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
knative "knative.dev/serving/pkg/client/clientset/versioned"
)
type Factory struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
knativeClient knative.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
@@ -34,6 +37,7 @@ type Factory struct {
func NewFactory(kubeClient kubernetes.Interface,
flaggerClient clientset.Interface,
knativeClient knative.Interface,
configTracker Tracker,
labels []string,
includeLabelPrefix []string,
@@ -41,6 +45,7 @@ func NewFactory(kubeClient kubernetes.Interface,
return &Factory{
kubeClient: kubeClient,
flaggerClient: flaggerClient,
knativeClient: knativeClient,
logger: logger,
configTracker: configTracker,
labels: labels,
@@ -48,7 +53,7 @@ func NewFactory(kubeClient kubernetes.Interface,
}
}
func (factory *Factory) Controller(kind string) Controller {
func (factory *Factory) Controller(obj v1beta1.LocalObjectReference) Controller {
deploymentCtrl := &DeploymentController{
logger: factory.logger,
kubeClient: factory.kubeClient,
@@ -71,14 +76,22 @@ func (factory *Factory) Controller(kind string) Controller {
flaggerClient: factory.flaggerClient,
includeLabelPrefix: factory.includeLabelPrefix,
}
knativeCtrl := &KnativeController{
flaggerClient: factory.flaggerClient,
knativeClient: factory.knativeClient,
}
switch kind {
switch obj.Kind {
case "DaemonSet":
return daemonSetCtrl
case "Deployment":
return deploymentCtrl
case "Service":
return serviceCtrl
if obj.IsKnativeService() {
return knativeCtrl
} else {
return serviceCtrl
}
default:
return deploymentCtrl
}

View File

@@ -0,0 +1,161 @@
package canary
import (
"context"
"fmt"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
"go.uber.org/zap"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
serving "knative.dev/serving/pkg/apis/serving/v1"
knative "knative.dev/serving/pkg/client/clientset/versioned"
)
type KnativeController struct {
flaggerClient clientset.Interface
knativeClient knative.Interface
logger *zap.SugaredLogger
}
// IsPrimaryReady checks if the primary revision is ready
func (kc *KnativeController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error) {
service, err := kc.knativeClient.ServingV1().Services(cd.Namespace).Get(context.TODO(), cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Knative Service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
revisionName, exists := service.Annotations["flagger.app/primary-revision"]
if !exists {
return true, fmt.Errorf("Knative Service %s.%s primary revision annotation not found", cd.Spec.TargetRef.Name, cd.Namespace)
}
revision, err := kc.knativeClient.ServingV1().Revisions(cd.Namespace).Get(context.TODO(), revisionName, metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Knative Revision %s.%s get query error: %w", revisionName, cd.Namespace, err)
}
if !revision.IsReady() {
return true, fmt.Errorf("Knative Revision %s.%s is not ready", revision.Name, cd.Namespace)
}
return true, nil
}
// IsCanaryReady checks if the canary revision is ready
func (kc *KnativeController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error) {
service, err := kc.knativeClient.ServingV1().Services(cd.Namespace).Get(context.TODO(), cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Knative Service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
revision, err := kc.knativeClient.ServingV1().Revisions(cd.Namespace).Get(context.TODO(), service.Status.LatestCreatedRevisionName, metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Knative Revision %s.%s get query error: %w", service.Status.LatestCreatedRevisionName, cd.Namespace, err)
}
if !revision.IsReady() {
return true, fmt.Errorf("Knative Revision %s.%s is not ready", revision.Name, cd.Namespace)
}
return true, nil
}
func (kc *KnativeController) GetMetadata(canary *flaggerv1.Canary) (string, string, map[string]int32, error) {
return "", "", make(map[string]int32), nil
}
// SyncStatus encodes list of revisions and updates the canary status
func (kc *KnativeController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
service, err := kc.knativeClient.ServingV1().Services(cd.Namespace).Get(context.TODO(), cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("Knative Service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
return syncCanaryStatus(kc.flaggerClient, cd, status, service.Status.LatestCreatedRevisionName, func(copy *flaggerv1.Canary) {})
}
// SetStatusFailedChecks updates the canary failed checks counter
func (kc *KnativeController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
return setStatusFailedChecks(kc.flaggerClient, cd, val)
}
// SetStatusWeight updates the canary status weight value
func (kc *KnativeController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
return setStatusWeight(kc.flaggerClient, cd, val)
}
// SetStatusIterations updates the canary status iterations value
func (kc *KnativeController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
return setStatusIterations(kc.flaggerClient, cd, val)
}
// SetStatusPhase updates the canary status phase
func (kc *KnativeController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
return setStatusPhase(kc.flaggerClient, cd, phase)
}
// Initialize configures the Knative Service to be used for canary rollouts.
func (kc *KnativeController) Initialize(cd *flaggerv1.Canary) (bool, error) {
if cd.Status.Phase == "" || cd.Status.Phase == flaggerv1.CanaryPhaseInitializing {
service, err := kc.knativeClient.ServingV1().Services(cd.Namespace).Get(context.TODO(), cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Knative Service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
if service.Annotations == nil {
service.Annotations = make(map[string]string, 0)
}
service.Annotations["flagger.app/primary-revision"] = service.Status.LatestCreatedRevisionName
canaryPercent := int64(0)
primaryPercent := int64(100)
latestRevision := true
traffic := []serving.TrafficTarget{
{
LatestRevision: &latestRevision,
Percent: &canaryPercent,
},
{
RevisionName: service.Status.LatestCreatedRevisionName,
Percent: &primaryPercent,
},
}
service.Spec.Traffic = traffic
_, err = kc.knativeClient.ServingV1().Services(cd.Namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
if err != nil {
return true, fmt.Errorf("Knative Service %s.%s update query error %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
}
return true, nil
}
func (kc *KnativeController) Promote(cd *flaggerv1.Canary) error {
service, err := kc.knativeClient.ServingV1().Services(cd.Namespace).Get(context.TODO(), cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("Knative Service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
service.Annotations["flagger.app/primary-revision"] = service.Status.LatestCreatedRevisionName
_, err = kc.knativeClient.ServingV1().Services(cd.Namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("Knative Service %s.%s update query error %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
return nil
}
func (kc *KnativeController) HasTargetChanged(cd *flaggerv1.Canary) (bool, error) {
service, err := kc.knativeClient.ServingV1().Services(cd.Namespace).Get(context.TODO(), cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
return true, fmt.Errorf("Knative Service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
return hasSpecChanged(cd, service.Status.LatestCreatedRevisionName)
}
func (kc *KnativeController) HaveDependenciesChanged(canary *flaggerv1.Canary) (bool, error) {
return false, nil
}
func (kc *KnativeController) ScaleToZero(canary *flaggerv1.Canary) error {
return nil
}
func (kc *KnativeController) ScaleFromZero(canary *flaggerv1.Canary) error {
return nil
}
func (kc *KnativeController) Finalize(canary *flaggerv1.Canary) error {
return nil
}

View File

@@ -0,0 +1,79 @@
package canary
import (
"context"
"testing"
"github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestKnativeController_Promote(t *testing.T) {
mocks := newKnativeServiceFixture("podinfo")
_, err := mocks.controller.Initialize(mocks.canary)
require.NoError(t, err)
service, err := mocks.knativeClient.ServingV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
service.Status.LatestCreatedRevisionName = "latest-revision"
_, err = mocks.knativeClient.ServingV1().Services("default").UpdateStatus(context.TODO(), service, metav1.UpdateOptions{})
require.NoError(t, err)
err = mocks.controller.Promote(mocks.canary)
require.NoError(t, err)
service, err = mocks.knativeClient.ServingV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, "latest-revision", service.Annotations["flagger.app/primary-revision"])
}
func TestKnativeController_Initialize(t *testing.T) {
mocks := newKnativeServiceFixture("podinfo")
mocks.canary.Status.Phase = v1beta1.CanaryPhasePromoting
ok, err := mocks.controller.Initialize(mocks.canary)
require.NoError(t, err)
assert.Equal(t, true, ok)
service, err := mocks.knativeClient.ServingV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
assert.Len(t, service.Annotations, 0)
assert.Len(t, service.Spec.Traffic, 0)
mocks.canary.Status.Phase = v1beta1.CanaryPhaseInitializing
ok, err = mocks.controller.Initialize(mocks.canary)
require.NoError(t, err)
assert.Equal(t, true, ok)
service, err = mocks.knativeClient.ServingV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, "podinfo-00001", service.Annotations["flagger.app/primary-revision"])
assert.Len(t, service.Spec.Traffic, 2)
assert.Equal(t, *service.Spec.Traffic[0].Percent, int64(0))
assert.True(t, *service.Spec.Traffic[0].LatestRevision)
assert.Equal(t, *service.Spec.Traffic[1].Percent, int64(100))
assert.Equal(t, service.Spec.Traffic[1].RevisionName, "podinfo-00001")
}
func TestKnativeController_HasTargetChanged(t *testing.T) {
mocks := newKnativeServiceFixture("podinfo")
_, err := mocks.controller.Initialize(mocks.canary)
require.NoError(t, err)
service, err := mocks.knativeClient.ServingV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
mocks.canary.Status.LastAppliedSpec = ComputeHash(service.Status.LatestCreatedRevisionName)
service.Status.LatestCreatedRevisionName = "latest-revision"
_, err = mocks.knativeClient.ServingV1().Services("default").UpdateStatus(context.TODO(), service, metav1.UpdateOptions{})
require.NoError(t, err)
ok, err := mocks.controller.HasTargetChanged(mocks.canary)
require.NoError(t, err)
assert.True(t, ok)
}

View File

@@ -0,0 +1,99 @@
package canary
import (
"go.uber.org/zap"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
fakeFlagger "github.com/fluxcd/flagger/pkg/client/clientset/versioned/fake"
"github.com/fluxcd/flagger/pkg/logger"
serving "knative.dev/serving/pkg/apis/serving/v1"
knative "knative.dev/serving/pkg/client/clientset/versioned"
fakeKnative "knative.dev/serving/pkg/client/clientset/versioned/fake"
)
type knativeControllerFixture struct {
canary *flaggerv1.Canary
flaggerClient clientset.Interface
knativeClient knative.Interface
controller KnativeController
logger *zap.SugaredLogger
}
func newKnativeServiceFixture(name string) knativeControllerFixture {
canary := newKnativeControllerTestCanary(name)
flaggerClient := fakeFlagger.NewSimpleClientset(canary)
knativeClient := fakeKnative.NewSimpleClientset(newKnativeControllerTestService(name))
logger, _ := logger.NewLogger("debug")
ctrl := KnativeController{
flaggerClient: flaggerClient,
knativeClient: knativeClient,
logger: logger,
}
return knativeControllerFixture{
canary: canary,
controller: ctrl,
logger: logger,
flaggerClient: flaggerClient,
knativeClient: knativeClient,
}
}
func newKnativeControllerTestService(name string) *serving.Service {
s := &serving.Service{
TypeMeta: metav1.TypeMeta{APIVersion: serving.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "default",
},
Spec: serving.ServiceSpec{
ConfigurationSpec: serving.ConfigurationSpec{
Template: serving.RevisionTemplateSpec{
Spec: serving.RevisionSpec{
PodSpec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "podinfo",
Image: "quay.io/stefanprodan/podinfo:1.2.0",
},
},
},
},
},
},
},
Status: serving.ServiceStatus{
ConfigurationStatusFields: serving.ConfigurationStatusFields{
LatestCreatedRevisionName: "podinfo-00001",
},
},
}
return s
}
func newKnativeControllerTestCanary(name string) *flaggerv1.Canary {
cd := &flaggerv1.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: flaggerv1.CanarySpec{
Provider: "knative",
TargetRef: flaggerv1.LocalObjectReference{
Name: name,
APIVersion: "serving.knative.dev/v1",
Kind: "Service",
},
Analysis: &flaggerv1.CanaryAnalysis{},
},
}
return cd
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// ScaledObjectReconciler is a ScalerReconciler that reconciles KEDA ScaledObjects.
@@ -47,6 +48,8 @@ func (sor *ScaledObjectReconciler) reconcilePrimaryScaler(cd *flaggerv1.Canary,
setPrimaryScaledObjectQueries(cd, targetSoClone.Spec.Triggers)
setPrimaryScaledObjectHPA(targetSoClone)
soSpec := keda.ScaledObjectSpec{
ScaleTargetRef: &keda.ScaleTarget{
Name: primaryName,
@@ -77,7 +80,8 @@ func (sor *ScaledObjectReconciler) reconcilePrimaryScaler(cd *flaggerv1.Canary,
primarySo, err := sor.flaggerClient.KedaV1alpha1().ScaledObjects(cd.Namespace).Get(context.TODO(), primarySoName, metav1.GetOptions{})
if errors.IsNotFound(err) {
primarySo = &keda.ScaledObject{
ObjectMeta: makeObjectMeta(primarySoName, targetSoClone.Labels, cd),
// Passing in the annotations from the targetSo so that they are carried over to the primarySo. This is required so that the transfer ownership annotation can be added.
ObjectMeta: makeObjectMetaSo(primarySoName, targetSoClone.Labels, targetSoClone.Annotations, cd),
Spec: soSpec,
}
_, err = sor.flaggerClient.KedaV1alpha1().ScaledObjects(cd.Namespace).Create(context.TODO(), primarySo, metav1.CreateOptions{})
@@ -206,3 +210,33 @@ func setPrimaryScaledObjectQueries(cd *flaggerv1.Canary, triggers []keda.ScaleTr
}
}
}
func makeObjectMetaSo(name string, labels map[string]string, annotations map[string]string, cd *flaggerv1.Canary) metav1.ObjectMeta {
return metav1.ObjectMeta{
Name: name,
Namespace: cd.Namespace,
Labels: filterMetadata(labels),
Annotations: filterMetadata(annotations),
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(cd, schema.GroupVersionKind{
Group: flaggerv1.SchemeGroupVersion.Group,
Version: flaggerv1.SchemeGroupVersion.Version,
Kind: flaggerv1.CanaryKind,
}),
},
}
}
func setPrimaryScaledObjectHPA(targetSoClone *keda.ScaledObject) {
if targetSoClone.Spec.Advanced == nil {
targetSoClone.Spec.Advanced = &keda.AdvancedConfig{}
}
if targetSoClone.Spec.Advanced.HorizontalPodAutoscalerConfig == nil {
targetSoClone.Spec.Advanced.HorizontalPodAutoscalerConfig = &keda.HorizontalPodAutoscalerConfig{}
}
if targetSoClone.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != "" {
// if the target scaled object has the hpa name set, then append "-primary" to the primary scaled object hpa name
// if the target scaled object does not have the hpa name set, then it will use the default set by keda
targetSoClone.Spec.Advanced.HorizontalPodAutoscalerConfig.Name = fmt.Sprintf("%s-primary", targetSoClone.Spec.Advanced.HorizontalPodAutoscalerConfig.Name)
}
}

View File

@@ -29,6 +29,10 @@ func Test_reconcilePrimaryScaledObject(t *testing.T) {
primarySO, err := mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
require.NoError(t, err)
// test that the hpa ownership annotation is added to the primarySO
assert.Equal(t, primarySO.ObjectMeta.Annotations["scaledobject.keda.sh/transfer-hpa-ownership"], "true")
// test that the horizontalpodautoscalerconfig is set to 'podinfo-primary', so that it takes over ownership of the HPA
assert.Equal(t, primarySO.Spec.Advanced.HorizontalPodAutoscalerConfig.Name, "podinfo-primary")
assert.Equal(t, primarySO.Spec.ScaleTargetRef.Name, fmt.Sprintf("%s-primary", mocks.canary.Spec.TargetRef.Name))
assert.Equal(t, int(*primarySO.Spec.PollingInterval), 10)
assert.Equal(t, int(*primarySO.Spec.MinReplicaCount), 1)
@@ -99,7 +103,7 @@ func Test_setPrimaryScaledObjectQueries(t *testing.T) {
TargetRef: flaggerv1.LocalObjectReference{
Name: "podinfo",
},
AutoscalerRef: &flaggerv1.AutoscalerRefernce{
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
},
},

View File

@@ -154,11 +154,19 @@ func newScaledObject() *keda.ScaledObject {
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
Annotations: map[string]string{
"scaledobject.keda.sh/transfer-hpa-ownership": "true",
},
},
Spec: keda.ScaledObjectSpec{
ScaleTargetRef: &keda.ScaleTarget{
Name: "podinfo",
},
Advanced: &keda.AdvancedConfig{
HorizontalPodAutoscalerConfig: &keda.HorizontalPodAutoscalerConfig{
Name: "podinfo",
},
},
PollingInterval: int32p(10),
MinReplicaCount: int32p(1),
MaxReplicaCount: int32p(4),

View File

@@ -165,6 +165,11 @@ func buildService(canary *flaggerv1.Canary, name string, src *corev1.Service) *c
// Operation cannot be fulfilled on services "mysvc-canary": the object has been modified; please apply your changes to the latest version and try again
delete(svc.ObjectMeta.Annotations, "kubectl.kubernetes.io/last-applied-configuration")
}
if v := canary.Spec.Service.TrafficDistribution; v != "" {
svc.Spec.TrafficDistribution = &v
}
return svc
}

View File

@@ -19,8 +19,8 @@ limitations under the License.
package versioned
import (
"fmt"
"net/http"
fmt "fmt"
http "net/http"
apisixv2 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/apisix/v2"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"

View File

@@ -52,6 +52,7 @@ import (
fakesplitv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha3/fake"
traefikv1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/traefik/v1alpha1"
faketraefikv1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
@@ -79,9 +80,13 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
var opts metav1.ListOptions
if watchActcion, ok := action.(testing.WatchActionImpl); ok {
opts = watchActcion.ListOptions
}
gvr := action.GetResource()
ns := action.GetNamespace()
watch, err := o.Watch(gvr, ns)
watch, err := o.Watch(gvr, ns, opts)
if err != nil {
return false, nil, err
}

View File

@@ -19,10 +19,10 @@ limitations under the License.
package v2
import (
"net/http"
http "net/http"
v2 "github.com/fluxcd/flagger/pkg/apis/apisix/v2"
"github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
apisixv2 "github.com/fluxcd/flagger/pkg/apis/apisix/v2"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
@@ -45,9 +45,7 @@ func (c *ApisixV2Client) ApisixRoutes(namespace string) ApisixRouteInterface {
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*ApisixV2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@@ -59,9 +57,7 @@ func NewForConfig(c *rest.Config) (*ApisixV2Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ApisixV2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@@ -84,17 +80,15 @@ func New(c rest.Interface) *ApisixV2Client {
return &ApisixV2Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v2.SchemeGroupVersion
func setConfigDefaults(config *rest.Config) {
gv := apisixv2.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@@ -19,9 +19,9 @@ limitations under the License.
package v2
import (
"context"
context "context"
v2 "github.com/fluxcd/flagger/pkg/apis/apisix/v2"
apisixv2 "github.com/fluxcd/flagger/pkg/apis/apisix/v2"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
@@ -37,33 +37,34 @@ type ApisixRoutesGetter interface {
// ApisixRouteInterface has methods to work with ApisixRoute resources.
type ApisixRouteInterface interface {
Create(ctx context.Context, apisixRoute *v2.ApisixRoute, opts v1.CreateOptions) (*v2.ApisixRoute, error)
Update(ctx context.Context, apisixRoute *v2.ApisixRoute, opts v1.UpdateOptions) (*v2.ApisixRoute, error)
Create(ctx context.Context, apisixRoute *apisixv2.ApisixRoute, opts v1.CreateOptions) (*apisixv2.ApisixRoute, error)
Update(ctx context.Context, apisixRoute *apisixv2.ApisixRoute, opts v1.UpdateOptions) (*apisixv2.ApisixRoute, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, apisixRoute *v2.ApisixRoute, opts v1.UpdateOptions) (*v2.ApisixRoute, error)
UpdateStatus(ctx context.Context, apisixRoute *apisixv2.ApisixRoute, opts v1.UpdateOptions) (*apisixv2.ApisixRoute, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v2.ApisixRoute, error)
List(ctx context.Context, opts v1.ListOptions) (*v2.ApisixRouteList, error)
Get(ctx context.Context, name string, opts v1.GetOptions) (*apisixv2.ApisixRoute, error)
List(ctx context.Context, opts v1.ListOptions) (*apisixv2.ApisixRouteList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v2.ApisixRoute, err error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apisixv2.ApisixRoute, err error)
ApisixRouteExpansion
}
// apisixRoutes implements ApisixRouteInterface
type apisixRoutes struct {
*gentype.ClientWithList[*v2.ApisixRoute, *v2.ApisixRouteList]
*gentype.ClientWithList[*apisixv2.ApisixRoute, *apisixv2.ApisixRouteList]
}
// newApisixRoutes returns a ApisixRoutes
func newApisixRoutes(c *ApisixV2Client, namespace string) *apisixRoutes {
return &apisixRoutes{
gentype.NewClientWithList[*v2.ApisixRoute, *v2.ApisixRouteList](
gentype.NewClientWithList[*apisixv2.ApisixRoute, *apisixv2.ApisixRouteList](
"apisixroutes",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *v2.ApisixRoute { return &v2.ApisixRoute{} },
func() *v2.ApisixRouteList { return &v2.ApisixRouteList{} }),
func() *apisixv2.ApisixRoute { return &apisixv2.ApisixRoute{} },
func() *apisixv2.ApisixRouteList { return &apisixv2.ApisixRouteList{} },
),
}
}

View File

@@ -29,7 +29,7 @@ type FakeApisixV2 struct {
}
func (c *FakeApisixV2) ApisixRoutes(namespace string) v2.ApisixRouteInterface {
return &FakeApisixRoutes{c, namespace}
return newFakeApisixRoutes(c, namespace)
}
// RESTClient returns a RESTClient that is used to communicate

View File

@@ -19,129 +19,30 @@ limitations under the License.
package fake
import (
"context"
v2 "github.com/fluxcd/flagger/pkg/apis/apisix/v2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
apisixv2 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/apisix/v2"
gentype "k8s.io/client-go/gentype"
)
// FakeApisixRoutes implements ApisixRouteInterface
type FakeApisixRoutes struct {
// fakeApisixRoutes implements ApisixRouteInterface
type fakeApisixRoutes struct {
*gentype.FakeClientWithList[*v2.ApisixRoute, *v2.ApisixRouteList]
Fake *FakeApisixV2
ns string
}
var apisixroutesResource = v2.SchemeGroupVersion.WithResource("apisixroutes")
var apisixroutesKind = v2.SchemeGroupVersion.WithKind("ApisixRoute")
// Get takes name of the apisixRoute, and returns the corresponding apisixRoute object, and an error if there is any.
func (c *FakeApisixRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v2.ApisixRoute, err error) {
emptyResult := &v2.ApisixRoute{}
obj, err := c.Fake.
Invokes(testing.NewGetActionWithOptions(apisixroutesResource, c.ns, name, options), emptyResult)
if obj == nil {
return emptyResult, err
func newFakeApisixRoutes(fake *FakeApisixV2, namespace string) apisixv2.ApisixRouteInterface {
return &fakeApisixRoutes{
gentype.NewFakeClientWithList[*v2.ApisixRoute, *v2.ApisixRouteList](
fake.Fake,
namespace,
v2.SchemeGroupVersion.WithResource("apisixroutes"),
v2.SchemeGroupVersion.WithKind("ApisixRoute"),
func() *v2.ApisixRoute { return &v2.ApisixRoute{} },
func() *v2.ApisixRouteList { return &v2.ApisixRouteList{} },
func(dst, src *v2.ApisixRouteList) { dst.ListMeta = src.ListMeta },
func(list *v2.ApisixRouteList) []*v2.ApisixRoute { return gentype.ToPointerSlice(list.Items) },
func(list *v2.ApisixRouteList, items []*v2.ApisixRoute) { list.Items = gentype.FromPointerSlice(items) },
),
fake,
}
return obj.(*v2.ApisixRoute), err
}
// List takes label and field selectors, and returns the list of ApisixRoutes that match those selectors.
func (c *FakeApisixRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v2.ApisixRouteList, err error) {
emptyResult := &v2.ApisixRouteList{}
obj, err := c.Fake.
Invokes(testing.NewListActionWithOptions(apisixroutesResource, apisixroutesKind, c.ns, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v2.ApisixRouteList{ListMeta: obj.(*v2.ApisixRouteList).ListMeta}
for _, item := range obj.(*v2.ApisixRouteList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested apisixRoutes.
func (c *FakeApisixRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchActionWithOptions(apisixroutesResource, c.ns, opts))
}
// Create takes the representation of a apisixRoute and creates it. Returns the server's representation of the apisixRoute, and an error, if there is any.
func (c *FakeApisixRoutes) Create(ctx context.Context, apisixRoute *v2.ApisixRoute, opts v1.CreateOptions) (result *v2.ApisixRoute, err error) {
emptyResult := &v2.ApisixRoute{}
obj, err := c.Fake.
Invokes(testing.NewCreateActionWithOptions(apisixroutesResource, c.ns, apisixRoute, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v2.ApisixRoute), err
}
// Update takes the representation of a apisixRoute and updates it. Returns the server's representation of the apisixRoute, and an error, if there is any.
func (c *FakeApisixRoutes) Update(ctx context.Context, apisixRoute *v2.ApisixRoute, opts v1.UpdateOptions) (result *v2.ApisixRoute, err error) {
emptyResult := &v2.ApisixRoute{}
obj, err := c.Fake.
Invokes(testing.NewUpdateActionWithOptions(apisixroutesResource, c.ns, apisixRoute, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v2.ApisixRoute), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeApisixRoutes) UpdateStatus(ctx context.Context, apisixRoute *v2.ApisixRoute, opts v1.UpdateOptions) (result *v2.ApisixRoute, err error) {
emptyResult := &v2.ApisixRoute{}
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceActionWithOptions(apisixroutesResource, "status", c.ns, apisixRoute, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v2.ApisixRoute), err
}
// Delete takes name of the apisixRoute and deletes it. Returns an error if one occurs.
func (c *FakeApisixRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(apisixroutesResource, c.ns, name, opts), &v2.ApisixRoute{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeApisixRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionActionWithOptions(apisixroutesResource, c.ns, opts, listOpts)
_, err := c.Fake.Invokes(action, &v2.ApisixRouteList{})
return err
}
// Patch applies the patch and returns the patched apisixRoute.
func (c *FakeApisixRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v2.ApisixRoute, err error) {
emptyResult := &v2.ApisixRoute{}
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceActionWithOptions(apisixroutesResource, c.ns, name, pt, data, opts, subresources...), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v2.ApisixRoute), err
}

View File

@@ -19,10 +19,10 @@ limitations under the License.
package v1beta1
import (
"net/http"
http "net/http"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
"github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
@@ -55,9 +55,7 @@ func (c *AppmeshV1beta1Client) VirtualServices(namespace string) VirtualServiceI
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*AppmeshV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@@ -69,9 +67,7 @@ func NewForConfig(c *rest.Config) (*AppmeshV1beta1Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AppmeshV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@@ -94,17 +90,15 @@ func New(c rest.Interface) *AppmeshV1beta1Client {
return &AppmeshV1beta1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta1.SchemeGroupVersion
func setConfigDefaults(config *rest.Config) {
gv := appmeshv1beta1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@@ -29,15 +29,15 @@ type FakeAppmeshV1beta1 struct {
}
func (c *FakeAppmeshV1beta1) Meshes() v1beta1.MeshInterface {
return &FakeMeshes{c}
return newFakeMeshes(c)
}
func (c *FakeAppmeshV1beta1) VirtualNodes(namespace string) v1beta1.VirtualNodeInterface {
return &FakeVirtualNodes{c, namespace}
return newFakeVirtualNodes(c, namespace)
}
func (c *FakeAppmeshV1beta1) VirtualServices(namespace string) v1beta1.VirtualServiceInterface {
return &FakeVirtualServices{c, namespace}
return newFakeVirtualServices(c, namespace)
}
// RESTClient returns a RESTClient that is used to communicate

View File

@@ -19,120 +19,30 @@ limitations under the License.
package fake
import (
"context"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"
gentype "k8s.io/client-go/gentype"
)
// FakeMeshes implements MeshInterface
type FakeMeshes struct {
// fakeMeshes implements MeshInterface
type fakeMeshes struct {
*gentype.FakeClientWithList[*v1beta1.Mesh, *v1beta1.MeshList]
Fake *FakeAppmeshV1beta1
}
var meshesResource = v1beta1.SchemeGroupVersion.WithResource("meshes")
var meshesKind = v1beta1.SchemeGroupVersion.WithKind("Mesh")
// Get takes name of the mesh, and returns the corresponding mesh object, and an error if there is any.
func (c *FakeMeshes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Mesh, err error) {
emptyResult := &v1beta1.Mesh{}
obj, err := c.Fake.
Invokes(testing.NewRootGetActionWithOptions(meshesResource, name, options), emptyResult)
if obj == nil {
return emptyResult, err
func newFakeMeshes(fake *FakeAppmeshV1beta1) appmeshv1beta1.MeshInterface {
return &fakeMeshes{
gentype.NewFakeClientWithList[*v1beta1.Mesh, *v1beta1.MeshList](
fake.Fake,
"",
v1beta1.SchemeGroupVersion.WithResource("meshes"),
v1beta1.SchemeGroupVersion.WithKind("Mesh"),
func() *v1beta1.Mesh { return &v1beta1.Mesh{} },
func() *v1beta1.MeshList { return &v1beta1.MeshList{} },
func(dst, src *v1beta1.MeshList) { dst.ListMeta = src.ListMeta },
func(list *v1beta1.MeshList) []*v1beta1.Mesh { return gentype.ToPointerSlice(list.Items) },
func(list *v1beta1.MeshList, items []*v1beta1.Mesh) { list.Items = gentype.FromPointerSlice(items) },
),
fake,
}
return obj.(*v1beta1.Mesh), err
}
// List takes label and field selectors, and returns the list of Meshes that match those selectors.
func (c *FakeMeshes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.MeshList, err error) {
emptyResult := &v1beta1.MeshList{}
obj, err := c.Fake.
Invokes(testing.NewRootListActionWithOptions(meshesResource, meshesKind, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta1.MeshList{ListMeta: obj.(*v1beta1.MeshList).ListMeta}
for _, item := range obj.(*v1beta1.MeshList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested meshes.
func (c *FakeMeshes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchActionWithOptions(meshesResource, opts))
}
// Create takes the representation of a mesh and creates it. Returns the server's representation of the mesh, and an error, if there is any.
func (c *FakeMeshes) Create(ctx context.Context, mesh *v1beta1.Mesh, opts v1.CreateOptions) (result *v1beta1.Mesh, err error) {
emptyResult := &v1beta1.Mesh{}
obj, err := c.Fake.
Invokes(testing.NewRootCreateActionWithOptions(meshesResource, mesh, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.Mesh), err
}
// Update takes the representation of a mesh and updates it. Returns the server's representation of the mesh, and an error, if there is any.
func (c *FakeMeshes) Update(ctx context.Context, mesh *v1beta1.Mesh, opts v1.UpdateOptions) (result *v1beta1.Mesh, err error) {
emptyResult := &v1beta1.Mesh{}
obj, err := c.Fake.
Invokes(testing.NewRootUpdateActionWithOptions(meshesResource, mesh, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.Mesh), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeMeshes) UpdateStatus(ctx context.Context, mesh *v1beta1.Mesh, opts v1.UpdateOptions) (result *v1beta1.Mesh, err error) {
emptyResult := &v1beta1.Mesh{}
obj, err := c.Fake.
Invokes(testing.NewRootUpdateSubresourceActionWithOptions(meshesResource, "status", mesh, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.Mesh), err
}
// Delete takes name of the mesh and deletes it. Returns an error if one occurs.
func (c *FakeMeshes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteActionWithOptions(meshesResource, name, opts), &v1beta1.Mesh{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeMeshes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewRootDeleteCollectionActionWithOptions(meshesResource, opts, listOpts)
_, err := c.Fake.Invokes(action, &v1beta1.MeshList{})
return err
}
// Patch applies the patch and returns the patched mesh.
func (c *FakeMeshes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Mesh, err error) {
emptyResult := &v1beta1.Mesh{}
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceActionWithOptions(meshesResource, name, pt, data, opts, subresources...), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.Mesh), err
}

View File

@@ -19,129 +19,32 @@ limitations under the License.
package fake
import (
"context"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"
gentype "k8s.io/client-go/gentype"
)
// FakeVirtualNodes implements VirtualNodeInterface
type FakeVirtualNodes struct {
// fakeVirtualNodes implements VirtualNodeInterface
type fakeVirtualNodes struct {
*gentype.FakeClientWithList[*v1beta1.VirtualNode, *v1beta1.VirtualNodeList]
Fake *FakeAppmeshV1beta1
ns string
}
var virtualnodesResource = v1beta1.SchemeGroupVersion.WithResource("virtualnodes")
var virtualnodesKind = v1beta1.SchemeGroupVersion.WithKind("VirtualNode")
// Get takes name of the virtualNode, and returns the corresponding virtualNode object, and an error if there is any.
func (c *FakeVirtualNodes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.VirtualNode, err error) {
emptyResult := &v1beta1.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewGetActionWithOptions(virtualnodesResource, c.ns, name, options), emptyResult)
if obj == nil {
return emptyResult, err
func newFakeVirtualNodes(fake *FakeAppmeshV1beta1, namespace string) appmeshv1beta1.VirtualNodeInterface {
return &fakeVirtualNodes{
gentype.NewFakeClientWithList[*v1beta1.VirtualNode, *v1beta1.VirtualNodeList](
fake.Fake,
namespace,
v1beta1.SchemeGroupVersion.WithResource("virtualnodes"),
v1beta1.SchemeGroupVersion.WithKind("VirtualNode"),
func() *v1beta1.VirtualNode { return &v1beta1.VirtualNode{} },
func() *v1beta1.VirtualNodeList { return &v1beta1.VirtualNodeList{} },
func(dst, src *v1beta1.VirtualNodeList) { dst.ListMeta = src.ListMeta },
func(list *v1beta1.VirtualNodeList) []*v1beta1.VirtualNode { return gentype.ToPointerSlice(list.Items) },
func(list *v1beta1.VirtualNodeList, items []*v1beta1.VirtualNode) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
return obj.(*v1beta1.VirtualNode), err
}
// List takes label and field selectors, and returns the list of VirtualNodes that match those selectors.
func (c *FakeVirtualNodes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.VirtualNodeList, err error) {
emptyResult := &v1beta1.VirtualNodeList{}
obj, err := c.Fake.
Invokes(testing.NewListActionWithOptions(virtualnodesResource, virtualnodesKind, c.ns, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta1.VirtualNodeList{ListMeta: obj.(*v1beta1.VirtualNodeList).ListMeta}
for _, item := range obj.(*v1beta1.VirtualNodeList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested virtualNodes.
func (c *FakeVirtualNodes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchActionWithOptions(virtualnodesResource, c.ns, opts))
}
// Create takes the representation of a virtualNode and creates it. Returns the server's representation of the virtualNode, and an error, if there is any.
func (c *FakeVirtualNodes) Create(ctx context.Context, virtualNode *v1beta1.VirtualNode, opts v1.CreateOptions) (result *v1beta1.VirtualNode, err error) {
emptyResult := &v1beta1.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewCreateActionWithOptions(virtualnodesResource, c.ns, virtualNode, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualNode), err
}
// Update takes the representation of a virtualNode and updates it. Returns the server's representation of the virtualNode, and an error, if there is any.
func (c *FakeVirtualNodes) Update(ctx context.Context, virtualNode *v1beta1.VirtualNode, opts v1.UpdateOptions) (result *v1beta1.VirtualNode, err error) {
emptyResult := &v1beta1.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewUpdateActionWithOptions(virtualnodesResource, c.ns, virtualNode, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualNode), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeVirtualNodes) UpdateStatus(ctx context.Context, virtualNode *v1beta1.VirtualNode, opts v1.UpdateOptions) (result *v1beta1.VirtualNode, err error) {
emptyResult := &v1beta1.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceActionWithOptions(virtualnodesResource, "status", c.ns, virtualNode, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualNode), err
}
// Delete takes name of the virtualNode and deletes it. Returns an error if one occurs.
func (c *FakeVirtualNodes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(virtualnodesResource, c.ns, name, opts), &v1beta1.VirtualNode{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeVirtualNodes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionActionWithOptions(virtualnodesResource, c.ns, opts, listOpts)
_, err := c.Fake.Invokes(action, &v1beta1.VirtualNodeList{})
return err
}
// Patch applies the patch and returns the patched virtualNode.
func (c *FakeVirtualNodes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.VirtualNode, err error) {
emptyResult := &v1beta1.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceActionWithOptions(virtualnodesResource, c.ns, name, pt, data, opts, subresources...), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualNode), err
}

View File

@@ -19,129 +19,34 @@ limitations under the License.
package fake
import (
"context"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"
gentype "k8s.io/client-go/gentype"
)
// FakeVirtualServices implements VirtualServiceInterface
type FakeVirtualServices struct {
// fakeVirtualServices implements VirtualServiceInterface
type fakeVirtualServices struct {
*gentype.FakeClientWithList[*v1beta1.VirtualService, *v1beta1.VirtualServiceList]
Fake *FakeAppmeshV1beta1
ns string
}
var virtualservicesResource = v1beta1.SchemeGroupVersion.WithResource("virtualservices")
var virtualservicesKind = v1beta1.SchemeGroupVersion.WithKind("VirtualService")
// Get takes name of the virtualService, and returns the corresponding virtualService object, and an error if there is any.
func (c *FakeVirtualServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.VirtualService, err error) {
emptyResult := &v1beta1.VirtualService{}
obj, err := c.Fake.
Invokes(testing.NewGetActionWithOptions(virtualservicesResource, c.ns, name, options), emptyResult)
if obj == nil {
return emptyResult, err
func newFakeVirtualServices(fake *FakeAppmeshV1beta1, namespace string) appmeshv1beta1.VirtualServiceInterface {
return &fakeVirtualServices{
gentype.NewFakeClientWithList[*v1beta1.VirtualService, *v1beta1.VirtualServiceList](
fake.Fake,
namespace,
v1beta1.SchemeGroupVersion.WithResource("virtualservices"),
v1beta1.SchemeGroupVersion.WithKind("VirtualService"),
func() *v1beta1.VirtualService { return &v1beta1.VirtualService{} },
func() *v1beta1.VirtualServiceList { return &v1beta1.VirtualServiceList{} },
func(dst, src *v1beta1.VirtualServiceList) { dst.ListMeta = src.ListMeta },
func(list *v1beta1.VirtualServiceList) []*v1beta1.VirtualService {
return gentype.ToPointerSlice(list.Items)
},
func(list *v1beta1.VirtualServiceList, items []*v1beta1.VirtualService) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
return obj.(*v1beta1.VirtualService), err
}
// List takes label and field selectors, and returns the list of VirtualServices that match those selectors.
func (c *FakeVirtualServices) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.VirtualServiceList, err error) {
emptyResult := &v1beta1.VirtualServiceList{}
obj, err := c.Fake.
Invokes(testing.NewListActionWithOptions(virtualservicesResource, virtualservicesKind, c.ns, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta1.VirtualServiceList{ListMeta: obj.(*v1beta1.VirtualServiceList).ListMeta}
for _, item := range obj.(*v1beta1.VirtualServiceList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested virtualServices.
func (c *FakeVirtualServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchActionWithOptions(virtualservicesResource, c.ns, opts))
}
// Create takes the representation of a virtualService and creates it. Returns the server's representation of the virtualService, and an error, if there is any.
func (c *FakeVirtualServices) Create(ctx context.Context, virtualService *v1beta1.VirtualService, opts v1.CreateOptions) (result *v1beta1.VirtualService, err error) {
emptyResult := &v1beta1.VirtualService{}
obj, err := c.Fake.
Invokes(testing.NewCreateActionWithOptions(virtualservicesResource, c.ns, virtualService, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualService), err
}
// Update takes the representation of a virtualService and updates it. Returns the server's representation of the virtualService, and an error, if there is any.
func (c *FakeVirtualServices) Update(ctx context.Context, virtualService *v1beta1.VirtualService, opts v1.UpdateOptions) (result *v1beta1.VirtualService, err error) {
emptyResult := &v1beta1.VirtualService{}
obj, err := c.Fake.
Invokes(testing.NewUpdateActionWithOptions(virtualservicesResource, c.ns, virtualService, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualService), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeVirtualServices) UpdateStatus(ctx context.Context, virtualService *v1beta1.VirtualService, opts v1.UpdateOptions) (result *v1beta1.VirtualService, err error) {
emptyResult := &v1beta1.VirtualService{}
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceActionWithOptions(virtualservicesResource, "status", c.ns, virtualService, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualService), err
}
// Delete takes name of the virtualService and deletes it. Returns an error if one occurs.
func (c *FakeVirtualServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(virtualservicesResource, c.ns, name, opts), &v1beta1.VirtualService{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeVirtualServices) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionActionWithOptions(virtualservicesResource, c.ns, opts, listOpts)
_, err := c.Fake.Invokes(action, &v1beta1.VirtualServiceList{})
return err
}
// Patch applies the patch and returns the patched virtualService.
func (c *FakeVirtualServices) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.VirtualService, err error) {
emptyResult := &v1beta1.VirtualService{}
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceActionWithOptions(virtualservicesResource, c.ns, name, pt, data, opts, subresources...), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta1.VirtualService), err
}

View File

@@ -19,9 +19,9 @@ limitations under the License.
package v1beta1
import (
"context"
context "context"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
@@ -37,33 +37,34 @@ type MeshesGetter interface {
// MeshInterface has methods to work with Mesh resources.
type MeshInterface interface {
Create(ctx context.Context, mesh *v1beta1.Mesh, opts v1.CreateOptions) (*v1beta1.Mesh, error)
Update(ctx context.Context, mesh *v1beta1.Mesh, opts v1.UpdateOptions) (*v1beta1.Mesh, error)
Create(ctx context.Context, mesh *appmeshv1beta1.Mesh, opts v1.CreateOptions) (*appmeshv1beta1.Mesh, error)
Update(ctx context.Context, mesh *appmeshv1beta1.Mesh, opts v1.UpdateOptions) (*appmeshv1beta1.Mesh, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, mesh *v1beta1.Mesh, opts v1.UpdateOptions) (*v1beta1.Mesh, error)
UpdateStatus(ctx context.Context, mesh *appmeshv1beta1.Mesh, opts v1.UpdateOptions) (*appmeshv1beta1.Mesh, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.Mesh, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta1.MeshList, error)
Get(ctx context.Context, name string, opts v1.GetOptions) (*appmeshv1beta1.Mesh, error)
List(ctx context.Context, opts v1.ListOptions) (*appmeshv1beta1.MeshList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Mesh, err error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *appmeshv1beta1.Mesh, err error)
MeshExpansion
}
// meshes implements MeshInterface
type meshes struct {
*gentype.ClientWithList[*v1beta1.Mesh, *v1beta1.MeshList]
*gentype.ClientWithList[*appmeshv1beta1.Mesh, *appmeshv1beta1.MeshList]
}
// newMeshes returns a Meshes
func newMeshes(c *AppmeshV1beta1Client) *meshes {
return &meshes{
gentype.NewClientWithList[*v1beta1.Mesh, *v1beta1.MeshList](
gentype.NewClientWithList[*appmeshv1beta1.Mesh, *appmeshv1beta1.MeshList](
"meshes",
c.RESTClient(),
scheme.ParameterCodec,
"",
func() *v1beta1.Mesh { return &v1beta1.Mesh{} },
func() *v1beta1.MeshList { return &v1beta1.MeshList{} }),
func() *appmeshv1beta1.Mesh { return &appmeshv1beta1.Mesh{} },
func() *appmeshv1beta1.MeshList { return &appmeshv1beta1.MeshList{} },
),
}
}

View File

@@ -19,9 +19,9 @@ limitations under the License.
package v1beta1
import (
"context"
context "context"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
@@ -37,33 +37,34 @@ type VirtualNodesGetter interface {
// VirtualNodeInterface has methods to work with VirtualNode resources.
type VirtualNodeInterface interface {
Create(ctx context.Context, virtualNode *v1beta1.VirtualNode, opts v1.CreateOptions) (*v1beta1.VirtualNode, error)
Update(ctx context.Context, virtualNode *v1beta1.VirtualNode, opts v1.UpdateOptions) (*v1beta1.VirtualNode, error)
Create(ctx context.Context, virtualNode *appmeshv1beta1.VirtualNode, opts v1.CreateOptions) (*appmeshv1beta1.VirtualNode, error)
Update(ctx context.Context, virtualNode *appmeshv1beta1.VirtualNode, opts v1.UpdateOptions) (*appmeshv1beta1.VirtualNode, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, virtualNode *v1beta1.VirtualNode, opts v1.UpdateOptions) (*v1beta1.VirtualNode, error)
UpdateStatus(ctx context.Context, virtualNode *appmeshv1beta1.VirtualNode, opts v1.UpdateOptions) (*appmeshv1beta1.VirtualNode, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.VirtualNode, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta1.VirtualNodeList, error)
Get(ctx context.Context, name string, opts v1.GetOptions) (*appmeshv1beta1.VirtualNode, error)
List(ctx context.Context, opts v1.ListOptions) (*appmeshv1beta1.VirtualNodeList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.VirtualNode, err error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *appmeshv1beta1.VirtualNode, err error)
VirtualNodeExpansion
}
// virtualNodes implements VirtualNodeInterface
type virtualNodes struct {
*gentype.ClientWithList[*v1beta1.VirtualNode, *v1beta1.VirtualNodeList]
*gentype.ClientWithList[*appmeshv1beta1.VirtualNode, *appmeshv1beta1.VirtualNodeList]
}
// newVirtualNodes returns a VirtualNodes
func newVirtualNodes(c *AppmeshV1beta1Client, namespace string) *virtualNodes {
return &virtualNodes{
gentype.NewClientWithList[*v1beta1.VirtualNode, *v1beta1.VirtualNodeList](
gentype.NewClientWithList[*appmeshv1beta1.VirtualNode, *appmeshv1beta1.VirtualNodeList](
"virtualnodes",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *v1beta1.VirtualNode { return &v1beta1.VirtualNode{} },
func() *v1beta1.VirtualNodeList { return &v1beta1.VirtualNodeList{} }),
func() *appmeshv1beta1.VirtualNode { return &appmeshv1beta1.VirtualNode{} },
func() *appmeshv1beta1.VirtualNodeList { return &appmeshv1beta1.VirtualNodeList{} },
),
}
}

View File

@@ -19,9 +19,9 @@ limitations under the License.
package v1beta1
import (
"context"
context "context"
v1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
appmeshv1beta1 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta1"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
@@ -37,33 +37,34 @@ type VirtualServicesGetter interface {
// VirtualServiceInterface has methods to work with VirtualService resources.
type VirtualServiceInterface interface {
Create(ctx context.Context, virtualService *v1beta1.VirtualService, opts v1.CreateOptions) (*v1beta1.VirtualService, error)
Update(ctx context.Context, virtualService *v1beta1.VirtualService, opts v1.UpdateOptions) (*v1beta1.VirtualService, error)
Create(ctx context.Context, virtualService *appmeshv1beta1.VirtualService, opts v1.CreateOptions) (*appmeshv1beta1.VirtualService, error)
Update(ctx context.Context, virtualService *appmeshv1beta1.VirtualService, opts v1.UpdateOptions) (*appmeshv1beta1.VirtualService, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, virtualService *v1beta1.VirtualService, opts v1.UpdateOptions) (*v1beta1.VirtualService, error)
UpdateStatus(ctx context.Context, virtualService *appmeshv1beta1.VirtualService, opts v1.UpdateOptions) (*appmeshv1beta1.VirtualService, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.VirtualService, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta1.VirtualServiceList, error)
Get(ctx context.Context, name string, opts v1.GetOptions) (*appmeshv1beta1.VirtualService, error)
List(ctx context.Context, opts v1.ListOptions) (*appmeshv1beta1.VirtualServiceList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.VirtualService, err error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *appmeshv1beta1.VirtualService, err error)
VirtualServiceExpansion
}
// virtualServices implements VirtualServiceInterface
type virtualServices struct {
*gentype.ClientWithList[*v1beta1.VirtualService, *v1beta1.VirtualServiceList]
*gentype.ClientWithList[*appmeshv1beta1.VirtualService, *appmeshv1beta1.VirtualServiceList]
}
// newVirtualServices returns a VirtualServices
func newVirtualServices(c *AppmeshV1beta1Client, namespace string) *virtualServices {
return &virtualServices{
gentype.NewClientWithList[*v1beta1.VirtualService, *v1beta1.VirtualServiceList](
gentype.NewClientWithList[*appmeshv1beta1.VirtualService, *appmeshv1beta1.VirtualServiceList](
"virtualservices",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *v1beta1.VirtualService { return &v1beta1.VirtualService{} },
func() *v1beta1.VirtualServiceList { return &v1beta1.VirtualServiceList{} }),
func() *appmeshv1beta1.VirtualService { return &appmeshv1beta1.VirtualService{} },
func() *appmeshv1beta1.VirtualServiceList { return &appmeshv1beta1.VirtualServiceList{} },
),
}
}

View File

@@ -19,10 +19,10 @@ limitations under the License.
package v1beta2
import (
"net/http"
http "net/http"
v1beta2 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta2"
"github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
appmeshv1beta2 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta2"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
@@ -55,9 +55,7 @@ func (c *AppmeshV1beta2Client) VirtualServices(namespace string) VirtualServiceI
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*AppmeshV1beta2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@@ -69,9 +67,7 @@ func NewForConfig(c *rest.Config) (*AppmeshV1beta2Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AppmeshV1beta2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@@ -94,17 +90,15 @@ func New(c rest.Interface) *AppmeshV1beta2Client {
return &AppmeshV1beta2Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta2.SchemeGroupVersion
func setConfigDefaults(config *rest.Config) {
gv := appmeshv1beta2.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@@ -29,15 +29,15 @@ type FakeAppmeshV1beta2 struct {
}
func (c *FakeAppmeshV1beta2) VirtualNodes(namespace string) v1beta2.VirtualNodeInterface {
return &FakeVirtualNodes{c, namespace}
return newFakeVirtualNodes(c, namespace)
}
func (c *FakeAppmeshV1beta2) VirtualRouters(namespace string) v1beta2.VirtualRouterInterface {
return &FakeVirtualRouters{c, namespace}
return newFakeVirtualRouters(c, namespace)
}
func (c *FakeAppmeshV1beta2) VirtualServices(namespace string) v1beta2.VirtualServiceInterface {
return &FakeVirtualServices{c, namespace}
return newFakeVirtualServices(c, namespace)
}
// RESTClient returns a RESTClient that is used to communicate

View File

@@ -19,129 +19,32 @@ limitations under the License.
package fake
import (
"context"
v1beta2 "github.com/fluxcd/flagger/pkg/apis/appmesh/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
appmeshv1beta2 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta2"
gentype "k8s.io/client-go/gentype"
)
// FakeVirtualNodes implements VirtualNodeInterface
type FakeVirtualNodes struct {
// fakeVirtualNodes implements VirtualNodeInterface
type fakeVirtualNodes struct {
*gentype.FakeClientWithList[*v1beta2.VirtualNode, *v1beta2.VirtualNodeList]
Fake *FakeAppmeshV1beta2
ns string
}
var virtualnodesResource = v1beta2.SchemeGroupVersion.WithResource("virtualnodes")
var virtualnodesKind = v1beta2.SchemeGroupVersion.WithKind("VirtualNode")
// Get takes name of the virtualNode, and returns the corresponding virtualNode object, and an error if there is any.
func (c *FakeVirtualNodes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.VirtualNode, err error) {
emptyResult := &v1beta2.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewGetActionWithOptions(virtualnodesResource, c.ns, name, options), emptyResult)
if obj == nil {
return emptyResult, err
func newFakeVirtualNodes(fake *FakeAppmeshV1beta2, namespace string) appmeshv1beta2.VirtualNodeInterface {
return &fakeVirtualNodes{
gentype.NewFakeClientWithList[*v1beta2.VirtualNode, *v1beta2.VirtualNodeList](
fake.Fake,
namespace,
v1beta2.SchemeGroupVersion.WithResource("virtualnodes"),
v1beta2.SchemeGroupVersion.WithKind("VirtualNode"),
func() *v1beta2.VirtualNode { return &v1beta2.VirtualNode{} },
func() *v1beta2.VirtualNodeList { return &v1beta2.VirtualNodeList{} },
func(dst, src *v1beta2.VirtualNodeList) { dst.ListMeta = src.ListMeta },
func(list *v1beta2.VirtualNodeList) []*v1beta2.VirtualNode { return gentype.ToPointerSlice(list.Items) },
func(list *v1beta2.VirtualNodeList, items []*v1beta2.VirtualNode) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
return obj.(*v1beta2.VirtualNode), err
}
// List takes label and field selectors, and returns the list of VirtualNodes that match those selectors.
func (c *FakeVirtualNodes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.VirtualNodeList, err error) {
emptyResult := &v1beta2.VirtualNodeList{}
obj, err := c.Fake.
Invokes(testing.NewListActionWithOptions(virtualnodesResource, virtualnodesKind, c.ns, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta2.VirtualNodeList{ListMeta: obj.(*v1beta2.VirtualNodeList).ListMeta}
for _, item := range obj.(*v1beta2.VirtualNodeList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested virtualNodes.
func (c *FakeVirtualNodes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchActionWithOptions(virtualnodesResource, c.ns, opts))
}
// Create takes the representation of a virtualNode and creates it. Returns the server's representation of the virtualNode, and an error, if there is any.
func (c *FakeVirtualNodes) Create(ctx context.Context, virtualNode *v1beta2.VirtualNode, opts v1.CreateOptions) (result *v1beta2.VirtualNode, err error) {
emptyResult := &v1beta2.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewCreateActionWithOptions(virtualnodesResource, c.ns, virtualNode, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta2.VirtualNode), err
}
// Update takes the representation of a virtualNode and updates it. Returns the server's representation of the virtualNode, and an error, if there is any.
func (c *FakeVirtualNodes) Update(ctx context.Context, virtualNode *v1beta2.VirtualNode, opts v1.UpdateOptions) (result *v1beta2.VirtualNode, err error) {
emptyResult := &v1beta2.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewUpdateActionWithOptions(virtualnodesResource, c.ns, virtualNode, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta2.VirtualNode), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeVirtualNodes) UpdateStatus(ctx context.Context, virtualNode *v1beta2.VirtualNode, opts v1.UpdateOptions) (result *v1beta2.VirtualNode, err error) {
emptyResult := &v1beta2.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceActionWithOptions(virtualnodesResource, "status", c.ns, virtualNode, opts), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta2.VirtualNode), err
}
// Delete takes name of the virtualNode and deletes it. Returns an error if one occurs.
func (c *FakeVirtualNodes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(virtualnodesResource, c.ns, name, opts), &v1beta2.VirtualNode{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeVirtualNodes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionActionWithOptions(virtualnodesResource, c.ns, opts, listOpts)
_, err := c.Fake.Invokes(action, &v1beta2.VirtualNodeList{})
return err
}
// Patch applies the patch and returns the patched virtualNode.
func (c *FakeVirtualNodes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.VirtualNode, err error) {
emptyResult := &v1beta2.VirtualNode{}
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceActionWithOptions(virtualnodesResource, c.ns, name, pt, data, opts, subresources...), emptyResult)
if obj == nil {
return emptyResult, err
}
return obj.(*v1beta2.VirtualNode), err
}

Some files were not shown because too many files have changed in this diff Show More