Compare commits

...

22 Commits
v1.6.0 ... main

Author SHA1 Message Date
Matheus Pimenta a22d67edbb
Merge pull request #1141 from cappyzawa/feat/optional-address-field
Make address field optional for providers that generate URLs internally
2025-07-15 16:32:09 +01:00
cappyzawa 955d24142c
Make address field optional for providers that generate URLs internally
This change removes the generic address validation from event_handlers.go
that was preventing address-optional providers from functioning without
specifying a dummy address value. Some providers generate URLs internally
and don't require external address configuration.

This allows providers that generate URLs internally to work without
requiring dummy address values in the provider configuration.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-07-16 00:13:29 +09:00
Matheus Pimenta e8a909fac7
Merge pull request #1143 from fluxcd/upgrade-auth
Upgrade Kubernetes to 1.33.2
2025-07-14 17:06:13 +01:00
Matheus Pimenta febff88be7
Upgrade Kubernetes to 1.33.2
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-07-14 16:45:44 +01:00
Matheus Pimenta 5bea87e16b
Merge pull request #1142 from cappyzawa/fix/double-secret-fetch
Fix double secret fetching in BasicAuth processing
2025-07-10 11:50:12 +01:00
cappyzawa a8c2fc0759
Fix double secret fetching in BasicAuth processing
This commit addresses the performance issue where spec.secretRef
was being fetched twice - once in extractAuthFromSecret and again
in secrets.BasicAuthFromSecret. The fix moves BasicAuth processing
directly into extractAuthFromSecret using the already-fetched
secret data, eliminating the redundant API call.

This aligns with the special nature of spec.secretRef that contains
multiple authentication methods and follows the advice to not use
runtime/secrets for special requirements as discussed in flux2#5433.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-07-10 19:38:33 +09:00
Stefan Prodan 6e18c487d7
Merge pull request #1139 from cappyzawa/feat/basicauth-runtime-secrets
Unify BasicAuth processing using pkg/runtime/secrets
2025-07-10 11:05:01 +03:00
cappyzawa d1c85df902
Unify BasicAuth processing using pkg/runtime/secrets
This commit refactors the createNotifier function to use
pkg/runtime/secrets.BasicAuthFromSecret for standardized BasicAuth
handling while maintaining token-first authentication precedence.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-07-10 16:56:33 +09:00
Stefan Prodan 9e150256c7
Merge pull request #1140 from cappyzawa/feat/telegram-proxy-support
Add proxy support to Telegram notifier
2025-07-08 16:31:38 +03:00
cappyzawa e4160c509c
fixup! Add proxy support to Telegram notifier
Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-07-08 21:42:27 +09:00
cappyzawa fc4adfd030
Add proxy support to Telegram notifier
Replace shoutrrr with direct Telegram Bot API calls to enable proxy
configuration through postMessage function.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-07-08 00:15:02 +09:00
Matheus Pimenta 3f4e962c83
Merge pull request #1137 from cappyzawa/feat/mtls-postmessage-notifiers
Add mTLS support for postMessage-based notifiers
2025-07-06 12:26:54 +01:00
cappyzawa 98ecf2de79
Add mTLS support for postMessage-based notifiers
- Implement mTLS support for 10 postMessage notifiers
- Unify constructor signatures with tlsConfig parameter
- Make TLSConfig field public for consistency
- Update factory functions and fuzz tests
- Add mTLS test cases
- Replace CertPool with TLSConfig using runtime/secrets

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-07-03 21:39:16 +09:00
Matheus Pimenta a3e6dd6a10
Merge pull request #1133 from cappyzawa/feat/proxy-secret-ref
Add ProxySecretRef field to Provider API
2025-06-27 15:57:40 +01:00
cappyzawa 8858332c27
Add ProxySecretRef field to Provider API
Introduce spec.proxySecretRef to enable secure proxy configuration
through dedicated Secrets. This provides a more secure alternative
to the deprecated spec.proxy field and secret proxy key.

The new field integrates with runtime/secrets for unified proxy
handling and maintains backward compatibility. Deprecation warnings
are implemented for existing proxy configuration methods.

Proxy priority: ProxySecretRef > secret proxy key > spec.proxy

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-06-27 23:31:02 +09:00
Stefan Prodan 2503bda903
Merge pull request #1128 from fluxcd/fix-docs-markdown
Fix links in provider API doc
2025-05-29 17:17:07 +03:00
Stefan Prodan fc126284ab
Fix links in provider API doc
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-05-29 17:09:54 +03:00
Stefan Prodan 8f207d65db
Merge pull request #1127 from fluxcd/dependabot-up
Update dependabot config
2025-05-28 16:17:48 +03:00
Stefan Prodan 61f24e3376
Update dependabot config
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-05-28 16:04:13 +03:00
Matheus Pimenta 3f4ba79594
Merge pull request #1123 from fluxcd/update-labels
Add 1.6.x release label
2025-05-28 08:30:32 +01:00
Matheus Pimenta d65c81d035
Add 1.6.x release label
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-05-28 08:23:38 +01:00
Matheus Pimenta 47b95d86a3
Merge pull request #1122 from fluxcd/release/v1.6.x
Release/v1.6.x
2025-05-27 17:29:48 +01:00
51 changed files with 1263 additions and 855 deletions

View File

@ -5,7 +5,7 @@ updates:
directory: "/"
labels: ["dependencies"]
schedule:
interval: "daily"
interval: "monthly"
groups:
go-deps:
patterns:
@ -27,4 +27,4 @@ updates:
patterns:
- "*"
schedule:
interval: "daily"
interval: "monthly"

3
.github/labels.yaml vendored
View File

@ -25,3 +25,6 @@
- name: backport:release/v1.5.x
description: To be backported to release/v1.5.x
color: '#ffd700'
- name: backport:release/v1.6.x
description: To be backported to release/v1.6.x
color: '#ffd700'

View File

@ -3,8 +3,8 @@ module github.com/fluxcd/notification-controller/api
go 1.24.0
require (
github.com/fluxcd/pkg/apis/meta v1.12.0
k8s.io/apimachinery v0.33.0
github.com/fluxcd/pkg/apis/meta v1.17.0
k8s.io/apimachinery v0.33.2
sigs.k8s.io/controller-runtime v0.21.0
)
@ -21,8 +21,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/text v0.25.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/text v0.27.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
@ -30,5 +31,5 @@ require (
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
sigs.k8s.io/yaml v1.5.0 // indirect
)

View File

@ -2,8 +2,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fluxcd/pkg/apis/meta v1.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fluxcd/pkg/apis/meta v1.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E=
github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@ -54,6 +54,8 @@ 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.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
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=
@ -63,8 +65,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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=
@ -75,14 +77,14 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
@ -96,8 +98,8 @@ 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.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
@ -111,5 +113,6 @@ 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/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

View File

@ -97,12 +97,20 @@ type ProviderSpec struct {
Timeout *metav1.Duration `json:"timeout,omitempty"`
// Proxy the HTTP/S address of the proxy server.
// Deprecated: Use ProxySecretRef instead. Will be removed in v1.
// +kubebuilder:validation:Pattern="^(http|https)://.*$"
// +kubebuilder:validation:MaxLength:=2048
// +kubebuilder:validation:Optional
// +optional
Proxy string `json:"proxy,omitempty"`
// ProxySecretRef specifies the Secret containing the proxy configuration
// for this Provider. The Secret should contain an 'address' key with the
// HTTP/S address of the proxy server. Optional 'username' and 'password'
// keys can be provided for proxy authentication.
// +optional
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`
// SecretRef specifies the Secret containing the authentication
// credentials for this Provider.
// +optional
@ -115,12 +123,17 @@ type ProviderSpec struct {
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// CertSecretRef specifies the Secret containing
// a PEM-encoded CA certificate (in the `ca.crt` key).
// +optional
// CertSecretRef specifies the Secret containing TLS certificates
// for secure communication.
//
// Note: Support for the `caFile` key has
// been deprecated.
// Supported configurations:
// - CA-only: Server authentication (provide ca.crt only)
// - mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
// - Client-only: Client authentication with system CA (provide tls.crt + tls.key only)
//
// Legacy keys "caFile", "certFile", "keyFile" are supported but deprecated. Use "ca.crt", "tls.crt", "tls.key" instead.
//
// +optional
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
// Suspend tells the controller to suspend subsequent

View File

@ -196,6 +196,11 @@ func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) {
*out = new(metav1.Duration)
**out = **in
}
if in.ProxySecretRef != nil {
in, out := &in.ProxySecretRef, &out.ProxySecretRef
*out = new(meta.LocalObjectReference)
**out = **in
}
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(meta.LocalObjectReference)

View File

@ -443,11 +443,15 @@ spec:
type: string
certSecretRef:
description: |-
CertSecretRef specifies the Secret containing
a PEM-encoded CA certificate (in the `ca.crt` key).
CertSecretRef specifies the Secret containing TLS certificates
for secure communication.
Note: Support for the `caFile` key has
been deprecated.
Supported configurations:
- CA-only: Server authentication (provide ca.crt only)
- mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
- Client-only: Client authentication with system CA (provide tls.crt + tls.key only)
Legacy keys "caFile", "certFile", "keyFile" are supported but deprecated. Use "ca.crt", "tls.crt", "tls.key" instead.
properties:
name:
description: Name of the referent.
@ -475,10 +479,25 @@ spec:
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
type: string
proxy:
description: Proxy the HTTP/S address of the proxy server.
description: |-
Proxy the HTTP/S address of the proxy server.
Deprecated: Use ProxySecretRef instead. Will be removed in v1.
maxLength: 2048
pattern: ^(http|https)://.*$
type: string
proxySecretRef:
description: |-
ProxySecretRef specifies the Secret containing the proxy configuration
for this Provider. The Secret should contain an 'address' key with the
HTTP/S address of the proxy server. Optional 'username' and 'password'
keys can be provided for proxy authentication.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
secretRef:
description: |-
SecretRef specifies the Secret containing the authentication

View File

@ -330,7 +330,25 @@ string
</td>
<td>
<em>(Optional)</em>
<p>Proxy the HTTP/S address of the proxy server.</p>
<p>Proxy the HTTP/S address of the proxy server.
Deprecated: Use ProxySecretRef instead. Will be removed in v1.</p>
</td>
</tr>
<tr>
<td>
<code>proxySecretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ProxySecretRef specifies the Secret containing the proxy configuration
for this Provider. The Secret should contain an &lsquo;address&rsquo; key with the
HTTP/S address of the proxy server. Optional &lsquo;username&rsquo; and &lsquo;password&rsquo;
keys can be provided for proxy authentication.</p>
</td>
</tr>
<tr>
@ -374,10 +392,13 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<em>(Optional)</em>
<p>CertSecretRef specifies the Secret containing
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
<p>Note: Support for the <code>caFile</code> key has
been deprecated.</p>
<p>CertSecretRef specifies the Secret containing TLS certificates
for secure communication.</p>
<p>Supported configurations:
- CA-only: Server authentication (provide ca.crt only)
- mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
- Client-only: Client authentication with system CA (provide tls.crt + tls.key only)</p>
<p>Legacy keys &ldquo;caFile&rdquo;, &ldquo;certFile&rdquo;, &ldquo;keyFile&rdquo; are supported but deprecated. Use &ldquo;ca.crt&rdquo;, &ldquo;tls.crt&rdquo;, &ldquo;tls.key&rdquo; instead.</p>
</td>
</tr>
<tr>
@ -650,7 +671,25 @@ string
</td>
<td>
<em>(Optional)</em>
<p>Proxy the HTTP/S address of the proxy server.</p>
<p>Proxy the HTTP/S address of the proxy server.
Deprecated: Use ProxySecretRef instead. Will be removed in v1.</p>
</td>
</tr>
<tr>
<td>
<code>proxySecretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ProxySecretRef specifies the Secret containing the proxy configuration
for this Provider. The Secret should contain an &lsquo;address&rsquo; key with the
HTTP/S address of the proxy server. Optional &lsquo;username&rsquo; and &lsquo;password&rsquo;
keys can be provided for proxy authentication.</p>
</td>
</tr>
<tr>
@ -694,10 +733,13 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<em>(Optional)</em>
<p>CertSecretRef specifies the Secret containing
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
<p>Note: Support for the <code>caFile</code> key has
been deprecated.</p>
<p>CertSecretRef specifies the Secret containing TLS certificates
for secure communication.</p>
<p>Supported configurations:
- CA-only: Server authentication (provide ca.crt only)
- mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
- Client-only: Client authentication with system CA (provide tls.crt + tls.key only)</p>
<p>Legacy keys &ldquo;caFile&rdquo;, &ldquo;certFile&rdquo;, &ldquo;keyFile&rdquo; are supported but deprecated. Use &ldquo;ca.crt&rdquo;, &ldquo;tls.crt&rdquo;, &ldquo;tls.key&rdquo; instead.</p>
</td>
</tr>
<tr>

View File

@ -284,7 +284,7 @@ field](https://api.slack.com/methods/chat.postMessage#arg_username) to the
payload, defaulting to the name of the reporting controller.
This Provider type supports the configuration of a [proxy URL](#https-proxy)
and/or [TLS certificates](#tls-certificates).
and/or [certificate secret reference](#certificate-secret-reference).
###### Slack example
@ -363,7 +363,7 @@ In both cases the Event metadata is attached as facts, and the involved object a
The severity of the Event is used to set the color of the message.
This Provider type supports the configuration of a [proxy URL](#https-proxy)
and/or [TLS certificates](#tls-certificates), but lacks support for
and/or [certificate secret reference](#certificate-secret-reference), but lacks support for
configuring a [Channel](#channel). This can be configured during the
creation of the Incoming Webhook Workflow in Microsoft Teams.
@ -403,7 +403,7 @@ The Event will be formatted into a [DataDog Event](https://docs.datadoghq.com/ap
API endpoint of the provided DataDog [Address](#address).
This Provider type supports the configuration of a [proxy URL](#https-proxy)
and/or [TLS certificates](#tls-certificates).
and/or [certificate secret reference](#certificate-secret-reference).
The metadata of the Event is included in the DataDog event as extra tags.
@ -459,7 +459,7 @@ The Event will be formatted into a [Slack message](#slack) and send to the
`/slack` endpoint of the provided Discord [Address](#address).
This Provider type supports the configuration of a [proxy URL](#https-proxy)
and/or [TLS certificates](#tls-certificates), but lacks support for
and/or [certificate secret reference](#certificate-secret-reference), but lacks support for
configuring a [Channel](#channel). This can be configured [during the creation
of the address](https://discord.com/developers/docs/resources/webhook#create-webhook)
@ -507,7 +507,7 @@ The Provider's [Channel](#channel) is used to set the `environment` on the
Sentry client.
This Provider type supports the configuration of
[TLS certificates](#tls-certificates).
[certificate secret reference](#certificate-secret-reference).
###### Sentry example
@ -544,7 +544,7 @@ stringData:
##### Telegram
When `.spec.type` is set to `telegram`, the controller will send a payload for
an [Event](events.md#event-structure) to the provided Telegram [Address](#address).
an [Event](events.md#event-structure) to the Telegram Bot API.
The Event will be formatted into a message string, with the metadata attached
as a list of key-value pairs.
@ -554,15 +554,16 @@ This can be a unique identifier (`-1234567890`) for the target chat,
a unique identifier with the topic identifier (`-1234567890:1`) for the forum chat,
or the username (`@username`) of the target channel.
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
or [TLS certificates](#tls-certificates).
This Provider type does not support the configuration of a [certificate secret reference](#certificate-secret-reference).
**Note:** The Telegram provider always ignores the `address` field and uses the default
Telegram Bot API endpoint (`https://api.telegram.org`).
###### Telegram example
To configure a Provider for Telegram, create a Secret with [the `token`](#token-example)
obtained from [the BotFather](https://core.telegram.org/bots#how-do-i-create-a-bot),
and a `telegram` Provider with a [Secret reference](#secret-reference), and the
`address` set to `https://api.telegram.org`.
and a `telegram` Provider with a [Secret reference](#secret-reference).
```yaml
---
@ -573,7 +574,6 @@ metadata:
namespace: default
spec:
type: telegram
address: https://api.telegram.org
channel: "@fluxcd" # or "-1557265138" (channel id) or "-1552289257:1" (forum chat id with topic id)
secretRef:
name: telegram-token
@ -623,7 +623,7 @@ The Event will be formatted into a [Lark Message card](https://open.larksuite.co
with the metadata written to the message string.
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
or [TLS certificates](#tls-certificates).
or [certificate secret reference](#certificate-secret-reference).
###### Lark example
@ -660,7 +660,7 @@ The Event will be formatted into a [Slack message](#slack) and send as a
payload the provided Rocket [Address](#address).
This Provider type does support the configuration of a [proxy URL](#https-proxy)
and [TLS certificates](#tls-certificates).
and [certificate secret reference](#certificate-secret-reference).
###### Rocket example
@ -742,7 +742,7 @@ You can optionally add [attributes](https://cloud.google.com/pubsub/docs/samples
to the Pub/Sub message using a [`headers` key in the referenced Secret](#http-headers-example).
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
or [TLS certificates](#tls-certificates).
or [certificate secret reference](#certificate-secret-reference).
###### Google Pub/Sub with JSON Credentials and Custom Headers Example
@ -788,7 +788,7 @@ with the metadata added to the [`details` field](https://docs.opsgenie.com/docs/
as a list of key-value pairs.
This Provider type does support the configuration of a [proxy URL](#https-proxy)
and [TLS certificates](#tls-certificates).
and [certificate secret reference](#certificate-secret-reference).
###### Opsgenie example
@ -831,7 +831,7 @@ The provider will also send [Change Events](https://developer.pagerduty.com/api-
for `info` level `Severity`, which will be displayed in the PagerDuty service's timeline to track changes.
This Provider type supports the configuration of a [proxy URL](#https-proxy)
and [TLS certificates](#tls-certificates).
and [certificate secret reference](#certificate-secret-reference).
The [Channel](#channel) is used to set the routing key to send the event to the appropriate integration.
@ -916,7 +916,7 @@ global:
```
This Provider type does support the configuration of a [proxy URL](#https-proxy)
and [TLS certificates](#tls-certificates).
and [certificate secret reference](#certificate-secret-reference).
###### Prometheus Alertmanager example
@ -988,7 +988,7 @@ The [Channel](#channel) is used to set the ID of the room to send the message
to.
This Provider type does support the configuration of a [proxy URL](#https-proxy)
and [TLS certificates](#tls-certificates).
and [certificate secret reference](#certificate-secret-reference).
###### Webex example
@ -1097,7 +1097,7 @@ credentials for the provider API.
The Kubernetes secret can have any of the following keys:
- `address` - overrides `.spec.address`
- `proxy` - overrides `.spec.proxy`
- `proxy` - overrides `.spec.proxy` (deprecated, use `.spec.proxySecretRef` instead. **Support for this key will be removed in v1**)
- `token` - used for authentication
- `username` - overrides `.spec.username`
- `headers` - HTTP headers values included in the POST request
@ -1155,7 +1155,7 @@ stringData:
#### Proxy auth example
Some networks need to use an authenticated proxy to access external services.
Therefore, the proxy address can be stored as a secret to hide parameters like the username and password:
The recommended approach is to use `.spec.proxySecretRef` with a dedicated Secret:
```yaml
---
@ -1164,15 +1164,56 @@ kind: Secret
metadata:
name: my-provider-proxy
namespace: default
stringData:
address: "http://proxy_url:proxy_port"
username: "proxy_username"
password: "proxy_password"
```
**Legacy approach (deprecated):**
The proxy address can also be stored in the main secret to hide parameters like the username and password:
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: my-provider-proxy-legacy
namespace: default
stringData:
proxy: "http://username:password@proxy_url:proxy_port"
```
### TLS certificates
### Certificate secret reference
`.spec.certSecretRef` is an optional field to specify a name reference to a
Secret in the same namespace as the Provider, containing the TLS CA certificate.
The secret must be of type `kubernetes.io/tls` or `Opaque`.
Secret in the same namespace as the Provider, containing TLS certificates for
secure communication. The secret must be of type `kubernetes.io/tls` or `Opaque`.
#### Supported configurations
- **CA-only**: Server authentication (provide `ca.crt` only)
- **mTLS**: Client certificate authentication (provide `tls.crt` + `tls.key`, optionally with `ca.crt`)
#### Providers supporting client certificate authentication
The following webhook-based providers support client certificate authentication:
| Provider Type | Description |
|---------------------|--------------------------------|
| `alertmanager` | Prometheus Alertmanager |
| `discord` | Discord webhooks |
| `forwarder` | Generic forwarder |
| `grafana` | Grafana annotations API |
| `matrix` | Matrix rooms |
| `msteams` | Microsoft Teams |
| `opsgenie` | Opsgenie alerts |
| `pagerduty` | PagerDuty events |
| `rocket` | Rocket.Chat |
| `slack` | Slack API |
| `webex` | Webex messages |
Support for client certificate authentication is being expanded to additional providers over time.
#### Example
@ -1210,10 +1251,17 @@ the controller will log a deprecation warning.
### HTTP/S proxy
`.spec.proxy` is an optional field to specify an HTTP/S proxy address.
**Warning:** This field is deprecated, use `.spec.proxySecretRef` instead. **Support for this field will be removed in v1.**
`.spec.proxySecretRef` is an optional field to specify a name reference to a
Secret in the same namespace as the Provider, containing the proxy configuration.
The Secret should contain an `address` key with the HTTP/S address of the proxy server.
Optional `username` and `password` keys can be provided for proxy authentication.
If the proxy address contains sensitive information such as basic auth credentials, it is
recommended to store the proxy in the Kubernetes secret referenced by `.spec.secretRef.name`.
When the referenced Secret contains a `proxy` key, the `.spec.proxy` value is ignored.
recommended to use `.spec.proxySecretRef` instead of `.spec.proxy`.
When `.spec.proxySecretRef` is specified, both `.spec.proxy` and the `proxy` key from
`.spec.secretRef` are ignored.
### Timeout
@ -1305,10 +1353,8 @@ spec:
The `address` is the address of your repository where you want to send webhooks to trigger GitHub workflows.
GitHub uses [personal access
tokens]((https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token))
or [GitHub
app]((https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation))
GitHub uses [personal access tokens](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)
or [GitHub app](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation)
for authentication with its API:
#### GitHub personal access token

144
go.mod
View File

@ -10,64 +10,65 @@ require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
github.com/Azure/azure-amqp-common-go/v4 v4.2.0
github.com/Azure/azure-event-hubs-go/v3 v3.6.2
github.com/DataDog/datadog-api-client-go/v2 v2.37.1
github.com/DataDog/datadog-api-client-go/v2 v2.42.0
github.com/PagerDuty/go-pagerduty v1.8.0
github.com/cdevents/sdk-go v0.4.1
github.com/chainguard-dev/git-urls v1.0.2
github.com/containrrr/shoutrrr v0.8.0
github.com/elazarl/goproxy v1.7.2
github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/fluxcd/cli-utils v0.36.0-flux.14
github.com/fluxcd/notification-controller/api v1.6.0
github.com/fluxcd/pkg/apis/event v0.17.0
github.com/fluxcd/pkg/apis/meta v1.12.0
github.com/fluxcd/pkg/auth v0.16.0
github.com/fluxcd/pkg/cache v0.9.0
github.com/fluxcd/pkg/git v0.31.0
github.com/fluxcd/pkg/apis/event v0.18.0
github.com/fluxcd/pkg/apis/meta v1.17.0
github.com/fluxcd/pkg/auth v0.21.0
github.com/fluxcd/pkg/cache v0.10.0
github.com/fluxcd/pkg/git v0.34.0
github.com/fluxcd/pkg/masktoken v0.7.0
github.com/fluxcd/pkg/runtime v0.60.0
github.com/fluxcd/pkg/ssa v0.48.0
github.com/fluxcd/pkg/ssh v0.18.0
github.com/getsentry/sentry-go v0.32.0
github.com/go-logr/logr v1.4.2
github.com/google/cel-go v0.23.2
github.com/fluxcd/pkg/runtime v0.69.0
github.com/fluxcd/pkg/ssa v0.51.0
github.com/fluxcd/pkg/ssh v0.20.0
github.com/getsentry/sentry-go v0.34.1
github.com/go-logr/logr v1.4.3
github.com/google/cel-go v0.25.0
github.com/google/go-github/v64 v64.0.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/ktrysmt/go-bitbucket v0.9.85
github.com/hashicorp/go-retryablehttp v0.7.8
github.com/ktrysmt/go-bitbucket v0.9.86
github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1
github.com/nats-io/nats.go v1.41.2
github.com/nats-io/nats.go v1.43.0
github.com/onsi/gomega v1.37.0
github.com/sethvargo/go-limiter v1.0.0
github.com/slok/go-http-metrics v0.13.0
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
gitlab.com/gitlab-org/api/client-go v0.122.0
gitlab.com/gitlab-org/api/client-go v0.134.0
golang.org/x/oauth2 v0.30.0
golang.org/x/text v0.25.0
google.golang.org/api v0.230.0
k8s.io/api v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
golang.org/x/text v0.27.0
google.golang.org/api v0.241.0
k8s.io/api v0.33.2
k8s.io/apimachinery v0.33.2
k8s.io/client-go v0.33.2
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/yaml v1.4.0
sigs.k8s.io/yaml v1.5.0
)
// Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require (
cel.dev/expr v0.20.0 // indirect
cel.dev/expr v0.23.1 // indirect
cloud.google.com/go v0.120.0 // indirect
cloud.google.com/go/auth v0.16.0 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.4.2 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
github.com/42wim/httpsig v1.2.2 // indirect
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect
github.com/Azure/go-amqp v1.3.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@ -81,11 +82,12 @@ require (
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/DataDog/zstd v1.5.2 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bradleyfalzon/ghinstallation/v2 v2.15.0 // indirect
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0 // indirect
github.com/carapace-sh/carapace-shlex v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect
@ -94,25 +96,24 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/devigned/tab v0.1.1 // indirect
github.com/docker/cli v27.5.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/docker/cli v28.2.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v1.10.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v1.11.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
@ -122,15 +123,14 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/go-github/v71 v71.0.0 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/go-github/v72 v72.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@ -145,8 +145,6 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@ -167,8 +165,8 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
@ -180,39 +178,41 @@ require (
github.com/xlab/treeprint v1.2.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp 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
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/time v0.11.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
google.golang.org/grpc v1.72.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/time v0.12.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.0 // indirect
k8s.io/cli-runtime v0.33.0 // indirect
k8s.io/component-base v0.33.0 // indirect
k8s.io/apiextensions-apiserver v0.33.2 // indirect
k8s.io/cli-runtime v0.33.2 // indirect
k8s.io/component-base v0.33.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kubectl v0.33.0 // indirect
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 // indirect
k8s.io/kubectl v0.33.2 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/kustomize/api v0.20.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
)

311
go.sum
View File

@ -1,20 +1,20 @@
cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU=
cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q=
cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34=
cloud.google.com/go/kms v1.21.1 h1:r1Auo+jlfJSf8B7mUnVw5K0fI7jWyoUy65bV53VjKyk=
cloud.google.com/go/kms v1.21.1/go.mod h1:s0wCyByc9LjTdCjG88toVs70U9W+cc6RKFc8zAqX7nE=
cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/kms v1.21.2 h1:c/PRUSMNQ8zXrc1sdAUnsenWWaNXN+PzTXfXOcSFdoE=
cloud.google.com/go/kms v1.21.2/go.mod h1:8wkMtHV/9Z8mLXEXr1GK7xPSBdi6knuLXIhqjuWcI6w=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySPo=
cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY=
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
@ -35,8 +35,12 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3Vp
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k=
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 h1:figxyQZXzZQIcP3njhC68bYUiTw45J8/SsHaLW8Ax0M=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0/go.mod h1:TmlMW4W5OvXOmOyKNnor8nlMMiO1ctIyzmHme/VHsrA=
github.com/Azure/go-amqp v1.3.0 h1://1rikYhoIQNXJFXyoO/Rlb4+4EkHYfJceNtLlys2/4=
github.com/Azure/go-amqp v1.3.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
@ -70,16 +74,16 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/datadog-api-client-go/v2 v2.37.1 h1:weZhrGMO//sMEoSKWngoSQwMp4zBSlEX4p3/YWy9ltw=
github.com/DataDog/datadog-api-client-go/v2 v2.37.1/go.mod h1:d3tOEgUd2kfsr9uuHQdY+nXrWp4uikgTgVCPdKNK30U=
github.com/DataDog/datadog-api-client-go/v2 v2.42.0 h1:0L03LqChbOf7IaaiBUZpXi1Ss6PseGGhQEQrNqD9nU8=
github.com/DataDog/datadog-api-client-go/v2 v2.42.0/go.mod h1:d3tOEgUd2kfsr9uuHQdY+nXrWp4uikgTgVCPdKNK30U=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+oxH3zyluI=
github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI=
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@ -88,8 +92,10 @@ 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bradleyfalzon/ghinstallation/v2 v2.15.0 h1:7r2rPUM04rgszMP0U1UZ1M5VoVVIlsaBSnpABfYxcQY=
github.com/bradleyfalzon/ghinstallation/v2 v2.15.0/go.mod h1:PoH9Vhy82OeRFZfxsVrk3mfQhVkEzou9OOwPOsEhiXE=
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0 h1:B91r9bHtXp/+XRgS5aZm6ZzTdz3ahgJYmkt4xZkgDz8=
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0/go.mod h1:OeVe5ggFzoBnmgitZe/A+BqGOnv1DvU/0uiLQi1wutM=
github.com/carapace-sh/carapace-shlex v1.0.1 h1:ww0JCgWpOVuqWG7k3724pJ18Lq8gh5pHQs9j3ojUs1c=
github.com/carapace-sh/carapace-shlex v1.0.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/cdevents/sdk-go v0.4.1 h1:Cr/iH/I51Z+slxKRx9AV7stn6hr2pjRHQ5wpPJhRLTU=
github.com/cdevents/sdk-go v0.4.1/go.mod h1:3IhWLoY4vsyUEzv7XJbyr0BRQ0KPgvNx+wiD2hQGFNU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -105,8 +111,6 @@ github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@ -128,14 +132,14 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM=
github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/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.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -150,40 +154,40 @@ 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/fluxcd/cli-utils v0.36.0-flux.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6iwbNZz7OzM=
github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA=
github.com/fluxcd/cli-utils v0.36.0-flux.14 h1:I//AMVUXTc+M04UtIXArMXQZCazGMwfemodV1j/yG8c=
github.com/fluxcd/cli-utils v0.36.0-flux.14/go.mod h1:uDo7BYOfbdmk/asnHuI0IQPl6u0FCgcN54AHDu3Y5As=
github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0=
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlbYi5NU7M=
github.com/fluxcd/pkg/apis/event v0.17.0/go.mod h1:0fLhLFiHlRTDKPDXdRnv+tS7mCMIQ0fJxnEfmvGM/5A=
github.com/fluxcd/pkg/apis/kustomize v1.10.0 h1:47EeSzkQvlQZdH92vHMe2lK2iR8aOSEJq95avw5idts=
github.com/fluxcd/pkg/apis/kustomize v1.10.0/go.mod h1:UsqMV4sqNa1Yg0pmTsdkHRJr7bafBOENIJoAN+3ezaQ=
github.com/fluxcd/pkg/apis/meta v1.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fluxcd/pkg/auth v0.16.0 h1:YEjSaNqlpYoXfoFAGhU/Z8y0322nGsT24W6zCh+sbGw=
github.com/fluxcd/pkg/auth v0.16.0/go.mod h1:+BRnAO61Nr6fACEjJS6eNRdOk1nXhX/FCPylYn1ypNc=
github.com/fluxcd/pkg/cache v0.9.0 h1:EGKfOLMG3fOwWnH/4Axl5xd425mxoQbZzlZoLfd8PDk=
github.com/fluxcd/pkg/cache v0.9.0/go.mod h1:jMwabjWfsC5lW8hE7NM3wtGNwSJ38Javx6EKbEi7INU=
github.com/fluxcd/pkg/git v0.31.0 h1:hVUJcRujNa+GA5zrjrMpuVcgHbCBjfq0CZIZJqJl22I=
github.com/fluxcd/pkg/git v0.31.0/go.mod h1:rUgLXVQGBkBggHOLVMhHMHaweQ8Oc6HwZiN2Zm08Zxs=
github.com/fluxcd/pkg/apis/event v0.18.0 h1:PNbWk9gvX8gMIi6VsJapnuDO+giLEeY+6olLVXvXFkk=
github.com/fluxcd/pkg/apis/event v0.18.0/go.mod h1:7S/DGboLolfbZ6stO6dcDhG1SfkPWQ9foCULvbiYpiA=
github.com/fluxcd/pkg/apis/kustomize v1.11.0 h1:0IzDgxZkc4v+5SDNCvgZhfwfkdkQLPXCner7TNaJFWE=
github.com/fluxcd/pkg/apis/kustomize v1.11.0/go.mod h1:j302mJGDww8cn9qvMsRQ0LJ1HPAPs/IlX7CSsoJV7BI=
github.com/fluxcd/pkg/apis/meta v1.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E=
github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fluxcd/pkg/auth v0.21.0 h1:ckAQqP12wuptXEkMY18SQKWEY09m9e6yI0mEMsDV15M=
github.com/fluxcd/pkg/auth v0.21.0/go.mod h1:MXmpsXT97c874HCw5hnfqFUP7TsG8/Ss1vFrk8JccfM=
github.com/fluxcd/pkg/cache v0.10.0 h1:M+OGDM4da1cnz7q+sZSBtkBJHpiJsLnKVmR9OdMWxEY=
github.com/fluxcd/pkg/cache v0.10.0/go.mod h1:pPXRzQUDQagsCniuOolqVhnAkbNgYOg8d2cTliPs7ME=
github.com/fluxcd/pkg/git v0.34.0 h1:qTViWkfpEDnjzySyKRKliqUeGj/DznqlkmPhaDNIsFY=
github.com/fluxcd/pkg/git v0.34.0/go.mod h1:F9Asm3MlLW4uZx3FF92+bqho+oktdMdnTn/QmXe56NE=
github.com/fluxcd/pkg/masktoken v0.7.0 h1:pitmyOg2pUVdW+nn2Lk/xqm2TaA08uxvOC0ns3sz6bM=
github.com/fluxcd/pkg/masktoken v0.7.0/go.mod h1:Lc1uoDjO1GY6+YdkK+ZqqBIBWquyV58nlSJ5S1N1IYU=
github.com/fluxcd/pkg/runtime v0.60.0 h1:d++EkV3FlycB+bzakB5NumwY4J8xts8i7lbvD6jBLeU=
github.com/fluxcd/pkg/runtime v0.60.0/go.mod h1:UeU0/eZLErYC/1bTmgzBfNXhiHy9fuQzjfLK0HxRgxY=
github.com/fluxcd/pkg/ssa v0.48.0 h1:DW+4DG8L/yZEi30UltOEXPB1d/ZFn4HfVhpJQp5oc2o=
github.com/fluxcd/pkg/ssa v0.48.0/go.mod h1:T50TO0U2obLodZnrFgOrxollfBEy4V673OkM2aTUF1c=
github.com/fluxcd/pkg/ssh v0.18.0 h1:SB0RrZ/YZIla3chTUulsfVmiCzJv5pEWfHM3dHMC8AU=
github.com/fluxcd/pkg/ssh v0.18.0/go.mod h1:G5o0ZD7iR3KFoG5gPnFelX243ciI/PIiVW7J4eBrt5Y=
github.com/fluxcd/pkg/runtime v0.69.0 h1:5gPY95NSFI34GlQTj0+NHjOFpirSwviCUb9bM09b5nA=
github.com/fluxcd/pkg/runtime v0.69.0/go.mod h1:ug+pat+I4wfOBuCy2E/pLmBNd3kOOo4cP2jxnxefPwY=
github.com/fluxcd/pkg/ssa v0.51.0 h1:sFarxKZcS0J8sjq9qvs/r+1XiJqNgRodEiPjV75F8R4=
github.com/fluxcd/pkg/ssa v0.51.0/go.mod h1:v+h9RC0JxWIqMTK2Eo+8Nh700AXyZChZ2TiLVj4tf3M=
github.com/fluxcd/pkg/ssh v0.20.0 h1:Ak0laIYIc/L8lEfqls/LDWRW8wYPESGaravQsCRGLb8=
github.com/fluxcd/pkg/ssh v0.20.0/go.mod h1:sRfAAkxx1GwCGjYirKPnTKdNkNrJRo9kqzWLVFXKv7E=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
github.com/getsentry/sentry-go v0.34.1 h1:HSjc1C/OsnZttohEPrrqKH42Iud0HuLCXpv8cU1pWcw=
github.com/getsentry/sentry-go v0.34.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
@ -191,18 +195,18 @@ github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
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.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/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
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-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -211,7 +215,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
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/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
@ -242,10 +245,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4=
github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
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.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -256,31 +259,29 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/go-github/v64 v64.0.0 h1:4G61sozmY3eiPAjjoOHponXDBONm+utovTKbyUb2Qdg=
github.com/google/go-github/v64 v64.0.0/go.mod h1:xB3vqMQNdHzilXBiO2I+M7iEFtHf+DP/omBOv6tQzVo=
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
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-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
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/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
@ -289,14 +290,12 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
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/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@ -318,8 +317,8 @@ 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/ktrysmt/go-bitbucket v0.9.85 h1:WSKYSmpgasEmtnsr+TEhD2UtiZjCZpeTBF5T4f6/d8k=
github.com/ktrysmt/go-bitbucket v0.9.85/go.mod h1:ZgvxUOaC6eHrNaC/DbjFvJUXaKpKeDYvfhh4U592jcs=
github.com/ktrysmt/go-bitbucket v0.9.86 h1:co80t9wS4kKgRLsvDgi+3wQPiLY30u10ehcTxgXFvzw=
github.com/ktrysmt/go-bitbucket v0.9.86/go.mod h1:/lsYmiQrBHNPTnPKF0Q+safIS7peInQOGbJXu0xPRho=
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@ -330,7 +329,6 @@ 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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1 h1:ACnM5CwgTH6OSQHErzZDrotEG0rffPdJxtF/WOWglAw=
@ -356,8 +354,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nats-io/nats.go v1.41.2 h1:5UkfLAtu/036s99AhFRlyNDI1Ieylb36qbGjJzHixos=
github.com/nats-io/nats.go v1.41.2/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug=
github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
@ -386,10 +384,10 @@ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
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/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@ -434,28 +432,28 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
gitlab.com/gitlab-org/api/client-go v0.122.0 h1:Nog85APtgquS+HHkMkP4DiZ6lXlUZYhQKqguS4OJYNM=
gitlab.com/gitlab-org/api/client-go v0.122.0/go.mod h1:Jh0qjLILEdbO6z/OY94RD+3NDQRUKiuFSFYozN6cpKM=
gitlab.com/gitlab-org/api/client-go v0.134.0 h1:J4i6qPN5hRLsqatPxVbe9w2C0A3JEItyCQrzsP52S2k=
gitlab.com/gitlab-org/api/client-go v0.134.0/go.mod h1:crkp9sCwMQ8gDwuMLgk11sDT336t6U3kESBT0BGsOBo=
go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ=
go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
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.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
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.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
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.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
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/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -464,14 +462,18 @@ 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.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.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
@ -480,8 +482,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -493,8 +495,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
@ -503,8 +505,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -514,21 +516,19 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -537,34 +537,34 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
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.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.230.0 h1:2u1hni3E+UXAXrONrrkfWpi/V6cyKVAbfGVeGtC3OxM=
google.golang.org/api v0.230.0/go.mod h1:aqvtoMk7YkiXx+6U12arQFExiRV9D/ekvMCwCd/TksQ=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE=
google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY=
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -592,38 +592,39 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs=
k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/cli-runtime v0.33.0 h1:Lbl/pq/1o8BaIuyn+aVLdEPHVN665tBAXUePs8wjX7c=
k8s.io/cli-runtime v0.33.0/go.mod h1:QcA+r43HeUM9jXFJx7A+yiTPfCooau/iCcP1wQh4NFw=
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk=
k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU=
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y=
k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88=
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0=
k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k=
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-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g=
k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 h1:gAXU86Fmbr/ktY17lkHwSjw5aoThQvhnstGGIYKlKYc=
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw=
k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y=
k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
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/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
sigs.k8s.io/kustomize/api v0.20.0 h1:xPLqcobHI0bThyRUteO+nCV8G4d1Rlo5HafO57VRcas=
sigs.k8s.io/kustomize/api v0.20.0/go.mod h1:F6CfaV27oevRCMJgehLqyX81dlUnRX/Fc13Uo7+OSo4=
sigs.k8s.io/kustomize/kyaml v0.20.0 h1:tT8KMKi4R3hCJ1+9HDdek2VoXpkerP92ZfF6fDgGw14=
sigs.k8s.io/kustomize/kyaml v0.20.0/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
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/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"encoding/json"
"fmt"
"net/url"
@ -32,10 +32,10 @@ import (
)
type Alertmanager struct {
URL string
ProxyURL string
CertPool *x509.CertPool
Token string
URL string
ProxyURL string
TLSConfig *tls.Config
Token string
}
type AlertManagerAlert struct {
@ -74,17 +74,17 @@ func (a *AlertManagerTime) UnmarshalJSON(jsonRepr []byte) error {
return nil
}
func NewAlertmanager(hookURL string, proxyURL string, certPool *x509.CertPool, token string) (*Alertmanager, error) {
func NewAlertmanager(hookURL string, proxyURL string, tlsConfig *tls.Config, token string) (*Alertmanager, error) {
_, err := url.ParseRequestURI(hookURL)
if err != nil {
return nil, fmt.Errorf("invalid Alertmanager URL %s: '%w'", hookURL, err)
}
return &Alertmanager{
URL: hookURL,
ProxyURL: proxyURL,
CertPool: certPool,
Token: token,
URL: hookURL,
ProxyURL: proxyURL,
Token: token,
TLSConfig: tlsConfig,
}, nil
}
@ -141,8 +141,8 @@ func (s *Alertmanager) Post(ctx context.Context, event eventv1.Event) error {
if s.ProxyURL != "" {
opts = append(opts, withProxy(s.ProxyURL))
}
if s.CertPool != nil {
opts = append(opts, withCertPool(s.CertPool))
if s.TLSConfig != nil {
opts = append(opts, withTLSConfig(s.TLSConfig))
}
if s.Token != "" {
opts = append(opts, withRequestModifier(func(request *retryablehttp.Request) {

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -43,10 +43,10 @@ func Fuzz_AlertManager(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
alertmanager, err := NewAlertmanager(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, "")
alertmanager, err := NewAlertmanager(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &tlsConfig, "")
if err != nil {
return
}

View File

@ -20,16 +20,15 @@ import (
"net/url"
"strings"
"github.com/Azure/azure-amqp-common-go/v4/auth"
azauth "github.com/Azure/azure-amqp-common-go/v4/auth"
eventhub "github.com/Azure/azure-event-hubs-go/v3"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
pkgauth "github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/auth/azure"
"github.com/fluxcd/pkg/cache"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/notification-controller/api/v1beta3"
)
@ -40,7 +39,9 @@ type AzureEventHub struct {
}
// NewAzureEventHub creates a eventhub client
func NewAzureEventHub(ctx context.Context, endpointURL, token, eventHubNamespace, proxy, serviceAccountName, providerName, providerNamespace string, tokenClient client.Client, tokenCache *pkgcache.TokenCache) (*AzureEventHub, error) {
func NewAzureEventHub(ctx context.Context, endpointURL, token, eventHubNamespace, proxy,
serviceAccountName, providerName, providerNamespace string, tokenClient client.Client,
tokenCache *cache.TokenCache) (*AzureEventHub, error) {
var hub *eventhub.Hub
var err error
@ -109,9 +110,9 @@ func NewJWTProvider(jwt string) *PureJWT {
}
// GetToken uses a JWT token, we assume that we will get new tokens when needed, thus no Expiry defined
func (j *PureJWT) GetToken(uri string) (*auth.Token, error) {
return &auth.Token{
TokenType: auth.CBSTokenTypeJWT,
func (j *PureJWT) GetToken(uri string) (*azauth.Token, error) {
return &azauth.Token{
TokenType: azauth.CBSTokenTypeJWT,
Token: j.jwt,
Expiry: "",
}, nil
@ -139,14 +140,15 @@ func newSASHub(address string) (*eventhub.Hub, error) {
}
// newManagedIdentityToken is used to attempt credential-free authentication.
func newManagedIdentityToken(ctx context.Context, proxy, serviceAccountName, providerName, providerNamespace string, tokenClient client.Client, tokenCache *pkgcache.TokenCache) (string, error) {
opts := []pkgauth.Option{pkgauth.WithScopes(azure.ScopeEventHubs)}
func newManagedIdentityToken(ctx context.Context, proxy, serviceAccountName, providerName,
providerNamespace string, tokenClient client.Client, tokenCache *cache.TokenCache) (string, error) {
opts := []auth.Option{auth.WithScopes(azure.ScopeEventHubs)}
if proxy != "" {
proxyURL, err := url.Parse(proxy)
if err != nil {
return "", fmt.Errorf("error parsing proxy URL : %w", err)
}
opts = append(opts, pkgauth.WithProxyURL(*proxyURL))
opts = append(opts, auth.WithProxyURL(*proxyURL))
}
if serviceAccountName != "" {
@ -154,7 +156,7 @@ func newManagedIdentityToken(ctx context.Context, proxy, serviceAccountName, pro
Name: serviceAccountName,
Namespace: providerNamespace,
}
opts = append(opts, pkgauth.WithServiceAccount(serviceAccount, tokenClient))
opts = append(opts, auth.WithServiceAccount(serviceAccount, tokenClient))
}
if tokenCache != nil {
@ -164,10 +166,10 @@ func newManagedIdentityToken(ctx context.Context, proxy, serviceAccountName, pro
Namespace: providerNamespace,
Operation: OperationPost,
}
opts = append(opts, pkgauth.WithCache(*tokenCache, involvedObject))
opts = append(opts, auth.WithCache(*tokenCache, involvedObject))
}
token, err := pkgauth.GetToken(ctx, azure.Provider{}, opts...)
token, err := auth.GetAccessToken(ctx, azure.Provider{}, opts...)
if err != nil {
return "", fmt.Errorf("failed to get token for azure event hub: %w", err)
}
@ -177,6 +179,10 @@ func newManagedIdentityToken(ctx context.Context, proxy, serviceAccountName, pro
// validateAuthOptions checks if the authentication options are valid
func validateAuthOptions(endpointURL, token, serviceAccountName string) error {
if endpointURL == "" {
return fmt.Errorf("endpoint URL cannot be empty")
}
if isSASAuth(endpointURL) {
if err := validateSASAuth(token, serviceAccountName); err != nil {
return err

View File

@ -47,7 +47,7 @@ func TestNewAzureEventHub(t *testing.T) {
endpointURL: "azure-nc-eventhub",
token: "",
eventHubNamespace: "namespace",
err: errors.New("failed to create a eventhub using managed identity failed to get token for azure event hub: failed to create provider access token for the controlller: ManagedIdentityCredential: failed to authenticate a system assigned identity. The endpoint responded with {\"error\":\"invalid_request\",\"error_description\":\"Identity not found\"}"),
err: errors.New("failed to create a eventhub using managed identity failed to get token for azure event hub: failed to create provider access token for the controller: ManagedIdentityCredential: failed to authenticate a system assigned identity. The endpoint responded with {\"error\":\"invalid_request\",\"error_description\":\"Identity not found\"}"),
},
{
name: "SAS auth with serviceAccountName set",
@ -72,6 +72,13 @@ func TestNewAzureEventHub(t *testing.T) {
eventHubNamespace: "namespace",
err: errors.New("invalid authentication options: serviceAccountName and jwt token authentication cannot be set at the same time"),
},
{
name: "empty endpoint URL",
endpointURL: "",
token: "test-token",
eventHubNamespace: "namespace",
err: errors.New("invalid authentication options: endpoint URL cannot be empty"),
},
}
for _, tt := range tests {

View File

@ -19,14 +19,11 @@ package notifier
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"runtime"
"time"
"github.com/hashicorp/go-retryablehttp"
@ -34,7 +31,7 @@ import (
type postOptions struct {
proxy string
certPool *x509.CertPool
tlsConfig *tls.Config
requestModifier func(*retryablehttp.Request)
responseValidator func(statusCode int, body []byte) error
}
@ -103,9 +100,9 @@ func withProxy(proxy string) postOption {
}
}
func withCertPool(certPool *x509.CertPool) postOption {
func withTLSConfig(tlsConfig *tls.Config) postOption {
return func(opts *postOptions) {
opts.certPool = certPool
opts.tlsConfig = tlsConfig
}
}
@ -123,12 +120,11 @@ func withResponseValidator(respValidator func(statusCode int, body []byte) error
func newHTTPClient(opts *postOptions) (*retryablehttp.Client, error) {
httpClient := retryablehttp.NewClient()
if opts.certPool != nil {
httpClient.HTTPClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: opts.certPool,
},
}
transport := httpClient.HTTPClient.Transport.(*http.Transport)
if opts.tlsConfig != nil {
transport.TLSClientConfig = opts.tlsConfig
}
if opts.proxy != "" {
@ -136,27 +132,7 @@ func newHTTPClient(opts *postOptions) (*retryablehttp.Client, error) {
if err != nil {
return nil, fmt.Errorf("unable to parse proxy URL: %w", err)
}
var tlsConfig *tls.Config
if opts.certPool != nil {
tlsConfig = &tls.Config{
RootCAs: opts.certPool,
}
}
httpClient.HTTPClient.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: tlsConfig,
DialContext: (&net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
}
transport.Proxy = http.ProxyURL(proxyURL)
}
// Disable the timeout for the HTTP client,

View File

@ -18,6 +18,7 @@ package notifier
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
@ -80,7 +81,8 @@ func Test_postSelfSignedCert(t *testing.T) {
require.NoError(t, err)
certpool := x509.NewCertPool()
certpool.AddCert(cert)
err = postMessage(context.Background(), ts.URL, map[string]string{"status": "success"}, withCertPool(certpool))
tlsConfig := &tls.Config{RootCAs: certpool}
err = postMessage(context.Background(), ts.URL, map[string]string{"status": "success"}, withTLSConfig(tlsConfig))
require.NoError(t, err)
}

View File

@ -18,12 +18,13 @@ package notifier
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/cache"
apiv1 "github.com/fluxcd/notification-controller/api/v1beta3"
)
@ -69,21 +70,24 @@ type notifierMap map[string]factoryFunc
type factoryFunc func(opts notifierOptions) (Interface, error)
type notifierOptions struct {
Context context.Context
URL string
ProxyURL string
Username string
Channel string
Token string
Headers map[string]string
Context context.Context
URL string
ProxyURL string
Username string
Channel string
Token string
Headers map[string]string
// CertPool is kept for Git platform providers (GitHub, GitLab, etc.) that use third-party SDKs.
// TODO: Remove this field once all notifiers support client certificate authentication via TLSConfig.
CertPool *x509.CertPool
TLSConfig *tls.Config
Password string
CommitStatus string
ProviderName string
ProviderNamespace string
SecretData map[string][]byte
ServiceAccountName string
TokenCache *pkgcache.TokenCache
TokenCache *cache.TokenCache
TokenClient client.Client
}
@ -136,6 +140,13 @@ func WithCertPool(certPool *x509.CertPool) Option {
}
}
// WithTLSConfig sets the TLS configuration for the notifier.
func WithTLSConfig(tlsConfig *tls.Config) Option {
return func(o *notifierOptions) {
o.TLSConfig = tlsConfig
}
}
// WithPassword sets the password for the notifier.
func WithPassword(password string) Option {
return func(o *notifierOptions) {
@ -172,7 +183,7 @@ func WithSecretData(data map[string][]byte) Option {
}
// WithTokenCache sets the token cache for the notifier.
func WithTokenCache(cache *pkgcache.TokenCache) Option {
func WithTokenCache(cache *cache.TokenCache) Option {
return func(o *notifierOptions) {
o.TokenCache = cache
}
@ -192,11 +203,17 @@ func WithServiceAccount(serviceAccountName string) Option {
}
}
// NewFactory creates a new notifier factory with the given URL and optional configurations.
func NewFactory(ctx context.Context, url string, opts ...Option) *Factory {
// WithURL sets the webhook URL for the notifier.
func WithURL(url string) Option {
return func(o *notifierOptions) {
o.URL = url
}
}
// NewFactory creates a new notifier factory with optional configurations.
func NewFactory(ctx context.Context, opts ...Option) *Factory {
options := notifierOptions{
Context: ctx,
URL: url,
}
for _, opt := range opts {
@ -209,36 +226,24 @@ func NewFactory(ctx context.Context, url string, opts ...Option) *Factory {
}
func (f Factory) Notifier(provider string) (Interface, error) {
if f.URL == "" {
return &NopNotifier{}, nil
notifier, ok := notifiers[provider]
if !ok {
return nil, fmt.Errorf("provider %s not supported", provider)
}
var (
n Interface
err error
)
if notifier, ok := notifiers[provider]; ok {
n, err = notifier(f.notifierOptions)
} else {
err = fmt.Errorf("provider %s not supported", provider)
}
if err != nil {
n = &NopNotifier{}
}
return n, err
return notifier(f.notifierOptions)
}
func genericNotifierFunc(opts notifierOptions) (Interface, error) {
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.CertPool, nil)
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, nil)
}
func genericHMACNotifierFunc(opts notifierOptions) (Interface, error) {
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.CertPool, []byte(opts.Token))
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, []byte(opts.Token))
}
func slackNotifierFunc(opts notifierOptions) (Interface, error) {
return NewSlack(opts.URL, opts.ProxyURL, opts.Token, opts.CertPool, opts.Username, opts.Channel)
return NewSlack(opts.URL, opts.ProxyURL, opts.Token, opts.TLSConfig, opts.Username, opts.Channel)
}
func discordNotifierFunc(opts notifierOptions) (Interface, error) {
@ -246,11 +251,11 @@ func discordNotifierFunc(opts notifierOptions) (Interface, error) {
}
func rocketNotifierFunc(opts notifierOptions) (Interface, error) {
return NewRocket(opts.URL, opts.ProxyURL, opts.CertPool, opts.Username, opts.Channel)
return NewRocket(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Username, opts.Channel)
}
func msteamsNotifierFunc(opts notifierOptions) (Interface, error) {
return NewMSTeams(opts.URL, opts.ProxyURL, opts.CertPool)
return NewMSTeams(opts.URL, opts.ProxyURL, opts.TLSConfig)
}
func googleChatNotifierFunc(opts notifierOptions) (Interface, error) {
@ -262,7 +267,7 @@ func googlePubSubNotifierFunc(opts notifierOptions) (Interface, error) {
}
func webexNotifierFunc(opts notifierOptions) (Interface, error) {
return NewWebex(opts.URL, opts.ProxyURL, opts.CertPool, opts.Channel, opts.Token)
return NewWebex(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Channel, opts.Token)
}
func sentryNotifierFunc(opts notifierOptions) (Interface, error) {
@ -274,7 +279,7 @@ func azureEventHubNotifierFunc(opts notifierOptions) (Interface, error) {
}
func telegramNotifierFunc(opts notifierOptions) (Interface, error) {
return NewTelegram(opts.Channel, opts.Token)
return NewTelegram(opts.ProxyURL, opts.Channel, opts.Token)
}
func larkNotifierFunc(opts notifierOptions) (Interface, error) {
@ -282,23 +287,23 @@ func larkNotifierFunc(opts notifierOptions) (Interface, error) {
}
func matrixNotifierFunc(opts notifierOptions) (Interface, error) {
return NewMatrix(opts.URL, opts.Token, opts.Channel, opts.CertPool)
return NewMatrix(opts.URL, opts.Token, opts.Channel, opts.TLSConfig)
}
func opsgenieNotifierFunc(opts notifierOptions) (Interface, error) {
return NewOpsgenie(opts.URL, opts.ProxyURL, opts.CertPool, opts.Token)
return NewOpsgenie(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Token)
}
func alertmanagerNotifierFunc(opts notifierOptions) (Interface, error) {
return NewAlertmanager(opts.URL, opts.ProxyURL, opts.CertPool, opts.Token)
return NewAlertmanager(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Token)
}
func grafanaNotifierFunc(opts notifierOptions) (Interface, error) {
return NewGrafana(opts.URL, opts.ProxyURL, opts.Token, opts.CertPool, opts.Username, opts.Password)
return NewGrafana(opts.URL, opts.ProxyURL, opts.Token, opts.TLSConfig, opts.Username, opts.Password)
}
func pagerDutyNotifierFunc(opts notifierOptions) (Interface, error) {
return NewPagerDuty(opts.URL, opts.ProxyURL, opts.CertPool, opts.Channel)
return NewPagerDuty(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Channel)
}
func dataDogNotifierFunc(opts notifierOptions) (Interface, error) {

View File

@ -20,7 +20,7 @@ import (
"context"
"crypto/hmac"
"crypto/sha256"
"crypto/x509"
"crypto/tls"
"encoding/json"
"fmt"
"net/url"
@ -37,14 +37,14 @@ const NotificationHeader = "gotk-component"
// Forwarder is an implementation of the notification Interface that posts the
// body as an HTTP request using an optional proxy.
type Forwarder struct {
URL string
ProxyURL string
Headers map[string]string
CertPool *x509.CertPool
HMACKey []byte
URL string
ProxyURL string
Headers map[string]string
TLSConfig *tls.Config
HMACKey []byte
}
func NewForwarder(hookURL string, proxyURL string, headers map[string]string, certPool *x509.CertPool, hmacKey []byte) (*Forwarder, error) {
func NewForwarder(hookURL string, proxyURL string, headers map[string]string, tlsConfig *tls.Config, hmacKey []byte) (*Forwarder, error) {
if _, err := url.ParseRequestURI(hookURL); err != nil {
return nil, fmt.Errorf("invalid hook URL %s: %w", hookURL, err)
}
@ -54,11 +54,11 @@ func NewForwarder(hookURL string, proxyURL string, headers map[string]string, ce
}
return &Forwarder{
URL: hookURL,
ProxyURL: proxyURL,
Headers: headers,
CertPool: certPool,
HMACKey: hmacKey,
URL: hookURL,
ProxyURL: proxyURL,
Headers: headers,
HMACKey: hmacKey,
TLSConfig: tlsConfig,
}, nil
}
@ -92,8 +92,8 @@ func (f *Forwarder) Post(ctx context.Context, event eventv1.Event) error {
if f.ProxyURL != "" {
opts = append(opts, withProxy(f.ProxyURL))
}
if f.CertPool != nil {
opts = append(opts, withCertPool(f.CertPool))
if f.TLSConfig != nil {
opts = append(opts, withTLSConfig(f.TLSConfig))
}
if err := postMessage(ctx, f.URL, event, opts...); err != nil {

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -41,13 +41,13 @@ func Fuzz_Forwarder(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
header := make(map[string]string)
_ = fuzz.NewConsumer(seed).FuzzMap(&header)
forwarder, err := NewForwarder(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", header, &cert, hmacKey)
forwarder, err := NewForwarder(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", header, &tlsConfig, hmacKey)
if err != nil {
return
}

View File

@ -26,7 +26,7 @@ import (
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/cache"
)
type GitHub struct {
@ -36,13 +36,17 @@ type GitHub struct {
Client *github.Client
}
func NewGitHub(commitStatus string, addr string, token string, certPool *x509.CertPool, proxyURL string, providerName string, providerNamespace string, secretData map[string][]byte, tokenCache *pkgcache.TokenCache) (*GitHub, error) {
func NewGitHub(commitStatus string, addr string, token string, certPool *x509.CertPool,
proxyURL string, providerName string, providerNamespace string, secretData map[string][]byte,
tokenCache *cache.TokenCache) (*GitHub, error) {
// this should never happen
if commitStatus == "" {
return nil, errors.New("commit status cannot be empty")
}
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool, proxyURL, providerName, providerNamespace, secretData, tokenCache)
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool,
proxyURL, providerName, providerNamespace, secretData, tokenCache)
if err != nil {
return nil, err
}

View File

@ -23,7 +23,7 @@ import (
"fmt"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/cache"
"github.com/google/go-github/v64/github"
)
@ -34,8 +34,12 @@ type GitHubDispatch struct {
Client *github.Client
}
func NewGitHubDispatch(addr string, token string, certPool *x509.CertPool, proxyURL string, providerName string, providerNamespace string, secretData map[string][]byte, tokenCache *pkgcache.TokenCache) (*GitHubDispatch, error) {
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool, proxyURL, providerName, providerNamespace, secretData, tokenCache)
func NewGitHubDispatch(addr string, token string, certPool *x509.CertPool, proxyURL string,
providerName string, providerNamespace string, secretData map[string][]byte,
tokenCache *cache.TokenCache) (*GitHubDispatch, error) {
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool,
proxyURL, providerName, providerNamespace, secretData, tokenCache)
if err != nil {
return nil, err
}

View File

@ -65,7 +65,7 @@ func TestNewGithubDispatchProvider(t *testing.T) {
kp, _ := ssh.GenerateKeyPair(ssh.RSA_4096)
expiresAt := time.Now().UTC().Add(time.Hour)
var tests = []struct {
for _, tt := range []struct {
name string
secretData map[string][]byte
wantErr error
@ -85,7 +85,7 @@ func TestNewGithubDispatchProvider(t *testing.T) {
"githubAppInstallationID": []byte(installationID),
"githubAppPrivateKey": kp.PrivateKey,
},
wantErr: errors.New("app ID must be provided to use github app authentication"),
wantErr: errors.New("github token or github app details must be specified"),
},
{
name: "provider with missing app installation ID in options ",
@ -111,32 +111,32 @@ func TestNewGithubDispatchProvider(t *testing.T) {
"githubAppPrivateKey": kp.PrivateKey,
},
},
}
} {
t.Run(tt.name, func(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
var response []byte
var err error
response, err = json.Marshal(&authgithub.AppToken{Token: "access-token", ExpiresAt: expiresAt})
assert.Nil(t, err)
w.Write(response)
}
srv := httptest.NewServer(http.HandlerFunc(handler))
t.Cleanup(func() {
srv.Close()
})
for _, tt := range tests {
handler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
var response []byte
var err error
response, err = json.Marshal(&authgithub.AppToken{Token: "access-token", ExpiresAt: expiresAt})
assert.Nil(t, err)
w.Write(response)
}
srv := httptest.NewServer(http.HandlerFunc(handler))
t.Cleanup(func() {
srv.Close()
if len(tt.secretData) > 0 {
tt.secretData["githubAppBaseURL"] = []byte(srv.URL)
}
_, err := NewGitHubDispatch("https://github.com/foo/bar", "", nil, "", "foo", "bar", tt.secretData, nil)
if tt.wantErr != nil {
assert.NotNil(t, err)
assert.Equal(t, tt.wantErr, err)
} else {
assert.Nil(t, err)
}
})
if tt.secretData != nil && len(tt.secretData) > 0 {
tt.secretData["githubAppBaseURL"] = []byte(srv.URL)
}
_, err := NewGitHubDispatch("https://github.com/foo/bar", "", nil, "", "foo", "bar", tt.secretData, nil)
if tt.wantErr != nil {
assert.NotNil(t, err)
assert.Equal(t, tt.wantErr, err)
} else {
assert.Nil(t, err)
}
}
}

View File

@ -26,13 +26,13 @@ import (
"net/url"
"strings"
"github.com/fluxcd/notification-controller/api/v1beta3"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/git/github"
authgithub "github.com/fluxcd/pkg/git/github"
gogithub "github.com/google/go-github/v64/github"
"golang.org/x/oauth2"
gogithub "github.com/google/go-github/v64/github"
"github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/git/github"
"github.com/fluxcd/notification-controller/api/v1beta3"
)
// repoInfo is an internal type encapsulating owner, repo and client
@ -43,20 +43,13 @@ type repoInfo struct {
}
// getGitHubAppOptions constructs the github app authentication options.
func getGitHubAppOptions(providerName, providerNamespace, proxy string, secretData map[string][]byte, tokenCache *pkgcache.TokenCache) ([]github.OptFunc, error) {
githubOpts := []github.OptFunc{}
if val, ok := secretData[github.AppIDKey]; ok {
githubOpts = append(githubOpts, github.WithAppID(string(val)))
}
if val, ok := secretData[github.AppInstallationIDKey]; ok {
githubOpts = append(githubOpts, github.WithInstllationID(string(val)))
}
if val, ok := secretData[github.AppPrivateKey]; ok {
githubOpts = append(githubOpts, github.WithPrivateKey(val))
}
if val, ok := secretData[github.AppBaseUrlKey]; ok {
githubOpts = append(githubOpts, github.WithAppBaseURL(string(val)))
func getGitHubAppOptions(providerName, providerNamespace, proxy string,
secretData map[string][]byte, tokenCache *cache.TokenCache) ([]github.OptFunc, error) {
githubOpts := []github.OptFunc{
github.WithAppData(secretData),
}
if len(githubOpts) > 0 && proxy != "" {
proxyURL, err := url.Parse(proxy)
if err != nil {
@ -64,26 +57,31 @@ func getGitHubAppOptions(providerName, providerNamespace, proxy string, secretDa
}
githubOpts = append(githubOpts, github.WithProxyURL(proxyURL))
}
if len(githubOpts) > 0 && tokenCache != nil {
githubOpts = append(githubOpts, github.WithCache(tokenCache, v1beta3.ProviderKind, providerName, providerNamespace, OperationPost))
githubOpts = append(githubOpts, github.WithCache(tokenCache,
v1beta3.ProviderKind, providerName, providerNamespace, OperationPost))
}
return githubOpts, nil
}
// getRepoInfoAndGithubClient gets the github client and repository info used by Github and GithubDispatch providers
func getRepoInfoAndGithubClient(addr string, token string, certPool *x509.CertPool, proxyURL string, providerName string, providerNamespace string, secretData map[string][]byte, tokenCache *pkgcache.TokenCache) (*repoInfo, error) {
func getRepoInfoAndGithubClient(addr string, token string, certPool *x509.CertPool,
proxyURL string, providerName string, providerNamespace string,
secretData map[string][]byte, tokenCache *cache.TokenCache) (*repoInfo, error) {
if len(token) == 0 {
if _, ok := secretData[github.KeyAppID]; !ok {
return nil, errors.New("github token or github app details must be specified")
}
githubOpts, err := getGitHubAppOptions(providerName, providerNamespace, proxyURL, secretData, tokenCache)
if err != nil {
return nil, err
}
if len(githubOpts) == 0 {
return nil, errors.New("github token or github app details must be specified")
}
client, err := authgithub.New(githubOpts...)
client, err := github.New(githubOpts...)
if err != nil {
return nil, err
}

View File

@ -70,7 +70,7 @@ func TestNewGithubProvider(t *testing.T) {
kp, _ := ssh.GenerateKeyPair(ssh.RSA_4096)
expiresAt := time.Now().UTC().Add(time.Hour)
var tests = []struct {
for _, tt := range []struct {
name string
secretData map[string][]byte
wantErr error
@ -90,7 +90,7 @@ func TestNewGithubProvider(t *testing.T) {
"githubAppInstallationID": []byte(installationID),
"githubAppPrivateKey": kp.PrivateKey,
},
wantErr: errors.New("app ID must be provided to use github app authentication"),
wantErr: errors.New("github token or github app details must be specified"),
},
{
name: "provider with missing app installation ID in options ",
@ -116,32 +116,32 @@ func TestNewGithubProvider(t *testing.T) {
"githubAppPrivateKey": kp.PrivateKey,
},
},
}
} {
t.Run(tt.name, func(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
var response []byte
var err error
response, err = json.Marshal(&authgithub.AppToken{Token: "access-token", ExpiresAt: expiresAt})
assert.Nil(t, err)
w.Write(response)
}
srv := httptest.NewServer(http.HandlerFunc(handler))
t.Cleanup(func() {
srv.Close()
})
for _, tt := range tests {
handler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
var response []byte
var err error
response, err = json.Marshal(&authgithub.AppToken{Token: "access-token", ExpiresAt: expiresAt})
assert.Nil(t, err)
w.Write(response)
}
srv := httptest.NewServer(http.HandlerFunc(handler))
t.Cleanup(func() {
srv.Close()
if len(tt.secretData) > 0 {
tt.secretData["githubAppBaseURL"] = []byte(srv.URL)
}
_, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar", "", nil, "", "foo", "bar", tt.secretData, nil)
if tt.wantErr != nil {
assert.NotNil(t, err)
assert.Equal(t, tt.wantErr, err)
} else {
assert.Nil(t, err)
}
})
if tt.secretData != nil && len(tt.secretData) > 0 {
tt.secretData["githubAppBaseURL"] = []byte(srv.URL)
}
_, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar", "", nil, "", "foo", "bar", tt.secretData, nil)
if tt.wantErr != nil {
assert.NotNil(t, err)
assert.Equal(t, tt.wantErr, err)
} else {
assert.Nil(t, err)
}
}
}

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"net/url"
"strings"
@ -28,12 +28,12 @@ import (
)
type Grafana struct {
URL string
Token string
ProxyURL string
CertPool *x509.CertPool
Username string
Password string
URL string
Token string
ProxyURL string
TLSConfig *tls.Config
Username string
Password string
}
// GraphitePayload represents a Grafana API annotation in Graphite format
@ -44,19 +44,19 @@ type GraphitePayload struct {
}
// NewGrafana validates the Grafana URL and returns a Grafana object
func NewGrafana(URL string, proxyURL string, token string, certPool *x509.CertPool, username string, password string) (*Grafana, error) {
func NewGrafana(URL string, proxyURL string, token string, tlsConfig *tls.Config, username string, password string) (*Grafana, error) {
_, err := url.ParseRequestURI(URL)
if err != nil {
return nil, fmt.Errorf("invalid Grafana URL %s", URL)
}
return &Grafana{
URL: URL,
ProxyURL: proxyURL,
Token: token,
CertPool: certPool,
Username: username,
Password: password,
URL: URL,
ProxyURL: proxyURL,
Token: token,
Username: username,
Password: password,
TLSConfig: tlsConfig,
}, nil
}
@ -97,8 +97,8 @@ func (g *Grafana) Post(ctx context.Context, event eventv1.Event) error {
if g.ProxyURL != "" {
opts = append(opts, withProxy(g.ProxyURL))
}
if g.CertPool != nil {
opts = append(opts, withCertPool(g.CertPool))
if g.TLSConfig != nil {
opts = append(opts, withTLSConfig(g.TLSConfig))
}
if err := postMessage(ctx, g.URL, payload, opts...); err != nil {

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -43,10 +43,10 @@ func Fuzz_Grafana(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
grafana, err := NewGrafana(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", token, &cert, username, password)
grafana, err := NewGrafana(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", token, &tlsConfig, username, password)
if err != nil {
return
}

View File

@ -3,7 +3,7 @@ package notifier
import (
"context"
"crypto/sha1"
"crypto/x509"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
@ -15,10 +15,10 @@ import (
)
type Matrix struct {
Token string
URL string
RoomId string
CertPool *x509.CertPool
Token string
URL string
RoomId string
TLSConfig *tls.Config
}
type MatrixPayload struct {
@ -26,17 +26,17 @@ type MatrixPayload struct {
MsgType string `json:"msgtype"`
}
func NewMatrix(serverURL, token, roomId string, certPool *x509.CertPool) (*Matrix, error) {
func NewMatrix(serverURL, token, roomId string, tlsConfig *tls.Config) (*Matrix, error) {
_, err := url.ParseRequestURI(serverURL)
if err != nil {
return nil, fmt.Errorf("invalid Matrix homeserver URL %s: '%w'", serverURL, err)
}
return &Matrix{
URL: serverURL,
RoomId: roomId,
Token: token,
CertPool: certPool,
URL: serverURL,
RoomId: roomId,
Token: token,
TLSConfig: tlsConfig,
}, nil
}
@ -71,8 +71,8 @@ func (m *Matrix) Post(ctx context.Context, event eventv1.Event) error {
req.Header.Add("Authorization", "Bearer "+m.Token)
}),
}
if m.CertPool != nil {
opts = append(opts, withCertPool(m.CertPool))
if m.TLSConfig != nil {
opts = append(opts, withTLSConfig(m.TLSConfig))
}
if err := postMessage(ctx, fullURL, payload, opts...); err != nil {

View File

@ -2,7 +2,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -27,10 +27,10 @@ func Fuzz_Matrix(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
matrix, err := NewMatrix(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, roomId, &cert)
matrix, err := NewMatrix(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, roomId, &tlsConfig)
if err != nil {
return
}

View File

@ -1,29 +0,0 @@
/*
Copyright 2020 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 notifier
import (
"context"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
)
type NopNotifier struct{}
func (n *NopNotifier) Post(ctx context.Context, event eventv1.Event) error {
return nil
}

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"errors"
"fmt"
"net/url"
@ -28,10 +28,10 @@ import (
)
type Opsgenie struct {
URL string
ProxyURL string
CertPool *x509.CertPool
ApiKey string
URL string
ProxyURL string
TLSConfig *tls.Config
ApiKey string
}
type OpsgenieAlert struct {
@ -40,7 +40,7 @@ type OpsgenieAlert struct {
Details map[string]string `json:"details"`
}
func NewOpsgenie(hookURL string, proxyURL string, certPool *x509.CertPool, token string) (*Opsgenie, error) {
func NewOpsgenie(hookURL string, proxyURL string, tlsConfig *tls.Config, token string) (*Opsgenie, error) {
_, err := url.ParseRequestURI(hookURL)
if err != nil {
return nil, fmt.Errorf("invalid Opsgenie hook URL %s: '%w'", hookURL, err)
@ -51,10 +51,10 @@ func NewOpsgenie(hookURL string, proxyURL string, certPool *x509.CertPool, token
}
return &Opsgenie{
URL: hookURL,
ProxyURL: proxyURL,
CertPool: certPool,
ApiKey: token,
URL: hookURL,
ProxyURL: proxyURL,
ApiKey: token,
TLSConfig: tlsConfig,
}, nil
}
@ -86,8 +86,8 @@ func (s *Opsgenie) Post(ctx context.Context, event eventv1.Event) error {
if s.ProxyURL != "" {
opts = append(opts, withProxy(s.ProxyURL))
}
if s.CertPool != nil {
opts = append(opts, withCertPool(s.CertPool))
if s.TLSConfig != nil {
opts = append(opts, withTLSConfig(s.TLSConfig))
}
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -42,10 +42,10 @@ func Fuzz_OpsGenie(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
opsgenie, err := NewOpsgenie(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, token)
opsgenie, err := NewOpsgenie(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &tlsConfig, token)
if err != nil {
return
}

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"net/url"
"time"
@ -33,10 +33,10 @@ type PagerDuty struct {
Endpoint string
RoutingKey string
ProxyURL string
CertPool *x509.CertPool
TLSConfig *tls.Config
}
func NewPagerDuty(endpoint string, proxyURL string, certPool *x509.CertPool, routingKey string) (*PagerDuty, error) {
func NewPagerDuty(endpoint string, proxyURL string, tlsConfig *tls.Config, routingKey string) (*PagerDuty, error) {
URL, err := url.ParseRequestURI(endpoint)
if err != nil {
return nil, fmt.Errorf("invalid PagerDuty endpoint URL %q: '%w'", endpoint, err)
@ -45,7 +45,7 @@ func NewPagerDuty(endpoint string, proxyURL string, certPool *x509.CertPool, rou
Endpoint: URL.Scheme + "://" + URL.Host,
RoutingKey: routingKey,
ProxyURL: proxyURL,
CertPool: certPool,
TLSConfig: tlsConfig,
}, nil
}
@ -59,8 +59,8 @@ func (p *PagerDuty) Post(ctx context.Context, event eventv1.Event) error {
if p.ProxyURL != "" {
opts = append(opts, withProxy(p.ProxyURL))
}
if p.CertPool != nil {
opts = append(opts, withCertPool(p.CertPool))
if p.TLSConfig != nil {
opts = append(opts, withTLSConfig(p.TLSConfig))
}
if err := postMessage(

View File

@ -2,7 +2,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"io"
"net/http"
"net/http/httptest"
@ -32,10 +32,10 @@ func Fuzz_PagerDuty(f *testing.F) {
ts := httptest.NewServer(mux)
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
pd, err := NewPagerDuty(ts.URL, "", &cert, routingKey)
pd, err := NewPagerDuty(ts.URL, "", &tlsConfig, routingKey)
if err != nil {
return
}

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"net/url"
"strings"
@ -28,26 +28,26 @@ import (
// Rocket holds the hook URL
type Rocket struct {
URL string
ProxyURL string
Username string
Channel string
CertPool *x509.CertPool
URL string
ProxyURL string
Username string
Channel string
TLSConfig *tls.Config
}
// NewRocket validates the Rocket URL and returns a Rocket object
func NewRocket(hookURL string, proxyURL string, certPool *x509.CertPool, username string, channel string) (*Rocket, error) {
func NewRocket(hookURL string, proxyURL string, tlsConfig *tls.Config, username string, channel string) (*Rocket, error) {
_, err := url.ParseRequestURI(hookURL)
if err != nil {
return nil, fmt.Errorf("invalid Rocket hook URL %s: '%w'", hookURL, err)
}
return &Rocket{
Channel: channel,
URL: hookURL,
ProxyURL: proxyURL,
Username: username,
CertPool: certPool,
Channel: channel,
URL: hookURL,
ProxyURL: proxyURL,
Username: username,
TLSConfig: tlsConfig,
}, nil
}
@ -87,8 +87,8 @@ func (s *Rocket) Post(ctx context.Context, event eventv1.Event) error {
if s.ProxyURL != "" {
opts = append(opts, withProxy(s.ProxyURL))
}
if s.CertPool != nil {
opts = append(opts, withCertPool(s.CertPool))
if s.TLSConfig != nil {
opts = append(opts, withTLSConfig(s.TLSConfig))
}
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {

View File

@ -18,7 +18,6 @@ package notifier
import (
"context"
"crypto/x509"
"fmt"
"io"
"net/http"
@ -42,10 +41,7 @@ func Fuzz_Rocket(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
rocket, err := NewRocket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, username, channel)
rocket, err := NewRocket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", nil, username, channel)
if err != nil {
return
}

View File

@ -34,6 +34,10 @@ type Sentry struct {
// NewSentry creates a Sentry client from the provided Data Source Name (DSN)
func NewSentry(certPool *x509.CertPool, dsn string, environment string) (*Sentry, error) {
if dsn == "" {
return nil, fmt.Errorf("DSN cannot be empty")
}
tr := &http.Transport{}
if certPool != nil {
tr = &http.Transport{

View File

@ -17,6 +17,7 @@ limitations under the License.
package notifier
import (
"errors"
"testing"
"time"
@ -25,15 +26,42 @@ import (
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/stretchr/testify/require"
)
func TestNewSentry(t *testing.T) {
s, err := NewSentry(nil, "https://test@localhost/1", "foo")
require.NoError(t, err)
assert.Equal(t, s.Client.Options().Dsn, "https://test@localhost/1")
assert.Equal(t, s.Client.Options().Environment, "foo")
tests := []struct {
name string
dsn string
environment string
err error
}{
{
name: "valid DSN",
dsn: "https://test@localhost/1",
environment: "foo",
err: nil,
},
{
name: "empty DSN",
dsn: "",
environment: "foo",
err: errors.New("DSN cannot be empty"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := NewSentry(nil, tt.dsn, tt.environment)
if tt.err != nil {
assert.Error(t, err)
assert.Equal(t, tt.err, err)
} else {
assert.NoError(t, err)
assert.Equal(t, s.Client.Options().Dsn, tt.dsn)
assert.Equal(t, s.Client.Options().Environment, tt.environment)
}
})
}
}
func TestToSentryEvent(t *testing.T) {

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"encoding/json"
"fmt"
"net/url"
@ -30,12 +30,12 @@ import (
// Slack holds the hook URL
type Slack struct {
URL string
ProxyURL string
Token string
Username string
Channel string
CertPool *x509.CertPool
URL string
ProxyURL string
Token string
Username string
Channel string
TLSConfig *tls.Config
}
// SlackPayload holds the channel and attachments
@ -64,19 +64,19 @@ type SlackField struct {
}
// NewSlack validates the Slack URL and returns a Slack object
func NewSlack(hookURL string, proxyURL string, token string, certPool *x509.CertPool, username string, channel string) (*Slack, error) {
func NewSlack(hookURL string, proxyURL string, token string, tlsConfig *tls.Config, username string, channel string) (*Slack, error) {
_, err := url.ParseRequestURI(hookURL)
if err != nil {
return nil, fmt.Errorf("invalid Slack hook URL %s: '%w'", hookURL, err)
}
return &Slack{
Channel: channel,
Username: username,
URL: hookURL,
ProxyURL: proxyURL,
Token: token,
CertPool: certPool,
Channel: channel,
Username: username,
URL: hookURL,
ProxyURL: proxyURL,
Token: token,
TLSConfig: tlsConfig,
}, nil
}
@ -129,8 +129,8 @@ func (s *Slack) Post(ctx context.Context, event eventv1.Event) error {
if s.ProxyURL != "" {
opts = append(opts, withProxy(s.ProxyURL))
}
if s.CertPool != nil {
opts = append(opts, withCertPool(s.CertPool))
if s.TLSConfig != nil {
opts = append(opts, withTLSConfig(s.TLSConfig))
}
if s.URL == "https://slack.com/api/chat.postMessage" {
opts = append(opts, withResponseValidator(validateSlackResponse))

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -43,10 +43,10 @@ func Fuzz_Slack(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
slack, err := NewSlack(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", token, &cert, username, channel)
slack, err := NewSlack(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", token, &tlsConfig, username, channel)
if err != nil {
return
}

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"net/url"
"slices"
@ -39,10 +39,10 @@ const (
// MS Teams holds the incoming webhook URL
type MSTeams struct {
URL string
ProxyURL string
CertPool *x509.CertPool
Schema int
URL string
ProxyURL string
Schema int
TLSConfig *tls.Config
}
// MSTeamsPayload holds the message card data
@ -121,17 +121,17 @@ type msAdaptiveCardFact struct {
}
// NewMSTeams validates the MS Teams URL and returns a MSTeams object
func NewMSTeams(hookURL string, proxyURL string, certPool *x509.CertPool) (*MSTeams, error) {
func NewMSTeams(hookURL string, proxyURL string, tlsConfig *tls.Config) (*MSTeams, error) {
u, err := url.ParseRequestURI(hookURL)
if err != nil {
return nil, fmt.Errorf("invalid MS Teams webhook URL %s: '%w'", hookURL, err)
}
provider := &MSTeams{
URL: hookURL,
ProxyURL: proxyURL,
CertPool: certPool,
Schema: msTeamsSchemaAdaptiveCard,
URL: hookURL,
ProxyURL: proxyURL,
Schema: msTeamsSchemaAdaptiveCard,
TLSConfig: tlsConfig,
}
// Check if the webhook URL is the deprecated connector and update the schema accordingly.
@ -165,8 +165,8 @@ func (s *MSTeams) Post(ctx context.Context, event eventv1.Event) error {
if s.ProxyURL != "" {
opts = append(opts, withProxy(s.ProxyURL))
}
if s.CertPool != nil {
opts = append(opts, withCertPool(s.CertPool))
if s.TLSConfig != nil {
opts = append(opts, withTLSConfig(s.TLSConfig))
}
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {

View File

@ -18,7 +18,6 @@ package notifier
import (
"context"
"crypto/x509"
"fmt"
"io"
"net/http"
@ -43,10 +42,7 @@ func Fuzz_MSTeams(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
teams, err := NewMSTeams(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert)
teams, err := NewMSTeams(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", nil)
if err != nil {
return
}

View File

@ -4,27 +4,46 @@ import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"github.com/containrrr/shoutrrr"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
)
const (
telegramBaseURL = "https://api.telegram.org/bot%s"
sendMessageMethodName = "sendMessage"
)
type Telegram struct {
Channel string
Token string
send func(url string, message string) error // this allows the send function to be overridden for testing
url string
ProxyURL string
Channel string
Token string
}
func NewTelegram(channel, token string) (*Telegram, error) {
// TelegramPayload represents the payload sent to Telegram Bot API
// Reference: https://core.telegram.org/bots/api#sendmessage
type TelegramPayload struct {
ChatID string `json:"chat_id"` // Unique identifier for the target chat
Text string `json:"text"` // Text of the message to be sent
ParseMode string `json:"parse_mode"` // Mode for parsing entities in the message text
}
func NewTelegram(proxyURL, channel, token string) (*Telegram, error) {
if channel == "" {
return nil, errors.New("empty Telegram channel")
}
if token == "" {
return nil, errors.New("empty Telegram token")
}
return &Telegram{
Channel: channel,
Token: token,
send: shoutrrr.Send,
url: fmt.Sprintf(telegramBaseURL, token),
ProxyURL: proxyURL,
Channel: channel,
Token: token,
}, nil
}
@ -46,9 +65,24 @@ func (t *Telegram) Post(ctx context.Context, event eventv1.Event) error {
metadata = metadata + fmt.Sprintf("\\- *%s*: %s\n", escapeString(k), escapeString(v))
}
message := fmt.Sprintf("*%s*\n%s\n%s", escapeString(heading), escapeString(event.Message), metadata)
url := fmt.Sprintf("telegram://%s@telegram?channels=%s&parseMode=markDownv2", t.Token, t.Channel)
err := t.send(url, message)
return err
payload := TelegramPayload{
ChatID: t.Channel,
Text: message,
ParseMode: "MarkdownV2", // https://core.telegram.org/bots/api#markdownv2-style
}
apiURL, err := url.JoinPath(t.url, sendMessageMethodName)
if err != nil {
return fmt.Errorf("failed to construct API URL: %w", err)
}
var opts []postOption
if t.ProxyURL != "" {
opts = append(opts, withProxy(t.ProxyURL))
}
return postMessage(ctx, apiURL, payload, opts...)
}
// The telegram API requires that some special characters are escaped

View File

@ -31,22 +31,21 @@ import (
func TestTelegram_Post(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/sendMessage", r.URL.Path)
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
b, err := io.ReadAll(r.Body)
require.NoError(t, err)
var payload = WebexPayload{}
var payload TelegramPayload
err = json.Unmarshal(b, &payload)
require.NoError(t, err)
}))
defer ts.Close()
telegram, err := NewTelegram("channel", "token")
require.NoError(t, err)
require.Equal(t, "channel", payload.ChatID)
require.Equal(t, "MarkdownV2", payload.ParseMode)
telegram.send = func(url, message string) error {
require.Equal(t, "telegram://token@telegram?channels=channel&parseMode=markDownv2", url)
lines := strings.Split(message, "\n")
lines := strings.Split(payload.Text, "\n")
require.Len(t, lines, 5)
slices.Sort(lines[2:4])
require.Equal(t, "*💫 gitrepository/webapp/gitops\\-system*", lines[0])
@ -57,8 +56,14 @@ func TestTelegram_Post(t *testing.T) {
"",
}, lines[2:])
return nil
}
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
telegram, err := NewTelegram("", "channel", "token")
require.NoError(t, err)
telegram.url = ts.URL
ev := testEvent()
ev.Metadata["kubernetes.io/somekey"] = "some.value"

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"net/url"
"strings"
@ -51,9 +51,8 @@ type Webex struct {
Token string
// optional: use a proxy as needed
ProxyURL string
// optional: x509 cert is no longer needed to post to a webex space
CertPool *x509.CertPool
ProxyURL string
TLSConfig *tls.Config
}
// WebexPayload holds the message text
@ -63,7 +62,7 @@ type WebexPayload struct {
}
// NewWebex validates the Webex URL and returns a Webex object
func NewWebex(hookURL, proxyURL string, certPool *x509.CertPool, channel string, token string) (*Webex, error) {
func NewWebex(hookURL, proxyURL string, tlsConfig *tls.Config, channel string, token string) (*Webex, error) {
_, err := url.ParseRequestURI(hookURL)
if err != nil {
@ -71,11 +70,11 @@ func NewWebex(hookURL, proxyURL string, certPool *x509.CertPool, channel string,
}
return &Webex{
URL: hookURL,
ProxyURL: proxyURL,
CertPool: certPool,
RoomId: channel,
Token: token,
URL: hookURL,
ProxyURL: proxyURL,
RoomId: channel,
Token: token,
TLSConfig: tlsConfig,
}, nil
}
@ -116,8 +115,8 @@ func (s *Webex) Post(ctx context.Context, event eventv1.Event) error {
if s.ProxyURL != "" {
opts = append(opts, withProxy(s.ProxyURL))
}
if s.CertPool != nil {
opts = append(opts, withCertPool(s.CertPool))
if s.TLSConfig != nil {
opts = append(opts, withTLSConfig(s.TLSConfig))
}
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {

View File

@ -18,7 +18,7 @@ package notifier
import (
"context"
"crypto/x509"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -42,10 +42,10 @@ func Fuzz_Webex(f *testing.F) {
}))
defer ts.Close()
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
var tlsConfig tls.Config
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
webex, err := NewWebex(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, channel, token)
webex, err := NewWebex(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &tlsConfig, channel, token)
if err != nil {
return
}

View File

@ -18,7 +18,6 @@ package server
import (
"context"
"crypto/x509"
"errors"
"fmt"
"net/http"
@ -38,8 +37,9 @@ import (
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/auth"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/masktoken"
"github.com/fluxcd/pkg/runtime/secrets"
apiv1 "github.com/fluxcd/notification-controller/api/v1"
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
@ -305,154 +305,144 @@ func createCommitStatus(ctx context.Context, provider *apiv1beta3.Provider, even
return commitStatus, nil
}
// createNotifier returns a notifier.Interface for the given Provider.
func createNotifier(ctx context.Context, kubeClient client.Client, provider *apiv1beta3.Provider, commitStatus string, tokenCache *pkgcache.TokenCache) (notifier.Interface, string, error) {
logger := log.FromContext(ctx)
// extractAuthFromSecret extracts notification-controller specific fields from the provider's secret
// that are not handled by pkg/runtime/secrets (address, proxy, token, headers).
// StandardizedSecret fields like BasicAuth, TLS, and ProxySecretRef are handled separately.
func extractAuthFromSecret(ctx context.Context, kubeClient client.Client, provider *apiv1beta3.Provider) ([]notifier.Option, map[string][]byte, error) {
options := []notifier.Option{}
webhook := provider.Spec.Address
username := provider.Spec.Username
proxy := provider.Spec.Proxy
token := ""
password := ""
headers := make(map[string]string)
secretName := types.NamespacedName{Namespace: provider.Namespace, Name: provider.Spec.SecretRef.Name}
var secret corev1.Secret
if provider.Spec.SecretRef != nil {
secretName := types.NamespacedName{Namespace: provider.Namespace, Name: provider.Spec.SecretRef.Name}
if err := kubeClient.Get(ctx, secretName, &secret); err != nil {
return nil, nil, fmt.Errorf("failed to read secret: %w", err)
}
err := kubeClient.Get(ctx, secretName, &secret)
if err != nil {
return nil, "", fmt.Errorf("failed to read secret: %w", err)
}
if val, ok := secret.Data["address"]; ok {
if len(val) > 2048 {
return nil, "", fmt.Errorf("invalid address in secret: address exceeds maximum length of %d bytes", 2048)
}
webhook = strings.TrimSpace(string(val))
}
if val, ok := secret.Data["password"]; ok {
password = strings.TrimSpace(string(val))
}
if val, ok := secret.Data["proxy"]; ok {
proxy = strings.TrimSpace(string(val))
_, err := url.Parse(proxy)
if err != nil {
return nil, "", fmt.Errorf("invalid 'proxy' in secret '%s/%s'", secret.Namespace, secret.Name)
}
}
if val, ok := secret.Data["token"]; ok {
token = strings.TrimSpace(string(val))
}
if val, ok := secret.Data["username"]; ok {
username = strings.TrimSpace(string(val))
}
if h, ok := secret.Data["headers"]; ok {
err := yaml.Unmarshal(h, &headers)
if err != nil {
return nil, "", fmt.Errorf("failed to read headers from secret: %w", err)
}
if val, ok := secret.Data["address"]; ok {
if len(val) > 2048 {
return nil, nil, fmt.Errorf("invalid address in secret: address exceeds maximum length of %d bytes", 2048)
}
}
var certPool *x509.CertPool
if provider.Spec.CertSecretRef != nil {
var secret corev1.Secret
secretName := types.NamespacedName{Namespace: provider.Namespace, Name: provider.Spec.CertSecretRef.Name}
err := kubeClient.Get(ctx, secretName, &secret)
if err != nil {
return nil, "", fmt.Errorf("failed to read cert secret: %w", err)
if val, ok := secret.Data["proxy"]; ok {
deprecatedProxy := strings.TrimSpace(string(val))
if _, err := url.Parse(deprecatedProxy); err != nil {
return nil, nil, fmt.Errorf("invalid 'proxy' in secret '%s'", secretName.String())
}
log.FromContext(ctx).Error(nil, "warning: specifying proxy with 'proxy' key in the referenced secret is deprecated, use spec.proxySecretRef with 'address' key instead. Support for the 'proxy' key will be removed in v1.")
options = append(options, notifier.WithProxyURL(deprecatedProxy))
}
switch secret.Type {
case corev1.SecretTypeOpaque, corev1.SecretTypeTLS, "":
default:
return nil, "", fmt.Errorf("cannot use Secret '%s' to get TLS certificate: invalid Secret type: '%s'", secret.Name, secret.Type)
if val, ok := secret.Data[secrets.KeyToken]; ok {
options = append(options, notifier.WithToken(strings.TrimSpace(string(val))))
}
if h, ok := secret.Data["headers"]; ok {
headers := make(map[string]string)
if err := yaml.Unmarshal(h, &headers); err != nil {
return nil, nil, fmt.Errorf("failed to read headers from secret: %w", err)
}
options = append(options, notifier.WithHeaders(headers))
}
caFile, ok := secret.Data["ca.crt"]
if !ok {
// TODO: Drop support for "caFile" field in v1 Provider API.
caFile, ok = secret.Data["caFile"]
if !ok {
return nil, "", fmt.Errorf("no 'ca.crt' key found in Secret '%s'", secret.Name)
}
logger.Info("warning: specifying CA cert via 'caFile' is deprecated, please use 'ca.crt' instead")
}
certPool = x509.NewCertPool()
ok = certPool.AppendCertsFromPEM(caFile)
if !ok {
return nil, "", fmt.Errorf("could not append to cert pool")
if user, ok := secret.Data["username"]; ok {
if pass, ok := secret.Data["password"]; ok {
options = append(options, notifier.WithUsername(strings.TrimSpace(string(user))))
options = append(options, notifier.WithPassword(strings.TrimSpace(string(pass))))
}
}
if webhook == "" {
return nil, "", fmt.Errorf("provider has no address")
}
return options, secret.Data, nil
}
// createNotifier constructs a notifier interface from the provider configuration,
// handling authentication, proxy settings, and TLS configuration.
func createNotifier(ctx context.Context, kubeClient client.Client, provider *apiv1beta3.Provider,
commitStatus string, tokenCache *cache.TokenCache) (notifier.Interface, string, error) {
options := []notifier.Option{
notifier.WithTokenClient(kubeClient),
notifier.WithProviderName(provider.Name),
notifier.WithProviderNamespace(provider.Namespace),
}
if commitStatus != "" {
options = append(options, notifier.WithCommitStatus(commitStatus))
}
if proxy != "" {
options = append(options, notifier.WithProxyURL(proxy))
}
if username != "" {
options = append(options, notifier.WithUsername(username))
}
if provider.Spec.Channel != "" {
options = append(options, notifier.WithChannel(provider.Spec.Channel))
}
if token != "" {
options = append(options, notifier.WithToken(token))
}
if len(headers) > 0 {
options = append(options, notifier.WithHeaders(headers))
}
if certPool != nil {
options = append(options, notifier.WithCertPool(certPool))
}
if password != "" {
options = append(options, notifier.WithPassword(password))
}
if provider.Name != "" {
options = append(options, notifier.WithProviderName(provider.Name))
}
if provider.Namespace != "" {
options = append(options, notifier.WithProviderNamespace(provider.Namespace))
}
if secret.Data != nil {
options = append(options, notifier.WithSecretData(secret.Data))
}
if tokenCache != nil {
options = append(options, notifier.WithTokenCache(tokenCache))
if provider.Spec.Username != "" {
options = append(options, notifier.WithUsername(provider.Spec.Username))
}
if provider.Spec.ServiceAccountName != "" {
options = append(options, notifier.WithServiceAccount(provider.Spec.ServiceAccountName))
}
factory := notifier.NewFactory(ctx, webhook, options...)
if tokenCache != nil {
options = append(options, notifier.WithTokenCache(tokenCache))
}
// TODO: Remove deprecated proxy handling when Provider v1 is released.
if provider.Spec.Proxy != "" {
log.FromContext(ctx).Error(nil, "warning: spec.proxy is deprecated, please use spec.proxySecretRef instead. Support for this field will be removed in v1.")
options = append(options, notifier.WithProxyURL(provider.Spec.Proxy))
}
webhook := provider.Spec.Address
var token string
var secretData map[string][]byte
if provider.Spec.SecretRef != nil {
secretOptions, sData, err := extractAuthFromSecret(ctx, kubeClient, provider)
if err != nil {
return nil, "", err
}
secretData = sData
options = append(options, secretOptions...)
if secretData != nil {
options = append(options, notifier.WithSecretData(secretData))
}
if val, ok := secretData["address"]; ok {
webhook = strings.TrimSpace(string(val))
}
if val, ok := secretData[secrets.KeyToken]; ok {
token = strings.TrimSpace(string(val))
}
}
if provider.Spec.ProxySecretRef != nil {
secretRef := types.NamespacedName{
Name: provider.Spec.ProxySecretRef.Name,
Namespace: provider.GetNamespace(),
}
proxyURL, err := secrets.ProxyURLFromSecretRef(ctx, kubeClient, secretRef)
if err != nil {
return nil, "", fmt.Errorf("failed to get proxy URL: %w", err)
}
options = append(options, notifier.WithProxyURL(proxyURL.String()))
}
if provider.Spec.CertSecretRef != nil {
secretRef := types.NamespacedName{
Name: provider.Spec.CertSecretRef.Name,
Namespace: provider.GetNamespace(),
}
tlsConfig, err := secrets.TLSConfigFromSecretRef(ctx, kubeClient, secretRef)
if err != nil {
return nil, "", fmt.Errorf("failed to get TLS config: %w", err)
}
options = append(options, notifier.WithTLSConfig(tlsConfig))
}
if webhook != "" {
options = append(options, notifier.WithURL(webhook))
}
factory := notifier.NewFactory(ctx, options...)
sender, err := factory.Notifier(provider.Spec.Type)
if err != nil {
return nil, "", fmt.Errorf("failed to initialize notifier: %w", err)

View File

@ -18,12 +18,21 @@ package server
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
@ -39,8 +48,11 @@ import (
apiv1 "github.com/fluxcd/notification-controller/api/v1"
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
"github.com/fluxcd/notification-controller/internal/notifier"
)
var fixedNow = time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
func TestFilterAlertsForEvent(t *testing.T) {
testNamespace := "foo-ns"
@ -548,21 +560,30 @@ func TestGetNotificationParams(t *testing.T) {
func TestCreateNotifier(t *testing.T) {
secretName := "foo-secret"
certSecretName := "cert-secret"
proxySecretName := "proxy-secret"
// Generate test certificates for mTLS testing
caCert, clientCert, clientKey := generateTestCertificates(t)
// Helper to create expected TLS configs
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caCert)
clientCertPair, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
t.Fatalf("Failed to create client cert pair: %v", err)
}
tests := []struct {
name string
providerSpec *apiv1beta3.ProviderSpec
secretType corev1.SecretType
secretData map[string][]byte
certSecretData map[string][]byte
wantErr bool
name string
providerSpec *apiv1beta3.ProviderSpec
secretType corev1.SecretType
secretData map[string][]byte
certSecretData map[string][]byte
proxySecretData map[string][]byte
wantErr bool
wantTLSConfig *tls.Config
}{
{
name: "no address, no secret ref",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "slack",
},
wantErr: true,
},
{
name: "valid address, no secret ref",
providerSpec: &apiv1beta3.ProviderSpec{
@ -578,6 +599,7 @@ func TestCreateNotifier(t *testing.T) {
},
wantErr: true,
},
// TODO: Remove deprecated secret proxy key tests when Provider v1 is released.
{
name: "reference to secret with valid address, proxy, headers",
providerSpec: &apiv1beta3.ProviderSpec{
@ -625,6 +647,7 @@ func TestCreateNotifier(t *testing.T) {
"address": []byte("https://example.com"),
},
},
// TODO: Remove deprecated spec.proxy field tests when Provider v1 is released.
{
name: "invalid spec proxy overridden by valid secret ref proxy",
providerSpec: &apiv1beta3.ProviderSpec{
@ -679,19 +702,9 @@ func TestCreateNotifier(t *testing.T) {
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
},
certSecretData: map[string][]byte{
// Based on https://pkg.go.dev/crypto/tls#X509KeyPair example.
"ca.crt": []byte(`-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`),
"ca.crt": caCert,
},
wantTLSConfig: &tls.Config{RootCAs: caPool},
},
{
name: "cert secret reference in caFile with valid CA",
@ -701,19 +714,9 @@ Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
},
certSecretData: map[string][]byte{
// Based on https://pkg.go.dev/crypto/tls#X509KeyPair example.
"caFile": []byte(`-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`),
"caFile": caCert,
},
wantTLSConfig: &tls.Config{RootCAs: caPool},
},
{
name: "cert secret reference in both ca.crt and caFile",
@ -750,6 +753,47 @@ Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
},
wantErr: true,
},
{
name: "mTLS with standard keys",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
},
certSecretData: map[string][]byte{
"ca.crt": caCert,
"tls.crt": clientCert,
"tls.key": clientKey,
},
wantTLSConfig: &tls.Config{RootCAs: caPool, Certificates: []tls.Certificate{clientCertPair}},
},
{
name: "mTLS with legacy keys",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
},
certSecretData: map[string][]byte{
"caFile": caCert,
"certFile": clientCert,
"keyFile": clientKey,
},
wantTLSConfig: &tls.Config{RootCAs: caPool, Certificates: []tls.Certificate{clientCertPair}},
},
{
name: "client cert without CA",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
},
certSecretData: map[string][]byte{
"tls.crt": clientCert,
"tls.key": clientKey,
},
wantTLSConfig: &tls.Config{Certificates: []tls.Certificate{clientCertPair}},
},
{
name: "unsupported provider",
providerSpec: &apiv1beta3.ProviderSpec{
@ -780,6 +824,73 @@ Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
},
wantErr: false,
},
{
name: "proxy from ProxySecretRef",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
ProxySecretRef: &meta.LocalObjectReference{Name: proxySecretName},
},
proxySecretData: map[string][]byte{
"address": []byte("http://proxy.example.com:8080"),
},
},
{
name: "proxy from ProxySecretRef with authentication",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
ProxySecretRef: &meta.LocalObjectReference{Name: proxySecretName},
},
proxySecretData: map[string][]byte{
"address": []byte("http://proxy.example.com:8080"),
"username": []byte("proxyuser"),
"password": []byte("proxypass"),
},
},
{
name: "ProxySecretRef reference to non-existing secret",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
ProxySecretRef: &meta.LocalObjectReference{Name: "non-existing"},
},
wantErr: true,
},
{
name: "ProxySecretRef missing address field",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
ProxySecretRef: &meta.LocalObjectReference{Name: proxySecretName},
},
proxySecretData: map[string][]byte{
"username": []byte("proxyuser"),
},
wantErr: true,
},
// TODO: Remove deprecated spec.proxy field tests when Provider v1 is released.
{
name: "deprecated spec.proxy field",
providerSpec: &apiv1beta3.ProviderSpec{
Type: "generic",
Address: "https://example.com",
Proxy: "http://proxy.example.com:8080",
},
},
{
name: "provider type that does not require address field",
providerSpec: &apiv1beta3.ProviderSpec{
// Telegram generates URLs internally, so address field is not required
Type: "telegram",
Channel: "test-channel",
SecretRef: &meta.LocalObjectReference{Name: secretName},
},
secretData: map[string][]byte{
"token": []byte("test-token"),
},
wantErr: false,
},
}
for _, tt := range tests {
@ -806,10 +917,40 @@ Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
}
builder.WithObjects(secret)
}
if tt.proxySecretData != nil {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: proxySecretName},
Data: tt.proxySecretData,
}
builder.WithObjects(secret)
}
provider := apiv1beta3.Provider{Spec: *tt.providerSpec}
_, _, err := createNotifier(context.TODO(), builder.Build(), &provider, "", nil)
notifier, _, err := createNotifier(context.TODO(), builder.Build(), &provider, "", nil)
g.Expect(err != nil).To(Equal(tt.wantErr))
if !tt.wantErr && tt.wantTLSConfig != nil {
g.Expect(notifier).ToNot(BeNil(), "Expected notifier to be created but got nil")
// Get TLS configuration from notifier
tlsConfig := getNotifierTLSConfig(notifier)
if tlsConfig == nil {
// Notifier doesn't support TLS via postMessage pattern, skip the check
return
}
g.Expect(tlsConfig).ToNot(BeNil(), "Expected TLS configuration but got nil")
if tt.wantTLSConfig.RootCAs != nil {
g.Expect(tlsConfig.RootCAs).ToNot(BeNil())
} else {
g.Expect(tlsConfig.RootCAs).To(BeNil())
}
g.Expect(tlsConfig.Certificates).To(HaveLen(len(tt.wantTLSConfig.Certificates)))
if len(tt.wantTLSConfig.Certificates) > 0 {
g.Expect(tlsConfig.Certificates[0]).To(Equal(tt.wantTLSConfig.Certificates[0]))
}
}
})
}
}
@ -1375,3 +1516,110 @@ func Test_excludeInternalMetadata(t *testing.T) {
})
}
}
// generateTestCertificates generates test certificates for mTLS testing.
// TODO: Move to pkg/runtime/secrets test helpers after mTLS implementation is complete
func generateTestCertificates(t *testing.T) (caCert, clientCert, clientKey []byte) {
t.Helper()
caPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("Failed to generate CA private key: %v", err)
}
caTemplate := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test CA"},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{""},
PostalCode: []string{""},
},
NotBefore: fixedNow,
NotAfter: fixedNow.Add(365 * 24 * time.Hour),
IsCA: true,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
t.Fatalf("Failed to create CA certificate: %v", err)
}
clientPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("Failed to generate client private key: %v", err)
}
clientTemplate := x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{
Organization: []string{"Test Client"},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{""},
PostalCode: []string{""},
},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
DNSNames: []string{"localhost"},
NotBefore: fixedNow,
NotAfter: fixedNow.Add(365 * 24 * time.Hour),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
clientCertDER, err := x509.CreateCertificate(rand.Reader, &clientTemplate, &caTemplate, &clientPrivKey.PublicKey, caPrivKey)
if err != nil {
t.Fatalf("Failed to create client certificate: %v", err)
}
caCertPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: caCertDER,
})
clientCertPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: clientCertDER,
})
clientKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(clientPrivKey),
})
return caCertPEM, clientCertPEM, clientKeyPEM
}
// getNotifierTLSConfig extracts TLSConfig from postMessage-based notifiers for testing
func getNotifierTLSConfig(n notifier.Interface) *tls.Config {
switch v := n.(type) {
case *notifier.Forwarder:
return v.TLSConfig
case *notifier.Slack:
return v.TLSConfig
case *notifier.Alertmanager:
return v.TLSConfig
case *notifier.Grafana:
return v.TLSConfig
case *notifier.Matrix:
return v.TLSConfig
case *notifier.Opsgenie:
return v.TLSConfig
case *notifier.PagerDuty:
return v.TLSConfig
case *notifier.Rocket:
return v.TLSConfig
case *notifier.MSTeams:
return v.TLSConfig
case *notifier.Webex:
return v.TLSConfig
default:
return nil
}
}

View File

@ -38,7 +38,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/cache"
)
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
@ -54,12 +54,14 @@ type EventServer struct {
kubeClient client.Client
noCrossNamespaceRefs bool
exportHTTPPathMetrics bool
tokenCache *pkgcache.TokenCache
tokenCache *cache.TokenCache
kuberecorder.EventRecorder
}
// NewEventServer returns an HTTP server that handles events
func NewEventServer(port string, logger logr.Logger, kubeClient client.Client, eventRecorder kuberecorder.EventRecorder, noCrossNamespaceRefs bool, exportHTTPPathMetrics bool, tokenCache *pkgcache.TokenCache) *EventServer {
func NewEventServer(port string, logger logr.Logger, kubeClient client.Client,
eventRecorder kuberecorder.EventRecorder, noCrossNamespaceRefs bool,
exportHTTPPathMetrics bool, tokenCache *cache.TokenCache) *EventServer {
return &EventServer{
port: port,
logger: logger.WithName("event-server"),

View File

@ -445,7 +445,7 @@ func (s *ReceiverServer) validate(ctx context.Context, receiver apiv1.Receiver,
case apiv1.NexusReceiver:
signature := r.Header.Get("X-Nexus-Webhook-Signature")
if len(signature) == 0 {
return fmt.Errorf("Nexus signature is missing from header")
return fmt.Errorf("the Nexus signature is missing from header")
}
b, err := io.ReadAll(r.Body)
@ -583,7 +583,7 @@ func authenticateGCRRequest(c *http.Client, bearer string, tokenIndex int) (err
}
if len(bearer) < tokenIndex {
return fmt.Errorf("Authorization header is missing or malformed: %v", bearer)
return fmt.Errorf("the Authorization header is missing or malformed: %v", bearer)
}
token := bearer[tokenIndex:]

View File

@ -29,7 +29,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/utils/pointer"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
@ -159,7 +159,7 @@ func main() {
LeaderElectionID: fmt.Sprintf("%s-leader-election", controllerName),
Logger: ctrl.Log,
Controller: config.Controller{
RecoverPanic: pointer.Bool(true),
RecoverPanic: ptr.To(true),
MaxConcurrentReconciles: concurrent,
},
Client: ctrlclient.Options{
@ -176,7 +176,7 @@ func main() {
if watchNamespace != "" {
mgrConfig.Cache = ctrlcache.Options{
DefaultNamespaces: map[string]ctrlcache.Config{
watchNamespace: ctrlcache.Config{},
watchNamespace: {},
},
}
}