Compare commits
294 Commits
api/v1.2.3
...
main
Author | SHA1 | Date |
---|---|---|
|
a22d67edbb | |
|
955d24142c | |
|
e8a909fac7 | |
|
febff88be7 | |
|
5bea87e16b | |
|
a8c2fc0759 | |
|
6e18c487d7 | |
|
d1c85df902 | |
|
9e150256c7 | |
|
e4160c509c | |
|
fc4adfd030 | |
|
3f4e962c83 | |
|
98ecf2de79 | |
|
a3e6dd6a10 | |
|
8858332c27 | |
|
2503bda903 | |
|
fc126284ab | |
|
8f207d65db | |
|
61f24e3376 | |
|
3f4ba79594 | |
|
d65c81d035 | |
|
47b95d86a3 | |
|
3a6de1fcef | |
|
a52f071bb9 | |
|
0e0e912182 | |
|
7383fe8be5 | |
|
2284a0db5a | |
|
d32f5e26cc | |
|
b416d68479 | |
|
3842f0c471 | |
|
f2e2340807 | |
|
01dfe1208a | |
|
0464717037 | |
|
f3065c67b3 | |
|
882383e44c | |
|
8dd496a132 | |
|
de484cd447 | |
|
b906831c5e | |
|
e95f8d5b38 | |
|
d78779e80b | |
|
637d55d0b9 | |
|
99c6dbb70f | |
|
c32f9e1559 | |
|
f5ddc97108 | |
|
0beb3d02f7 | |
|
5dfaa1a5ef | |
|
4d3a53ac86 | |
|
5b72532bb1 | |
|
3f7486168e | |
|
e970526b1e | |
|
0338a311f4 | |
|
671f7dd377 | |
|
fdeab17bff | |
|
f313afa287 | |
|
30d8d01687 | |
|
d62be26a3f | |
|
bc7166d419 | |
|
c476965793 | |
|
1a9858d725 | |
|
e2eac40c33 | |
|
1967bc0c74 | |
|
2763e548bd | |
|
cc51c36926 | |
|
543a2b0da2 | |
|
85d897c59d | |
|
ff00d11dae | |
|
2b3ccd7ddf | |
|
384ae9889f | |
|
2295fcf8e2 | |
|
b64453ba2f | |
|
3dc0b66390 | |
|
022b97cabe | |
|
7a34aee2bd | |
|
7e14e41f0f | |
|
b72ecab696 | |
|
5df5b78783 | |
|
c4eed6b243 | |
|
83ed0c59e5 | |
|
d52c41e0c4 | |
|
652d3d331e | |
|
232e66c985 | |
|
2eb3c35ec3 | |
|
28deef923f | |
|
9b83efa21f | |
|
e68bc3b7e3 | |
|
f4001109db | |
|
ed7f6adac6 | |
|
dcbf4a7f73 | |
|
b7cef8af24 | |
|
1aea3c2275 | |
|
7b8535bce0 | |
|
d9bb201c36 | |
|
764123c6d3 | |
|
7ee7239ff3 | |
|
b362a258fe | |
|
8235026aa1 | |
|
ecc3395615 | |
|
fa7d9f260c | |
|
9dc18128c8 | |
|
a2eb8ea3be | |
|
e0b98ca519 | |
|
5469e2a1f0 | |
|
56c5a5a4bc | |
|
d455ac69fe | |
|
939a16620f | |
|
51db46dfee | |
|
f3438f7709 | |
|
8b1d9a1e0b | |
|
a27f00191a | |
|
0667ba6629 | |
|
fb2adadc43 | |
|
276511a10a | |
|
effda518f4 | |
|
632437c78e | |
|
e7a4503e51 | |
|
2ebbf4803a | |
|
b6bdd3c2ce | |
|
278fd67d9a | |
|
2525f2ce4e | |
|
52a4049be2 | |
|
6fcfb6337c | |
|
c43e6a93a6 | |
|
b2ab2c2b07 | |
|
3acbe68075 | |
|
4997635afa | |
|
b4a9933f59 | |
|
519248ff29 | |
|
05be0bd5a1 | |
|
3585b77c40 | |
|
edb88c62fb | |
|
7d279b98f5 | |
|
7407570bee | |
|
46813c05b9 | |
|
7c15798aaa | |
|
205cd17fb0 | |
|
e0cf7a1fc7 | |
|
b81755d3f3 | |
|
c85b1eb391 | |
|
ab58c812bd | |
|
0871ad710a | |
|
42c2c79d2b | |
|
117bc7a91d | |
|
e6c53a0ae2 | |
|
478718ee48 | |
|
7373cf4b62 | |
|
d20a810652 | |
|
bd12728d0f | |
|
4e01193860 | |
|
f9610afa8d | |
|
fa93b71722 | |
|
1f4cdff23c | |
|
86f2a6d20f | |
|
fd2df2cffe | |
|
48675f2426 | |
|
86b33d0f3c | |
|
d9216e5c58 | |
|
84b8c037e9 | |
|
39f345231f | |
|
9c4d43f32a | |
|
a1cf5e0d43 | |
|
e70166d5e3 | |
|
0f8ff3c0d8 | |
|
b6037078a3 | |
|
5572c7fa04 | |
|
ef7db0026b | |
|
7adf4f2c41 | |
|
c518265dae | |
|
6240b16776 | |
|
3ae80f6475 | |
|
219f86fd11 | |
|
129f60e6e6 | |
|
580497beeb | |
|
f4e8df6360 | |
|
eb62655b73 | |
|
e0a1f00358 | |
|
a8f81b3a00 | |
|
696f065673 | |
|
1b5dbd997a | |
|
5bfdcc04fc | |
|
5f0f561ac0 | |
|
9a948c7290 | |
|
b2c799203d | |
|
05abe0a9de | |
|
3aba5bbd39 | |
|
35e2a0ebc0 | |
|
fd635f7dff | |
|
838514c874 | |
|
b7b9a018e8 | |
|
c4835fbdbd | |
|
3407bf5486 | |
|
6d7913063d | |
|
531c5873e7 | |
|
cf2f554c11 | |
|
d29f5f4919 | |
|
f9c93e2952 | |
|
f5026a7443 | |
|
678f374bc3 | |
|
633e33bf4b | |
|
f22dd2bfff | |
|
94ce6da0f2 | |
|
0bea894c2e | |
|
857c11dd26 | |
|
2bac104a69 | |
|
e3ae7c2d48 | |
|
952ccd000f | |
|
0106b53e02 | |
|
6ba1a713fe | |
|
c6b89cd2d6 | |
|
e30844fe7b | |
|
1fa1e28e33 | |
|
cb595180b1 | |
|
7659221a4a | |
|
08e74d5209 | |
|
17adf69aeb | |
|
b4949b6e05 | |
|
20eee15786 | |
|
bf918b9b3c | |
|
440aad84b0 | |
|
c2302f8fed | |
|
cd529f5588 | |
|
aef940033c | |
|
34a9099884 | |
|
e73db32382 | |
|
b3e4bf2ad5 | |
|
bd242e091c | |
|
36a83074f2 | |
|
eceae7774f | |
|
edb771fa6c | |
|
ee50f74164 | |
|
6dafac748f | |
|
333e6c3d07 | |
|
32c59b2474 | |
|
681ad49b98 | |
|
4e8bb2e4d5 | |
|
8cedefd341 | |
|
5ed66321a9 | |
|
f325f774d8 | |
|
bba04db89d | |
|
c71a90e01a | |
|
56120e9afc | |
|
f455608bfd | |
|
b816076657 | |
|
f5c0f6d33b | |
|
d72c7df6a5 | |
|
b4deeb98be | |
|
4df3ce451a | |
|
70b93d3f3d | |
|
ec61423cc5 | |
|
5371d8a79d | |
|
071bf10fa3 | |
|
d29d1f0804 | |
|
0d39d277d2 | |
|
c368adbbcc | |
|
69dfee9318 | |
|
e7a241d777 | |
|
8ce32f9553 | |
|
dcabd7c42b | |
|
5b152f583d | |
|
0b48a065e3 | |
|
a88f97eb44 | |
|
0bb0c6041c | |
|
6ae9bb6fa7 | |
|
f9933507d0 | |
|
20423eb80a | |
|
a817cf91c1 | |
|
68c38244cc | |
|
7834608f77 | |
|
7f5eea0a2e | |
|
73e1965419 | |
|
143a0b3ee0 | |
|
e48be82c89 | |
|
2a8ab214c9 | |
|
ef5c602fb1 | |
|
74bd37a992 | |
|
52da6c48ca | |
|
879f9576e2 | |
|
6ffb5f3f70 | |
|
40ba471d6b | |
|
791d34ef46 | |
|
57c32c15be | |
|
9492ef1b96 | |
|
b007f42139 | |
|
9f90225f04 | |
|
39a3853c5c | |
|
cd51b02fdd | |
|
c857eb65b3 | |
|
46d54b024d | |
|
c8d287e9c3 | |
|
f045217d70 | |
|
5acc9a12a8 | |
|
f4f6cdc73b | |
|
42870e46ce | |
|
fe333842be | |
|
29fb291738 |
|
@ -1,16 +1,30 @@
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
labels: ["dependencies"]
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
groups:
|
||||||
|
go-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
allow:
|
||||||
|
- dependency-type: "direct"
|
||||||
|
ignore:
|
||||||
|
# Kubernetes deps are updated by fluxcd/pkg
|
||||||
|
- dependency-name: "k8s.io/*"
|
||||||
|
- dependency-name: "sigs.k8s.io/*"
|
||||||
|
- dependency-name: "github.com/go-logr/*"
|
||||||
|
# Flux APIs pkg are updated at release time
|
||||||
|
- dependency-name: "github.com/fluxcd/notification-controller/api"
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
labels: ["area/ci", "dependencies"]
|
labels: ["area/ci", "dependencies"]
|
||||||
schedule:
|
|
||||||
# By default, this will be on a monday.
|
|
||||||
interval: "weekly"
|
|
||||||
groups:
|
groups:
|
||||||
# Group all updates together, so that they are all applied in a single PR.
|
|
||||||
# Grouped updates are currently in beta and is subject to change.
|
|
||||||
# xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
|
|
||||||
ci:
|
ci:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
|
|
@ -13,3 +13,18 @@
|
||||||
- name: backport:release/v1.1.x
|
- name: backport:release/v1.1.x
|
||||||
description: To be backported to release/v1.1.x
|
description: To be backported to release/v1.1.x
|
||||||
color: '#ffd700'
|
color: '#ffd700'
|
||||||
|
- name: backport:release/v1.2.x
|
||||||
|
description: To be backported to release/v1.2.x
|
||||||
|
color: '#ffd700'
|
||||||
|
- name: backport:release/v1.3.x
|
||||||
|
description: To be backported to release/v1.3.x
|
||||||
|
color: '#ffd700'
|
||||||
|
- name: backport:release/v1.4.x
|
||||||
|
description: To be backported to release/v1.4.x
|
||||||
|
color: '#ffd700'
|
||||||
|
- 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'
|
||||||
|
|
|
@ -13,11 +13,11 @@ jobs:
|
||||||
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
|
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Create backport PRs
|
- name: Create backport PRs
|
||||||
uses: korthout/backport-action@08bafb375e6e9a9a2b53a744b987e5d81a133191 # v2.1.1
|
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
|
||||||
# xref: https://github.com/korthout/backport-action#inputs
|
# xref: https://github.com/korthout/backport-action#inputs
|
||||||
with:
|
with:
|
||||||
# Use token to allow workflows to be triggered for the created PR
|
# Use token to allow workflows to be triggered for the created PR
|
||||||
|
|
|
@ -11,11 +11,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.24.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
**/go.sum
|
**/go.sum
|
||||||
**/go.mod
|
**/go.mod
|
||||||
|
|
|
@ -12,14 +12,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
- name: Cache Docker layers
|
- name: Cache Docker layers
|
||||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: /tmp/.buildx-cache
|
path: /tmp/.buildx-cache
|
||||||
|
@ -27,18 +27,16 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-buildx-ghcache-
|
${{ runner.os }}-buildx-ghcache-
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.24.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
**/go.sum
|
**/go.sum
|
||||||
**/go.mod
|
**/go.mod
|
||||||
- name: Setup Kubernetes
|
- name: Setup Kubernetes
|
||||||
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
|
||||||
with:
|
with:
|
||||||
version: v0.20.0
|
|
||||||
cluster_name: kind
|
cluster_name: kind
|
||||||
node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
|
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
packages: write # for pushing and signing container images.
|
packages: write # for pushing and signing container images.
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
|
@ -42,24 +42,24 @@ jobs:
|
||||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
- name: Generate images meta
|
- name: Generate images meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
fluxcd/${{ env.CONTROLLER }}
|
fluxcd/${{ env.CONTROLLER }}
|
||||||
|
@ -68,7 +68,7 @@ jobs:
|
||||||
type=raw,value=${{ steps.prep.outputs.VERSION }}
|
type=raw,value=${{ steps.prep.outputs.VERSION }}
|
||||||
- name: Publish images
|
- name: Publish images
|
||||||
id: build-push
|
id: build-push
|
||||||
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
|
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||||
with:
|
with:
|
||||||
sbom: true
|
sbom: true
|
||||||
provenance: true
|
provenance: true
|
||||||
|
@ -79,7 +79,7 @@ jobs:
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
- uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
||||||
- name: Sign images
|
- name: Sign images
|
||||||
env:
|
env:
|
||||||
COSIGN_EXPERIMENTAL: 1
|
COSIGN_EXPERIMENTAL: 1
|
||||||
|
@ -92,14 +92,14 @@ jobs:
|
||||||
mkdir -p config/release
|
mkdir -p config/release
|
||||||
kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.yaml
|
kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.yaml
|
||||||
kustomize build ./config/manager > ./config/release/${{ env.CONTROLLER }}.deployment.yaml
|
kustomize build ./config/manager > ./config/release/${{ env.CONTROLLER }}.deployment.yaml
|
||||||
- uses: anchore/sbom-action/download-syft@fd74a6fb98a204a1ad35bbfae0122c1a302ff88b # v0.15.0
|
- uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
|
||||||
- name: Create release and SBOM
|
- name: Create release and SBOM
|
||||||
id: run-goreleaser
|
id: run-goreleaser
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean --skip-validate
|
args: release --clean --skip=validate
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Generate SLSA metadata
|
- name: Generate SLSA metadata
|
||||||
|
@ -123,7 +123,7 @@ jobs:
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
contents: write # for uploading attestations to GitHub releases.
|
contents: write # for uploading attestations to GitHub releases.
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||||
with:
|
with:
|
||||||
provenance-name: "provenance.intoto.jsonl"
|
provenance-name: "provenance.intoto.jsonl"
|
||||||
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
||||||
|
@ -136,7 +136,7 @@ jobs:
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
packages: write # for uploading attestations.
|
packages: write # for uploading attestations.
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||||
with:
|
with:
|
||||||
image: ${{ needs.release.outputs.image_url }}
|
image: ${{ needs.release.outputs.image_url }}
|
||||||
digest: ${{ needs.release.outputs.image_digest }}
|
digest: ${{ needs.release.outputs.image_digest }}
|
||||||
|
@ -151,7 +151,7 @@ jobs:
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
packages: write # for uploading attestations.
|
packages: write # for uploading attestations.
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||||
with:
|
with:
|
||||||
image: ghcr.io/${{ needs.release.outputs.image_url }}
|
image: ghcr.io/${{ needs.release.outputs.image_url }}
|
||||||
digest: ${{ needs.release.outputs.image_digest }}
|
digest: ${{ needs.release.outputs.image_digest }}
|
||||||
|
|
|
@ -18,9 +18,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Run FOSSA scan and upload build data
|
- name: Run FOSSA scan and upload build data
|
||||||
uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0
|
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
|
||||||
with:
|
with:
|
||||||
# FOSSA Push-Only API Token
|
# FOSSA Push-Only API Token
|
||||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||||
|
@ -31,22 +31,22 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.24.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
**/go.sum
|
**/go.sum
|
||||||
**/go.mod
|
**/go.mod
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
# xref: https://codeql.github.com/codeql-query-help/go/
|
# xref: https://codeql.github.com/codeql-query-help/go/
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||||
|
|
|
@ -17,8 +17,8 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
|
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
|
||||||
with:
|
with:
|
||||||
# Configuration file
|
# Configuration file
|
||||||
config-file: |
|
config-file: |
|
||||||
|
|
|
@ -23,7 +23,7 @@ release:
|
||||||
To verify the images and their provenance (SLSA level 3), please see the [security documentation](https://fluxcd.io/flux/security/).
|
To verify the images and their provenance (SLSA level 3), please see the [security documentation](https://fluxcd.io/flux/security/).
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
skip: true
|
disable: true
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
extra_files:
|
extra_files:
|
||||||
|
|
382
CHANGELOG.md
382
CHANGELOG.md
|
@ -2,6 +2,388 @@
|
||||||
|
|
||||||
All notable changes to this project are documented in this file.
|
All notable changes to this project are documented in this file.
|
||||||
|
|
||||||
|
## 1.6.0
|
||||||
|
|
||||||
|
**Release date:** 2025-05-27
|
||||||
|
|
||||||
|
This minor release comes with various bug fixes and improvements.
|
||||||
|
|
||||||
|
### Provider
|
||||||
|
|
||||||
|
The `azureeventhub` provider now supports workload identity both
|
||||||
|
at the controller and object levels. For object level, the
|
||||||
|
`.spec.serviceAccountName` field can be set to the name of a
|
||||||
|
service account in the same namespace that was configured with
|
||||||
|
a Managed Identity.
|
||||||
|
For object level to work, the controller feature gate
|
||||||
|
`ObjectLevelWorkloadIdentity` must be enabled. See a complete guide
|
||||||
|
[here](https://fluxcd.io/flux/integrations/azure/).
|
||||||
|
|
||||||
|
The `github` and `githubdispatch` providers now support authenticating
|
||||||
|
with a GitHub App. See docs
|
||||||
|
[here](https://fluxcd.io/flux/components/notification/providers/#github)
|
||||||
|
and
|
||||||
|
[here](https://fluxcd.io/flux/components/notification/providers/#github-dispatch).
|
||||||
|
|
||||||
|
For commit status providers it is now possible to define a custom
|
||||||
|
status string by defining a CEL expression in the `.spec.commitStatusExpr`
|
||||||
|
field. The variables `event`, `alert` and `provider` are available
|
||||||
|
for the CEL expression. See
|
||||||
|
[docs](https://fluxcd.io/flux/components/notification/providers/#custom-commit-status-messages).
|
||||||
|
|
||||||
|
### General updates
|
||||||
|
|
||||||
|
In addition, the Kubernetes dependencies have been updated to v1.33 and
|
||||||
|
various other controller dependencies have been updated to their latest
|
||||||
|
version. The controller is now built with Go 1.24.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fix Slack chat.postMessage error handling
|
||||||
|
[#1086](https://github.com/fluxcd/notification-controller/pull/1086)
|
||||||
|
- Fix pass 'certPool' to Gitea client on creation
|
||||||
|
[#1084](https://github.com/fluxcd/notification-controller/pull/1084)
|
||||||
|
- CrossNamespaceObjectReference: Fix MaxLength validation to kubernetes max size of 253
|
||||||
|
[#1108](https://github.com/fluxcd/notification-controller/pull/1108)
|
||||||
|
- Sanitize proxy error logging
|
||||||
|
[#1093](https://github.com/fluxcd/notification-controller/pull/1093)
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- [RFC-0010] Workload Identity support for `azureeventhub` provider
|
||||||
|
[#1106](https://github.com/fluxcd/notification-controller/pull/1106)
|
||||||
|
[#1116](https://github.com/fluxcd/notification-controller/pull/1116)
|
||||||
|
[#1120](https://github.com/fluxcd/notification-controller/pull/1120)
|
||||||
|
[#1109](https://github.com/fluxcd/notification-controller/pull/1109)
|
||||||
|
[#1112](https://github.com/fluxcd/notification-controller/pull/1112)
|
||||||
|
- GitHub App authentication support for `github` and `githubdispatch`
|
||||||
|
[#1058](https://github.com/fluxcd/notification-controller/pull/1058)
|
||||||
|
- Support CEL expressions to construct commit statuses
|
||||||
|
[#1068](https://github.com/fluxcd/notification-controller/pull/1068)
|
||||||
|
- Add proxy support to `gitea` provider
|
||||||
|
[#1087](https://github.com/fluxcd/notification-controller/pull/1087)
|
||||||
|
- Various dependency updates
|
||||||
|
[#1101](https://github.com/fluxcd/notification-controller/pull/1101)
|
||||||
|
[#1119](https://github.com/fluxcd/notification-controller/pull/1119)
|
||||||
|
[#1118](https://github.com/fluxcd/notification-controller/pull/1118)
|
||||||
|
[#1113](https://github.com/fluxcd/notification-controller/pull/1113)
|
||||||
|
[#1104](https://github.com/fluxcd/notification-controller/pull/1104)
|
||||||
|
|
||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
**Release date:** 2025-02-13
|
||||||
|
|
||||||
|
This minor release comes with various bug fixes and improvements.
|
||||||
|
|
||||||
|
### Alert
|
||||||
|
|
||||||
|
Now notification-controller also sends event metadata specified in Flux objects through
|
||||||
|
annotations. See [docs](https://fluxcd.io/flux/components/notification/alerts/#event-metadata-from-object-annotations).
|
||||||
|
|
||||||
|
Now notification-controller is also capable of updating Git commit statuses
|
||||||
|
from events about Kustomizations that consume OCIRepositories. See
|
||||||
|
[docs](https://fluxcd.io/flux/cheatsheets/oci-artifacts/#git-commit-status-updates).
|
||||||
|
|
||||||
|
### Receiver
|
||||||
|
|
||||||
|
The Receiver API now supports filtering the declared resources that
|
||||||
|
match a given Common Expression Language (CEL) expression. See
|
||||||
|
[docs](https://fluxcd.io/flux/components/notification/receivers/#filtering-reconciled-objects-with-cel).
|
||||||
|
|
||||||
|
In addition, the Kubernetes dependencies have been updated to v1.32.1 and
|
||||||
|
various other controller dependencies have been updated to their latest
|
||||||
|
version.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Remove deprecated object metrics from controllers
|
||||||
|
[#997](https://github.com/fluxcd/notification-controller/pull/997)
|
||||||
|
- msteams notifier: adaptive cards full width
|
||||||
|
[#1017](https://github.com/fluxcd/notification-controller/pull/1017)
|
||||||
|
- fix: adding of duplicate commit statuses in gitlab
|
||||||
|
[#1010](https://github.com/fluxcd/notification-controller/pull/1010)
|
||||||
|
- Fix add missing return statement and a few style issues
|
||||||
|
[#1039](https://github.com/fluxcd/notification-controller/pull/1039)
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- [RFC-0008] Custom Event Metadata from Annotations
|
||||||
|
[#1014](https://github.com/fluxcd/notification-controller/pull/1014)
|
||||||
|
- Add support for MetaOriginRevisionKey from the Event API
|
||||||
|
[#1018](https://github.com/fluxcd/notification-controller/pull/1018)
|
||||||
|
- Add subsection for Git providers supporting commit status updates
|
||||||
|
[#1019](https://github.com/fluxcd/notification-controller/pull/1019)
|
||||||
|
- Add support for Bearer Token authentication to Provider alertmanager
|
||||||
|
[#1021](https://github.com/fluxcd/notification-controller/pull/1021)
|
||||||
|
- Enforce namespace check on receiver
|
||||||
|
[#1022](https://github.com/fluxcd/notification-controller/pull/1022)
|
||||||
|
- Implement Receiver resource filtering with CEL
|
||||||
|
[#948](https://github.com/fluxcd/notification-controller/pull/948)
|
||||||
|
- Clarify gitlab provider usage
|
||||||
|
[#953](https://github.com/fluxcd/notification-controller/pull/953)
|
||||||
|
- Add involved object reference as annotations for the grafana provider
|
||||||
|
[#1040](https://github.com/fluxcd/notification-controller/pull/1040)
|
||||||
|
- Improvements after CEL resource filtering
|
||||||
|
[#1041](https://github.com/fluxcd/notification-controller/pull/1041)
|
||||||
|
- Various dependency updates
|
||||||
|
[#1002](https://github.com/fluxcd/notification-controller/pull/1002)
|
||||||
|
[#1016](https://github.com/fluxcd/notification-controller/pull/1016)
|
||||||
|
[#1023](https://github.com/fluxcd/notification-controller/pull/1023)
|
||||||
|
[#1025](https://github.com/fluxcd/notification-controller/pull/1025)
|
||||||
|
[#1027](https://github.com/fluxcd/notification-controller/pull/1027)
|
||||||
|
[#1032](https://github.com/fluxcd/notification-controller/pull/1032)
|
||||||
|
[#1036](https://github.com/fluxcd/notification-controller/pull/1036)
|
||||||
|
[#1037](https://github.com/fluxcd/notification-controller/pull/1037)
|
||||||
|
[#1042](https://github.com/fluxcd/notification-controller/pull/1042)
|
||||||
|
|
||||||
|
## 1.4.0
|
||||||
|
|
||||||
|
**Release date:** 2024-09-27
|
||||||
|
|
||||||
|
This minor release comes with various bug fixes and improvements.
|
||||||
|
|
||||||
|
MS Teams Provider has been updated to support MS Adaptive Card payloads.
|
||||||
|
This allows users to migrate from the deprecated
|
||||||
|
[Office 365 Connector for Incoming Webhooks](https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/)
|
||||||
|
to the new [Microsoft Teams Incoming Webhooks with Workflows](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498).
|
||||||
|
See the [Provider API documentation](https://fluxcd.io/flux/components/notification/providers/#microsoft-teams)
|
||||||
|
for more information. After getting the URL for the new Incoming Webhook Workflow,
|
||||||
|
update the secret used by the `msteams` Provider object with the new URL.
|
||||||
|
|
||||||
|
In addition, the Kubernetes dependencies have been updated to v1.31.1 and
|
||||||
|
various other controller dependencies have been updated to their latest
|
||||||
|
version. The controller is now built with Go 1.23.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- telegram notifier should escape with metadata key
|
||||||
|
[#829](https://github.com/fluxcd/notification-controller/pull/829)
|
||||||
|
- docs: use stringData for secret of GitHub PAT
|
||||||
|
[#873](https://github.com/fluxcd/notification-controller/pull/873)
|
||||||
|
- Fix incorrect use of format strings with the conditions package.
|
||||||
|
[#879](https://github.com/fluxcd/notification-controller/pull/879)
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- New flag to disable detailed metrics for path
|
||||||
|
[#841](https://github.com/fluxcd/notification-controller/pull/841)
|
||||||
|
- Fix telegram test flake
|
||||||
|
[#894](https://github.com/fluxcd/notification-controller/pull/894)
|
||||||
|
- Build with Go 1.23
|
||||||
|
[#907](https://github.com/fluxcd/notification-controller/pull/907)
|
||||||
|
- Add MS Adaptive Card payload to msteams Provider
|
||||||
|
[#920](https://github.com/fluxcd/notification-controller/pull/920)
|
||||||
|
- Various dependency updates
|
||||||
|
[#845](https://github.com/fluxcd/notification-controller/pull/845)
|
||||||
|
[#855](https://github.com/fluxcd/notification-controller/pull/855)
|
||||||
|
[#854](https://github.com/fluxcd/notification-controller/pull/854)
|
||||||
|
[#857](https://github.com/fluxcd/notification-controller/pull/857)
|
||||||
|
[#865](https://github.com/fluxcd/notification-controller/pull/865)
|
||||||
|
[#866](https://github.com/fluxcd/notification-controller/pull/866)
|
||||||
|
[#905](https://github.com/fluxcd/notification-controller/pull/905)
|
||||||
|
[#903](https://github.com/fluxcd/notification-controller/pull/903)
|
||||||
|
[#912](https://github.com/fluxcd/notification-controller/pull/912)
|
||||||
|
[#925](https://github.com/fluxcd/notification-controller/pull/925)
|
||||||
|
[#931](https://github.com/fluxcd/notification-controller/pull/931)
|
||||||
|
[#932](https://github.com/fluxcd/notification-controller/pull/932)
|
||||||
|
[#933](https://github.com/fluxcd/notification-controller/pull/933)
|
||||||
|
[#934](https://github.com/fluxcd/notification-controller/pull/934)
|
||||||
|
|
||||||
|
## 1.3.0
|
||||||
|
|
||||||
|
**Release date:** 2024-05-06
|
||||||
|
|
||||||
|
This minor release comes with new features, improvements and bug fixes.
|
||||||
|
|
||||||
|
The `Receiver` API has been extended to support CDEvents,
|
||||||
|
for more information, please see the
|
||||||
|
[CDEvents Receiver API documentation](https://github.com/fluxcd/notification-controller/blob/release/v1.3.x/docs/spec/v1/receivers.md#cdevents).
|
||||||
|
|
||||||
|
Starting with this version, the controller allows grouping alerts for Alertmanager
|
||||||
|
by setting the `startsAt` label instead of `timestamp`. When sending alerts to
|
||||||
|
OpsGenie, the controller now sets the `severity` field to the alert's details.
|
||||||
|
|
||||||
|
In addition, the controller dependencies have been updated to Kubernetes v1.30
|
||||||
|
and controller-runtime v0.18. Various other dependencies have also been updated to
|
||||||
|
their latest version to patch upstream CVEs.
|
||||||
|
|
||||||
|
Lastly, the controller is now built with Go 1.22.
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Add CDEvent Receiver Support
|
||||||
|
[#772](https://github.com/fluxcd/notification-controller/pull/772)
|
||||||
|
- Add severity to opsgenie alerts
|
||||||
|
[#796](https://github.com/fluxcd/notification-controller/pull/796)
|
||||||
|
- Alertmanager: Change timestamp label to .StartsAt
|
||||||
|
[#795](https://github.com/fluxcd/notification-controller/pull/795)
|
||||||
|
- Use `password` as fallback for the Git provider `token` auth
|
||||||
|
[#790](https://github.com/fluxcd/notification-controller/pull/790)
|
||||||
|
- Add support for Bitbucket Context path
|
||||||
|
[#747](https://github.com/fluxcd/notification-controller/pull/747)
|
||||||
|
- Various dependency updates
|
||||||
|
[#816](https://github.com/fluxcd/notification-controller/pull/816)
|
||||||
|
[#814](https://github.com/fluxcd/notification-controller/pull/814)
|
||||||
|
[#813](https://github.com/fluxcd/notification-controller/pull/813)
|
||||||
|
[#810](https://github.com/fluxcd/notification-controller/pull/810)
|
||||||
|
[#809](https://github.com/fluxcd/notification-controller/pull/809)
|
||||||
|
[#787](https://github.com/fluxcd/notification-controller/pull/787)
|
||||||
|
[#783](https://github.com/fluxcd/notification-controller/pull/783)
|
||||||
|
[#763](https://github.com/fluxcd/notification-controller/pull/763)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Sanitize provider data loaded from secret
|
||||||
|
[#789](https://github.com/fluxcd/notification-controller/pull/789)
|
||||||
|
- Fix timeout propagation for alerts
|
||||||
|
[#757](https://github.com/fluxcd/notification-controller/pull/757)
|
||||||
|
- Fix Telegram MarkdownV2 escaping
|
||||||
|
[#776](https://github.com/fluxcd/notification-controller/pull/776)
|
||||||
|
- Remove `genclient:Namespaced` tag
|
||||||
|
[#749](https://github.com/fluxcd/notification-controller/pull/749)
|
||||||
|
|
||||||
|
## 1.2.4
|
||||||
|
|
||||||
|
**Release date:** 2024-02-01
|
||||||
|
|
||||||
|
This patch release fixes various issues, updates the Kubernetes dependencies
|
||||||
|
to v1.28.6 and various other dependencies to their latest version to patch
|
||||||
|
upstream CVEs.
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Various dependency updates
|
||||||
|
[#727](https://github.com/fluxcd/notification-controller/pull/727)
|
||||||
|
[#726](https://github.com/fluxcd/notification-controller/pull/726)
|
||||||
|
[#721](https://github.com/fluxcd/notification-controller/pull/721)
|
||||||
|
[#718](https://github.com/fluxcd/notification-controller/pull/718)
|
||||||
|
[#707](https://github.com/fluxcd/notification-controller/pull/707)
|
||||||
|
[#695](https://github.com/fluxcd/notification-controller/pull/695)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fix BitBucket status update panic
|
||||||
|
[#722](https://github.com/fluxcd/notification-controller/pull/722)
|
||||||
|
- fix typo in docs/spec/v1beta3/providers.md
|
||||||
|
[#699](https://github.com/fluxcd/notification-controller/pull/699)
|
||||||
|
- fix(grafana-provider): replace ":" character in eventMetadata
|
||||||
|
[#703](https://github.com/fluxcd/notification-controller/pull/703)
|
||||||
|
- Remove old/incorrect API version usage
|
||||||
|
[#693](https://github.com/fluxcd/notification-controller/pull/693)
|
||||||
|
|
||||||
|
## 1.2.3
|
||||||
|
|
||||||
|
**Release date:** 2023-12-14
|
||||||
|
|
||||||
|
This patch release fixes various issues, most notably, the Provider v1beta3 API
|
||||||
|
backwards compatibility issue when `.spec.interval` was explicitly set in a
|
||||||
|
v1beta2 version of Provider.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Exclude eventv1.MetaTokenKey from event metadata
|
||||||
|
[#686](https://github.com/fluxcd/notification-controller/pull/686)
|
||||||
|
- Add .spec.interval in v1beta3 Provider
|
||||||
|
[#683](https://github.com/fluxcd/notification-controller/pull/683)
|
||||||
|
- Remove URL syntax validation for provider address entirely
|
||||||
|
[#682](https://github.com/fluxcd/notification-controller/pull/682)
|
||||||
|
|
||||||
|
## 1.2.2
|
||||||
|
|
||||||
|
**Release date:** 2023-12-11
|
||||||
|
|
||||||
|
This patch releases updates a variety of dependencies, including an update of
|
||||||
|
the container base image to Alpine v3.19.
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- build: update Alpine to 3.19
|
||||||
|
[#675](https://github.com/fluxcd/notification-controller/pull/675)
|
||||||
|
- Update dependencies
|
||||||
|
[#677](https://github.com/fluxcd/notification-controller/pull/677)
|
||||||
|
|
||||||
|
## 1.2.1
|
||||||
|
|
||||||
|
**Release date:** 2023-12-08
|
||||||
|
|
||||||
|
This patch release updates the Go version the controller is built with to
|
||||||
|
`1.21.x`, while mitigating recently published security vulnerabilities in the
|
||||||
|
`net/http` package.
|
||||||
|
|
||||||
|
In addition, it ensures static analyzers no longer detect a vulnerability in the
|
||||||
|
`whilp/git-urls` module by using `chainguard-dev/git-urls`. For which the
|
||||||
|
(potential) issue itself got already addressed internally in the [previous
|
||||||
|
v1.2.0 release](#120).
|
||||||
|
|
||||||
|
Lastly, a small number of dependencies got updated to their latest versions.
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Update Go to 1.21.x
|
||||||
|
[#666](https://github.com/fluxcd/notification-controller/pull/666)
|
||||||
|
- Replace whilp/git-urls module by chainguard-dev/git-urls
|
||||||
|
[#667](https://github.com/fluxcd/notification-controller/pull/667)
|
||||||
|
- Update dependencies
|
||||||
|
[#669](https://github.com/fluxcd/notification-controller/pull/669)
|
||||||
|
|
||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
**Release date:** 2023-12-05
|
||||||
|
|
||||||
|
This minor release graduates the notification `Alert` and `Provider` APIs to
|
||||||
|
`v1beta3`. In addition, this version comes with alert Provider support for
|
||||||
|
[BitBucket
|
||||||
|
Server](https://github.com/fluxcd/notification-controller/blob/api/v1.2.0/docs/spec/v1beta3/providers.md#bitbucket-serverdata-center)
|
||||||
|
and
|
||||||
|
[NATS](https://github.com/fluxcd/notification-controller/blob/api/v1.2.0/docs/spec/v1beta3/providers.md#nats).
|
||||||
|
|
||||||
|
### `notification.toolkit.fluxcd.io/v1beta3`
|
||||||
|
|
||||||
|
After upgrading the controller to v1.2.0, please update the notification Custom
|
||||||
|
Resources for `Alert` and `Provider` in Git by replacing
|
||||||
|
`notification.toolkit.fluxcd.io/v1beta2` with
|
||||||
|
`notification.toolkit.fluxcd.io/v1beta3` in all the YAML manifests.
|
||||||
|
|
||||||
|
#### Static Alerts and Providers
|
||||||
|
|
||||||
|
The notification Alert and Provider API resources will become static objects
|
||||||
|
with configurations that will be used by the event handlers for processing the
|
||||||
|
respective incoming events. They will no longer be reconciled by a reconciler
|
||||||
|
and will not advertise any status. Once `Alerts` and `Providers` are created,
|
||||||
|
they can be considered ready. Users of
|
||||||
|
[kstatus](https://github.com/kubernetes-sigs/cli-utils/blob/master/pkg/kstatus/README.md)
|
||||||
|
shouldn't see any difference. Existing `Alerts` and `Providers` objects in
|
||||||
|
`v1beta2` API will undergo a one-time automatic migration to be converted into
|
||||||
|
static objects without any status.
|
||||||
|
|
||||||
|
#### Enhanced Alert events
|
||||||
|
|
||||||
|
The event handler will emit Kubernetes native events on the respective Alert
|
||||||
|
object for any relevant information, including failures due to any
|
||||||
|
misconfiguration.
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Add Provider for NATS Subject
|
||||||
|
[#651](https://github.com/fluxcd/notification-controller/pull/651)
|
||||||
|
- Cap provider address at 2048 bytes
|
||||||
|
[#654](https://github.com/fluxcd/notification-controller/pull/654)
|
||||||
|
- Refactor events and introduce v1beta3 API for Alert and Provider
|
||||||
|
[#540](https://github.com/fluxcd/notification-controller/pull/540)
|
||||||
|
- Add Bitbucket server/Bitbucket Data Center provider for git commit status
|
||||||
|
[#639](https://github.com/fluxcd/notification-controller/pull/639)
|
||||||
|
- Address miscellaneous issues throughout code base
|
||||||
|
[#627](https://github.com/fluxcd/notification-controller/pull/627)
|
||||||
|
- Update dependencies
|
||||||
|
[#609](https://github.com/fluxcd/notification-controller/pull/609)
|
||||||
|
[#612](https://github.com/fluxcd/notification-controller/pull/612)
|
||||||
|
[#613](https://github.com/fluxcd/notification-controller/pull/613)
|
||||||
|
[#617](https://github.com/fluxcd/notification-controller/pull/617)
|
||||||
|
[#621](https://github.com/fluxcd/notification-controller/pull/621)
|
||||||
|
[#623](https://github.com/fluxcd/notification-controller/pull/623)
|
||||||
|
[#628](https://github.com/fluxcd/notification-controller/pull/628)
|
||||||
|
[#629](https://github.com/fluxcd/notification-controller/pull/629)
|
||||||
|
[#632](https://github.com/fluxcd/notification-controller/pull/632)
|
||||||
|
[#635](https://github.com/fluxcd/notification-controller/pull/635)
|
||||||
|
[#637](https://github.com/fluxcd/notification-controller/pull/637)
|
||||||
|
[#641](https://github.com/fluxcd/notification-controller/pull/641)
|
||||||
|
[#643](https://github.com/fluxcd/notification-controller/pull/643)
|
||||||
|
[#646](https://github.com/fluxcd/notification-controller/pull/646)
|
||||||
|
[#648](https://github.com/fluxcd/notification-controller/pull/648)
|
||||||
|
[#652](https://github.com/fluxcd/notification-controller/pull/652)
|
||||||
|
[#656](https://github.com/fluxcd/notification-controller/pull/656)
|
||||||
|
[#657](https://github.com/fluxcd/notification-controller/pull/657)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fix README.md links to notification APIs
|
||||||
|
[#619](https://github.com/fluxcd/notification-controller/pull/619)
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
**Release date:** 2023-08-23
|
**Release date:** 2023-08-23
|
||||||
|
|
|
@ -24,7 +24,7 @@ If any of the above dependencies are not present on your system, the first invoc
|
||||||
## How to run the test suite
|
## How to run the test suite
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
- Go >= 1.17
|
- Go >= 1.24
|
||||||
|
|
||||||
You can run the test suite by simply doing:
|
You can run the test suite by simply doing:
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
ARG GO_VERSION=1.20
|
ARG GO_VERSION=1.24
|
||||||
ARG XX_VERSION=1.2.1
|
ARG XX_VERSION=1.6.1
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine as builder
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS builder
|
||||||
|
|
||||||
# Copy the build utilities.
|
# Copy the build utilities.
|
||||||
COPY --from=xx / /
|
COPY --from=xx / /
|
||||||
|
@ -30,7 +30,7 @@ COPY internal/ internal/
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
RUN xx-go build -trimpath -a -o notification-controller main.go
|
RUN xx-go build -trimpath -a -o notification-controller main.go
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.21
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,6 @@ as listed in
|
||||||
|
|
||||||
https://github.com/fluxcd/flux2/blob/main/MAINTAINERS
|
https://github.com/fluxcd/flux2/blob/main/MAINTAINERS
|
||||||
|
|
||||||
Somtochi Onyekwere, Weaveworks <somtochi@weave.works> (github: @SomtochiAma, slack: somtoxhi)
|
In alphabetical order:
|
||||||
|
|
||||||
|
Somtochi Onyekwere, Independent <somtochionyekwere@gmail.com> (github: @somtochiama, slack: somtochiama)
|
||||||
|
|
16
Makefile
16
Makefile
|
@ -2,14 +2,14 @@
|
||||||
IMG ?= fluxcd/notification-controller:latest
|
IMG ?= fluxcd/notification-controller:latest
|
||||||
# Produce CRDs that work back to Kubernetes 1.16
|
# Produce CRDs that work back to Kubernetes 1.16
|
||||||
CRD_OPTIONS ?= crd:crdVersions=v1
|
CRD_OPTIONS ?= crd:crdVersions=v1
|
||||||
SOURCE_VER ?= v1.1.2
|
SOURCE_VER ?= v1.2.4
|
||||||
|
|
||||||
# Repository root based on Git metadata
|
# Repository root based on Git metadata
|
||||||
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
|
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
|
||||||
BUILD_DIR := $(REPOSITORY_ROOT)/build
|
BUILD_DIR := $(REPOSITORY_ROOT)/build
|
||||||
|
|
||||||
# API (doc) generation utilities
|
# API (doc) generation utilities
|
||||||
CONTROLLER_GEN_VERSION ?= v0.12.0
|
CONTROLLER_GEN_VERSION ?= v0.16.1
|
||||||
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
|
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
|
||||||
|
|
||||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
|
@ -93,8 +93,8 @@ api-docs: gen-crd-api-reference-docs
|
||||||
|
|
||||||
# Run go mod tidy
|
# Run go mod tidy
|
||||||
tidy:
|
tidy:
|
||||||
cd api; rm -f go.sum; go mod tidy -compat=1.20
|
cd api; rm -f go.sum; go mod tidy -compat=1.24
|
||||||
rm -f go.sum; go mod tidy -compat=1.20
|
rm -f go.sum; go mod tidy -compat=1.24
|
||||||
|
|
||||||
# Run go fmt against code
|
# Run go fmt against code
|
||||||
fmt:
|
fmt:
|
||||||
|
@ -153,13 +153,13 @@ fuzz-native:
|
||||||
./tests/fuzz/native_go_run.sh
|
./tests/fuzz/native_go_run.sh
|
||||||
|
|
||||||
# Find or download controller-gen
|
# Find or download controller-gen
|
||||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
CONTROLLER_GEN = $(GOBIN)/controller-gen
|
||||||
.PHONY: controller-gen
|
.PHONY: controller-gen
|
||||||
controller-gen: ## Download controller-gen locally if necessary.
|
controller-gen: ## Download controller-gen locally if necessary.
|
||||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
|
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
|
||||||
|
|
||||||
# Find or download gen-crd-api-reference-docs
|
# Find or download gen-crd-api-reference-docs
|
||||||
GEN_CRD_API_REFERENCE_DOCS = $(shell pwd)/bin/gen-crd-api-reference-docs
|
GEN_CRD_API_REFERENCE_DOCS = $(GOBIN)/gen-crd-api-reference-docs
|
||||||
.PHONY: gen-crd-api-reference-docs
|
.PHONY: gen-crd-api-reference-docs
|
||||||
gen-crd-api-reference-docs:
|
gen-crd-api-reference-docs:
|
||||||
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@$(GEN_API_REF_DOCS_VERSION))
|
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@$(GEN_API_REF_DOCS_VERSION))
|
||||||
|
@ -171,7 +171,7 @@ install-envtest: setup-envtest
|
||||||
mkdir -p ${ENVTEST_ASSETS_DIR}
|
mkdir -p ${ENVTEST_ASSETS_DIR}
|
||||||
$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)
|
$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)
|
||||||
|
|
||||||
ENVTEST = $(shell pwd)/bin/setup-envtest
|
ENVTEST = $(GOBIN)/setup-envtest
|
||||||
.PHONY: envtest
|
.PHONY: envtest
|
||||||
setup-envtest: ## Download envtest-setup locally if necessary.
|
setup-envtest: ## Download envtest-setup locally if necessary.
|
||||||
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
|
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
|
||||||
|
@ -185,7 +185,7 @@ TMP_DIR=$$(mktemp -d) ;\
|
||||||
cd $$TMP_DIR ;\
|
cd $$TMP_DIR ;\
|
||||||
go mod init tmp ;\
|
go mod init tmp ;\
|
||||||
echo "Downloading $(2)" ;\
|
echo "Downloading $(2)" ;\
|
||||||
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
|
GOBIN=$(GOBIN) go install $(2) ;\
|
||||||
rm -rf $$TMP_DIR ;\
|
rm -rf $$TMP_DIR ;\
|
||||||
}
|
}
|
||||||
endef
|
endef
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
[](https://github.com/fluxcd/notification-controller/releases)
|
[](https://github.com/fluxcd/notification-controller/releases)
|
||||||
|
|
||||||
Event forwarder and notification dispatcher for the [GitOps Toolkit](https://fluxcd.io/flux/components/) controllers.
|
Event forwarder and notification dispatcher for the [GitOps Toolkit](https://fluxcd.io/flux/components/) controllers.
|
||||||
The notification-controller is an implementation of the [notification.toolkit.fluxcd.io](docs/spec/v1beta1/README.md)
|
The notification-controller is an implementation of the [notification.toolkit.fluxcd.io](docs/spec/v1beta3/README.md)
|
||||||
API based on the specifications described in the [RFC](docs/spec/README.md).
|
API based on the specifications described in the [RFC](docs/spec/README.md).
|
||||||
|
|
||||||

|

|
||||||
|
|
32
api/go.mod
32
api/go.mod
|
@ -1,31 +1,35 @@
|
||||||
module github.com/fluxcd/notification-controller/api
|
module github.com/fluxcd/notification-controller/api
|
||||||
|
|
||||||
go 1.20
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fluxcd/pkg/apis/meta v1.2.0
|
github.com/fluxcd/pkg/apis/meta v1.17.0
|
||||||
k8s.io/apimachinery v0.28.4
|
k8s.io/apimachinery v0.33.2
|
||||||
sigs.k8s.io/controller-runtime v0.16.3
|
sigs.k8s.io/controller-runtime v0.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fix CVE-2022-28948
|
// Fix CVE-2022-28948
|
||||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-logr/logr v1.3.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
golang.org/x/net v0.18.0 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
github.com/x448/float16 v0.8.4 // 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/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/klog/v2 v2.100.1 // indirect
|
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
|
||||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.5.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
96
api/go.sum
96
api/go.sum
|
@ -2,26 +2,29 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fluxcd/pkg/apis/meta v1.2.0 h1:O766PzGAdMdQKybSflGL8oV0+GgCNIkdsxfalRyzeO8=
|
github.com/fluxcd/pkg/apis/meta v1.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E=
|
||||||
github.com/fluxcd/pkg/apis/meta v1.2.0/go.mod h1:fU/Az9AoVyIxC0oI4ihG0NVMNnvrcCzdEym3wxjIQsc=
|
github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
|
||||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
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/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
@ -31,17 +34,28 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||||
|
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
|
||||||
|
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/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-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
@ -51,24 +65,26 @@ 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-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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
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-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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
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-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-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-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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
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-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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -78,21 +94,25 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
|
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
|
||||||
k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
|
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
|
||||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
|
||||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
|
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
|
||||||
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
|
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
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/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=
|
||||||
|
|
|
@ -40,13 +40,14 @@ const (
|
||||||
GCRReceiver string = "gcr"
|
GCRReceiver string = "gcr"
|
||||||
NexusReceiver string = "nexus"
|
NexusReceiver string = "nexus"
|
||||||
ACRReceiver string = "acr"
|
ACRReceiver string = "acr"
|
||||||
|
CDEventsReceiver string = "cdevents"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReceiverSpec defines the desired state of the Receiver.
|
// ReceiverSpec defines the desired state of the Receiver.
|
||||||
type ReceiverSpec struct {
|
type ReceiverSpec struct {
|
||||||
// Type of webhook sender, used to determine
|
// Type of webhook sender, used to determine
|
||||||
// the validation procedure and payload deserialization.
|
// the validation procedure and payload deserialization.
|
||||||
// +kubebuilder:validation:Enum=generic;generic-hmac;github;gitlab;bitbucket;harbor;dockerhub;quay;gcr;nexus;acr
|
// +kubebuilder:validation:Enum=generic;generic-hmac;github;gitlab;bitbucket;harbor;dockerhub;quay;gcr;nexus;acr;cdevents
|
||||||
// +required
|
// +required
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
@ -66,6 +67,16 @@ type ReceiverSpec struct {
|
||||||
// +required
|
// +required
|
||||||
Resources []CrossNamespaceObjectReference `json:"resources"`
|
Resources []CrossNamespaceObjectReference `json:"resources"`
|
||||||
|
|
||||||
|
// ResourceFilter is a CEL expression expected to return a boolean that is
|
||||||
|
// evaluated for each resource referenced in the Resources field when a
|
||||||
|
// webhook is received. If the expression returns false then the controller
|
||||||
|
// will not request a reconciliation for the resource.
|
||||||
|
// When the expression is specified the controller will parse it and mark
|
||||||
|
// the object as terminally failed if the expression is invalid or does not
|
||||||
|
// return a boolean.
|
||||||
|
// +optional
|
||||||
|
ResourceFilter string `json:"resourceFilter,omitempty"`
|
||||||
|
|
||||||
// SecretRef specifies the Secret containing the token used
|
// SecretRef specifies the Secret containing the token used
|
||||||
// to validate the payload authenticity.
|
// to validate the payload authenticity.
|
||||||
// +required
|
// +required
|
||||||
|
@ -122,7 +133,6 @@ func (in *Receiver) GetInterval() time.Duration {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:storageversion
|
// +kubebuilder:storageversion
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
|
|
|
@ -31,13 +31,13 @@ type CrossNamespaceObjectReference struct {
|
||||||
// Name of the referent
|
// Name of the referent
|
||||||
// If multiple resources are targeted `*` may be set.
|
// If multiple resources are targeted `*` may be set.
|
||||||
// +kubebuilder:validation:MinLength=1
|
// +kubebuilder:validation:MinLength=1
|
||||||
// +kubebuilder:validation:MaxLength=53
|
// +kubebuilder:validation:MaxLength=253
|
||||||
// +required
|
// +required
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Namespace of the referent
|
// Namespace of the referent
|
||||||
// +kubebuilder:validation:MinLength=1
|
// +kubebuilder:validation:MinLength=1
|
||||||
// +kubebuilder:validation:MaxLength=53
|
// +kubebuilder:validation:MaxLength=253
|
||||||
// +kubebuilder:validation:Optional
|
// +kubebuilder:validation:Optional
|
||||||
// +optional
|
// +optional
|
||||||
Namespace string `json:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build !ignore_autogenerated
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2023 The Flux authors
|
Copyright 2023 The Flux authors
|
||||||
|
|
|
@ -67,7 +67,6 @@ type AlertStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:deprecatedversion:warning="v1beta1 Alert is deprecated, upgrade to v1beta3"
|
// +kubebuilder:deprecatedversion:warning="v1beta1 Alert is deprecated, upgrade to v1beta3"
|
||||||
|
|
|
@ -110,7 +110,6 @@ type ProviderStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:deprecatedversion:warning="v1beta1 Provider is deprecated, upgrade to v1beta3"
|
// +kubebuilder:deprecatedversion:warning="v1beta1 Provider is deprecated, upgrade to v1beta3"
|
||||||
|
|
|
@ -97,7 +97,6 @@ func (in *Receiver) SetConditions(conditions []metav1.Condition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:deprecatedversion:warning="v1beta1 Receiver is deprecated, upgrade to v1"
|
// +kubebuilder:deprecatedversion:warning="v1beta1 Receiver is deprecated, upgrade to v1"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build !ignore_autogenerated
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2023 The Flux authors
|
Copyright 2023 The Flux authors
|
||||||
|
|
|
@ -88,7 +88,6 @@ type AlertStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:deprecatedversion:warning="v1beta2 Alert is deprecated, upgrade to v1beta3"
|
// +kubebuilder:deprecatedversion:warning="v1beta2 Alert is deprecated, upgrade to v1beta3"
|
||||||
|
|
|
@ -131,7 +131,6 @@ type ProviderStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:deprecatedversion:warning="v1beta2 Provider is deprecated, upgrade to v1beta3"
|
// +kubebuilder:deprecatedversion:warning="v1beta2 Provider is deprecated, upgrade to v1beta3"
|
||||||
|
|
|
@ -129,7 +129,6 @@ func (in *Receiver) GetInterval() time.Duration {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:deprecatedversion:warning="v1beta2 Receiver is deprecated, upgrade to v1"
|
// +kubebuilder:deprecatedversion:warning="v1beta2 Receiver is deprecated, upgrade to v1"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build !ignore_autogenerated
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2023 The Flux authors
|
Copyright 2023 The Flux authors
|
||||||
|
|
|
@ -64,8 +64,11 @@ type AlertSpec struct {
|
||||||
ExclusionList []string `json:"exclusionList,omitempty"`
|
ExclusionList []string `json:"exclusionList,omitempty"`
|
||||||
|
|
||||||
// Summary holds a short description of the impact and affected cluster.
|
// Summary holds a short description of the impact and affected cluster.
|
||||||
|
// Deprecated: Use EventMetadata instead.
|
||||||
|
//
|
||||||
// +kubebuilder:validation:MaxLength:=255
|
// +kubebuilder:validation:MaxLength:=255
|
||||||
// +optional
|
// +optional
|
||||||
|
// +deprecated
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitempty"`
|
||||||
|
|
||||||
// Suspend tells the controller to suspend subsequent
|
// Suspend tells the controller to suspend subsequent
|
||||||
|
@ -75,7 +78,6 @@ type AlertSpec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:storageversion
|
// +kubebuilder:storageversion
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
||||||
|
|
|
@ -55,12 +55,22 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProviderSpec defines the desired state of the Provider.
|
// ProviderSpec defines the desired state of the Provider.
|
||||||
|
// +kubebuilder:validation:XValidation:rule="self.type == 'github' || self.type == 'gitlab' || self.type == 'gitea' || self.type == 'bitbucketserver' || self.type == 'bitbucket' || self.type == 'azuredevops' || !has(self.commitStatusExpr)", message="spec.commitStatusExpr is only supported for the 'github', 'gitlab', 'gitea', 'bitbucketserver', 'bitbucket', 'azuredevops' provider types"
|
||||||
type ProviderSpec struct {
|
type ProviderSpec struct {
|
||||||
// Type specifies which Provider implementation to use.
|
// Type specifies which Provider implementation to use.
|
||||||
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucketserver;bitbucket;azuredevops;googlechat;googlepubsub;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;pagerduty;datadog;nats
|
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucketserver;bitbucket;azuredevops;googlechat;googlepubsub;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;pagerduty;datadog;nats
|
||||||
// +required
|
// +required
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Interval at which to reconcile the Provider with its Secret references.
|
||||||
|
// Deprecated and not used in v1beta3.
|
||||||
|
//
|
||||||
|
// +kubebuilder:validation:Type=string
|
||||||
|
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
|
||||||
|
// +optional
|
||||||
|
// +deprecated
|
||||||
|
Interval *metav1.Duration `json:"interval,omitempty"`
|
||||||
|
|
||||||
// Channel specifies the destination channel where events should be posted.
|
// Channel specifies the destination channel where events should be posted.
|
||||||
// +kubebuilder:validation:MaxLength:=2048
|
// +kubebuilder:validation:MaxLength:=2048
|
||||||
// +optional
|
// +optional
|
||||||
|
@ -87,33 +97,60 @@ type ProviderSpec struct {
|
||||||
Timeout *metav1.Duration `json:"timeout,omitempty"`
|
Timeout *metav1.Duration `json:"timeout,omitempty"`
|
||||||
|
|
||||||
// Proxy the HTTP/S address of the proxy server.
|
// 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:Pattern="^(http|https)://.*$"
|
||||||
// +kubebuilder:validation:MaxLength:=2048
|
// +kubebuilder:validation:MaxLength:=2048
|
||||||
// +kubebuilder:validation:Optional
|
// +kubebuilder:validation:Optional
|
||||||
// +optional
|
// +optional
|
||||||
Proxy string `json:"proxy,omitempty"`
|
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
|
// SecretRef specifies the Secret containing the authentication
|
||||||
// credentials for this Provider.
|
// credentials for this Provider.
|
||||||
// +optional
|
// +optional
|
||||||
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||||
|
|
||||||
// CertSecretRef specifies the Secret containing
|
// ServiceAccountName is the name of the service account used to
|
||||||
// a PEM-encoded CA certificate (in the `ca.crt` key).
|
// authenticate with services from cloud providers. An error is thrown if a
|
||||||
|
// static credential is also defined inside the Secret referenced by the
|
||||||
|
// SecretRef.
|
||||||
// +optional
|
// +optional
|
||||||
|
ServiceAccountName string `json:"serviceAccountName,omitempty"`
|
||||||
|
|
||||||
|
// CertSecretRef specifies the Secret containing TLS certificates
|
||||||
|
// for secure communication.
|
||||||
//
|
//
|
||||||
// Note: Support for the `caFile` key has
|
// Supported configurations:
|
||||||
// been deprecated.
|
// - 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"`
|
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
||||||
|
|
||||||
// Suspend tells the controller to suspend subsequent
|
// Suspend tells the controller to suspend subsequent
|
||||||
// events handling for this Provider.
|
// events handling for this Provider.
|
||||||
// +optional
|
// +optional
|
||||||
Suspend bool `json:"suspend,omitempty"`
|
Suspend bool `json:"suspend,omitempty"`
|
||||||
|
|
||||||
|
// CommitStatusExpr is a CEL expression that evaluates to a string value
|
||||||
|
// that can be used to generate a custom commit status message for use
|
||||||
|
// with eligible Provider types (github, gitlab, gitea, bitbucketserver,
|
||||||
|
// bitbucket, azuredevops). Supported variables are: event, provider,
|
||||||
|
// and alert.
|
||||||
|
// +optional
|
||||||
|
CommitStatusExpr string `json:"commitStatusExpr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
// +genclient:Namespaced
|
|
||||||
// +kubebuilder:storageversion
|
// +kubebuilder:storageversion
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build !ignore_autogenerated
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2023 The Flux authors
|
Copyright 2023 The Flux authors
|
||||||
|
@ -187,11 +186,21 @@ func (in *ProviderList) DeepCopyObject() runtime.Object {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) {
|
func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Interval != nil {
|
||||||
|
in, out := &in.Interval, &out.Interval
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.Timeout != nil {
|
if in.Timeout != nil {
|
||||||
in, out := &in.Timeout, &out.Timeout
|
in, out := &in.Timeout, &out.Timeout
|
||||||
*out = new(metav1.Duration)
|
*out = new(metav1.Duration)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.ProxySecretRef != nil {
|
||||||
|
in, out := &in.ProxySecretRef, &out.ProxySecretRef
|
||||||
|
*out = new(meta.LocalObjectReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.SecretRef != nil {
|
if in.SecretRef != nil {
|
||||||
in, out := &in.SecretRef, &out.SecretRef
|
in, out := &in.SecretRef, &out.SecretRef
|
||||||
*out = new(meta.LocalObjectReference)
|
*out = new(meta.LocalObjectReference)
|
||||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.12.0
|
controller-gen.kubebuilder.io/version: v0.16.1
|
||||||
name: alerts.notification.toolkit.fluxcd.io
|
name: alerts.notification.toolkit.fluxcd.io
|
||||||
spec:
|
spec:
|
||||||
group: notification.toolkit.fluxcd.io
|
group: notification.toolkit.fluxcd.io
|
||||||
|
@ -32,14 +32,19 @@ spec:
|
||||||
description: Alert is the Schema for the alerts API
|
description: Alert is the Schema for the alerts API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation
|
description: |-
|
||||||
of an object. Servers should convert recognized schemas to the latest
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this
|
description: |-
|
||||||
object represents. Servers may infer this from the endpoint the client
|
Kind is a string value representing the REST resource this object represents.
|
||||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
|
@ -49,7 +54,8 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
eventSeverity:
|
eventSeverity:
|
||||||
default: info
|
default: info
|
||||||
description: Filter events based on severity, defaults to ('info').
|
description: |-
|
||||||
|
Filter events based on severity, defaults to ('info').
|
||||||
If set to 'info' no events will be filtered.
|
If set to 'info' no events will be filtered.
|
||||||
enum:
|
enum:
|
||||||
- info
|
- info
|
||||||
|
@ -58,8 +64,9 @@ spec:
|
||||||
eventSources:
|
eventSources:
|
||||||
description: Filter events based on the involved objects.
|
description: Filter events based on the involved objects.
|
||||||
items:
|
items:
|
||||||
description: CrossNamespaceObjectReference contains enough information
|
description: |-
|
||||||
to let you locate the typed referenced object at cluster level
|
CrossNamespaceObjectReference contains enough information to let you locate the
|
||||||
|
typed referenced object at cluster level
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: API version of the referent
|
description: API version of the referent
|
||||||
|
@ -81,11 +88,10 @@ spec:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
description: MatchLabels is a map of {key,value} pairs. A single
|
description: |-
|
||||||
{key,value} in the matchLabels map is equivalent to an element
|
MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||||
of matchExpressions, whose key field is "key", the operator
|
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||||
is "In", and the values array contains only "value". The requirements
|
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||||
are ANDed.
|
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
description: Name of the referent
|
description: Name of the referent
|
||||||
|
@ -98,6 +104,7 @@ spec:
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- kind
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
@ -120,8 +127,9 @@ spec:
|
||||||
description: Short description of the impact and affected cluster.
|
description: Short description of the impact and affected cluster.
|
||||||
type: string
|
type: string
|
||||||
suspend:
|
suspend:
|
||||||
description: This flag tells the controller to suspend subsequent
|
description: |-
|
||||||
events dispatching. Defaults to false.
|
This flag tells the controller to suspend subsequent events dispatching.
|
||||||
|
Defaults to false.
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- eventSources
|
- eventSources
|
||||||
|
@ -134,43 +142,35 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
conditions:
|
conditions:
|
||||||
items:
|
items:
|
||||||
description: "Condition contains details for one aspect of the current
|
description: Condition contains details for one aspect of the current
|
||||||
state of this API Resource. --- This struct is intended for direct
|
state of this API Resource.
|
||||||
use as an array at the field path .status.conditions. For example,
|
|
||||||
\n type FooStatus struct{ // Represents the observations of a
|
|
||||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
|
||||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
|
||||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
|
||||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
|
||||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
|
||||||
properties:
|
properties:
|
||||||
lastTransitionTime:
|
lastTransitionTime:
|
||||||
description: lastTransitionTime is the last time the condition
|
description: |-
|
||||||
transitioned from one status to another. This should be when
|
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||||
the underlying condition changed. If that is not known, then
|
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||||
using the time when the API field changed is acceptable.
|
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
message:
|
message:
|
||||||
description: message is a human readable message indicating
|
description: |-
|
||||||
details about the transition. This may be an empty string.
|
message is a human readable message indicating details about the transition.
|
||||||
|
This may be an empty string.
|
||||||
maxLength: 32768
|
maxLength: 32768
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: observedGeneration represents the .metadata.generation
|
description: |-
|
||||||
that the condition was set based upon. For instance, if .metadata.generation
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||||
is currently 12, but the .status.conditions[x].observedGeneration
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||||
is 9, the condition is out of date with respect to the current
|
with respect to the current state of the instance.
|
||||||
state of the instance.
|
|
||||||
format: int64
|
format: int64
|
||||||
minimum: 0
|
minimum: 0
|
||||||
type: integer
|
type: integer
|
||||||
reason:
|
reason:
|
||||||
description: reason contains a programmatic identifier indicating
|
description: |-
|
||||||
the reason for the condition's last transition. Producers
|
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||||
of specific condition types may define expected values and
|
Producers of specific condition types may define expected values and meanings for this field,
|
||||||
meanings for this field, and whether the values are considered
|
and whether the values are considered a guaranteed API.
|
||||||
a guaranteed API. The value should be a CamelCase string.
|
The value should be a CamelCase string.
|
||||||
This field may not be empty.
|
This field may not be empty.
|
||||||
maxLength: 1024
|
maxLength: 1024
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -185,10 +185,6 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
--- Many .condition.type values are consistent across resources
|
|
||||||
like Available, but because arbitrary conditions can be useful
|
|
||||||
(see .node.status.conditions), the ability to deconflict is
|
|
||||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
|
||||||
maxLength: 316
|
maxLength: 316
|
||||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||||
type: string
|
type: string
|
||||||
|
@ -228,14 +224,19 @@ spec:
|
||||||
description: Alert is the Schema for the alerts API
|
description: Alert is the Schema for the alerts API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation
|
description: |-
|
||||||
of an object. Servers should convert recognized schemas to the latest
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this
|
description: |-
|
||||||
object represents. Servers may infer this from the endpoint the client
|
Kind is a string value representing the REST resource this object represents.
|
||||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
|
@ -246,27 +247,30 @@ spec:
|
||||||
eventMetadata:
|
eventMetadata:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
description: EventMetadata is an optional field for adding metadata
|
description: |-
|
||||||
to events dispatched by the controller. This can be used for enhancing
|
EventMetadata is an optional field for adding metadata to events dispatched by the
|
||||||
the context of the event. If a field would override one already
|
controller. This can be used for enhancing the context of the event. If a field
|
||||||
present on the original event as generated by the emitter, then
|
would override one already present on the original event as generated by the emitter,
|
||||||
the override doesn't happen, i.e. the original value is preserved,
|
then the override doesn't happen, i.e. the original value is preserved, and an info
|
||||||
and an info log is printed.
|
log is printed.
|
||||||
type: object
|
type: object
|
||||||
eventSeverity:
|
eventSeverity:
|
||||||
default: info
|
default: info
|
||||||
description: EventSeverity specifies how to filter events based on
|
description: |-
|
||||||
severity. If set to 'info' no events will be filtered.
|
EventSeverity specifies how to filter events based on severity.
|
||||||
|
If set to 'info' no events will be filtered.
|
||||||
enum:
|
enum:
|
||||||
- info
|
- info
|
||||||
- error
|
- error
|
||||||
type: string
|
type: string
|
||||||
eventSources:
|
eventSources:
|
||||||
description: EventSources specifies how to filter events based on
|
description: |-
|
||||||
the involved object kind, name and namespace.
|
EventSources specifies how to filter events based
|
||||||
|
on the involved object kind, name and namespace.
|
||||||
items:
|
items:
|
||||||
description: CrossNamespaceObjectReference contains enough information
|
description: |-
|
||||||
to let you locate the typed referenced object at cluster level
|
CrossNamespaceObjectReference contains enough information to let you locate the
|
||||||
|
typed referenced object at cluster level
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: API version of the referent
|
description: API version of the referent
|
||||||
|
@ -288,21 +292,22 @@ spec:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
description: MatchLabels is a map of {key,value} pairs. A single
|
description: |-
|
||||||
{key,value} in the matchLabels map is equivalent to an element
|
MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||||
of matchExpressions, whose key field is "key", the operator
|
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||||
is "In", and the values array contains only "value". The requirements
|
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||||
are ANDed. MatchLabels requires the name to be set to `*`.
|
MatchLabels requires the name to be set to `*`.
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
description: Name of the referent If multiple resources are
|
description: |-
|
||||||
targeted `*` may be set.
|
Name of the referent
|
||||||
maxLength: 53
|
If multiple resources are targeted `*` may be set.
|
||||||
|
maxLength: 253
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
description: Namespace of the referent
|
description: Namespace of the referent
|
||||||
maxLength: 53
|
maxLength: 253
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
@ -311,13 +316,15 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
exclusionList:
|
exclusionList:
|
||||||
description: ExclusionList specifies a list of Golang regular expressions
|
description: |-
|
||||||
|
ExclusionList specifies a list of Golang regular expressions
|
||||||
to be used for excluding messages.
|
to be used for excluding messages.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
inclusionList:
|
inclusionList:
|
||||||
description: InclusionList specifies a list of Golang regular expressions
|
description: |-
|
||||||
|
InclusionList specifies a list of Golang regular expressions
|
||||||
to be used for including messages.
|
to be used for including messages.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
@ -338,8 +345,9 @@ spec:
|
||||||
maxLength: 255
|
maxLength: 255
|
||||||
type: string
|
type: string
|
||||||
suspend:
|
suspend:
|
||||||
description: Suspend tells the controller to suspend subsequent events
|
description: |-
|
||||||
handling for this Alert.
|
Suspend tells the controller to suspend subsequent
|
||||||
|
events handling for this Alert.
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- eventSources
|
- eventSources
|
||||||
|
@ -353,43 +361,35 @@ spec:
|
||||||
conditions:
|
conditions:
|
||||||
description: Conditions holds the conditions for the Alert.
|
description: Conditions holds the conditions for the Alert.
|
||||||
items:
|
items:
|
||||||
description: "Condition contains details for one aspect of the current
|
description: Condition contains details for one aspect of the current
|
||||||
state of this API Resource. --- This struct is intended for direct
|
state of this API Resource.
|
||||||
use as an array at the field path .status.conditions. For example,
|
|
||||||
\n type FooStatus struct{ // Represents the observations of a
|
|
||||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
|
||||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
|
||||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
|
||||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
|
||||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
|
||||||
properties:
|
properties:
|
||||||
lastTransitionTime:
|
lastTransitionTime:
|
||||||
description: lastTransitionTime is the last time the condition
|
description: |-
|
||||||
transitioned from one status to another. This should be when
|
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||||
the underlying condition changed. If that is not known, then
|
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||||
using the time when the API field changed is acceptable.
|
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
message:
|
message:
|
||||||
description: message is a human readable message indicating
|
description: |-
|
||||||
details about the transition. This may be an empty string.
|
message is a human readable message indicating details about the transition.
|
||||||
|
This may be an empty string.
|
||||||
maxLength: 32768
|
maxLength: 32768
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: observedGeneration represents the .metadata.generation
|
description: |-
|
||||||
that the condition was set based upon. For instance, if .metadata.generation
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||||
is currently 12, but the .status.conditions[x].observedGeneration
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||||
is 9, the condition is out of date with respect to the current
|
with respect to the current state of the instance.
|
||||||
state of the instance.
|
|
||||||
format: int64
|
format: int64
|
||||||
minimum: 0
|
minimum: 0
|
||||||
type: integer
|
type: integer
|
||||||
reason:
|
reason:
|
||||||
description: reason contains a programmatic identifier indicating
|
description: |-
|
||||||
the reason for the condition's last transition. Producers
|
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||||
of specific condition types may define expected values and
|
Producers of specific condition types may define expected values and meanings for this field,
|
||||||
meanings for this field, and whether the values are considered
|
and whether the values are considered a guaranteed API.
|
||||||
a guaranteed API. The value should be a CamelCase string.
|
The value should be a CamelCase string.
|
||||||
This field may not be empty.
|
This field may not be empty.
|
||||||
maxLength: 1024
|
maxLength: 1024
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -404,10 +404,6 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
--- Many .condition.type values are consistent across resources
|
|
||||||
like Available, but because arbitrary conditions can be useful
|
|
||||||
(see .node.status.conditions), the ability to deconflict is
|
|
||||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
|
||||||
maxLength: 316
|
maxLength: 316
|
||||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||||
type: string
|
type: string
|
||||||
|
@ -420,9 +416,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
lastHandledReconcileAt:
|
lastHandledReconcileAt:
|
||||||
description: LastHandledReconcileAt holds the value of the most recent
|
description: |-
|
||||||
reconcile request value, so a change of the annotation value can
|
LastHandledReconcileAt holds the value of the most recent
|
||||||
be detected.
|
reconcile request value, so a change of the annotation value
|
||||||
|
can be detected.
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: ObservedGeneration is the last observed generation.
|
description: ObservedGeneration is the last observed generation.
|
||||||
|
@ -444,14 +441,19 @@ spec:
|
||||||
description: Alert is the Schema for the alerts API
|
description: Alert is the Schema for the alerts API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation
|
description: |-
|
||||||
of an object. Servers should convert recognized schemas to the latest
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this
|
description: |-
|
||||||
object represents. Servers may infer this from the endpoint the client
|
Kind is a string value representing the REST resource this object represents.
|
||||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
|
@ -462,27 +464,30 @@ spec:
|
||||||
eventMetadata:
|
eventMetadata:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
description: EventMetadata is an optional field for adding metadata
|
description: |-
|
||||||
to events dispatched by the controller. This can be used for enhancing
|
EventMetadata is an optional field for adding metadata to events dispatched by the
|
||||||
the context of the event. If a field would override one already
|
controller. This can be used for enhancing the context of the event. If a field
|
||||||
present on the original event as generated by the emitter, then
|
would override one already present on the original event as generated by the emitter,
|
||||||
the override doesn't happen, i.e. the original value is preserved,
|
then the override doesn't happen, i.e. the original value is preserved, and an info
|
||||||
and an info log is printed.
|
log is printed.
|
||||||
type: object
|
type: object
|
||||||
eventSeverity:
|
eventSeverity:
|
||||||
default: info
|
default: info
|
||||||
description: EventSeverity specifies how to filter events based on
|
description: |-
|
||||||
severity. If set to 'info' no events will be filtered.
|
EventSeverity specifies how to filter events based on severity.
|
||||||
|
If set to 'info' no events will be filtered.
|
||||||
enum:
|
enum:
|
||||||
- info
|
- info
|
||||||
- error
|
- error
|
||||||
type: string
|
type: string
|
||||||
eventSources:
|
eventSources:
|
||||||
description: EventSources specifies how to filter events based on
|
description: |-
|
||||||
the involved object kind, name and namespace.
|
EventSources specifies how to filter events based
|
||||||
|
on the involved object kind, name and namespace.
|
||||||
items:
|
items:
|
||||||
description: CrossNamespaceObjectReference contains enough information
|
description: |-
|
||||||
to let you locate the typed referenced object at cluster level
|
CrossNamespaceObjectReference contains enough information to let you locate the
|
||||||
|
typed referenced object at cluster level
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: API version of the referent
|
description: API version of the referent
|
||||||
|
@ -504,21 +509,22 @@ spec:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
description: MatchLabels is a map of {key,value} pairs. A single
|
description: |-
|
||||||
{key,value} in the matchLabels map is equivalent to an element
|
MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||||
of matchExpressions, whose key field is "key", the operator
|
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||||
is "In", and the values array contains only "value". The requirements
|
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||||
are ANDed. MatchLabels requires the name to be set to `*`.
|
MatchLabels requires the name to be set to `*`.
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
description: Name of the referent If multiple resources are
|
description: |-
|
||||||
targeted `*` may be set.
|
Name of the referent
|
||||||
maxLength: 53
|
If multiple resources are targeted `*` may be set.
|
||||||
|
maxLength: 253
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
description: Namespace of the referent
|
description: Namespace of the referent
|
||||||
maxLength: 53
|
maxLength: 253
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
@ -527,13 +533,15 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
exclusionList:
|
exclusionList:
|
||||||
description: ExclusionList specifies a list of Golang regular expressions
|
description: |-
|
||||||
|
ExclusionList specifies a list of Golang regular expressions
|
||||||
to be used for excluding messages.
|
to be used for excluding messages.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
inclusionList:
|
inclusionList:
|
||||||
description: InclusionList specifies a list of Golang regular expressions
|
description: |-
|
||||||
|
InclusionList specifies a list of Golang regular expressions
|
||||||
to be used for including messages.
|
to be used for including messages.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
@ -549,13 +557,15 @@ spec:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
summary:
|
summary:
|
||||||
description: Summary holds a short description of the impact and affected
|
description: |-
|
||||||
cluster.
|
Summary holds a short description of the impact and affected cluster.
|
||||||
|
Deprecated: Use EventMetadata instead.
|
||||||
maxLength: 255
|
maxLength: 255
|
||||||
type: string
|
type: string
|
||||||
suspend:
|
suspend:
|
||||||
description: Suspend tells the controller to suspend subsequent events
|
description: |-
|
||||||
handling for this Alert.
|
Suspend tells the controller to suspend subsequent
|
||||||
|
events handling for this Alert.
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- eventSources
|
- eventSources
|
||||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.12.0
|
controller-gen.kubebuilder.io/version: v0.16.1
|
||||||
name: providers.notification.toolkit.fluxcd.io
|
name: providers.notification.toolkit.fluxcd.io
|
||||||
spec:
|
spec:
|
||||||
group: notification.toolkit.fluxcd.io
|
group: notification.toolkit.fluxcd.io
|
||||||
|
@ -32,14 +32,19 @@ spec:
|
||||||
description: Provider is the Schema for the providers API
|
description: Provider is the Schema for the providers API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation
|
description: |-
|
||||||
of an object. Servers should convert recognized schemas to the latest
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this
|
description: |-
|
||||||
object represents. Servers may infer this from the endpoint the client
|
Kind is a string value representing the REST resource this object represents.
|
||||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
|
@ -51,7 +56,8 @@ spec:
|
||||||
pattern: ^(http|https)://
|
pattern: ^(http|https)://
|
||||||
type: string
|
type: string
|
||||||
certSecretRef:
|
certSecretRef:
|
||||||
description: CertSecretRef can be given the name of a secret containing
|
description: |-
|
||||||
|
CertSecretRef can be given the name of a secret containing
|
||||||
a PEM-encoded CA certificate (`caFile`)
|
a PEM-encoded CA certificate (`caFile`)
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
@ -68,7 +74,8 @@ spec:
|
||||||
pattern: ^(http|https)://
|
pattern: ^(http|https)://
|
||||||
type: string
|
type: string
|
||||||
secretRef:
|
secretRef:
|
||||||
description: Secret reference containing the provider webhook URL
|
description: |-
|
||||||
|
Secret reference containing the provider webhook URL
|
||||||
using "address" as data key
|
using "address" as data key
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
@ -78,8 +85,9 @@ spec:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
suspend:
|
suspend:
|
||||||
description: This flag tells the controller to suspend subsequent
|
description: |-
|
||||||
events handling. Defaults to false.
|
This flag tells the controller to suspend subsequent events handling.
|
||||||
|
Defaults to false.
|
||||||
type: boolean
|
type: boolean
|
||||||
timeout:
|
timeout:
|
||||||
description: Timeout for sending alerts to the provider.
|
description: Timeout for sending alerts to the provider.
|
||||||
|
@ -123,43 +131,35 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
conditions:
|
conditions:
|
||||||
items:
|
items:
|
||||||
description: "Condition contains details for one aspect of the current
|
description: Condition contains details for one aspect of the current
|
||||||
state of this API Resource. --- This struct is intended for direct
|
state of this API Resource.
|
||||||
use as an array at the field path .status.conditions. For example,
|
|
||||||
\n type FooStatus struct{ // Represents the observations of a
|
|
||||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
|
||||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
|
||||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
|
||||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
|
||||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
|
||||||
properties:
|
properties:
|
||||||
lastTransitionTime:
|
lastTransitionTime:
|
||||||
description: lastTransitionTime is the last time the condition
|
description: |-
|
||||||
transitioned from one status to another. This should be when
|
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||||
the underlying condition changed. If that is not known, then
|
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||||
using the time when the API field changed is acceptable.
|
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
message:
|
message:
|
||||||
description: message is a human readable message indicating
|
description: |-
|
||||||
details about the transition. This may be an empty string.
|
message is a human readable message indicating details about the transition.
|
||||||
|
This may be an empty string.
|
||||||
maxLength: 32768
|
maxLength: 32768
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: observedGeneration represents the .metadata.generation
|
description: |-
|
||||||
that the condition was set based upon. For instance, if .metadata.generation
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||||
is currently 12, but the .status.conditions[x].observedGeneration
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||||
is 9, the condition is out of date with respect to the current
|
with respect to the current state of the instance.
|
||||||
state of the instance.
|
|
||||||
format: int64
|
format: int64
|
||||||
minimum: 0
|
minimum: 0
|
||||||
type: integer
|
type: integer
|
||||||
reason:
|
reason:
|
||||||
description: reason contains a programmatic identifier indicating
|
description: |-
|
||||||
the reason for the condition's last transition. Producers
|
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||||
of specific condition types may define expected values and
|
Producers of specific condition types may define expected values and meanings for this field,
|
||||||
meanings for this field, and whether the values are considered
|
and whether the values are considered a guaranteed API.
|
||||||
a guaranteed API. The value should be a CamelCase string.
|
The value should be a CamelCase string.
|
||||||
This field may not be empty.
|
This field may not be empty.
|
||||||
maxLength: 1024
|
maxLength: 1024
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -174,10 +174,6 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
--- Many .condition.type values are consistent across resources
|
|
||||||
like Available, but because arbitrary conditions can be useful
|
|
||||||
(see .node.status.conditions), the ability to deconflict is
|
|
||||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
|
||||||
maxLength: 316
|
maxLength: 316
|
||||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||||
type: string
|
type: string
|
||||||
|
@ -217,14 +213,19 @@ spec:
|
||||||
description: Provider is the Schema for the providers API.
|
description: Provider is the Schema for the providers API.
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation
|
description: |-
|
||||||
of an object. Servers should convert recognized schemas to the latest
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this
|
description: |-
|
||||||
object represents. Servers may infer this from the endpoint the client
|
Kind is a string value representing the REST resource this object represents.
|
||||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
|
@ -232,17 +233,20 @@ spec:
|
||||||
description: ProviderSpec defines the desired state of the Provider.
|
description: ProviderSpec defines the desired state of the Provider.
|
||||||
properties:
|
properties:
|
||||||
address:
|
address:
|
||||||
description: Address specifies the endpoint, in a generic sense, to
|
description: |-
|
||||||
where alerts are sent. What kind of endpoint depends on the specific
|
Address specifies the endpoint, in a generic sense, to where alerts are sent.
|
||||||
Provider type being used. For the generic Provider, for example,
|
What kind of endpoint depends on the specific Provider type being used.
|
||||||
this is an HTTP/S address. For other Provider types this could be
|
For the generic Provider, for example, this is an HTTP/S address.
|
||||||
a project ID or a namespace.
|
For other Provider types this could be a project ID or a namespace.
|
||||||
maxLength: 2048
|
maxLength: 2048
|
||||||
type: string
|
type: string
|
||||||
certSecretRef:
|
certSecretRef:
|
||||||
description: "CertSecretRef specifies the Secret containing a PEM-encoded
|
description: |-
|
||||||
CA certificate (in the `ca.crt` key). \n Note: Support for the `caFile`
|
CertSecretRef specifies the Secret containing
|
||||||
key has been deprecated."
|
a PEM-encoded CA certificate (in the `ca.crt` key).
|
||||||
|
|
||||||
|
Note: Support for the `caFile` key has
|
||||||
|
been deprecated.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
description: Name of the referent.
|
description: Name of the referent.
|
||||||
|
@ -266,7 +270,8 @@ spec:
|
||||||
pattern: ^(http|https)://.*$
|
pattern: ^(http|https)://.*$
|
||||||
type: string
|
type: string
|
||||||
secretRef:
|
secretRef:
|
||||||
description: SecretRef specifies the Secret containing the authentication
|
description: |-
|
||||||
|
SecretRef specifies the Secret containing the authentication
|
||||||
credentials for this Provider.
|
credentials for this Provider.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
@ -276,8 +281,9 @@ spec:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
suspend:
|
suspend:
|
||||||
description: Suspend tells the controller to suspend subsequent events
|
description: |-
|
||||||
handling for this Provider.
|
Suspend tells the controller to suspend subsequent
|
||||||
|
events handling for this Provider.
|
||||||
type: boolean
|
type: boolean
|
||||||
timeout:
|
timeout:
|
||||||
description: Timeout for sending alerts to the Provider.
|
description: Timeout for sending alerts to the Provider.
|
||||||
|
@ -328,43 +334,35 @@ spec:
|
||||||
conditions:
|
conditions:
|
||||||
description: Conditions holds the conditions for the Provider.
|
description: Conditions holds the conditions for the Provider.
|
||||||
items:
|
items:
|
||||||
description: "Condition contains details for one aspect of the current
|
description: Condition contains details for one aspect of the current
|
||||||
state of this API Resource. --- This struct is intended for direct
|
state of this API Resource.
|
||||||
use as an array at the field path .status.conditions. For example,
|
|
||||||
\n type FooStatus struct{ // Represents the observations of a
|
|
||||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
|
||||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
|
||||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
|
||||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
|
||||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
|
||||||
properties:
|
properties:
|
||||||
lastTransitionTime:
|
lastTransitionTime:
|
||||||
description: lastTransitionTime is the last time the condition
|
description: |-
|
||||||
transitioned from one status to another. This should be when
|
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||||
the underlying condition changed. If that is not known, then
|
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||||
using the time when the API field changed is acceptable.
|
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
message:
|
message:
|
||||||
description: message is a human readable message indicating
|
description: |-
|
||||||
details about the transition. This may be an empty string.
|
message is a human readable message indicating details about the transition.
|
||||||
|
This may be an empty string.
|
||||||
maxLength: 32768
|
maxLength: 32768
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: observedGeneration represents the .metadata.generation
|
description: |-
|
||||||
that the condition was set based upon. For instance, if .metadata.generation
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||||
is currently 12, but the .status.conditions[x].observedGeneration
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||||
is 9, the condition is out of date with respect to the current
|
with respect to the current state of the instance.
|
||||||
state of the instance.
|
|
||||||
format: int64
|
format: int64
|
||||||
minimum: 0
|
minimum: 0
|
||||||
type: integer
|
type: integer
|
||||||
reason:
|
reason:
|
||||||
description: reason contains a programmatic identifier indicating
|
description: |-
|
||||||
the reason for the condition's last transition. Producers
|
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||||
of specific condition types may define expected values and
|
Producers of specific condition types may define expected values and meanings for this field,
|
||||||
meanings for this field, and whether the values are considered
|
and whether the values are considered a guaranteed API.
|
||||||
a guaranteed API. The value should be a CamelCase string.
|
The value should be a CamelCase string.
|
||||||
This field may not be empty.
|
This field may not be empty.
|
||||||
maxLength: 1024
|
maxLength: 1024
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -379,10 +377,6 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
--- Many .condition.type values are consistent across resources
|
|
||||||
like Available, but because arbitrary conditions can be useful
|
|
||||||
(see .node.status.conditions), the ability to deconflict is
|
|
||||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
|
||||||
maxLength: 316
|
maxLength: 316
|
||||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||||
type: string
|
type: string
|
||||||
|
@ -395,9 +389,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
lastHandledReconcileAt:
|
lastHandledReconcileAt:
|
||||||
description: LastHandledReconcileAt holds the value of the most recent
|
description: |-
|
||||||
reconcile request value, so a change of the annotation value can
|
LastHandledReconcileAt holds the value of the most recent
|
||||||
be detected.
|
reconcile request value, so a change of the annotation value
|
||||||
|
can be detected.
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: ObservedGeneration is the last reconciled generation.
|
description: ObservedGeneration is the last reconciled generation.
|
||||||
|
@ -419,14 +414,19 @@ spec:
|
||||||
description: Provider is the Schema for the providers API
|
description: Provider is the Schema for the providers API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation
|
description: |-
|
||||||
of an object. Servers should convert recognized schemas to the latest
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this
|
description: |-
|
||||||
object represents. Servers may infer this from the endpoint the client
|
Kind is a string value representing the REST resource this object represents.
|
||||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
|
@ -434,17 +434,24 @@ spec:
|
||||||
description: ProviderSpec defines the desired state of the Provider.
|
description: ProviderSpec defines the desired state of the Provider.
|
||||||
properties:
|
properties:
|
||||||
address:
|
address:
|
||||||
description: Address specifies the endpoint, in a generic sense, to
|
description: |-
|
||||||
where alerts are sent. What kind of endpoint depends on the specific
|
Address specifies the endpoint, in a generic sense, to where alerts are sent.
|
||||||
Provider type being used. For the generic Provider, for example,
|
What kind of endpoint depends on the specific Provider type being used.
|
||||||
this is an HTTP/S address. For other Provider types this could be
|
For the generic Provider, for example, this is an HTTP/S address.
|
||||||
a project ID or a namespace.
|
For other Provider types this could be a project ID or a namespace.
|
||||||
maxLength: 2048
|
maxLength: 2048
|
||||||
type: string
|
type: string
|
||||||
certSecretRef:
|
certSecretRef:
|
||||||
description: "CertSecretRef specifies the Secret containing a PEM-encoded
|
description: |-
|
||||||
CA certificate (in the `ca.crt` key). \n Note: Support for the `caFile`
|
CertSecretRef specifies the Secret containing TLS certificates
|
||||||
key has been deprecated."
|
for secure communication.
|
||||||
|
|
||||||
|
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:
|
properties:
|
||||||
name:
|
name:
|
||||||
description: Name of the referent.
|
description: Name of the referent.
|
||||||
|
@ -457,13 +464,43 @@ spec:
|
||||||
should be posted.
|
should be posted.
|
||||||
maxLength: 2048
|
maxLength: 2048
|
||||||
type: string
|
type: string
|
||||||
|
commitStatusExpr:
|
||||||
|
description: |-
|
||||||
|
CommitStatusExpr is a CEL expression that evaluates to a string value
|
||||||
|
that can be used to generate a custom commit status message for use
|
||||||
|
with eligible Provider types (github, gitlab, gitea, bitbucketserver,
|
||||||
|
bitbucket, azuredevops). Supported variables are: event, provider,
|
||||||
|
and alert.
|
||||||
|
type: string
|
||||||
|
interval:
|
||||||
|
description: |-
|
||||||
|
Interval at which to reconcile the Provider with its Secret references.
|
||||||
|
Deprecated and not used in v1beta3.
|
||||||
|
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
|
||||||
|
type: string
|
||||||
proxy:
|
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
|
maxLength: 2048
|
||||||
pattern: ^(http|https)://.*$
|
pattern: ^(http|https)://.*$
|
||||||
type: string
|
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:
|
secretRef:
|
||||||
description: SecretRef specifies the Secret containing the authentication
|
description: |-
|
||||||
|
SecretRef specifies the Secret containing the authentication
|
||||||
credentials for this Provider.
|
credentials for this Provider.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
@ -472,9 +509,17 @@ spec:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
|
serviceAccountName:
|
||||||
|
description: |-
|
||||||
|
ServiceAccountName is the name of the service account used to
|
||||||
|
authenticate with services from cloud providers. An error is thrown if a
|
||||||
|
static credential is also defined inside the Secret referenced by the
|
||||||
|
SecretRef.
|
||||||
|
type: string
|
||||||
suspend:
|
suspend:
|
||||||
description: Suspend tells the controller to suspend subsequent events
|
description: |-
|
||||||
handling for this Provider.
|
Suspend tells the controller to suspend subsequent
|
||||||
|
events handling for this Provider.
|
||||||
type: boolean
|
type: boolean
|
||||||
timeout:
|
timeout:
|
||||||
description: Timeout for sending alerts to the Provider.
|
description: Timeout for sending alerts to the Provider.
|
||||||
|
@ -518,6 +563,12 @@ spec:
|
||||||
required:
|
required:
|
||||||
- type
|
- type
|
||||||
type: object
|
type: object
|
||||||
|
x-kubernetes-validations:
|
||||||
|
- message: spec.commitStatusExpr is only supported for the 'github', 'gitlab',
|
||||||
|
'gitea', 'bitbucketserver', 'bitbucket', 'azuredevops' provider types
|
||||||
|
rule: self.type == 'github' || self.type == 'gitlab' || self.type ==
|
||||||
|
'gitea' || self.type == 'bitbucketserver' || self.type == 'bitbucket'
|
||||||
|
|| self.type == 'azuredevops' || !has(self.commitStatusExpr)
|
||||||
type: object
|
type: object
|
||||||
served: true
|
served: true
|
||||||
storage: true
|
storage: true
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,4 +6,4 @@ resources:
|
||||||
images:
|
images:
|
||||||
- name: fluxcd/notification-controller
|
- name: fluxcd/notification-controller
|
||||||
newName: fluxcd/notification-controller
|
newName: fluxcd/notification-controller
|
||||||
newTag: v1.1.0
|
newTag: v1.6.0
|
||||||
|
|
|
@ -19,6 +19,12 @@ rules:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- serviceaccounts/token
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- image.fluxcd.io
|
- image.fluxcd.io
|
||||||
resources:
|
resources:
|
||||||
|
@ -39,29 +45,7 @@ rules:
|
||||||
- notification.toolkit.fluxcd.io
|
- notification.toolkit.fluxcd.io
|
||||||
resources:
|
resources:
|
||||||
- alerts
|
- alerts
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- notification.toolkit.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- providers
|
- providers
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- notification.toolkit.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- receivers
|
- receivers
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- create
|
||||||
|
@ -83,53 +67,8 @@ rules:
|
||||||
- source.fluxcd.io
|
- source.fluxcd.io
|
||||||
resources:
|
resources:
|
||||||
- buckets
|
- buckets
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- source.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- buckets/status
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- apiGroups:
|
|
||||||
- source.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- gitrepositories
|
- gitrepositories
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- source.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- gitrepositories/status
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- apiGroups:
|
|
||||||
- source.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- helmrepositories
|
- helmrepositories
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- source.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- helmrepositories/status
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- apiGroups:
|
|
||||||
- source.fluxcd.io
|
|
||||||
resources:
|
|
||||||
- ocirepositories
|
- ocirepositories
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
|
@ -140,6 +79,9 @@ rules:
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- source.fluxcd.io
|
- source.fluxcd.io
|
||||||
resources:
|
resources:
|
||||||
|
- buckets/status
|
||||||
|
- gitrepositories/status
|
||||||
|
- helmrepositories/status
|
||||||
- ocirepositories/status
|
- ocirepositories/status
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta1
|
|
||||||
kind: Provider
|
|
||||||
metadata:
|
|
||||||
name: status-defaults
|
|
||||||
spec:
|
|
||||||
type: generic
|
|
||||||
|
|
|
@ -122,6 +122,24 @@ e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>resourceFilter</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ResourceFilter is a CEL expression expected to return a boolean that is
|
||||||
|
evaluated for each resource referenced in the Resources field when a
|
||||||
|
webhook is received. If the expression returns false then the controller
|
||||||
|
will not request a reconciliation for the resource.
|
||||||
|
When the expression is specified the controller will parse it and mark
|
||||||
|
the object as terminally failed if the expression is invalid or does not
|
||||||
|
return a boolean.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>secretRef</code><br>
|
<code>secretRef</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||||
|
@ -321,6 +339,24 @@ e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>resourceFilter</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ResourceFilter is a CEL expression expected to return a boolean that is
|
||||||
|
evaluated for each resource referenced in the Resources field when a
|
||||||
|
webhook is received. If the expression returns false then the controller
|
||||||
|
will not request a reconciliation for the resource.
|
||||||
|
When the expression is specified the controller will parse it and mark
|
||||||
|
the object as terminally failed if the expression is invalid or does not
|
||||||
|
return a boolean.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>secretRef</code><br>
|
<code>secretRef</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||||
|
|
|
@ -161,7 +161,8 @@ string
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<em>(Optional)</em>
|
||||||
<p>Summary holds a short description of the impact and affected cluster.</p>
|
<p>Summary holds a short description of the impact and affected cluster.
|
||||||
|
Deprecated: Use EventMetadata instead.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -254,6 +255,21 @@ string
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>interval</code><br>
|
||||||
|
<em>
|
||||||
|
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||||
|
Kubernetes meta/v1.Duration
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>Interval at which to reconcile the Provider with its Secret references.
|
||||||
|
Deprecated and not used in v1beta3.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>channel</code><br>
|
<code>channel</code><br>
|
||||||
<em>
|
<em>
|
||||||
string
|
string
|
||||||
|
@ -314,7 +330,25 @@ string
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<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 ‘address’ key with the
|
||||||
|
HTTP/S address of the proxy server. Optional ‘username’ and ‘password’
|
||||||
|
keys can be provided for proxy authentication.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -334,6 +368,21 @@ credentials for this Provider.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>serviceAccountName</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ServiceAccountName is the name of the service account used to
|
||||||
|
authenticate with services from cloud providers. An error is thrown if a
|
||||||
|
static credential is also defined inside the Secret referenced by the
|
||||||
|
SecretRef.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>certSecretRef</code><br>
|
<code>certSecretRef</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||||
|
@ -343,10 +392,13 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<em>(Optional)</em>
|
||||||
<p>CertSecretRef specifies the Secret containing
|
<p>CertSecretRef specifies the Secret containing TLS certificates
|
||||||
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
|
for secure communication.</p>
|
||||||
<p>Note: Support for the <code>caFile</code> key has
|
<p>Supported configurations:
|
||||||
been deprecated.</p>
|
- 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 “caFile”, “certFile”, “keyFile” are supported but deprecated. Use “ca.crt”, “tls.crt”, “tls.key” instead.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -362,6 +414,22 @@ bool
|
||||||
events handling for this Provider.</p>
|
events handling for this Provider.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>commitStatusExpr</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>CommitStatusExpr is a CEL expression that evaluates to a string value
|
||||||
|
that can be used to generate a custom commit status message for use
|
||||||
|
with eligible Provider types (github, gitlab, gitea, bitbucketserver,
|
||||||
|
bitbucket, azuredevops). Supported variables are: event, provider,
|
||||||
|
and alert.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -477,7 +545,8 @@ string
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<em>(Optional)</em>
|
||||||
<p>Summary holds a short description of the impact and affected cluster.</p>
|
<p>Summary holds a short description of the impact and affected cluster.
|
||||||
|
Deprecated: Use EventMetadata instead.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -527,6 +596,21 @@ string
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>interval</code><br>
|
||||||
|
<em>
|
||||||
|
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||||
|
Kubernetes meta/v1.Duration
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>Interval at which to reconcile the Provider with its Secret references.
|
||||||
|
Deprecated and not used in v1beta3.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>channel</code><br>
|
<code>channel</code><br>
|
||||||
<em>
|
<em>
|
||||||
string
|
string
|
||||||
|
@ -587,7 +671,25 @@ string
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<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 ‘address’ key with the
|
||||||
|
HTTP/S address of the proxy server. Optional ‘username’ and ‘password’
|
||||||
|
keys can be provided for proxy authentication.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -607,6 +709,21 @@ credentials for this Provider.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>serviceAccountName</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ServiceAccountName is the name of the service account used to
|
||||||
|
authenticate with services from cloud providers. An error is thrown if a
|
||||||
|
static credential is also defined inside the Secret referenced by the
|
||||||
|
SecretRef.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>certSecretRef</code><br>
|
<code>certSecretRef</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||||
|
@ -616,10 +733,13 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<em>(Optional)</em>
|
||||||
<p>CertSecretRef specifies the Secret containing
|
<p>CertSecretRef specifies the Secret containing TLS certificates
|
||||||
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
|
for secure communication.</p>
|
||||||
<p>Note: Support for the <code>caFile</code> key has
|
<p>Supported configurations:
|
||||||
been deprecated.</p>
|
- 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 “caFile”, “certFile”, “keyFile” are supported but deprecated. Use “ca.crt”, “tls.crt”, “tls.key” instead.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -635,6 +755,22 @@ bool
|
||||||
events handling for this Provider.</p>
|
events handling for this Provider.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>commitStatusExpr</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>CommitStatusExpr is a CEL expression that evaluates to a string value
|
||||||
|
that can be used to generate a custom commit status message for use
|
||||||
|
with eligible Provider types (github, gitlab, gitea, bitbucketserver,
|
||||||
|
bitbucket, azuredevops). Supported variables are: event, provider,
|
||||||
|
and alert.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -134,6 +134,7 @@ handle the incoming webhook request.
|
||||||
| [Nexus](#nexus) | `nexus` | ❌ |
|
| [Nexus](#nexus) | `nexus` | ❌ |
|
||||||
| [Azure Container Registry](#acr) | `acr` | ❌ |
|
| [Azure Container Registry](#acr) | `acr` | ❌ |
|
||||||
| [Google Container Registry](#gcr) | `gcr` | ❌ |
|
| [Google Container Registry](#gcr) | `gcr` | ❌ |
|
||||||
|
| [CDEvents](#cdevents) | `cdevents` | ✅ |
|
||||||
|
|
||||||
#### Generic
|
#### Generic
|
||||||
|
|
||||||
|
@ -613,6 +614,35 @@ spec:
|
||||||
name: webapp
|
name: webapp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### CDEvents
|
||||||
|
|
||||||
|
When a Receiver's `.spec.type` is set to `cdevents`, the controller will respond to
|
||||||
|
a [CDEvent Event Payload](https://cdevents.dev/docs/). It will verify the CDEvent
|
||||||
|
using the [CDEvent Go-SDK](https://github.com/cdevents/sdk-go).
|
||||||
|
|
||||||
|
This type of receiver supports filtering using [Events](#events) by comparing the
|
||||||
|
`type` header to the list of events.
|
||||||
|
|
||||||
|
##### CDEvents example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1
|
||||||
|
kind: Receiver
|
||||||
|
metadata:
|
||||||
|
name: cdevents-receiver
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
type: cdevents
|
||||||
|
events:
|
||||||
|
- "dev.cdevents.change.merged"
|
||||||
|
secretRef:
|
||||||
|
name: receiver-token
|
||||||
|
resources:
|
||||||
|
- kind: GitRepository
|
||||||
|
name: webapp
|
||||||
|
```
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
`.spec.events` is an optional field to specify a list of webhook payload event
|
`.spec.events` is an optional field to specify a list of webhook payload event
|
||||||
|
@ -670,6 +700,75 @@ resources:
|
||||||
**Note:** Cross-namespace references [can be disabled for security
|
**Note:** Cross-namespace references [can be disabled for security
|
||||||
reasons](#disabling-cross-namespace-selectors).
|
reasons](#disabling-cross-namespace-selectors).
|
||||||
|
|
||||||
|
#### Filtering reconciled objects with CEL
|
||||||
|
|
||||||
|
To filter the resources that are reconciled you can use [Common Expression Language (CEL)](https://cel.dev/).
|
||||||
|
|
||||||
|
For example, to trigger `ImageRepositories` on notifications from [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/configure-notifications#examples) you can define the following receiver:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1
|
||||||
|
kind: Receiver
|
||||||
|
metadata:
|
||||||
|
name: gar-receiver
|
||||||
|
namespace: apps
|
||||||
|
spec:
|
||||||
|
type: gcr
|
||||||
|
secretRef:
|
||||||
|
name: flux-gar-token
|
||||||
|
resources:
|
||||||
|
- apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: ImageRepository
|
||||||
|
name: "*"
|
||||||
|
matchLabels:
|
||||||
|
registry: gar
|
||||||
|
```
|
||||||
|
|
||||||
|
This will trigger the reconciliation of all `ImageRepositories` with the label `registry: gar`.
|
||||||
|
|
||||||
|
But if you want to only notify `ImageRepository` resources that are referenced from the incoming hook you can use CEL to filter the resources.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1
|
||||||
|
kind: Receiver
|
||||||
|
metadata:
|
||||||
|
name: gar-receiver
|
||||||
|
namespace: apps
|
||||||
|
spec:
|
||||||
|
type: gcr
|
||||||
|
secretRef:
|
||||||
|
name: flux-gar-token
|
||||||
|
resources:
|
||||||
|
- apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: ImageRepository
|
||||||
|
name: "*"
|
||||||
|
matchLabels:
|
||||||
|
registry: gar
|
||||||
|
resourceFilter: 'req.tag.contains(res.metadata.name)'
|
||||||
|
```
|
||||||
|
|
||||||
|
If the body of the incoming hook looks like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action":"INSERT",
|
||||||
|
"digest":"us-east1-docker.pkg.dev/my-project/my-repo/hello-world@sha256:6ec128e26cd5...",
|
||||||
|
"tag":"us-east1-docker.pkg.dev/my-project/my-repo/hello-world:1.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This simple example would match `ImageRepositories` containing the name `hello-world`.
|
||||||
|
|
||||||
|
If you want to do more complex processing:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
resourceFilter: has(res.metadata.annotations) && req.tag.split('/').last().value().split(":").first().value() == res.metadata.annotations['update-image']
|
||||||
|
```
|
||||||
|
|
||||||
|
This would look for an annotation "update-image" on the resource, and match it to the `hello-world` part of the tag name.
|
||||||
|
|
||||||
|
**Note:** Currently the `resource` value in the CEL expression only provides the object metadata, this means you can access things like `res.metadata.labels`, `res.metadata.annotations` and `res.metadata.name`.
|
||||||
|
|
||||||
### Secret reference
|
### Secret reference
|
||||||
|
|
||||||
`.spec.secretRef.name` is a required field to specify a name reference to a
|
`.spec.secretRef.name` is a required field to specify a name reference to a
|
||||||
|
@ -713,7 +812,7 @@ When the field is set to `false` or removed, it will resume.
|
||||||
On multi-tenant clusters, platform admins can disable cross-namespace
|
On multi-tenant clusters, platform admins can disable cross-namespace
|
||||||
references with the `--no-cross-namespace-refs=true` flag. When this flag is
|
references with the `--no-cross-namespace-refs=true` flag. When this flag is
|
||||||
set, Receivers can only refer to [Resources](#resources) in the same namespace
|
set, Receivers can only refer to [Resources](#resources) in the same namespace
|
||||||
as the [Alert](alerts.md) object, preventing tenants from triggering
|
as the Receiver object, preventing tenants from triggering
|
||||||
reconciliations to another tenant's resources.
|
reconciliations to another tenant's resources.
|
||||||
|
|
||||||
### Public Ingress considerations
|
### Public Ingress considerations
|
||||||
|
|
|
@ -368,7 +368,8 @@ and use `https://api.telegram.org/` as the api url.
|
||||||
--from-literal=address=https://api.telegram.org
|
--from-literal=address=https://api.telegram.org
|
||||||
```
|
```
|
||||||
|
|
||||||
Also note that `spec.channel` can be a unique identifier for the target chat
|
Also note that `spec.channel` can be a unique identifier for the target chat,
|
||||||
|
a unique identifier with the topic identifier for the forum chat
|
||||||
or username of the target channel (in the format @channelusername)
|
or username of the target channel (in the format @channelusername)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -379,7 +380,7 @@ metadata:
|
||||||
namespace: flux-system
|
namespace: flux-system
|
||||||
spec:
|
spec:
|
||||||
type: telegram
|
type: telegram
|
||||||
channel: "@fluxtest" # or "-1557265138" (channel id)
|
channel: "@fluxtest" # or "-1557265138" (channel id) or "-1552289257:1" (forum chat id with topic id)
|
||||||
secretRef:
|
secretRef:
|
||||||
name: telegram-token
|
name: telegram-token
|
||||||
```
|
```
|
||||||
|
|
|
@ -447,7 +447,7 @@ metadata:
|
||||||
stringData:
|
stringData:
|
||||||
token: <DataDog API Key>
|
token: <DataDog API Key>
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta1
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: datadog-info
|
name: datadog-info
|
||||||
|
@ -563,8 +563,9 @@ The Event will be formatted into a message string, with the metadata attached
|
||||||
as a list of key-value pairs.
|
as a list of key-value pairs.
|
||||||
|
|
||||||
The Provider's [Channel](#channel) is used to set the receiver of the message.
|
The Provider's [Channel](#channel) is used to set the receiver of the message.
|
||||||
This can be a unique identifier (`-1234567890`) for the target chat, or
|
This can be a unique identifier (`-1234567890`) for the target chat,
|
||||||
the username (`@username`) of the target channel.
|
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)
|
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
|
||||||
or [TLS certificates](#tls-certificates).
|
or [TLS certificates](#tls-certificates).
|
||||||
|
@ -586,7 +587,7 @@ metadata:
|
||||||
spec:
|
spec:
|
||||||
type: telegram
|
type: telegram
|
||||||
address: https://api.telegram.org
|
address: https://api.telegram.org
|
||||||
channel: "@fluxcd" # or "-1557265138" (channel id)
|
channel: "@fluxcd" # or "-1557265138" (channel id) or "-1552289257:1" (forum chat id with topic id)
|
||||||
secretRef:
|
secretRef:
|
||||||
name: telegram-token
|
name: telegram-token
|
||||||
```
|
```
|
||||||
|
@ -1539,6 +1540,8 @@ kubectl create secret generic bb-server-token --from-literal=token=<token>
|
||||||
The HTTP access token must have `Repositories (Read/Write)` permission for
|
The HTTP access token must have `Repositories (Read/Write)` permission for
|
||||||
the repository specified in `.spec.address`.
|
the repository specified in `.spec.address`.
|
||||||
|
|
||||||
|
**NOTE:** Please provide HTTPS clone URL in the `address` field of this provider. SSH URLs are not supported by this provider type.
|
||||||
|
|
||||||
#### Azure DevOps
|
#### Azure DevOps
|
||||||
|
|
||||||
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
||||||
|
|
|
@ -28,9 +28,13 @@ metadata:
|
||||||
name: slack
|
name: slack
|
||||||
namespace: flux-system
|
namespace: flux-system
|
||||||
spec:
|
spec:
|
||||||
summary: "Cluster addons impacted in us-east-2"
|
|
||||||
providerRef:
|
providerRef:
|
||||||
name: slack-bot
|
name: slack-bot
|
||||||
|
eventMetadata:
|
||||||
|
summary: Cluster addons impacted
|
||||||
|
env: prod
|
||||||
|
cluster: my-cluster
|
||||||
|
region: us-east-2
|
||||||
eventSeverity: error
|
eventSeverity: error
|
||||||
eventSources:
|
eventSources:
|
||||||
- kind: GitRepository
|
- kind: GitRepository
|
||||||
|
@ -51,7 +55,7 @@ In the above example:
|
||||||
all GitRepositories and Kustomizations in the `flux-system` namespace.
|
all GitRepositories and Kustomizations in the `flux-system` namespace.
|
||||||
- When an event with severity `error` is received, the controller posts
|
- When an event with severity `error` is received, the controller posts
|
||||||
a message on Slack channel from `.spec.channel`,
|
a message on Slack channel from `.spec.channel`,
|
||||||
containing the `summary` text and the reconciliation error.
|
containing the metadata and the reconciliation error.
|
||||||
|
|
||||||
You can run this example by saving the manifests into `slack-alerts.yaml`.
|
You can run this example by saving the manifests into `slack-alerts.yaml`.
|
||||||
|
|
||||||
|
@ -78,10 +82,15 @@ An Alert also needs a
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
`.spec.summary` is an optional field to specify a short description of the
|
`.spec.summary` is an optional field to specify a short description of the impact.
|
||||||
impact and affected cluster.
|
|
||||||
|
|
||||||
The summary max length can't be greater than 255 characters.
|
The summary max length can't be greater than 255 characters.
|
||||||
|
|
||||||
|
**Warning:** Support for `.spec.summary` has been deprecated and will be removed in
|
||||||
|
Alert API v1 GA. If you have any Alerts using this field, the controller will log a
|
||||||
|
deprecation warning. Please use [`.spec.eventMetadata.summary`](#event-metadata) or
|
||||||
|
[object annotations](#event-metadata-from-object-annotations) for defining alert
|
||||||
|
summary instead.
|
||||||
|
|
||||||
### Provider reference
|
### Provider reference
|
||||||
|
|
||||||
|
@ -146,10 +155,11 @@ preventing tenants from subscribing to another tenant's events.
|
||||||
### Event metadata
|
### Event metadata
|
||||||
|
|
||||||
`.spec.eventMetadata` is an optional field for adding metadata to events dispatched by
|
`.spec.eventMetadata` is an optional field for adding metadata to events dispatched by
|
||||||
the controller. This can be used for enhancing the context of the event. If a field
|
the controller. This can be used for enhancing the context of the event, e.g. with
|
||||||
would override one already present on the original event as generated by the emitter,
|
cluster-level information.
|
||||||
then the override doesn't happen, i.e. the original value is preserved, and an info
|
|
||||||
log is printed.
|
For all the event metadata sources and their precedence order, please refer to
|
||||||
|
[Event metadata from object annotations](#event-metadata-from-object-annotations).
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
@ -168,9 +178,68 @@ spec:
|
||||||
inclusionList:
|
inclusionList:
|
||||||
- ".*succeeded.*"
|
- ".*succeeded.*"
|
||||||
eventMetadata:
|
eventMetadata:
|
||||||
app.kubernetes.io/env: "production"
|
env: production
|
||||||
app.kubernetes.io/cluster: "my-cluster"
|
cluster: my-cluster
|
||||||
app.kubernetes.io/region: "us-east-1"
|
region: us-east-1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event metadata from object annotations
|
||||||
|
|
||||||
|
Event metadata has four sources. They are listed below in order of precedence,
|
||||||
|
from lowest to highest:
|
||||||
|
|
||||||
|
1. User-defined metadata on Flux objects, set with the `event.toolkit.fluxcd.io/`
|
||||||
|
prefix in the keys of the object's `.metadata.annotations`.
|
||||||
|
2. User-defined metadata on the Alert object, set with [`.spec.eventMetadata`](#event-metadata).
|
||||||
|
3. User-defined summary on the Alert object, set with [`.spec.summary`](#summary) (deprecated, see docs).
|
||||||
|
4. Controller-defined metadata, set with the `<controller group>.toolkit.fluxcd.io/`
|
||||||
|
prefix in the metadata keys of the event payload.
|
||||||
|
|
||||||
|
If there are any metadata key conflicts between the sources, the higher
|
||||||
|
precedence source will override the lower precedence source, and a warning
|
||||||
|
log and Kubernetes event will be emitted.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
|
kind: Alert
|
||||||
|
metadata:
|
||||||
|
name: <name>
|
||||||
|
spec:
|
||||||
|
eventSources:
|
||||||
|
- kind: HelmRelease
|
||||||
|
name: '*'
|
||||||
|
eventMetadata:
|
||||||
|
env: production
|
||||||
|
cluster: my-cluster
|
||||||
|
region: us-east-1
|
||||||
|
---
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: my-webapp
|
||||||
|
annotations:
|
||||||
|
event.toolkit.fluxcd.io/summary: "my-webapp impacted. Playbook: <URL to playbook>"
|
||||||
|
event.toolkit.fluxcd.io/deploymentID: e076e315-5a48-41c3-81c8-8d8bdee7d74d
|
||||||
|
spec:
|
||||||
|
... # fields omitted for brevity
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the event payload dispatched by the controller will look like this
|
||||||
|
(most fields omitted for highlighting the metadata):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"env": "production",
|
||||||
|
"cluster": "my-cluster",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"summary": "my-webapp impacted. Playbook: <URL to playbook>",
|
||||||
|
"deploymentID": "e076e315-5a48-41c3-81c8-8d8bdee7d74d"
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event severity
|
### Event severity
|
||||||
|
|
|
@ -11,7 +11,7 @@ install or upgrade [Flagger](https://github.com/fluxcd/flagger).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: slack-bot
|
name: slack-bot
|
||||||
|
@ -23,7 +23,7 @@ spec:
|
||||||
secretRef:
|
secretRef:
|
||||||
name: slack-bot-token
|
name: slack-bot-token
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: slack
|
name: slack
|
||||||
|
@ -109,7 +109,9 @@ The supported alerting providers are:
|
||||||
| [WebEx](#webex) | `webex` |
|
| [WebEx](#webex) | `webex` |
|
||||||
| [NATS](#nats) | `nats` |
|
| [NATS](#nats) | `nats` |
|
||||||
|
|
||||||
The supported providers for [Git commit status updates](#git-commit-status-updates) are:
|
#### Types supporting Git commit status updates
|
||||||
|
|
||||||
|
The providers supporting [Git commit status updates](#git-commit-status-updates) are:
|
||||||
|
|
||||||
| Provider | Type |
|
| Provider | Type |
|
||||||
|--------------------------------------------------------------|-------------------|
|
|--------------------------------------------------------------|-------------------|
|
||||||
|
@ -133,7 +135,7 @@ for example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"involvedObject": {
|
"involvedObject": {
|
||||||
"apiVersion": "kustomize.toolkit.fluxcd.io/v1beta2",
|
"apiVersion": "kustomize.toolkit.fluxcd.io/v1",
|
||||||
"kind": "Kustomization",
|
"kind": "Kustomization",
|
||||||
"name": "webapp",
|
"name": "webapp",
|
||||||
"namespace": "apps",
|
"namespace": "apps",
|
||||||
|
@ -282,7 +284,7 @@ field](https://api.slack.com/methods/chat.postMessage#arg_username) to the
|
||||||
payload, defaulting to the name of the reporting controller.
|
payload, defaulting to the name of the reporting controller.
|
||||||
|
|
||||||
This Provider type supports the configuration of a [proxy URL](#https-proxy)
|
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
|
###### Slack example
|
||||||
|
|
||||||
|
@ -299,7 +301,7 @@ by adding the integration to each channel.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: slack
|
name: slack
|
||||||
|
@ -328,7 +330,7 @@ and a `slack` Provider with a [Secret reference](#address-example).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: slack
|
name: slack
|
||||||
|
@ -350,27 +352,30 @@ stringData:
|
||||||
##### Microsoft Teams
|
##### Microsoft Teams
|
||||||
|
|
||||||
When `.spec.type` is set to `msteams`, the controller will send a payload for
|
When `.spec.type` is set to `msteams`, the controller will send a payload for
|
||||||
an [Event](events.md#event-structure) to the provided Microsoft Teams [Address](#address).
|
an [Event](events.md#event-structure) to the provided [Address](#address). The address
|
||||||
|
may be a [Microsoft Teams Incoming Webhook Workflow](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498), or
|
||||||
|
the deprecated [Office 365 Connector](https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/).
|
||||||
|
|
||||||
The Event will be formatted into a Microsoft Teams
|
**Note:** If the Address host contains the suffix `.webhook.office.com`, the controller will imply that
|
||||||
[connector message](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#example-of-connector-message),
|
the backend is the deprecated Office 365 Connector and is expecting the Event in the [connector message](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#example-of-connector-message) format. Otherwise, the controller will format the Event as a [Microsoft Adaptive Card](https://adaptivecards.io/explorer/) message.
|
||||||
with the metadata attached as facts, and the involved object as summary.
|
|
||||||
|
In both cases the Event metadata is attached as facts, and the involved object as a summary/title.
|
||||||
The severity of the Event is used to set the color of the message.
|
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)
|
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
|
configuring a [Channel](#channel). This can be configured during the
|
||||||
creation of the incoming webhook in Microsoft Teams.
|
creation of the Incoming Webhook Workflow in Microsoft Teams.
|
||||||
|
|
||||||
###### Microsoft Teams example
|
###### Microsoft Teams example
|
||||||
|
|
||||||
To configure a Provider for Microsoft Teams, create a Secret with [the
|
To configure a Provider for Microsoft Teams, create a Secret with [the
|
||||||
`address`](#address-example) set to the [webhook URL](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook#create-incoming-webhooks-1),
|
`address`](#address-example) set to the [webhook URL](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498),
|
||||||
and a `msteams` Provider with a [Secret reference](#address-example).
|
and an `msteams` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: msteams
|
name: msteams
|
||||||
|
@ -386,7 +391,7 @@ metadata:
|
||||||
name: msteams-webhook
|
name: msteams-webhook
|
||||||
namespace: default
|
namespace: default
|
||||||
stringData:
|
stringData:
|
||||||
address: "https://xxx.webhook.office.com/..."
|
address: https://prod-xxx.yyy.logic.azure.com:443/workflows/zzz/triggers/manual/paths/invoke?...
|
||||||
```
|
```
|
||||||
|
|
||||||
##### DataDog
|
##### DataDog
|
||||||
|
@ -398,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).
|
API endpoint of the provided DataDog [Address](#address).
|
||||||
|
|
||||||
This Provider type supports the configuration of a [proxy URL](#https-proxy)
|
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.
|
The metadata of the Event is included in the DataDog event as extra tags.
|
||||||
|
|
||||||
|
@ -410,7 +415,7 @@ set to a [DataDog API key](https://docs.datadoghq.com/account_management/api-app
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: datadog
|
name: datadog
|
||||||
|
@ -429,7 +434,7 @@ metadata:
|
||||||
stringData:
|
stringData:
|
||||||
token: <DataDog API Key>
|
token: <DataDog API Key>
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta1
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: datadog-info
|
name: datadog-info
|
||||||
|
@ -454,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).
|
`/slack` endpoint of the provided Discord [Address](#address).
|
||||||
|
|
||||||
This Provider type supports the configuration of a [proxy URL](#https-proxy)
|
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
|
configuring a [Channel](#channel). This can be configured [during the creation
|
||||||
of the address](https://discord.com/developers/docs/resources/webhook#create-webhook)
|
of the address](https://discord.com/developers/docs/resources/webhook#create-webhook)
|
||||||
|
|
||||||
|
@ -466,7 +471,7 @@ and a `discord` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: discord
|
name: discord
|
||||||
|
@ -502,7 +507,7 @@ The Provider's [Channel](#channel) is used to set the `environment` on the
|
||||||
Sentry client.
|
Sentry client.
|
||||||
|
|
||||||
This Provider type supports the configuration of
|
This Provider type supports the configuration of
|
||||||
[TLS certificates](#tls-certificates).
|
[certificate secret reference](#certificate-secret-reference).
|
||||||
|
|
||||||
###### Sentry example
|
###### Sentry example
|
||||||
|
|
||||||
|
@ -512,7 +517,7 @@ and a `sentry` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: sentry
|
name: sentry
|
||||||
|
@ -539,36 +544,37 @@ stringData:
|
||||||
##### Telegram
|
##### Telegram
|
||||||
|
|
||||||
When `.spec.type` is set to `telegram`, the controller will send a payload for
|
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
|
The Event will be formatted into a message string, with the metadata attached
|
||||||
as a list of key-value pairs.
|
as a list of key-value pairs.
|
||||||
|
|
||||||
The Provider's [Channel](#channel) is used to set the receiver of the message.
|
The Provider's [Channel](#channel) is used to set the receiver of the message.
|
||||||
This can be a unique identifier (`-1234567890`) for the target chat, or
|
This can be a unique identifier (`-1234567890`) for the target chat,
|
||||||
the username (`@username`) of the target channel.
|
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)
|
This Provider type does not support the configuration of a [certificate secret reference](#certificate-secret-reference).
|
||||||
or [TLS certificates](#tls-certificates).
|
|
||||||
|
**Note:** The Telegram provider always ignores the `address` field and uses the default
|
||||||
|
Telegram Bot API endpoint (`https://api.telegram.org`).
|
||||||
|
|
||||||
###### Telegram example
|
###### Telegram example
|
||||||
|
|
||||||
To configure a Provider for Telegram, create a Secret with [the `token`](#token-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),
|
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
|
and a `telegram` Provider with a [Secret reference](#secret-reference).
|
||||||
`address` set to `https://api.telegram.org`.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: telegram
|
name: telegram
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
type: telegram
|
type: telegram
|
||||||
address: https://api.telegram.org
|
channel: "@fluxcd" # or "-1557265138" (channel id) or "-1552289257:1" (forum chat id with topic id)
|
||||||
channel: "@fluxcd" # or "-1557265138" (channel id)
|
|
||||||
secretRef:
|
secretRef:
|
||||||
name: telegram-token
|
name: telegram-token
|
||||||
```
|
```
|
||||||
|
@ -595,7 +601,7 @@ obtained from [the Matrix endpoint](https://matrix.org/docs/guides/client-server
|
||||||
and a `matrix` Provider with a [Secret reference](#secret-reference).
|
and a `matrix` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: matrix
|
name: matrix
|
||||||
|
@ -617,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.
|
with the metadata written to the message string.
|
||||||
|
|
||||||
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
|
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
|
###### Lark example
|
||||||
|
|
||||||
|
@ -626,7 +632,7 @@ obtained from [adding a bot to a group](https://open.larksuite.com/document/uAjL
|
||||||
and a `lark` Provider with a [Secret reference](#secret-reference).
|
and a `lark` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: lark
|
name: lark
|
||||||
|
@ -654,7 +660,7 @@ The Event will be formatted into a [Slack message](#slack) and send as a
|
||||||
payload the provided Rocket [Address](#address).
|
payload the provided Rocket [Address](#address).
|
||||||
|
|
||||||
This Provider type does support the configuration of a [proxy URL](#https-proxy)
|
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
|
###### Rocket example
|
||||||
|
|
||||||
|
@ -664,7 +670,7 @@ and a `rocket` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: rocket
|
name: rocket
|
||||||
|
@ -694,7 +700,7 @@ and a `googlechat` Provider with a [Secret reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: google
|
name: google
|
||||||
|
@ -736,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).
|
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)
|
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
|
###### Google Pub/Sub with JSON Credentials and Custom Headers Example
|
||||||
|
|
||||||
|
@ -748,7 +754,7 @@ YAML string-to-string dictionary, and a `googlepubsub` Provider with the associa
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: googlepubsub-provider
|
name: googlepubsub-provider
|
||||||
|
@ -782,7 +788,7 @@ with the metadata added to the [`details` field](https://docs.opsgenie.com/docs/
|
||||||
as a list of key-value pairs.
|
as a list of key-value pairs.
|
||||||
|
|
||||||
This Provider type does support the configuration of a [proxy URL](#https-proxy)
|
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
|
###### Opsgenie example
|
||||||
|
|
||||||
|
@ -793,7 +799,7 @@ and a `opsgenie` Provider with a [Secret reference](#secret-reference) and the
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: opsgenie
|
name: opsgenie
|
||||||
|
@ -825,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.
|
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)
|
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.
|
The [Channel](#channel) is used to set the routing key to send the event to the appropriate integration.
|
||||||
|
|
||||||
|
@ -842,7 +848,7 @@ When adding an integration for a service on PagerDuty, it is recommended to use
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: pagerduty
|
name: pagerduty
|
||||||
|
@ -857,7 +863,7 @@ only those sources you want to trigger an incident for that service. For example
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: my-service-pagerduty
|
name: my-service-pagerduty
|
||||||
|
@ -878,9 +884,10 @@ an [Event](events.md#event-structure) to the provided Prometheus Alertmanager
|
||||||
[Address](#address).
|
[Address](#address).
|
||||||
|
|
||||||
The Event will be formatted into a `firing` [Prometheus Alertmanager
|
The Event will be formatted into a `firing` [Prometheus Alertmanager
|
||||||
alert](https://prometheus.io/docs/alerting/latest/notifications/#alert),
|
alert](https://prometheus.io/docs/alerting/latest/notifications/#alert), with
|
||||||
with the metadata added to the `labels` fields, and the `message` (and optional
|
the metadata added to the `labels` fields, and the `message` (and optional
|
||||||
`.metadata.summary`) added as annotations.
|
`.metadata.summary`) added as annotations. Event timestamp will be used to set
|
||||||
|
alert start time (`.StartsAt`).
|
||||||
|
|
||||||
In addition to the metadata from the Event, the following labels will be added:
|
In addition to the metadata from the Event, the following labels will be added:
|
||||||
|
|
||||||
|
@ -888,26 +895,43 @@ In addition to the metadata from the Event, the following labels will be added:
|
||||||
|-----------|------------------------------------------------------------------------------------------------------|
|
|-----------|------------------------------------------------------------------------------------------------------|
|
||||||
| alertname | The string Flux followed by the Kind and the reason for the event e.g `FluxKustomizationProgressing` |
|
| alertname | The string Flux followed by the Kind and the reason for the event e.g `FluxKustomizationProgressing` |
|
||||||
| severity | The severity of the event (`error` or `info`) |
|
| severity | The severity of the event (`error` or `info`) |
|
||||||
| timestamp | The timestamp of the event |
|
|
||||||
| reason | The machine readable reason for the objects transition into the current status |
|
| reason | The machine readable reason for the objects transition into the current status |
|
||||||
| kind | The kind of the involved object associated with the event |
|
| kind | The kind of the involved object associated with the event |
|
||||||
| name | The name of the involved object associated with the event |
|
| name | The name of the involved object associated with the event |
|
||||||
| namespace | The namespace of the involved object associated with the event |
|
| namespace | The namespace of the involved object associated with the event |
|
||||||
|
|
||||||
|
Note that due to the way other Flux controllers currently emit events, there's
|
||||||
|
no way for notification-controller to figure out the time the event ends to set
|
||||||
|
`.EndsAt` (a reasonable estimate being double the reconciliation interval of the
|
||||||
|
resource involved) that doesn't involve a Kubernetes API roundtrip. A
|
||||||
|
possible workaround could be setting
|
||||||
|
[`global.resolve_timeout`][am_config_global] to an interval large enough for
|
||||||
|
events to reoccur:
|
||||||
|
|
||||||
|
[am_config_global]: https://prometheus.io/docs/alerting/latest/configuration/#file-layout-and-global-settings
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
global:
|
||||||
|
resolve_timeout: 1h
|
||||||
|
```
|
||||||
|
|
||||||
This Provider type does support the configuration of a [proxy URL](#https-proxy)
|
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
|
###### Prometheus Alertmanager example
|
||||||
|
|
||||||
To configure a Provider for Prometheus Alertmanager, create a Secret with [the
|
To configure a Provider for Prometheus Alertmanager, authentication can be done using either Basic Authentication or a Bearer Token.
|
||||||
`address`](#address-example) set to the Prometheus Alertmanager [HTTP API
|
Both methods are supported, but using authentication is optional based on your setup.
|
||||||
|
|
||||||
|
Basic Authentication:
|
||||||
|
Create a Secret with [the `address`](#address-example) set to the Prometheus Alertmanager [HTTP API
|
||||||
URL](https://prometheus.io/docs/alerting/latest/https/#http-traffic)
|
URL](https://prometheus.io/docs/alerting/latest/https/#http-traffic)
|
||||||
including Basic Auth credentials, and a `alertmanager` Provider with a [Secret
|
including Basic Auth credentials, and an `alertmanager` Provider with a [Secret
|
||||||
reference](#secret-reference).
|
reference](#secret-reference).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: alertmanager
|
name: alertmanager
|
||||||
|
@ -923,7 +947,33 @@ metadata:
|
||||||
name: alertmanager-address
|
name: alertmanager-address
|
||||||
namespace: default
|
namespace: default
|
||||||
stringData:
|
stringData:
|
||||||
address: https://username:password@<alertmanager-url>/api/v2/alerts/"
|
address: https://<username>:<password>@<alertmanager-hostport>/api/v2/alerts/
|
||||||
|
```
|
||||||
|
Bearer Token Authentication:
|
||||||
|
Create a Secret with [the `token`](#token-example), and an `alertmanager` Provider with a [Secret
|
||||||
|
reference](#secret-reference) and the Prometheus Alertmanager [HTTP API
|
||||||
|
URL](https://prometheus.io/docs/alerting/latest/https/#http-traffic) set directly in the `.spec.address` field.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
|
kind: Provider
|
||||||
|
metadata:
|
||||||
|
name: alertmanager
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
type: alertmanager
|
||||||
|
address: https://<alertmanager-hostport>/api/v2/alerts/
|
||||||
|
secretRef:
|
||||||
|
name: alertmanager-token
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: alertmanager-token
|
||||||
|
namespace: default
|
||||||
|
stringData:
|
||||||
|
token: <token>
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Webex
|
##### Webex
|
||||||
|
@ -938,7 +988,7 @@ The [Channel](#channel) is used to set the ID of the room to send the message
|
||||||
to.
|
to.
|
||||||
|
|
||||||
This Provider type does support the configuration of a [proxy URL](#https-proxy)
|
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
|
###### Webex example
|
||||||
|
|
||||||
|
@ -953,7 +1003,7 @@ controller.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: webex
|
name: webex
|
||||||
|
@ -1047,7 +1097,7 @@ credentials for the provider API.
|
||||||
The Kubernetes secret can have any of the following keys:
|
The Kubernetes secret can have any of the following keys:
|
||||||
|
|
||||||
- `address` - overrides `.spec.address`
|
- `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
|
- `token` - used for authentication
|
||||||
- `username` - overrides `.spec.username`
|
- `username` - overrides `.spec.username`
|
||||||
- `headers` - HTTP headers values included in the POST request
|
- `headers` - HTTP headers values included in the POST request
|
||||||
|
@ -1105,7 +1155,7 @@ stringData:
|
||||||
#### Proxy auth example
|
#### Proxy auth example
|
||||||
|
|
||||||
Some networks need to use an authenticated proxy to access external services.
|
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
|
```yaml
|
||||||
---
|
---
|
||||||
|
@ -1114,15 +1164,56 @@ kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: my-provider-proxy
|
name: my-provider-proxy
|
||||||
namespace: default
|
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:
|
stringData:
|
||||||
proxy: "http://username:password@proxy_url:proxy_port"
|
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
|
`.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.
|
Secret in the same namespace as the Provider, containing TLS certificates for
|
||||||
The secret must be of type `kubernetes.io/tls` or `Opaque`.
|
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
|
#### Example
|
||||||
|
|
||||||
|
@ -1131,7 +1222,7 @@ using a self-signed TLS certificate, set the `ca.crt` like so:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: my-webhook
|
name: my-webhook
|
||||||
|
@ -1160,10 +1251,25 @@ the controller will log a deprecation warning.
|
||||||
### HTTP/S proxy
|
### HTTP/S proxy
|
||||||
|
|
||||||
`.spec.proxy` is an optional field to specify an HTTP/S proxy address.
|
`.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
|
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`.
|
recommended to use `.spec.proxySecretRef` instead of `.spec.proxy`.
|
||||||
When the referenced Secret contains a `proxy` key, the `.spec.proxy` value is ignored.
|
When `.spec.proxySecretRef` is specified, both `.spec.proxy` and the `proxy` key from
|
||||||
|
`.spec.secretRef` are ignored.
|
||||||
|
|
||||||
|
### Timeout
|
||||||
|
|
||||||
|
`.spec.timeout` is an optional field to specify the timeout for the
|
||||||
|
HTTP/S request sent to the provider endpoint.
|
||||||
|
The value must be in a
|
||||||
|
[Go recognized duration string format](https://pkg.go.dev/time#ParseDuration),
|
||||||
|
e.g. `5m30s` for a timeout of five minutes and thirty seconds.
|
||||||
|
|
||||||
### Suspend
|
### Suspend
|
||||||
|
|
||||||
|
@ -1201,7 +1307,7 @@ kubectl create secret generic grafana-token \
|
||||||
Create a provider of type `grafana` and reference the `grafana-token` secret:
|
Create a provider of type `grafana` and reference the `grafana-token` secret:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: grafana
|
name: grafana
|
||||||
|
@ -1213,6 +1319,11 @@ spec:
|
||||||
name: grafana-token
|
name: grafana-token
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Besides the tag `flux` and the tag containing the reporting controller (e.g. `source-controller`),
|
||||||
|
the event metadata is also included as tags of the form `${metadataKey}: ${metadataValue}`, and
|
||||||
|
the tags `kind: ${event.InvolvedObject.Kind}`, `name: ${event.InvolvedObject.Name}` and
|
||||||
|
`namespace: ${event.InvolvedObject.Namespace}` are also included.
|
||||||
|
|
||||||
### GitHub dispatch
|
### GitHub dispatch
|
||||||
|
|
||||||
The `githubdispatch` provider generates GitHub events of type
|
The `githubdispatch` provider generates GitHub events of type
|
||||||
|
@ -1228,7 +1339,7 @@ The request includes the `event_type` and `client_payload` fields:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: github-dispatch
|
name: github-dispatch
|
||||||
|
@ -1237,14 +1348,16 @@ spec:
|
||||||
type: githubdispatch
|
type: githubdispatch
|
||||||
address: https://github.com/stefanprodan/podinfo
|
address: https://github.com/stefanprodan/podinfo
|
||||||
secretRef:
|
secretRef:
|
||||||
name: api-token
|
name: auth-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
The `address` is the address of your repository where you want to send webhooks to trigger GitHub workflows.
|
The `address` is the address of your repository where you want to send webhooks to trigger GitHub workflows.
|
||||||
|
|
||||||
GitHub uses personal access tokens for authentication with its API:
|
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](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)
|
#### GitHub personal access token
|
||||||
|
|
||||||
The provider requires a secret in the same format, with the personal access token as the value for the token key:
|
The provider requires a secret in the same format, with the personal access token as the value for the token key:
|
||||||
|
|
||||||
|
@ -1253,12 +1366,19 @@ The provider requires a secret in the same format, with the personal access toke
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: api-token
|
name: auth-secret
|
||||||
namespace: default
|
namespace: default
|
||||||
data:
|
stringData:
|
||||||
token: <personal-access-tokens>
|
token: <personal-access-tokens>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### GitHub App
|
||||||
|
|
||||||
|
To use Github App authentication, make sure the GitHub App is registered and
|
||||||
|
installed with the necessary permissions and the github app secret is created as
|
||||||
|
described
|
||||||
|
[here](https://fluxcd.io/flux/components/source/gitrepositories/#github).
|
||||||
|
|
||||||
#### Setting up a GitHub workflow
|
#### Setting up a GitHub workflow
|
||||||
|
|
||||||
To trigger a GitHub Actions workflow when a Flux Kustomization finishes reconciling,
|
To trigger a GitHub Actions workflow when a Flux Kustomization finishes reconciling,
|
||||||
|
@ -1293,7 +1413,7 @@ You can then create a flux kustomization resource for the app to have unique `ev
|
||||||
The kustomization manifest for app1/staging:
|
The kustomization manifest for app1/staging:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
apiVersion: kustomize.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
metadata:
|
metadata:
|
||||||
name: app1
|
name: app1
|
||||||
|
@ -1306,7 +1426,7 @@ You would also like to know from the notification which cluster is being used fo
|
||||||
You can add the `spec.summary` field to the Flux alert configuration to mention the relevant cluster:
|
You can add the `spec.summary` field to the Flux alert configuration to mention the relevant cluster:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: github-dispatch
|
name: github-dispatch
|
||||||
|
@ -1321,7 +1441,7 @@ spec:
|
||||||
name: 'podinfo'
|
name: 'podinfo'
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can the trigger tests in the GitHub workflow for app1 in a staging cluster when
|
Now you can trigger the tests in the GitHub workflow for app1 in a staging cluster when
|
||||||
the app1 resources defined in `./app1/staging/` are reconciled by Flux:
|
the app1 resources defined in `./app1/staging/` are reconciled by Flux:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -1340,13 +1460,62 @@ jobs:
|
||||||
|
|
||||||
### Azure Event Hub
|
### Azure Event Hub
|
||||||
|
|
||||||
The Azure Event Hub supports two authentication methods, [JWT](https://docs.microsoft.com/en-us/azure/event-hubs/authenticate-application)
|
The Azure Event Hub provider supports the following authentication methods,
|
||||||
and [SAS](https://docs.microsoft.com/en-us/azure/event-hubs/authorize-access-shared-access-signature) based.
|
- [Managed
|
||||||
|
Identity](https://learn.microsoft.com/en-us/azure/event-hubs/authenticate-managed-identity)
|
||||||
|
- [JWT](https://docs.microsoft.com/en-us/azure/event-hubs/authenticate-application)
|
||||||
|
- [SAS](https://docs.microsoft.com/en-us/azure/event-hubs/authorize-access-shared-access-signature)
|
||||||
|
based.
|
||||||
|
|
||||||
|
#### Managed Identity
|
||||||
|
|
||||||
|
Managed identity authentication can be setup using Azure Workload identity.
|
||||||
|
|
||||||
|
##### Pre-requisites
|
||||||
|
|
||||||
|
- Ensure Workload Identity is properly [set up on your
|
||||||
|
cluster](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster#create-an-aks-cluster).
|
||||||
|
|
||||||
|
##### Configure workload identity
|
||||||
|
|
||||||
|
- Create a managed identity to access Azure Event Hub.
|
||||||
|
- Grant the managed identity the necessary permissions to send events to Azure
|
||||||
|
Event hub as described
|
||||||
|
[here](https://learn.microsoft.com/en-us/azure/event-hubs/authenticate-managed-identity#to-assign-azure-roles-using-the-azure-portal).
|
||||||
|
|
||||||
|
- Establish a federated identity credential between the managed identity and the
|
||||||
|
service account to be used for authentication. Ensure the federated credential
|
||||||
|
uses the correct namespace and name of the service account. For more details,
|
||||||
|
please refer to this
|
||||||
|
[guide](https://azure.github.io/azure-workload-identity/docs/quick-start.html#6-establish-federated-identity-credential-between-the-identity-and-the-service-account-issuer--subject).
|
||||||
|
|
||||||
|
##### Single tenant approach
|
||||||
|
|
||||||
|
This approach uses the notification-controller service account for setting up
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
- In the default installation, the notification-controller service account is
|
||||||
|
located in the `flux-system` namespace with name `notification-controller`.
|
||||||
|
|
||||||
|
- Configure workload identity with notification-controller as described in the
|
||||||
|
docs [here](/flux/installation/configuration/workload-identity/).
|
||||||
|
|
||||||
|
##### Multi-tenant approach
|
||||||
|
|
||||||
|
For multi-tenant clusters, set `.spec.serviceAccountName` of the provider to
|
||||||
|
the service account to be used for authentication. Ensure that the service
|
||||||
|
account has the
|
||||||
|
[annotations](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview?tabs=dotnet#service-account-annotations)
|
||||||
|
for the client-id and tenant-id of the managed identity.
|
||||||
|
|
||||||
|
For a complete guide on how to set up authentication for an Azure Event Hub,
|
||||||
|
see the integration [docs](/flux/integrations/azure/).
|
||||||
|
|
||||||
#### JWT based auth
|
#### JWT based auth
|
||||||
|
|
||||||
In JWT we use 3 input values. Channel, token and address.
|
In JWT we use 3 input values. Channel, token and address. We perform the
|
||||||
We perform the following translation to match we the data we need to communicate with Azure Event Hub.
|
following translation to match we the data we need to communicate with Azure
|
||||||
|
Event Hub.
|
||||||
|
|
||||||
- channel = Azure Event Hub namespace
|
- channel = Azure Event Hub namespace
|
||||||
- address = Azure Event Hub name
|
- address = Azure Event Hub name
|
||||||
|
@ -1354,7 +1523,7 @@ We perform the following translation to match we the data we need to communicate
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: azure
|
name: azure
|
||||||
|
@ -1376,11 +1545,13 @@ stringData:
|
||||||
```
|
```
|
||||||
|
|
||||||
The controller doesn't take any responsibility for the JWT token to be updated.
|
The controller doesn't take any responsibility for the JWT token to be updated.
|
||||||
You need to use a secondary tool to make sure that the token in the secret is renewed.
|
You need to use a secondary tool to make sure that the token in the secret is
|
||||||
|
renewed.
|
||||||
|
|
||||||
If you want to make a easy test assuming that you have setup a Azure Enterprise application and you called it
|
If you want to make a easy test assuming that you have setup a Azure Enterprise
|
||||||
event-hub you can follow most of the bellow commands. You will need to provide the `client_secret` that you got
|
application and you called it event-hub you can follow most of the bellow
|
||||||
when generating the Azure Enterprise Application.
|
commands. You will need to provide the `client_secret` that you got when
|
||||||
|
generating the Azure Enterprise Application.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
export AZURE_CLIENT=$(az ad app list --filter "startswith(displayName,'event-hub')" --query '[].appId' |jq -r '.[0]')
|
export AZURE_CLIENT=$(az ad app list --filter "startswith(displayName,'event-hub')" --query '[].appId' |jq -r '.[0]')
|
||||||
|
@ -1403,7 +1574,7 @@ When using SAS auth, we only use the `address` field in the secret.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: azure
|
name: azure
|
||||||
|
@ -1423,8 +1594,8 @@ stringData:
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming that you have created the Azure event hub and namespace you should be
|
Assuming that you have created the Azure event hub and namespace you should be
|
||||||
able to use a similar command to get your connection string. This will give
|
able to use a similar command to get your connection string. This will give you
|
||||||
you the default Root SAS, which is NOT supposed to be used in production.
|
the default Root SAS, which is NOT supposed to be used in production.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
az eventhubs namespace authorization-rule keys list --resource-group <rg-name> --namespace-name <namespace-name> --name RootManageSharedAccessKey -o tsv --query primaryConnectionString
|
az eventhubs namespace authorization-rule keys list --resource-group <rg-name> --namespace-name <namespace-name> --name RootManageSharedAccessKey -o tsv --query primaryConnectionString
|
||||||
|
@ -1450,7 +1621,7 @@ The following is an example of how to update the Git commit status for the GitHu
|
||||||
Flux was bootstrapped with `flux bootstrap github --owner=my-gh-org --repository=my-gh-repo`.
|
Flux was bootstrapped with `flux bootstrap github --owner=my-gh-org --repository=my-gh-repo`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: github-status
|
name: github-status
|
||||||
|
@ -1461,7 +1632,7 @@ spec:
|
||||||
secretRef:
|
secretRef:
|
||||||
name: github-token
|
name: github-token
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: github-status
|
name: github-status
|
||||||
|
@ -1476,10 +1647,16 @@ spec:
|
||||||
|
|
||||||
#### GitHub
|
#### GitHub
|
||||||
|
|
||||||
When `.spec.type` is set to `github`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `github`, the referenced secret can contain a
|
||||||
[GitHub personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
|
personal access token OR github app details.
|
||||||
|
|
||||||
The token must have permissions to update the commit status for the GitHub repository specified in `.spec.address`.
|
##### Personal Access Token
|
||||||
|
|
||||||
|
To use personal access tokens, the secret must contain a key called `token` with
|
||||||
|
the value set to a [GitHub personal access
|
||||||
|
token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
|
||||||
|
The token must have permissions to update the commit status for the GitHub
|
||||||
|
repository specified in `.spec.address`.
|
||||||
|
|
||||||
You can create the secret with `kubectl` like this:
|
You can create the secret with `kubectl` like this:
|
||||||
|
|
||||||
|
@ -1487,12 +1664,21 @@ You can create the secret with `kubectl` like this:
|
||||||
kubectl create secret generic github-token --from-literal=token=<GITHUB-TOKEN>
|
kubectl create secret generic github-token --from-literal=token=<GITHUB-TOKEN>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### GitHub App
|
||||||
|
|
||||||
|
To use [Github App
|
||||||
|
authentication](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation),
|
||||||
|
make sure the GitHub App is registered and installed with the necessary
|
||||||
|
permissions to update the commit status and the github app secret is created as
|
||||||
|
described
|
||||||
|
[here](https://fluxcd.io/flux/components/source/gitrepositories/#github).
|
||||||
|
|
||||||
#### GitLab
|
#### GitLab
|
||||||
|
|
||||||
When `.spec.type` is set to `gitlab`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `gitlab`, the referenced secret must contain a key called `token` with the value set to a
|
||||||
[GitLab personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html).
|
[GitLab personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). If available group and project access tokens can also be used.
|
||||||
|
|
||||||
The token must have permissions to update the commit status for the GitLab repository specified in `.spec.address`.
|
The token must have permissions to update the commit status for the GitLab repository specified in `.spec.address` (grant it the `api` scope).
|
||||||
|
|
||||||
You can create the secret with `kubectl` like this:
|
You can create the secret with `kubectl` like this:
|
||||||
|
|
||||||
|
@ -1500,6 +1686,10 @@ You can create the secret with `kubectl` like this:
|
||||||
kubectl create secret generic gitlab-token --from-literal=token=<GITLAB-TOKEN>
|
kubectl create secret generic gitlab-token --from-literal=token=<GITLAB-TOKEN>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For gitlab.com and current self-hosted gitlab installations that support only the Gitlab v4 API you need to use the project ID in the address instead of the project name.
|
||||||
|
Use an address like `https://gitlab.com/1234` (with `1234` being the project ID).
|
||||||
|
You can find out the ID by opening the project in the browser and clicking on the three dot button in the top right corner. The menu that opens shows the project ID.
|
||||||
|
|
||||||
#### Gitea
|
#### Gitea
|
||||||
|
|
||||||
When `.spec.type` is set to `gitea`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `gitea`, the referenced secret must contain a key called `token` with the value set to a
|
||||||
|
@ -1559,6 +1749,8 @@ kubectl create secret generic bb-server-token --from-literal=token=<token>
|
||||||
The HTTP access token must have `Repositories (Read/Write)` permission for
|
The HTTP access token must have `Repositories (Read/Write)` permission for
|
||||||
the repository specified in `.spec.address`.
|
the repository specified in `.spec.address`.
|
||||||
|
|
||||||
|
**NOTE:** Please provide HTTPS clone URL in the `address` field of this provider. SSH URLs are not supported by this provider type.
|
||||||
|
|
||||||
#### Azure DevOps
|
#### Azure DevOps
|
||||||
|
|
||||||
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
When `.spec.type` is set to `azuredevops`, the referenced secret must contain a key called `token` with the value set to a
|
||||||
|
@ -1571,3 +1763,30 @@ You can create the secret with `kubectl` like this:
|
||||||
```shell
|
```shell
|
||||||
kubectl create secret generic azuredevops-token --from-literal=token=<AZURE-TOKEN>
|
kubectl create secret generic azuredevops-token --from-literal=token=<AZURE-TOKEN>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Custom Commit Status Messages
|
||||||
|
|
||||||
|
Git providers supporting commit status updates can use a CEL expression to build a custom commit status message by setting the optional field `spec.commitStatusExpr`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||||
|
kind: Provider
|
||||||
|
metadata:
|
||||||
|
name: github-status
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
type: github
|
||||||
|
address: https://github.com/my-gh-org/my-gh-repo
|
||||||
|
secretRef:
|
||||||
|
name: github-token
|
||||||
|
commitStatusExpr: "(event.involvedObject.kind + '/' + event.involvedObject.name + '/' + event.metadata.foo + '/' + provider.metadata.uid.split('-').first().value()).lowerAscii()"
|
||||||
|
```
|
||||||
|
|
||||||
|
The CEL expression can access the following variables:
|
||||||
|
- `event`: The Flux event object containing metadata about the reconciliation
|
||||||
|
- `provider`: The Provider object
|
||||||
|
- `alert`: The Alert object
|
||||||
|
|
||||||
|
If the `spec.commitStatusExpr` field is not specified, the notification-controller will use a default commit status message based on the involved object kind, name, and a truncated provider UID to generate a commit status (e.g. `kustomization/gitops-system/0c9c2e41`).
|
||||||
|
|
||||||
|
A useful tool for building and testing CEL expressions is the [CEL Playground](https://playcel.undistro.io/).
|
||||||
|
|
281
go.mod
281
go.mod
|
@ -1,173 +1,218 @@
|
||||||
module github.com/fluxcd/notification-controller
|
module github.com/fluxcd/notification-controller
|
||||||
|
|
||||||
go 1.20
|
go 1.24.0
|
||||||
|
|
||||||
replace github.com/fluxcd/notification-controller/api => ./api
|
replace github.com/fluxcd/notification-controller/api => ./api
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/pubsub v1.33.0
|
cloud.google.com/go/pubsub v1.49.0
|
||||||
code.gitea.io/sdk/gitea v0.17.0
|
code.gitea.io/sdk/gitea v0.21.0
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
|
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-amqp-common-go/v4 v4.2.0
|
||||||
github.com/Azure/azure-event-hubs-go/v3 v3.6.1
|
github.com/Azure/azure-event-hubs-go/v3 v3.6.2
|
||||||
github.com/DataDog/datadog-api-client-go/v2 v2.19.0
|
github.com/DataDog/datadog-api-client-go/v2 v2.42.0
|
||||||
github.com/PagerDuty/go-pagerduty v1.7.0
|
github.com/PagerDuty/go-pagerduty v1.8.0
|
||||||
github.com/containrrr/shoutrrr v0.8.0
|
github.com/cdevents/sdk-go v0.4.1
|
||||||
github.com/fluxcd/cli-utils v0.36.0-flux.1
|
github.com/chainguard-dev/git-urls v1.0.2
|
||||||
github.com/fluxcd/notification-controller/api v1.1.0
|
github.com/elazarl/goproxy v1.7.2
|
||||||
github.com/fluxcd/pkg/apis/event v0.6.0
|
github.com/fluxcd/cli-utils v0.36.0-flux.14
|
||||||
github.com/fluxcd/pkg/apis/meta v1.2.0
|
github.com/fluxcd/notification-controller/api v1.6.0
|
||||||
github.com/fluxcd/pkg/git v0.16.0
|
github.com/fluxcd/pkg/apis/event v0.18.0
|
||||||
github.com/fluxcd/pkg/masktoken v0.2.0
|
github.com/fluxcd/pkg/apis/meta v1.17.0
|
||||||
github.com/fluxcd/pkg/runtime v0.43.0
|
github.com/fluxcd/pkg/auth v0.21.0
|
||||||
github.com/fluxcd/pkg/ssa v0.35.0
|
github.com/fluxcd/pkg/cache v0.10.0
|
||||||
github.com/getsentry/sentry-go v0.25.0
|
github.com/fluxcd/pkg/git v0.34.0
|
||||||
github.com/go-logr/logr v1.3.0
|
github.com/fluxcd/pkg/masktoken v0.7.0
|
||||||
github.com/google/go-github/v53 v53.2.0
|
github.com/fluxcd/pkg/runtime v0.69.0
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
github.com/fluxcd/pkg/ssa v0.51.0
|
||||||
github.com/ktrysmt/go-bitbucket v0.9.72
|
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.8
|
||||||
|
github.com/ktrysmt/go-bitbucket v0.9.86
|
||||||
github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1
|
github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1
|
||||||
github.com/nats-io/nats.go v1.31.0
|
github.com/nats-io/nats.go v1.43.0
|
||||||
github.com/onsi/gomega v1.30.0
|
github.com/onsi/gomega v1.37.0
|
||||||
github.com/sethvargo/go-limiter v0.7.2
|
github.com/sethvargo/go-limiter v1.0.0
|
||||||
github.com/slok/go-http-metrics v0.11.0
|
github.com/slok/go-http-metrics v0.13.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.6
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/whilp/git-urls v1.0.0
|
gitlab.com/gitlab-org/api/client-go v0.134.0
|
||||||
github.com/xanzy/go-gitlab v0.94.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/oauth2 v0.15.0
|
golang.org/x/text v0.27.0
|
||||||
golang.org/x/text v0.14.0
|
google.golang.org/api v0.241.0
|
||||||
google.golang.org/api v0.152.0
|
k8s.io/api v0.33.2
|
||||||
k8s.io/api v0.28.4
|
k8s.io/apimachinery v0.33.2
|
||||||
k8s.io/apimachinery v0.28.4
|
k8s.io/client-go v0.33.2
|
||||||
k8s.io/client-go v0.28.4
|
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||||
k8s.io/utils v0.0.0-20231127182322-b307cd553661
|
sigs.k8s.io/controller-runtime v0.21.0
|
||||||
sigs.k8s.io/controller-runtime v0.16.3
|
sigs.k8s.io/yaml v1.5.0
|
||||||
sigs.k8s.io/yaml v1.4.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fix CVE-2022-28948
|
// Fix CVE-2022-28948
|
||||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.110.10 // indirect
|
cel.dev/expr v0.23.1 // indirect
|
||||||
cloud.google.com/go/compute v1.23.3 // indirect
|
cloud.google.com/go v0.120.0 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/auth v0.16.2 // indirect
|
||||||
cloud.google.com/go/iam v1.1.5 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||||
github.com/Azure/go-amqp v1.0.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-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||||
github.com/DataDog/zstd v1.5.2 // indirect
|
github.com/DataDog/zstd v1.5.2 // indirect
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20231012073058-a7379d079e0e // 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/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/blang/semver/v4 v4.0.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/chai2010/gettext-go v1.0.2 // indirect
|
||||||
github.com/cloudflare/circl v1.3.6 // indirect
|
github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||||
github.com/devigned/tab v0.1.1 // indirect
|
github.com/devigned/tab v0.1.1 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/docker/cli v28.2.2+incompatible // indirect
|
||||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||||
github.com/evanphx/json-patch/v5 v5.7.0 // 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/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||||
github.com/fatih/color v1.15.0 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
|
github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fluxcd/pkg/apis/kustomize v1.11.0 // indirect
|
||||||
github.com/go-errors/errors v1.4.2 // 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-fed/httpsig v1.1.0 // indirect
|
||||||
github.com/go-logr/zapr v1.2.4 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-logr/zapr v1.3.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||||
github.com/go-openapi/swag v0.22.3 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // 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
|
||||||
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/gnostic-models v0.7.0 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.7.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/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
|
||||||
github.com/google/uuid v1.4.0 // indirect
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.15 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/jpillora/backoff v1.0.0 // indirect
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.1 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/moby/spdystream v0.5.0 // indirect
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.6 // indirect
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||||
|
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/package-url/packageurl-go v0.1.1 // indirect
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/client_golang v1.17.0 // indirect
|
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.45.0 // indirect
|
github.com/prometheus/common v0.65.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.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/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/spf13/cobra v1.8.0 // indirect
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||||
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/spf13/cobra v1.9.1 // indirect
|
||||||
|
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xlab/treeprint v1.2.0 // indirect
|
github.com/xlab/treeprint v1.2.0 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||||
|
go.opentelemetry.io/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/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.25.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/crypto v0.16.0 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
golang.org/x/term v0.15.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
golang.org/x/term v0.33.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||||
google.golang.org/grpc v1.59.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||||
gopkg.in/evanphx/json-patch.v5 v5.6.0 // 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/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.28.4 // indirect
|
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
||||||
k8s.io/cli-runtime v0.28.4 // indirect
|
k8s.io/cli-runtime v0.33.2 // indirect
|
||||||
k8s.io/component-base v0.28.4 // indirect
|
k8s.io/component-base v0.33.2 // indirect
|
||||||
k8s.io/klog/v2 v2.100.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e // indirect
|
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 // indirect
|
||||||
k8s.io/kubectl v0.28.4 // indirect
|
k8s.io/kubectl v0.33.2 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||||
sigs.k8s.io/kustomize/api v0.15.0 // indirect
|
sigs.k8s.io/kustomize/api v0.20.0 // indirect
|
||||||
sigs.k8s.io/kustomize/kyaml v0.15.0 // indirect
|
sigs.k8s.io/kustomize/kyaml v0.20.0 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,7 +19,6 @@ package controller
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
kuberecorder "k8s.io/client-go/tools/record"
|
kuberecorder "k8s.io/client-go/tools/record"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||||
|
@ -28,12 +27,16 @@ import (
|
||||||
|
|
||||||
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||||
|
"github.com/fluxcd/pkg/cache"
|
||||||
"github.com/fluxcd/pkg/runtime/patch"
|
"github.com/fluxcd/pkg/runtime/patch"
|
||||||
|
|
||||||
|
"github.com/fluxcd/notification-controller/internal/notifier"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=notification.toolkit.fluxcd.io,resources=providers,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=notification.toolkit.fluxcd.io,resources=providers,verbs=get;list;watch;create;update;patch;delete
|
||||||
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
||||||
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
|
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
|
||||||
|
// +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create
|
||||||
|
|
||||||
// ProviderReconciler reconciles a Provider object to migrate it to static
|
// ProviderReconciler reconciles a Provider object to migrate it to static
|
||||||
// Provider.
|
// Provider.
|
||||||
|
@ -41,40 +44,21 @@ type ProviderReconciler struct {
|
||||||
client.Client
|
client.Client
|
||||||
kuberecorder.EventRecorder
|
kuberecorder.EventRecorder
|
||||||
|
|
||||||
ControllerName string
|
TokenCache *cache.TokenCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&apiv1beta3.Provider{}, builder.WithPredicates(finalizerPredicate{})).
|
For(&apiv1beta3.Provider{}, builder.WithPredicates(providerPredicate{})).
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
||||||
log := ctrl.LoggerFrom(ctx)
|
|
||||||
|
|
||||||
obj := &apiv1beta3.Provider{}
|
obj := &apiv1beta3.Provider{}
|
||||||
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Early return if no migration is needed.
|
|
||||||
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examine if the object is under deletion.
|
|
||||||
var delete bool
|
|
||||||
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
|
||||||
delete = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if it's suspend and not being deleted.
|
|
||||||
if obj.Spec.Suspend && !delete {
|
|
||||||
log.Info("reconciliation is suspended for this object")
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
patcher, err := patch.NewHelper(obj, r.Client)
|
patcher, err := patch.NewHelper(obj, r.Client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
@ -86,11 +70,29 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Remove the notification-controller finalizer.
|
// Examine if the object is under deletion.
|
||||||
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
|
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||||
|
return r.reconcileDelete(obj)
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("removed finalizer from Provider to migrate to static Provider")
|
// Add finalizer if it doesn't exist.
|
||||||
r.Event(obj, corev1.EventTypeNormal, "Migration", "removed finalizer from Provider to migrate to static Provider")
|
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
|
||||||
|
controllerutil.AddFinalizer(obj, apiv1.NotificationFinalizer)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reconcileDelete handles the deletion of the object.
|
||||||
|
// It cleans up the caches and removes the finalizer.
|
||||||
|
func (r *ProviderReconciler) reconcileDelete(obj *apiv1beta3.Provider) (ctrl.Result, error) {
|
||||||
|
// Remove our finalizer from the list
|
||||||
|
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
|
||||||
|
|
||||||
|
// Cleanup caches.
|
||||||
|
r.TokenCache.DeleteEventsForObject(apiv1beta3.ProviderKind,
|
||||||
|
obj.GetName(), obj.GetNamespace(), notifier.OperationPost)
|
||||||
|
|
||||||
|
// Stop reconciliation as the object is being deleted
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -52,61 +52,29 @@ func TestProviderReconciler(t *testing.T) {
|
||||||
}
|
}
|
||||||
providerKey := client.ObjectKeyFromObject(provider)
|
providerKey := client.ObjectKeyFromObject(provider)
|
||||||
|
|
||||||
// Remove finalizer at create.
|
// Create without finalizer.
|
||||||
|
|
||||||
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, "foo.bar", apiv1.NotificationFinalizer)
|
|
||||||
provider.Spec = apiv1beta3.ProviderSpec{
|
provider.Spec = apiv1beta3.ProviderSpec{
|
||||||
Type: "slack",
|
Type: "generic",
|
||||||
}
|
}
|
||||||
g.Expect(testEnv.Create(ctx, provider)).ToNot(HaveOccurred())
|
g.Expect(testEnv.Create(ctx, provider)).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Should eventually have finalizer.
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
_ = testEnv.Get(ctx, providerKey, provider)
|
_ = testEnv.Get(ctx, providerKey, provider)
|
||||||
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
||||||
}, timeout, time.Second).Should(BeTrue())
|
}, timeout, time.Second).Should(BeTrue())
|
||||||
|
|
||||||
// Remove finalizer at update.
|
// Remove finalizer.
|
||||||
|
|
||||||
patchHelper, err := patch.NewHelper(provider, testEnv.Client)
|
patchHelper, err := patch.NewHelper(provider, testEnv.Client)
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, apiv1.NotificationFinalizer)
|
controllerutil.RemoveFinalizer(provider, apiv1.NotificationFinalizer)
|
||||||
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Should eventually have finalizer again.
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
_ = testEnv.Get(ctx, providerKey, provider)
|
_ = testEnv.Get(ctx, providerKey, provider)
|
||||||
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
|
||||||
}, timeout, time.Second).Should(BeTrue())
|
|
||||||
|
|
||||||
// Remove finalizer at delete.
|
|
||||||
|
|
||||||
patchHelper, err = patch.NewHelper(provider, testEnv.Client)
|
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
// Suspend the provider to prevent finalizer from getting removed.
|
|
||||||
// Ensure only flux finalizer is set to allow the object to be garbage
|
|
||||||
// collected at the end.
|
|
||||||
// NOTE: Suspending and updating finalizers are done separately here as
|
|
||||||
// doing them in a single patch results in flaky test where the finalizer
|
|
||||||
// update doesn't gets registered with the kube-apiserver, resulting in
|
|
||||||
// timeout waiting for finalizer to appear on the object below.
|
|
||||||
provider.Spec.Suspend = true
|
|
||||||
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
|
||||||
g.Eventually(func() bool {
|
|
||||||
_ = k8sClient.Get(ctx, providerKey, provider)
|
|
||||||
return provider.Spec.Suspend == true
|
|
||||||
}, timeout).Should(BeTrue())
|
|
||||||
|
|
||||||
patchHelper, err = patch.NewHelper(provider, testEnv.Client)
|
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
// Add finalizer and verify that finalizer exists on the object using a live
|
|
||||||
// client.
|
|
||||||
provider.ObjectMeta.Finalizers = []string{apiv1.NotificationFinalizer}
|
|
||||||
g.Expect(patchHelper.Patch(ctx, provider)).ToNot(HaveOccurred())
|
|
||||||
g.Eventually(func() bool {
|
|
||||||
_ = k8sClient.Get(ctx, providerKey, provider)
|
|
||||||
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
||||||
}, timeout).Should(BeTrue())
|
}, timeout, time.Second).Should(BeTrue())
|
||||||
|
|
||||||
// Delete the object and verify.
|
// Delete the object and verify.
|
||||||
g.Expect(testEnv.Delete(ctx, provider)).ToNot(HaveOccurred())
|
g.Expect(testEnv.Delete(ctx, provider)).ToNot(HaveOccurred())
|
||||||
|
@ -117,3 +85,85 @@ func TestProviderReconciler(t *testing.T) {
|
||||||
return false
|
return false
|
||||||
}, timeout).Should(BeTrue())
|
}, timeout).Should(BeTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProviderReconciler_APIServerValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
providerType string
|
||||||
|
commitStatusExpr string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "github provider types can create providers with commitStatusExpr",
|
||||||
|
providerType: "github",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gitlab provider types can create providers with commitStatusExpr",
|
||||||
|
providerType: "gitlab",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gitea provider types can create providers with commitStatusExpr",
|
||||||
|
providerType: "gitea",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bitbucketserver provider types can create providers with commitStatusExpr",
|
||||||
|
providerType: "bitbucketserver",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bitbucket provider types can create providers with commitStatusExpr",
|
||||||
|
providerType: "bitbucket",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "azuredevops provider types can create providers with commitStatusExpr",
|
||||||
|
providerType: "azuredevops",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported provider types cannot create providers with commitStatusExpr",
|
||||||
|
providerType: "slack",
|
||||||
|
commitStatusExpr: "event.metadata.namespace + '/' + event.metadata.name + '/' + provider.metadata.uid",
|
||||||
|
err: "spec.commitStatusExpr is only supported for the 'github', 'gitlab', 'gitea', 'bitbucketserver', 'bitbucket', 'azuredevops' provider types",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "github provider types can create providers without commitStatusExpr",
|
||||||
|
providerType: "github",
|
||||||
|
commitStatusExpr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
obj := &apiv1beta3.Provider{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "provider-reconcile-",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: apiv1beta3.ProviderSpec{
|
||||||
|
Type: tt.providerType,
|
||||||
|
CommitStatusExpr: tt.commitStatusExpr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEnv.Create(ctx, obj)
|
||||||
|
if err == nil {
|
||||||
|
defer func() {
|
||||||
|
err := testEnv.Delete(ctx, obj)
|
||||||
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.err != "" {
|
||||||
|
g.Expect(err.Error()).To(ContainSubstring(tt.err))
|
||||||
|
} else {
|
||||||
|
g.Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
Copyright 2025 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||||
|
|
||||||
|
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
|
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// providerPredicate implements predicate functions for the Provider API.
|
||||||
|
type providerPredicate struct{}
|
||||||
|
|
||||||
|
func (providerPredicate) Create(e event.CreateEvent) bool {
|
||||||
|
return !controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (providerPredicate) Update(e event.UpdateEvent) bool {
|
||||||
|
if e.ObjectNew == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !controllerutil.ContainsFinalizer(e.ObjectNew, apiv1.NotificationFinalizer) ||
|
||||||
|
!e.ObjectNew.(*apiv1beta3.Provider).ObjectMeta.DeletionTimestamp.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (providerPredicate) Delete(e event.DeleteEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (providerPredicate) Generic(e event.GenericEvent) bool {
|
||||||
|
return !controllerutil.ContainsFinalizer(e.Object, apiv1.NotificationFinalizer)
|
||||||
|
}
|
|
@ -26,13 +26,14 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
kuberecorder "k8s.io/client-go/tools/record"
|
kuberecorder "k8s.io/client-go/tools/record"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
"github.com/fluxcd/pkg/runtime/conditions"
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
@ -54,7 +55,7 @@ type ReceiverReconciler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceiverReconcilerOptions struct {
|
type ReceiverReconcilerOptions struct {
|
||||||
RateLimiter ratelimiter.RateLimiter
|
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReceiverReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *ReceiverReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
|
@ -112,9 +113,7 @@ func (r *ReceiverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record Prometheus metrics.
|
// Record Prometheus metrics.
|
||||||
r.Metrics.RecordReadiness(ctx, obj)
|
|
||||||
r.Metrics.RecordDuration(ctx, obj, reconcileStart)
|
r.Metrics.RecordDuration(ctx, obj, reconcileStart)
|
||||||
r.Metrics.RecordSuspend(ctx, obj, obj.Spec.Suspend)
|
|
||||||
|
|
||||||
// Emit warning event if the reconciliation failed.
|
// Emit warning event if the reconciliation failed.
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
|
@ -157,25 +156,40 @@ func (r *ReceiverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
|
||||||
// reconcile steps through the actual reconciliation tasks for the object, it returns early on the first step that
|
// reconcile steps through the actual reconciliation tasks for the object, it returns early on the first step that
|
||||||
// produces an error.
|
// produces an error.
|
||||||
func (r *ReceiverReconciler) reconcile(ctx context.Context, obj *apiv1.Receiver) (ctrl.Result, error) {
|
func (r *ReceiverReconciler) reconcile(ctx context.Context, obj *apiv1.Receiver) (ctrl.Result, error) {
|
||||||
|
log := ctrl.LoggerFrom(ctx)
|
||||||
|
|
||||||
|
if filter := obj.Spec.ResourceFilter; filter != "" {
|
||||||
|
if err := server.ValidateResourceFilter(filter); err != nil {
|
||||||
|
const msg = "Reconciliation failed terminally due to configuration error"
|
||||||
|
errMsg := fmt.Sprintf("%s: %v", msg, err)
|
||||||
|
conditions.MarkFalse(obj, meta.ReadyCondition, meta.InvalidCELExpressionReason, "%s", errMsg)
|
||||||
|
conditions.MarkStalled(obj, meta.InvalidCELExpressionReason, "%s", errMsg)
|
||||||
|
obj.Status.ObservedGeneration = obj.Generation
|
||||||
|
log.Error(err, msg)
|
||||||
|
r.Event(obj, corev1.EventTypeWarning, meta.InvalidCELExpressionReason, errMsg)
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mark the resource as under reconciliation.
|
// Mark the resource as under reconciliation.
|
||||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "Reconciliation in progress")
|
conditions.MarkReconciling(obj, meta.ProgressingReason, "Reconciliation in progress")
|
||||||
|
|
||||||
token, err := r.token(ctx, obj)
|
token, err := r.token(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conditions.MarkFalse(obj, meta.ReadyCondition, apiv1.TokenNotFoundReason, err.Error())
|
conditions.MarkFalse(obj, meta.ReadyCondition, apiv1.TokenNotFoundReason, "%s", err)
|
||||||
obj.Status.WebhookPath = ""
|
obj.Status.WebhookPath = ""
|
||||||
return ctrl.Result{Requeue: true}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
webhookPath := obj.GetWebhookPath(token)
|
webhookPath := obj.GetWebhookPath(token)
|
||||||
msg := fmt.Sprintf("Receiver initialized for path: %s", webhookPath)
|
msg := fmt.Sprintf("Receiver initialized for path: %s", webhookPath)
|
||||||
|
|
||||||
// Mark the resource as ready and set the webhook path in status.
|
// Mark the resource as ready and set the webhook path in status.
|
||||||
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, msg)
|
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "%s", msg)
|
||||||
|
|
||||||
if obj.Status.WebhookPath != webhookPath {
|
if obj.Status.WebhookPath != webhookPath {
|
||||||
obj.Status.WebhookPath = webhookPath
|
obj.Status.WebhookPath = webhookPath
|
||||||
ctrl.LoggerFrom(ctx).Info(msg)
|
log.Info(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctrl.Result{RequeueAfter: obj.GetInterval()}, nil
|
return ctrl.Result{RequeueAfter: obj.GetInterval()}, nil
|
||||||
|
|
|
@ -144,6 +144,45 @@ func TestReceiverReconciler_Reconcile(t *testing.T) {
|
||||||
g.Expect(resultR.Spec.Interval.Duration).To(BeIdenticalTo(10 * time.Minute))
|
g.Expect(resultR.Spec.Interval.Duration).To(BeIdenticalTo(10 * time.Minute))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("fails with invalid CEL resource filter", func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(receiver), resultR)).To(Succeed())
|
||||||
|
|
||||||
|
// Incomplete CEL expression
|
||||||
|
patch := []byte(`{"spec":{"resourceFilter":"has(res.metadata.annotations"}}`)
|
||||||
|
g.Expect(k8sClient.Patch(context.Background(), resultR, client.RawPatch(types.MergePatchType, patch))).To(Succeed())
|
||||||
|
|
||||||
|
g.Eventually(func() bool {
|
||||||
|
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(receiver), resultR)
|
||||||
|
return !conditions.IsReady(resultR)
|
||||||
|
}, timeout, time.Second).Should(BeTrue())
|
||||||
|
|
||||||
|
g.Expect(resultR.Status.ObservedGeneration).To(Equal(resultR.Generation))
|
||||||
|
|
||||||
|
g.Expect(conditions.GetReason(resultR, meta.ReadyCondition)).To(BeIdenticalTo(meta.InvalidCELExpressionReason))
|
||||||
|
g.Expect(conditions.GetMessage(resultR, meta.ReadyCondition)).To(ContainSubstring("annotations"))
|
||||||
|
|
||||||
|
g.Expect(conditions.Has(resultR, meta.StalledCondition)).To(BeTrue())
|
||||||
|
g.Expect(conditions.GetReason(resultR, meta.StalledCondition)).To(BeIdenticalTo(meta.InvalidCELExpressionReason))
|
||||||
|
g.Expect(conditions.GetObservedGeneration(resultR, meta.StalledCondition)).To(BeIdenticalTo(resultR.Generation))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("recovers when the CEL expression is valid", func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
// Incomplete CEL expression
|
||||||
|
patch := []byte(`{"spec":{"resourceFilter":"has(res.metadata.annotations)"}}`)
|
||||||
|
g.Expect(k8sClient.Patch(context.Background(), resultR, client.RawPatch(types.MergePatchType, patch))).To(Succeed())
|
||||||
|
|
||||||
|
g.Eventually(func() bool {
|
||||||
|
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(receiver), resultR)
|
||||||
|
return conditions.IsReady(resultR)
|
||||||
|
}, timeout, time.Second).Should(BeTrue())
|
||||||
|
|
||||||
|
g.Expect(conditions.GetObservedGeneration(resultR, meta.ReadyCondition)).To(BeIdenticalTo(resultR.Generation))
|
||||||
|
g.Expect(resultR.Status.ObservedGeneration).To(BeIdenticalTo(resultR.Generation))
|
||||||
|
g.Expect(conditions.Has(resultR, meta.ReconcilingCondition)).To(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("fails with secret not found error", func(t *testing.T) {
|
t.Run("fails with secret not found error", func(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
@ -239,7 +278,7 @@ func TestReceiverReconciler_EventHandler(t *testing.T) {
|
||||||
|
|
||||||
// Use the client from the manager as the server handler needs to list objects from the cache
|
// Use the client from the manager as the server handler needs to list objects from the cache
|
||||||
// which the "live" k8s client does not have access to.
|
// which the "live" k8s client does not have access to.
|
||||||
receiverServer := server.NewReceiverServer("127.0.0.1:56788", logf.Log, testEnv.GetClient())
|
receiverServer := server.NewReceiverServer("127.0.0.1:56788", logf.Log, testEnv.GetClient(), true, true)
|
||||||
receiverMdlw := middleware.New(middleware.Config{
|
receiverMdlw := middleware.New(middleware.Config{
|
||||||
Recorder: prommetrics.NewRecorder(prommetrics.Config{
|
Recorder: prommetrics.NewRecorder(prommetrics.Config{
|
||||||
Prefix: "gotk_receiver",
|
Prefix: "gotk_receiver",
|
||||||
|
|
|
@ -40,6 +40,7 @@ import (
|
||||||
"github.com/fluxcd/pkg/runtime/metrics"
|
"github.com/fluxcd/pkg/runtime/metrics"
|
||||||
"github.com/fluxcd/pkg/runtime/testenv"
|
"github.com/fluxcd/pkg/runtime/testenv"
|
||||||
"github.com/fluxcd/pkg/ssa"
|
"github.com/fluxcd/pkg/ssa"
|
||||||
|
ssautil "github.com/fluxcd/pkg/ssa/utils"
|
||||||
|
|
||||||
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
apiv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
|
apiv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
|
@ -81,9 +82,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := (&ProviderReconciler{
|
if err := (&ProviderReconciler{
|
||||||
Client: testEnv,
|
Client: testEnv,
|
||||||
ControllerName: controllerName,
|
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
||||||
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
|
||||||
}).SetupWithManager(testEnv); err != nil {
|
}).SetupWithManager(testEnv); err != nil {
|
||||||
panic(fmt.Sprintf("Failed to start ProviderReconciler: %v", err))
|
panic(fmt.Sprintf("Failed to start ProviderReconciler: %v", err))
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ func readManifest(manifest, namespace string) (*unstructured.Unstructured, error
|
||||||
}
|
}
|
||||||
yml := fmt.Sprintf(string(data), namespace)
|
yml := fmt.Sprintf(string(data), namespace)
|
||||||
|
|
||||||
object, err := ssa.ReadObject(strings.NewReader(yml))
|
object, err := ssautil.ReadObject(strings.NewReader(yml))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,10 @@ limitations under the License.
|
||||||
// and their default states.
|
// and their default states.
|
||||||
package features
|
package features
|
||||||
|
|
||||||
import feathelper "github.com/fluxcd/pkg/runtime/features"
|
import (
|
||||||
|
"github.com/fluxcd/pkg/auth"
|
||||||
|
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
|
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
|
||||||
|
@ -35,6 +38,10 @@ var features = map[string]bool{
|
||||||
CacheSecretsAndConfigMaps: false,
|
CacheSecretsAndConfigMaps: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
auth.SetFeatureGates(features)
|
||||||
|
}
|
||||||
|
|
||||||
// FeatureGates contains a list of all supported feature gates and
|
// FeatureGates contains a list of all supported feature gates and
|
||||||
// their default values.
|
// their default values.
|
||||||
func FeatureGates() map[string]bool {
|
func FeatureGates() map[string]bool {
|
||||||
|
|
|
@ -18,10 +18,13 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
@ -29,27 +32,59 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Alertmanager struct {
|
type Alertmanager struct {
|
||||||
URL string
|
URL string
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
CertPool *x509.CertPool
|
TLSConfig *tls.Config
|
||||||
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertManagerAlert struct {
|
type AlertManagerAlert struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Labels map[string]string `json:"labels"`
|
Labels map[string]string `json:"labels"`
|
||||||
Annotations map[string]string `json:"annotations"`
|
Annotations map[string]string `json:"annotations"`
|
||||||
|
|
||||||
|
StartsAt AlertManagerTime `json:"startsAt"`
|
||||||
|
EndsAt AlertManagerTime `json:"endsAt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlertmanager(hookURL string, proxyURL string, certPool *x509.CertPool) (*Alertmanager, error) {
|
// AlertManagerTime takes care of representing time.Time as RFC3339.
|
||||||
|
// See https://prometheus.io/docs/alerting/0.27/clients/
|
||||||
|
type AlertManagerTime time.Time
|
||||||
|
|
||||||
|
func (a AlertManagerTime) String() string {
|
||||||
|
return time.Time(a).Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AlertManagerTime) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(a.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AlertManagerTime) UnmarshalJSON(jsonRepr []byte) error {
|
||||||
|
var serializedTime string
|
||||||
|
if err := json.Unmarshal(jsonRepr, &serializedTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, serializedTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = AlertManagerTime(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAlertmanager(hookURL string, proxyURL string, tlsConfig *tls.Config, token string) (*Alertmanager, error) {
|
||||||
_, err := url.ParseRequestURI(hookURL)
|
_, err := url.ParseRequestURI(hookURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Alertmanager URL %s: '%w'", hookURL, err)
|
return nil, fmt.Errorf("invalid Alertmanager URL %s: '%w'", hookURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Alertmanager{
|
return &Alertmanager{
|
||||||
URL: hookURL,
|
URL: hookURL,
|
||||||
ProxyURL: proxyURL,
|
ProxyURL: proxyURL,
|
||||||
CertPool: certPool,
|
Token: token,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,25 +110,49 @@ func (s *Alertmanager) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
labels["alertname"] = "Flux" + event.InvolvedObject.Kind + cases.Title(language.Und).String(event.Reason)
|
labels["alertname"] = "Flux" + event.InvolvedObject.Kind + cases.Title(language.Und).String(event.Reason)
|
||||||
labels["severity"] = event.Severity
|
labels["severity"] = event.Severity
|
||||||
labels["reason"] = event.Reason
|
labels["reason"] = event.Reason
|
||||||
labels["timestamp"] = event.Timestamp.String()
|
|
||||||
|
|
||||||
labels["kind"] = event.InvolvedObject.Kind
|
labels["kind"] = event.InvolvedObject.Kind
|
||||||
labels["name"] = event.InvolvedObject.Name
|
labels["name"] = event.InvolvedObject.Name
|
||||||
labels["namespace"] = event.InvolvedObject.Namespace
|
labels["namespace"] = event.InvolvedObject.Namespace
|
||||||
labels["reportingcontroller"] = event.ReportingController
|
labels["reportingcontroller"] = event.ReportingController
|
||||||
|
|
||||||
|
// The best reasonable `endsAt` value would be multiplying
|
||||||
|
// InvolvedObject's reconciliation interval by 2 then adding that to
|
||||||
|
// `startsAt` (the next successful reconciliation would make sure
|
||||||
|
// the alert is cleared after the timeout). Due to
|
||||||
|
// event.InvolvedObject only containing the object reference (namely
|
||||||
|
// the GVKNN) best we can do is leave it unset up to Alertmanager's
|
||||||
|
// default `resolve_timeout`.
|
||||||
|
//
|
||||||
|
// https://prometheus.io/docs/alerting/0.27/configuration/#file-layout-and-global-settings
|
||||||
|
startsAt := AlertManagerTime(event.Timestamp.Time)
|
||||||
|
|
||||||
payload := []AlertManagerAlert{
|
payload := []AlertManagerAlert{
|
||||||
{
|
{
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
Annotations: annotations,
|
Annotations: annotations,
|
||||||
Status: "firing",
|
Status: "firing",
|
||||||
|
|
||||||
|
StartsAt: startsAt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := postMessage(ctx, s.URL, s.ProxyURL, s.CertPool, payload)
|
var opts []postOption
|
||||||
|
if s.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(s.ProxyURL))
|
||||||
|
}
|
||||||
|
if s.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(s.TLSConfig))
|
||||||
|
}
|
||||||
|
if s.Token != "" {
|
||||||
|
opts = append(opts, withRequestModifier(func(request *retryablehttp.Request) {
|
||||||
|
request.Header.Add("Authorization", "Bearer "+s.Token)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -43,10 +43,10 @@ func Fuzz_AlertManager(f *testing.F) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
var tlsConfig tls.Config
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = 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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func TestAlertmanager_Post(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
alertmanager, err := NewAlertmanager(ts.URL, "", nil)
|
alertmanager, err := NewAlertmanager(ts.URL, "", nil, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = alertmanager.Post(context.TODO(), testEvent())
|
err = alertmanager.Post(context.TODO(), testEvent())
|
||||||
|
|
|
@ -40,14 +40,14 @@ type azureDevOpsClient interface {
|
||||||
|
|
||||||
// AzureDevOps is an Azure DevOps notifier.
|
// AzureDevOps is an Azure DevOps notifier.
|
||||||
type AzureDevOps struct {
|
type AzureDevOps struct {
|
||||||
Project string
|
Project string
|
||||||
Repo string
|
Repo string
|
||||||
ProviderUID string
|
CommitStatus string
|
||||||
Client azureDevOpsClient
|
Client azureDevOpsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureDevOps creates and returns a new AzureDevOps notifier.
|
// NewAzureDevOps creates and returns a new AzureDevOps notifier.
|
||||||
func NewAzureDevOps(providerUID string, addr string, token string, certPool *x509.CertPool) (*AzureDevOps, error) {
|
func NewAzureDevOps(commitStatus string, addr string, token string, certPool *x509.CertPool) (*AzureDevOps, error) {
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
return nil, errors.New("azure devops token cannot be empty")
|
return nil, errors.New("azure devops token cannot be empty")
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,11 @@ func NewAzureDevOps(providerUID string, addr string, token string, certPool *x50
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should never happen
|
||||||
|
if commitStatus == "" {
|
||||||
|
return nil, errors.New("commit status cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
comp := strings.Split(id, "/")
|
comp := strings.Split(id, "/")
|
||||||
if len(comp) != 4 {
|
if len(comp) != 4 {
|
||||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
return nil, fmt.Errorf("invalid repository id %q", id)
|
||||||
|
@ -77,10 +82,10 @@ func NewAzureDevOps(providerUID string, addr string, token string, certPool *x50
|
||||||
Client: *client,
|
Client: *client,
|
||||||
}
|
}
|
||||||
return &AzureDevOps{
|
return &AzureDevOps{
|
||||||
Project: proj,
|
Project: proj,
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
ProviderUID: providerUID,
|
CommitStatus: commitStatus,
|
||||||
Client: gitClient,
|
Client: gitClient,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +96,7 @@ func (a AzureDevOps) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
revString, ok := event.GetRevision()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing revision metadata")
|
return errors.New("missing revision metadata")
|
||||||
}
|
}
|
||||||
|
@ -108,7 +113,7 @@ func (a AzureDevOps) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
g := commitStatusGenre(event)
|
g := commitStatusGenre(event)
|
||||||
|
|
||||||
_, desc := formatNameAndDescription(event)
|
_, desc := formatNameAndDescription(event)
|
||||||
id := generateCommitStatusID(a.ProviderUID, event)
|
id := a.CommitStatus
|
||||||
createArgs := git.CreateCommitStatusArgs{
|
createArgs := git.CreateCommitStatusArgs{
|
||||||
Project: &a.Project,
|
Project: &a.Project,
|
||||||
RepositoryId: &a.Repo,
|
RepositoryId: &a.Repo,
|
||||||
|
|
|
@ -33,12 +33,12 @@ import (
|
||||||
const apiLocations = `{"count":0,"value":[{"area":"","id":"428dd4fb-fda5-4722-af02-9313b80305da","routeTemplate":"","resourceName":"","maxVersion":"6.0","minVersion":"5.0","releasedVersion":"6.0"}]}`
|
const apiLocations = `{"count":0,"value":[{"area":"","id":"428dd4fb-fda5-4722-af02-9313b80305da","routeTemplate":"","resourceName":"","maxVersion":"6.0","minVersion":"5.0","releasedVersion":"6.0"}]}`
|
||||||
|
|
||||||
func Fuzz_AzureDevOps(f *testing.F) {
|
func Fuzz_AzureDevOps(f *testing.F) {
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "error", "", []byte{}, []byte(`{"count":1,"value":[{"state":"error","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "error", "", []byte{}, []byte(`{"count":1,"value":[{"state":"error","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":1,"value":[{"state":"info","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":1,"value":[{"state":"info","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":0,"value":[]}`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":0,"value":[]}`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "", "", "Progressing", []byte{}, []byte{})
|
f.Add("kustomization/gitops-system/0c9c2e41", "alakazam", "org/proj/_git/repo", "", "", "Progressing", []byte{}, []byte{})
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
|
f.Fuzz(func(t *testing.T, commitStatus, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if strings.HasSuffix(r.URL.Path, "_apis") {
|
if strings.HasSuffix(r.URL.Path, "_apis") {
|
||||||
w.Write([]byte(apiLocations))
|
w.Write([]byte(apiLocations))
|
||||||
|
@ -54,7 +54,7 @@ func Fuzz_AzureDevOps(f *testing.F) {
|
||||||
var cert x509.CertPool
|
var cert x509.CertPool
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||||
|
|
||||||
azureDevOps, err := NewAzureDevOps(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
azureDevOps, err := NewAzureDevOps(commitStatus, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,19 +27,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewAzureDevOpsBasic(t *testing.T) {
|
func TestNewAzureDevOpsBasic(t *testing.T) {
|
||||||
a, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://dev.azure.com/foo/bar/_git/baz", "foo", nil)
|
a, err := NewAzureDevOps("kustomization/gitops-system/0c9c2e41", "https://dev.azure.com/foo/bar/_git/baz", "foo", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, a.Project, "bar")
|
assert.Equal(t, a.Project, "bar")
|
||||||
assert.Equal(t, a.Repo, "baz")
|
assert.Equal(t, a.Repo, "baz")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAzureDevOpsInvalidUrl(t *testing.T) {
|
func TestNewAzureDevOpsInvalidUrl(t *testing.T) {
|
||||||
_, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://dev.azure.com/foo/bar/baz", "foo", nil)
|
_, err := NewAzureDevOps("kustomization/gitops-system/0c9c2e41", "https://dev.azure.com/foo/bar/baz", "foo", nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAzureDevOpsMissingToken(t *testing.T) {
|
func TestNewAzureDevOpsMissingToken(t *testing.T) {
|
||||||
_, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://dev.azure.com/foo/bar/baz", "", nil)
|
_, err := NewAzureDevOps("kustomization/gitops-system/0c9c2e41", "https://dev.azure.com/foo/bar/baz", "", nil)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAzureDevOpsEmptyCommitStatus(t *testing.T) {
|
||||||
|
_, err := NewAzureDevOps("", "https://dev.azure.com/foo/bar/_git/baz", "foo", nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +105,34 @@ func TestAzureDevOps_Post(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "event with origin revision",
|
||||||
|
event: eventv1.Event{
|
||||||
|
Severity: eventv1.EventSeverityInfo,
|
||||||
|
InvolvedObject: corev1.ObjectReference{
|
||||||
|
Kind: "Kustomization",
|
||||||
|
Name: "gitops-system",
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
eventv1.MetaRevisionKey: "main@sha1:69b59063470310ebbd88a9156325322a124e55a3",
|
||||||
|
eventv1.MetaOriginRevisionKey: "main@sha1:bd88a9156325322a124e55a369b59063470310eb",
|
||||||
|
},
|
||||||
|
Reason: "ApplySucceeded",
|
||||||
|
},
|
||||||
|
want: git.CreateCommitStatusArgs{
|
||||||
|
CommitId: strPtr("bd88a9156325322a124e55a369b59063470310eb"),
|
||||||
|
Project: strPtr("bar"),
|
||||||
|
RepositoryId: strPtr("baz"),
|
||||||
|
GitCommitStatusToCreate: &git.GitStatus{
|
||||||
|
Description: strPtr("apply succeeded"),
|
||||||
|
State: &git.GitStatusStateValues.Succeeded,
|
||||||
|
Context: &git.GitStatusContext{
|
||||||
|
Genre: strPtr("fluxcd"),
|
||||||
|
Name: strPtr("kustomization/gitops-system/0c9c2e41"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "event with summary",
|
name: "event with summary",
|
||||||
event: eventv1.Event{
|
event: eventv1.Event{
|
||||||
|
@ -132,7 +165,7 @@ func TestAzureDevOps_Post(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range postTests {
|
for _, tt := range postTests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
a, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com/foo/bar/_git/baz", "foo", nil)
|
a, err := NewAzureDevOps("kustomization/gitops-system/0c9c2e41", "https://example.com/foo/bar/_git/baz", "foo", nil)
|
||||||
fakeClient := &fakeDevOpsClient{}
|
fakeClient := &fakeDevOpsClient{}
|
||||||
a.Client = fakeClient
|
a.Client = fakeClient
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
|
@ -17,10 +17,20 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"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"
|
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"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
|
"github.com/fluxcd/pkg/auth"
|
||||||
|
"github.com/fluxcd/pkg/auth/azure"
|
||||||
|
"github.com/fluxcd/pkg/cache"
|
||||||
|
|
||||||
|
"github.com/fluxcd/notification-controller/api/v1beta3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AzureEventHub holds the eventhub client
|
// AzureEventHub holds the eventhub client
|
||||||
|
@ -29,21 +39,33 @@ type AzureEventHub struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureEventHub creates a eventhub client
|
// NewAzureEventHub creates a eventhub client
|
||||||
func NewAzureEventHub(endpointURL, token, eventHubNamespace string) (*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 hub *eventhub.Hub
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// token should only be defined if JWT is used
|
if err := validateAuthOptions(endpointURL, token, serviceAccountName); err != nil {
|
||||||
if token != "" {
|
return nil, fmt.Errorf("invalid authentication options: %v", err)
|
||||||
hub, err = newJWTHub(endpointURL, token, eventHubNamespace)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create a eventhub using JWT %v", err)
|
if isSASAuth(endpointURL) {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hub, err = newSASHub(endpointURL)
|
hub, err = newSASHub(endpointURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create a eventhub using SAS %v", err)
|
return nil, fmt.Errorf("failed to create a eventhub using SAS %v", err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// if token doesn't exist, try to create a new token using managed identity
|
||||||
|
if token == "" {
|
||||||
|
token, err = newManagedIdentityToken(ctx, proxy, serviceAccountName, providerName, providerNamespace, tokenClient, tokenCache)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create a eventhub using managed identity %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hub, err = newJWTHub(endpointURL, token, eventHubNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create a eventhub using authentication token %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AzureEventHub{
|
return &AzureEventHub{
|
||||||
|
@ -88,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
|
// 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) {
|
func (j *PureJWT) GetToken(uri string) (*azauth.Token, error) {
|
||||||
return &auth.Token{
|
return &azauth.Token{
|
||||||
TokenType: auth.CBSTokenTypeJWT,
|
TokenType: azauth.CBSTokenTypeJWT,
|
||||||
Token: j.jwt,
|
Token: j.jwt,
|
||||||
Expiry: "",
|
Expiry: "",
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -116,3 +138,75 @@ func newSASHub(address string) (*eventhub.Hub, error) {
|
||||||
|
|
||||||
return hub, nil
|
return hub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newManagedIdentityToken is used to attempt credential-free authentication.
|
||||||
|
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, auth.WithProxyURL(*proxyURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceAccountName != "" {
|
||||||
|
serviceAccount := types.NamespacedName{
|
||||||
|
Name: serviceAccountName,
|
||||||
|
Namespace: providerNamespace,
|
||||||
|
}
|
||||||
|
opts = append(opts, auth.WithServiceAccount(serviceAccount, tokenClient))
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenCache != nil {
|
||||||
|
involvedObject := cache.InvolvedObject{
|
||||||
|
Kind: v1beta3.ProviderKind,
|
||||||
|
Name: providerName,
|
||||||
|
Namespace: providerNamespace,
|
||||||
|
Operation: OperationPost,
|
||||||
|
}
|
||||||
|
opts = append(opts, auth.WithCache(*tokenCache, involvedObject))
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := auth.GetAccessToken(ctx, azure.Provider{}, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get token for azure event hub: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.(*azure.Token).AccessToken.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
} else if serviceAccountName != "" && token != "" {
|
||||||
|
return fmt.Errorf("serviceAccountName and jwt token authentication cannot be set at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSASAuth checks if the endpoint URL contains SAS authentication parameters
|
||||||
|
func isSASAuth(endpointURL string) bool {
|
||||||
|
return strings.Contains(endpointURL, "SharedAccessKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSASAuth checks if SAS authentication is used correctly
|
||||||
|
func validateSASAuth(token, serviceAccountName string) error {
|
||||||
|
if serviceAccountName != "" {
|
||||||
|
return fmt.Errorf("serviceAccountName and SAS authentication cannot be set at the same time")
|
||||||
|
}
|
||||||
|
if token != "" {
|
||||||
|
return fmt.Errorf("jwt token and SAS authentication cannot be set at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAzureEventHub(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
endpointURL string
|
||||||
|
token string
|
||||||
|
eventHubNamespace string
|
||||||
|
serviceAccountName string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "JWT Authentication",
|
||||||
|
endpointURL: "azure-nc-eventhub",
|
||||||
|
token: "jwt-token",
|
||||||
|
eventHubNamespace: "namespace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SAS Authentication",
|
||||||
|
endpointURL: "Endpoint=sb://example.com/;SharedAccessKeyName=keyName;SharedAccessKey=key;EntityPath=eventhub",
|
||||||
|
token: "",
|
||||||
|
eventHubNamespace: "namespace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Default Azure Credential",
|
||||||
|
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 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",
|
||||||
|
endpointURL: "Endpoint=sb://example.com/;SharedAccessKeyName=keyName;SharedAccessKey=key;EntityPath=eventhub",
|
||||||
|
token: "",
|
||||||
|
serviceAccountName: "test-service-account",
|
||||||
|
eventHubNamespace: "namespace",
|
||||||
|
err: errors.New("invalid authentication options: serviceAccountName and SAS authentication cannot be set at the same time"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SAS auth with token set",
|
||||||
|
endpointURL: "Endpoint=sb://example.com/;SharedAccessKeyName=keyName;SharedAccessKey=key;EntityPath=eventhub",
|
||||||
|
token: "test-token",
|
||||||
|
eventHubNamespace: "namespace",
|
||||||
|
err: errors.New("invalid authentication options: jwt token and SAS authentication cannot be set at the same time"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token auth with serviceAccountName set",
|
||||||
|
endpointURL: "azure-nc-eventhub",
|
||||||
|
token: "test-token",
|
||||||
|
serviceAccountName: "test-service-account",
|
||||||
|
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 {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
client, err := NewAzureEventHub(context.TODO(), tt.endpointURL, tt.token, tt.eventHubNamespace, "", tt.serviceAccountName, "", "", nil, nil)
|
||||||
|
if tt.err != nil {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, tt.err, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, client)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,18 +34,23 @@ import (
|
||||||
|
|
||||||
// Bitbucket is a Bitbucket Server notifier.
|
// Bitbucket is a Bitbucket Server notifier.
|
||||||
type Bitbucket struct {
|
type Bitbucket struct {
|
||||||
Owner string
|
Owner string
|
||||||
Repo string
|
Repo string
|
||||||
ProviderUID string
|
CommitStatus string
|
||||||
Client *bitbucket.Client
|
Client *bitbucket.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBitbucket creates and returns a new Bitbucket notifier.
|
// NewBitbucket creates and returns a new Bitbucket notifier.
|
||||||
func NewBitbucket(providerUID string, addr string, token string, certPool *x509.CertPool) (*Bitbucket, error) {
|
func NewBitbucket(commitStatus string, addr string, token string, certPool *x509.CertPool) (*Bitbucket, error) {
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
return nil, errors.New("bitbucket token cannot be empty")
|
return nil, errors.New("bitbucket token cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should never happen
|
||||||
|
if commitStatus == "" {
|
||||||
|
return nil, errors.New("commit status cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
_, id, err := parseGitAddress(addr)
|
_, id, err := parseGitAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -77,10 +82,10 @@ func NewBitbucket(providerUID string, addr string, token string, certPool *x509.
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Bitbucket{
|
return &Bitbucket{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
ProviderUID: providerUID,
|
CommitStatus: commitStatus,
|
||||||
Client: client,
|
Client: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +96,7 @@ func (b Bitbucket) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
revString, ok := event.GetRevision()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing revision metadata")
|
return errors.New("missing revision metadata")
|
||||||
}
|
}
|
||||||
|
@ -105,7 +110,7 @@ func (b Bitbucket) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
name, desc := formatNameAndDescription(event)
|
name, desc := formatNameAndDescription(event)
|
||||||
id := generateCommitStatusID(b.ProviderUID, event)
|
id := b.CommitStatus
|
||||||
// key has a limitation of 40 characters in bitbucket api
|
// key has a limitation of 40 characters in bitbucket api
|
||||||
key := sha1String(id)
|
key := sha1String(id)
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Fuzz_Bitbucket(f *testing.F) {
|
func Fuzz_Bitbucket(f *testing.F) {
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "user:pass", "org/repo", "revision/dsa123a", "info", []byte{}, []byte(`{"state":"SUCCESSFUL","description":"","key":"","name":"","url":""}`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "user:pass", "org/repo", "revision/dsa123a", "info", []byte{}, []byte(`{"state":"SUCCESSFUL","description":"","key":"","name":"","url":""}`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "user:pass", "org/repo", "revision/dsa123a", "error", []byte{}, []byte(`{}`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "user:pass", "org/repo", "revision/dsa123a", "error", []byte{}, []byte(`{}`))
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity string, seed, response []byte) {
|
f.Fuzz(func(t *testing.T, commitStatus, token, urlSuffix, revision, severity string, seed, response []byte) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
io.Copy(io.Discard, r.Body)
|
io.Copy(io.Discard, r.Body)
|
||||||
w.Write(response)
|
w.Write(response)
|
||||||
|
@ -45,7 +45,7 @@ func Fuzz_Bitbucket(f *testing.F) {
|
||||||
var cert x509.CertPool
|
var cert x509.CertPool
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||||
|
|
||||||
bitbucket, err := NewBitbucket(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
bitbucket, err := NewBitbucket(commitStatus, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,18 +23,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewBitbucketBasic(t *testing.T) {
|
func TestNewBitbucketBasic(t *testing.T) {
|
||||||
b, err := NewBitbucket("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://bitbucket.org/foo/bar", "foo:bar", nil)
|
b, err := NewBitbucket("kustomization/gitops-system/0c9c2e41", "https://bitbucket.org/foo/bar", "foo:bar", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, b.Owner, "foo")
|
assert.Equal(t, b.Owner, "foo")
|
||||||
assert.Equal(t, b.Repo, "bar")
|
assert.Equal(t, b.Repo, "bar")
|
||||||
|
assert.Equal(t, b.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBitbucketEmptyCommitStatus(t *testing.T) {
|
||||||
|
_, err := NewBitbucket("", "https://bitbucket.org/foo/bar", "foo:bar", nil)
|
||||||
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBitbucketInvalidUrl(t *testing.T) {
|
func TestNewBitbucketInvalidUrl(t *testing.T) {
|
||||||
_, err := NewBitbucket("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://bitbucket.org/foo/bar/baz", "foo:bar", nil)
|
_, err := NewBitbucket("kustomization/gitops-system/0c9c2e41", "https://bitbucket.org/foo/bar/baz", "foo:bar", nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBitbucketInvalidToken(t *testing.T) {
|
func TestNewBitbucketInvalidToken(t *testing.T) {
|
||||||
_, err := NewBitbucket("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://bitbucket.org/foo/bar", "bar", nil)
|
_, err := NewBitbucket("kustomization/gitops-system/0c9c2e41", "https://bitbucket.org/foo/bar", "bar", nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,9 @@ import (
|
||||||
|
|
||||||
// BitbucketServer is a notifier for BitBucket Server and Data Center.
|
// BitbucketServer is a notifier for BitBucket Server and Data Center.
|
||||||
type BitbucketServer struct {
|
type BitbucketServer struct {
|
||||||
ProjectKey string
|
CommitStatus string
|
||||||
RepositorySlug string
|
Url *url.URL
|
||||||
ProviderUID string
|
|
||||||
ProviderAddress string
|
ProviderAddress string
|
||||||
Host string
|
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
Token string
|
Token string
|
||||||
|
@ -49,8 +47,10 @@ type BitbucketServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bbServerEndPointTmpl = "/rest/api/latest/projects/%[1]s/repos/%[2]s/commits/%[3]s/builds"
|
bbServerEndPointCommitsTmpl = "%[1]s/rest/api/latest/projects/%[2]s/repos/%[3]s/commits"
|
||||||
|
bbServerEndPointBuildsTmpl = "%[1]s/builds"
|
||||||
bbServerGetBuildStatusQueryString = "key"
|
bbServerGetBuildStatusQueryString = "key"
|
||||||
|
bbServerSourceCodeMgmtString = "/scm/"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bbServerBuildStatus struct {
|
type bbServerBuildStatus struct {
|
||||||
|
@ -81,18 +81,16 @@ type bbServerBuildStatusSetRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBitbucketServer creates and returns a new BitbucketServer notifier.
|
// NewBitbucketServer creates and returns a new BitbucketServer notifier.
|
||||||
func NewBitbucketServer(providerUID string, addr string, token string, certPool *x509.CertPool, username string, password string) (*BitbucketServer, error) {
|
func NewBitbucketServer(commitStatus string, addr string, token string, certPool *x509.CertPool, username string, password string) (*BitbucketServer, error) {
|
||||||
hst, id, err := parseBitbucketServerGitAddress(addr)
|
url, err := parseBitbucketServerGitAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
comp := strings.Split(id, "/")
|
// this should never happen
|
||||||
if len(comp) != 2 {
|
if commitStatus == "" {
|
||||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
return nil, errors.New("commit status cannot be empty")
|
||||||
}
|
}
|
||||||
projectkey := comp[0]
|
|
||||||
reposlug := comp[1]
|
|
||||||
|
|
||||||
httpClient := retryablehttp.NewClient()
|
httpClient := retryablehttp.NewClient()
|
||||||
if certPool != nil {
|
if certPool != nil {
|
||||||
|
@ -114,10 +112,8 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BitbucketServer{
|
return &BitbucketServer{
|
||||||
ProjectKey: projectkey,
|
CommitStatus: commitStatus,
|
||||||
RepositorySlug: reposlug,
|
Url: url,
|
||||||
ProviderUID: providerUID,
|
|
||||||
Host: hst,
|
|
||||||
ProviderAddress: addr,
|
ProviderAddress: addr,
|
||||||
Token: token,
|
Token: token,
|
||||||
Username: username,
|
Username: username,
|
||||||
|
@ -132,7 +128,7 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
if event.HasReason(meta.ProgressingReason) {
|
if event.HasReason(meta.ProgressingReason) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
revString, ok := event.GetRevision()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing revision metadata")
|
return errors.New("missing revision metadata")
|
||||||
}
|
}
|
||||||
|
@ -147,18 +143,18 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
|
|
||||||
name, desc := formatNameAndDescription(event)
|
name, desc := formatNameAndDescription(event)
|
||||||
name = name + " [" + desc + "]" //Bitbucket server displays this data on browser. Thus adding description here.
|
name = name + " [" + desc + "]" //Bitbucket server displays this data on browser. Thus adding description here.
|
||||||
id := generateCommitStatusID(b.ProviderUID, event)
|
id := b.CommitStatus
|
||||||
// key has a limitation of 40 characters in bitbucket api
|
// key has a limitation of 40 characters in bitbucket api
|
||||||
key := sha1String(id)
|
key := sha1String(id)
|
||||||
|
|
||||||
u := b.Host + b.createApiPath(rev)
|
u := b.Url.JoinPath(b.createBuildPath(rev)).String()
|
||||||
dupe, err := b.duplicateBitbucketServerStatus(ctx, rev, state, name, desc, id, key, u)
|
dupe, err := b.duplicateBitbucketServerStatus(ctx, state, name, desc, key, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get existing commit status: %w", err)
|
return fmt.Errorf("could not get existing commit status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dupe {
|
if !dupe {
|
||||||
_, err = b.postBuildStatus(ctx, rev, state, name, desc, id, key, u)
|
_, err = b.postBuildStatus(ctx, state, name, desc, key, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not post build status: %w", err)
|
return fmt.Errorf("could not post build status: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -178,9 +174,9 @@ func (b BitbucketServer) state(severity string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev, state, name, desc, id, key, u string) (bool, error) {
|
func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, state, name, desc, key, u string) (bool, error) {
|
||||||
// Prepare request object
|
// Prepare request object
|
||||||
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet, key, rev)
|
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("could not check duplicate commit status: %w", err)
|
return false, fmt.Errorf("could not check duplicate commit status: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -192,10 +188,10 @@ func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev
|
||||||
|
|
||||||
// Make a GET call
|
// Make a GET call
|
||||||
d, err := b.Client.Do(req)
|
d, err := b.Client.Do(req)
|
||||||
if err != nil && d.StatusCode != http.StatusNotFound {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed api call to check duplicate commit status: %w", err)
|
return false, fmt.Errorf("failed api call to check duplicate commit status: %w", err)
|
||||||
}
|
}
|
||||||
if isError(d) && d.StatusCode != http.StatusNotFound {
|
if d != nil && isError(d) && d.StatusCode != http.StatusNotFound {
|
||||||
defer d.Body.Close()
|
defer d.Body.Close()
|
||||||
return false, fmt.Errorf("failed api call to check duplicate commit status: %d - %s", d.StatusCode, http.StatusText(d.StatusCode))
|
return false, fmt.Errorf("failed api call to check duplicate commit status: %d - %s", d.StatusCode, http.StatusText(d.StatusCode))
|
||||||
}
|
}
|
||||||
|
@ -219,7 +215,7 @@ func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name, desc, id, key, url string) (*http.Response, error) {
|
func (b BitbucketServer) postBuildStatus(ctx context.Context, state, name, desc, key, url string) (*http.Response, error) {
|
||||||
//Prepare json body
|
//Prepare json body
|
||||||
j := &bbServerBuildStatusSetRequest{
|
j := &bbServerBuildStatusSetRequest{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
@ -235,7 +231,7 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Prepare request
|
//Prepare request
|
||||||
req, err := b.prepareCommonRequest(ctx, url, p, http.MethodPost, key, rev)
|
req, err := b.prepareCommonRequest(ctx, url, p, http.MethodPost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed preparing request for post build commit status: %w", err)
|
return nil, fmt.Errorf("failed preparing request for post build commit status: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -257,21 +253,46 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name,
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) createApiPath(rev string) string {
|
func (b BitbucketServer) createBuildPath(rev string) string {
|
||||||
return fmt.Sprintf(bbServerEndPointTmpl, b.ProjectKey, b.RepositorySlug, rev)
|
return fmt.Sprintf(bbServerEndPointBuildsTmpl, rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBitbucketServerGitAddress(s string) (string, string, error) {
|
func parseBitbucketServerGitAddress(s string) (*url.URL, error) {
|
||||||
host, id, err := parseGitAddress(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("could not parse git address: %w", err)
|
return nil, fmt.Errorf("could not parse git address: %w", err)
|
||||||
}
|
}
|
||||||
//Remove "scm/" --> https://community.atlassian.com/t5/Bitbucket-questions/remote-url-in-Bitbucket-server-what-does-scm-represent-is-it/qaq-p/2060987
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
id = strings.TrimPrefix(id, "scm/")
|
return nil, fmt.Errorf("could not parse git address: unsupported scheme type in address: %s. Must be http or https", u.Scheme)
|
||||||
return host, id, nil
|
}
|
||||||
|
|
||||||
|
idWithContext := strings.TrimSuffix(u.Path, ".git")
|
||||||
|
|
||||||
|
// /scm/ is always part of http/https clone urls : https://community.atlassian.com/t5/Bitbucket-questions/remote-url-in-Bitbucket-server-what-does-scm-represent-is-it/qaq-p/2060987
|
||||||
|
lastIndex := strings.LastIndex(idWithContext, bbServerSourceCodeMgmtString)
|
||||||
|
if lastIndex < 0 {
|
||||||
|
return nil, fmt.Errorf("could not parse git address: supplied provider address is not http(s) git clone url")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle context scenarios --> https://confluence.atlassian.com/bitbucketserver/change-bitbucket-s-context-path-776640153.html
|
||||||
|
cntxtPath := idWithContext[:lastIndex] // Context path is anything that comes before last /scm/
|
||||||
|
|
||||||
|
id := idWithContext[lastIndex+len(bbServerSourceCodeMgmtString):] // Remove last `/scm/` from id as it is not used in API calls
|
||||||
|
|
||||||
|
comp := strings.Split(id, "/")
|
||||||
|
if len(comp) != 2 {
|
||||||
|
return nil, fmt.Errorf("could not parse git address: invalid repository id %q", id)
|
||||||
|
}
|
||||||
|
projectkey := comp[0]
|
||||||
|
reposlug := comp[1]
|
||||||
|
|
||||||
|
// Update the path till commits endpoint. The final builds endpoint would be added in Post function.
|
||||||
|
u.Path = fmt.Sprintf(bbServerEndPointCommitsTmpl, cntxtPath, projectkey, reposlug)
|
||||||
|
|
||||||
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BitbucketServer) prepareCommonRequest(ctx context.Context, path string, body io.Reader, method string, key, rev string) (*retryablehttp.Request, error) {
|
func (b BitbucketServer) prepareCommonRequest(ctx context.Context, path string, body io.Reader, method string) (*retryablehttp.Request, error) {
|
||||||
req, err := retryablehttp.NewRequestWithContext(ctx, method, path, body)
|
req, err := retryablehttp.NewRequestWithContext(ctx, method, path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not prepare request: %w", err)
|
return nil, fmt.Errorf("could not prepare request: %w", err)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -33,33 +34,91 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewBitbucketServerBasic(t *testing.T) {
|
func TestNewBitbucketServerBasicNoContext(t *testing.T) {
|
||||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, b.Username, "dummyuser")
|
assert.Equal(t, b.Username, "dummyuser")
|
||||||
assert.Equal(t, b.Password, "testpassword")
|
assert.Equal(t, b.Password, "testpassword")
|
||||||
|
assert.Equal(t, b.Url.Scheme, "https")
|
||||||
|
assert.Equal(t, b.Url.Host, "example.com:7990")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBitbucketServerBasicWithContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/context/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, b.Username, "dummyuser")
|
||||||
|
assert.Equal(t, b.Password, "testpassword")
|
||||||
|
assert.Equal(t, b.Url.Scheme, "https")
|
||||||
|
assert.Equal(t, b.Url.Host, "example.com:7990")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathNoContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathOneWordContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/context1/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/context1/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathMultipleWordContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/context1/context2/context3/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/context1/context2/context3/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathOneWordScmInContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/scm/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathMultipleWordScmInContext(t *testing.T) {
|
||||||
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/context2/scm/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u := b.Url.JoinPath(b.createBuildPath("00151b98e303e19610378e6f1c49e31e5e80cd3b")).String()
|
||||||
|
assert.Equal(t, u, "https://example.com:7990/scm/context2/scm/rest/api/latest/projects/projectfoo/repos/repobar/commits/00151b98e303e19610378e6f1c49e31e5e80cd3b/builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerApiPathScmAlreadyRemovedInInput(t *testing.T) {
|
||||||
|
_, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/context1/context2/context3/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "could not parse git address: supplied provider address is not http(s) git clone url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketServerSshAddress(t *testing.T) {
|
||||||
|
_, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "ssh://git@mybitbucket:2222/ap/fluxcd-sandbox.git", "", nil, "", "")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "could not parse git address: unsupported scheme type in address: ssh. Must be http or https")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBitbucketServerToken(t *testing.T) {
|
func TestNewBitbucketServerToken(t *testing.T) {
|
||||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, b.Token, "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP")
|
assert.Equal(t, b.Token, "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBitbucketServerInvalidCreds(t *testing.T) {
|
func TestNewBitbucketServerInvalidCreds(t *testing.T) {
|
||||||
_, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "", "")
|
_, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "", "")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equal(t, err.Error(), "invalid credentials, expected to be one of username/password or API Token")
|
assert.Equal(t, err.Error(), "invalid credentials, expected to be one of username/password or API Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBitbucketServerInvalidRepo(t *testing.T) {
|
func TestNewBitbucketServerInvalidRepo(t *testing.T) {
|
||||||
_, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar/invalid.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
_, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar/invalid.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equal(t, err.Error(), "invalid repository id \"projectfoo/repobar/invalid\"")
|
assert.Equal(t, err.Error(), "could not parse git address: invalid repository id \"projectfoo/repobar/invalid\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostBitbucketServerMissingRevision(t *testing.T) {
|
func TestPostBitbucketServerMissingRevision(t *testing.T) {
|
||||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
//Validate missing revision
|
//Validate missing revision
|
||||||
|
@ -70,8 +129,14 @@ func TestPostBitbucketServerMissingRevision(t *testing.T) {
|
||||||
assert.Equal(t, err.Error(), "missing revision metadata")
|
assert.Equal(t, err.Error(), "missing revision metadata")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewBitbucketServerEmptyCommitStatus(t *testing.T) {
|
||||||
|
_, err := NewBitbucketServer("", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "commit status cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
func TestPostBitbucketServerBadCommitHash(t *testing.T) {
|
func TestPostBitbucketServerBadCommitHash(t *testing.T) {
|
||||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
//Validate extract commit hash
|
//Validate extract commit hash
|
||||||
|
@ -84,7 +149,7 @@ func TestPostBitbucketServerBadCommitHash(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostBitbucketServerBadBitbucketState(t *testing.T) {
|
func TestPostBitbucketServerBadBitbucketState(t *testing.T) {
|
||||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
//Validate conversion to bitbucket state
|
//Validate conversion to bitbucket state
|
||||||
|
@ -123,13 +188,14 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
password string
|
password string
|
||||||
token string
|
token string
|
||||||
event eventv1.Event
|
event eventv1.Event
|
||||||
provideruid string
|
commitStatus string
|
||||||
key string
|
key string
|
||||||
|
uriHash string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Validate Token Auth ",
|
name: "Validate Token Auth ",
|
||||||
token: "goodtoken",
|
token: "goodtoken",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Bearer goodtoken",
|
"Authorization": "Bearer goodtoken",
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -138,15 +204,30 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("info", map[string]string{
|
event: generateTestEventKustomization("info", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Validate Basic Auth and Post State=Successful",
|
name: "Event with origin revision",
|
||||||
username: "hello",
|
token: "goodtoken",
|
||||||
password: "password",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
headers: map[string]string{
|
||||||
|
"Authorization": "Bearer goodtoken",
|
||||||
|
"x-atlassian-token": "no-check",
|
||||||
|
"x-requested-with": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
event: generateTestEventKustomization("info", map[string]string{
|
||||||
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
|
eventv1.MetaOriginRevisionKey: "main@sha1:e7c17dd8b8384bbc84b7e7385394cb7f48332b2d",
|
||||||
|
}),
|
||||||
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
|
uriHash: "e7c17dd8b8384bbc84b7e7385394cb7f48332b2d",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Validate Basic Auth and Post State=Successful",
|
||||||
|
username: "hello",
|
||||||
|
password: "password",
|
||||||
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -155,15 +236,14 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("info", map[string]string{
|
event: generateTestEventKustomization("info", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Validate Post State=Failed",
|
name: "Validate Post State=Failed",
|
||||||
username: "hello",
|
username: "hello",
|
||||||
password: "password",
|
password: "password",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -172,9 +252,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("error", map[string]string{
|
event: generateTestEventKustomization("error", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Fail if bad json response in existing commit status",
|
name: "Fail if bad json response in existing commit status",
|
||||||
|
@ -182,7 +261,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
errorString: "could not get existing commit status: could not unmarshal json response body for duplicate commit status: unexpected end of JSON input",
|
errorString: "could not get existing commit status: could not unmarshal json response body for duplicate commit status: unexpected end of JSON input",
|
||||||
username: "hello",
|
username: "hello",
|
||||||
password: "password",
|
password: "password",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -191,9 +270,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("error", map[string]string{
|
event: generateTestEventKustomization("error", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Fail if status code is non-200 in existing commit status",
|
name: "Fail if status code is non-200 in existing commit status",
|
||||||
|
@ -201,7 +279,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
errorString: "could not get existing commit status: failed api call to check duplicate commit status: 400 - Bad Request",
|
errorString: "could not get existing commit status: failed api call to check duplicate commit status: 400 - Bad Request",
|
||||||
username: "hello",
|
username: "hello",
|
||||||
password: "password",
|
password: "password",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -210,9 +288,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("error", map[string]string{
|
event: generateTestEventKustomization("error", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bad post- Unauthorized",
|
name: "Bad post- Unauthorized",
|
||||||
|
@ -220,7 +297,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
errorString: "could not post build status: could not post build commit status: 401 - Unauthorized",
|
errorString: "could not post build status: could not post build commit status: 401 - Unauthorized",
|
||||||
username: "hello",
|
username: "hello",
|
||||||
password: "password",
|
password: "password",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -229,15 +306,14 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("error", map[string]string{
|
event: generateTestEventKustomization("error", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Validate duplicate commit status successful match",
|
name: "Validate duplicate commit status successful match",
|
||||||
username: "hello",
|
username: "hello",
|
||||||
password: "password",
|
password: "password",
|
||||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||||
"x-atlassian-token": "no-check",
|
"x-atlassian-token": "no-check",
|
||||||
|
@ -246,9 +322,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
event: generateTestEventKustomization("info", map[string]string{
|
event: generateTestEventKustomization("info", map[string]string{
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}),
|
}),
|
||||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{
|
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||||
}))),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +337,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate URI
|
// Validate URI
|
||||||
require.Equal(t, r.URL.Path, "/rest/api/latest/projects/projectfoo/repos/repobar/commits/5394cb7f48332b2de7c17dd8b8384bbc84b7e738/builds")
|
path := fmt.Sprintf("/rest/api/latest/projects/projectfoo/repos/repobar/commits/%s/builds", tt.uriHash)
|
||||||
|
require.Equal(t, r.URL.Path, path)
|
||||||
|
|
||||||
// Validate Get Build Status call
|
// Validate Get Build Status call
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
|
@ -281,7 +357,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
jsondata, _ := json.Marshal(&bbServerBuildStatus{
|
jsondata, _ := json.Marshal(&bbServerBuildStatus{
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: desc,
|
Description: desc,
|
||||||
Key: sha1String(generateCommitStatusID(tt.provideruid, tt.event)),
|
Key: sha1String(tt.commitStatus),
|
||||||
State: "SUCCESSFUL",
|
State: "SUCCESSFUL",
|
||||||
Url: "https://example.com:7990/scm/projectfoo/repobar.git",
|
Url: "https://example.com:7990/scm/projectfoo/repobar.git",
|
||||||
})
|
})
|
||||||
|
@ -364,7 +440,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
c, err := NewBitbucketServer(tt.provideruid, ts.URL+"/scm/projectfoo/repobar.git", tt.token, nil, tt.username, tt.password)
|
c, err := NewBitbucketServer(tt.commitStatus, ts.URL+"/scm/projectfoo/repobar.git", tt.token, nil, tt.username, tt.password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = c.Post(context.TODO(), tt.event)
|
err = c.Post(context.TODO(), tt.event)
|
||||||
if tt.testFailReason == "" {
|
if tt.testFailReason == "" {
|
||||||
|
|
|
@ -19,91 +19,130 @@ package notifier
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestOptFunc func(*retryablehttp.Request)
|
type postOptions struct {
|
||||||
|
proxy string
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
requestModifier func(*retryablehttp.Request)
|
||||||
|
responseValidator func(statusCode int, body []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
func postMessage(ctx context.Context, address, proxy string, certPool *x509.CertPool, payload interface{}, reqOpts ...requestOptFunc) error {
|
type postOption func(*postOptions)
|
||||||
httpClient := retryablehttp.NewClient()
|
|
||||||
if certPool != nil {
|
|
||||||
httpClient.HTTPClient.Transport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: certPool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxy != "" {
|
func postMessage(ctx context.Context, address string, payload interface{}, opts ...postOption) error {
|
||||||
proxyURL, err := url.Parse(proxy)
|
options := &postOptions{
|
||||||
if err != nil {
|
// Default validateResponse function verifies that the response status code is 200, 202 or 201.
|
||||||
return fmt.Errorf("unable to parse proxy URL '%s', error: %w", proxy, err)
|
responseValidator: func(statusCode int, body []byte) error {
|
||||||
}
|
if statusCode == http.StatusOK ||
|
||||||
var tlsConfig *tls.Config
|
statusCode == http.StatusAccepted ||
|
||||||
if certPool != nil {
|
statusCode == http.StatusCreated {
|
||||||
tlsConfig = &tls.Config{
|
return nil
|
||||||
RootCAs: certPool,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
httpClient.HTTPClient.Transport = &http.Transport{
|
return fmt.Errorf("request failed with status code %d, %s", statusCode, string(body))
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient.HTTPClient.Timeout = 15 * time.Second
|
for _, o := range opts {
|
||||||
httpClient.RetryWaitMin = 2 * time.Second
|
o(options)
|
||||||
httpClient.RetryWaitMax = 30 * time.Second
|
}
|
||||||
httpClient.RetryMax = 4
|
|
||||||
httpClient.Logger = nil
|
httpClient, err := newHTTPClient(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(payload)
|
data, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshalling notification payload failed: %w", err)
|
return fmt.Errorf("marshalling notification payload failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := retryablehttp.NewRequest(http.MethodPost, address, data)
|
req, err := retryablehttp.NewRequestWithContext(ctx, http.MethodPost, address, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create a new request: %w", err)
|
return fmt.Errorf("failed to create a new request: %w", err)
|
||||||
}
|
}
|
||||||
if ctx != nil {
|
|
||||||
req = req.WithContext(ctx)
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
for _, o := range reqOpts {
|
if options.requestModifier != nil {
|
||||||
o(req)
|
options.requestModifier(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := httpClient.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute request: %w", err)
|
return fmt.Errorf("failed to execute request: %w", err)
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated {
|
body, err := io.ReadAll(resp.Body)
|
||||||
b, err := io.ReadAll(resp.Body)
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("failed to read response body: %w", err)
|
||||||
return fmt.Errorf("unable to read response body, %s", err)
|
}
|
||||||
}
|
|
||||||
return fmt.Errorf("request failed with status code %d, %s", resp.StatusCode, string(b))
|
if err := options.responseValidator(resp.StatusCode, body); err != nil {
|
||||||
|
return fmt.Errorf("request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withProxy(proxy string) postOption {
|
||||||
|
return func(opts *postOptions) {
|
||||||
|
opts.proxy = proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withTLSConfig(tlsConfig *tls.Config) postOption {
|
||||||
|
return func(opts *postOptions) {
|
||||||
|
opts.tlsConfig = tlsConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRequestModifier(reqModifier func(*retryablehttp.Request)) postOption {
|
||||||
|
return func(opts *postOptions) {
|
||||||
|
opts.requestModifier = reqModifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withResponseValidator(respValidator func(statusCode int, body []byte) error) postOption {
|
||||||
|
return func(opts *postOptions) {
|
||||||
|
opts.responseValidator = respValidator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHTTPClient(opts *postOptions) (*retryablehttp.Client, error) {
|
||||||
|
httpClient := retryablehttp.NewClient()
|
||||||
|
|
||||||
|
transport := httpClient.HTTPClient.Transport.(*http.Transport)
|
||||||
|
|
||||||
|
if opts.tlsConfig != nil {
|
||||||
|
transport.TLSClientConfig = opts.tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.proxy != "" {
|
||||||
|
proxyURL, err := url.Parse(opts.proxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse proxy URL: %w", err)
|
||||||
|
}
|
||||||
|
transport.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the timeout for the HTTP client,
|
||||||
|
// as we set the provider timeout on the context.
|
||||||
|
httpClient.HTTPClient.Timeout = 0
|
||||||
|
|
||||||
|
httpClient.RetryWaitMin = 2 * time.Second
|
||||||
|
httpClient.RetryWaitMax = 30 * time.Second
|
||||||
|
httpClient.RetryMax = 4
|
||||||
|
httpClient.Logger = nil
|
||||||
|
|
||||||
|
return httpClient, nil
|
||||||
|
}
|
||||||
|
|
|
@ -18,14 +18,19 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
@ -44,10 +49,21 @@ func Test_postMessage(t *testing.T) {
|
||||||
require.Equal(t, "success", payload["status"])
|
require.Equal(t, "success", payload["status"])
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
err := postMessage(context.Background(), ts.URL, "", nil, map[string]string{"status": "success"})
|
err := postMessage(context.Background(), ts.URL, map[string]string{"status": "success"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_postMessage_timeout(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err := postMessage(ctx, ts.URL, map[string]string{"status": "success"})
|
||||||
|
require.Error(t, err, "context deadline exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
func Test_postSelfSignedCert(t *testing.T) {
|
func Test_postSelfSignedCert(t *testing.T) {
|
||||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
b, err := io.ReadAll(r.Body)
|
b, err := io.ReadAll(r.Body)
|
||||||
|
@ -65,10 +81,43 @@ func Test_postSelfSignedCert(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
certpool := x509.NewCertPool()
|
certpool := x509.NewCertPool()
|
||||||
certpool.AddCert(cert)
|
certpool.AddCert(cert)
|
||||||
err = postMessage(context.Background(), ts.URL, "", certpool, map[string]string{"status": "success"})
|
tlsConfig := &tls.Config{RootCAs: certpool}
|
||||||
|
err = postMessage(context.Background(), ts.URL, map[string]string{"status": "success"}, withTLSConfig(tlsConfig))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_postMessage_requestModifier(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
err := postMessage(context.Background(), ts.URL, map[string]string{"status": "success"}, withRequestModifier(func(req *retryablehttp.Request) {
|
||||||
|
req.Header.Set("Authorization", "Bearer token")
|
||||||
|
}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_postMessage_responseValidator(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Default response validator determines success, but the custom validator below will determine failure .
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("error: bad request"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
err := postMessage(context.Background(), ts.URL, map[string]string{"status": "success"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = postMessage(context.Background(), ts.URL, map[string]string{"status": "success"}, withResponseValidator(func(_ int, body []byte) error {
|
||||||
|
if strings.HasPrefix(string(body), "error:") {
|
||||||
|
return errors.New(string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
require.ErrorContains(t, err, "request failed: error: bad request")
|
||||||
|
}
|
||||||
|
|
||||||
func testEvent() eventv1.Event {
|
func testEvent() eventv1.Event {
|
||||||
return eventv1.Event{
|
return eventv1.Event{
|
||||||
InvolvedObject: corev1.ObjectReference{
|
InvolvedObject: corev1.ObjectReference{
|
||||||
|
|
|
@ -90,8 +90,12 @@ func (s *Discord) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
|
|
||||||
payload.Attachments = []SlackAttachment{a}
|
payload.Attachments = []SlackAttachment{a}
|
||||||
|
|
||||||
err := postMessage(ctx, s.URL, s.ProxyURL, nil, payload)
|
var opts []postOption
|
||||||
if err != nil {
|
if s.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(s.ProxyURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,114 +17,339 @@ limitations under the License.
|
||||||
package notifier
|
package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/cache"
|
||||||
|
|
||||||
apiv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
apiv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Factory struct {
|
var (
|
||||||
URL string
|
// notifiers is a map of notifier names to factory functions.
|
||||||
ProxyURL string
|
notifiers = notifierMap{
|
||||||
Username string
|
// GenericProvider is the default notifier
|
||||||
Channel string
|
apiv1.GenericProvider: genericNotifierFunc,
|
||||||
Token string
|
apiv1.GenericHMACProvider: genericHMACNotifierFunc,
|
||||||
Headers map[string]string
|
apiv1.SlackProvider: slackNotifierFunc,
|
||||||
CertPool *x509.CertPool
|
apiv1.DiscordProvider: discordNotifierFunc,
|
||||||
Password string
|
apiv1.RocketProvider: rocketNotifierFunc,
|
||||||
ProviderUID string
|
apiv1.MSTeamsProvider: msteamsNotifierFunc,
|
||||||
|
apiv1.GoogleChatProvider: googleChatNotifierFunc,
|
||||||
|
apiv1.GooglePubSubProvider: googlePubSubNotifierFunc,
|
||||||
|
apiv1.WebexProvider: webexNotifierFunc,
|
||||||
|
apiv1.SentryProvider: sentryNotifierFunc,
|
||||||
|
apiv1.AzureEventHubProvider: azureEventHubNotifierFunc,
|
||||||
|
apiv1.TelegramProvider: telegramNotifierFunc,
|
||||||
|
apiv1.LarkProvider: larkNotifierFunc,
|
||||||
|
apiv1.Matrix: matrixNotifierFunc,
|
||||||
|
apiv1.OpsgenieProvider: opsgenieNotifierFunc,
|
||||||
|
apiv1.AlertManagerProvider: alertmanagerNotifierFunc,
|
||||||
|
apiv1.GrafanaProvider: grafanaNotifierFunc,
|
||||||
|
apiv1.PagerDutyProvider: pagerDutyNotifierFunc,
|
||||||
|
apiv1.DataDogProvider: dataDogNotifierFunc,
|
||||||
|
apiv1.NATSProvider: natsNotifierFunc,
|
||||||
|
apiv1.GitHubProvider: gitHubNotifierFunc,
|
||||||
|
apiv1.GitHubDispatchProvider: gitHubDispatchNotifierFunc,
|
||||||
|
apiv1.GitLabProvider: gitLabNotifierFunc,
|
||||||
|
apiv1.GiteaProvider: giteaNotifierFunc,
|
||||||
|
apiv1.BitbucketServerProvider: bitbucketServerNotifierFunc,
|
||||||
|
apiv1.BitbucketProvider: bitbucketNotifierFunc,
|
||||||
|
apiv1.AzureDevOpsProvider: azureDevOpsNotifierFunc,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// notifierMap is a map of provider names to notifier factory functions
|
||||||
|
type notifierMap map[string]factoryFunc
|
||||||
|
|
||||||
|
// factoryFunc is a factory function that creates a new notifier
|
||||||
|
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
|
||||||
|
// 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 *cache.TokenCache
|
||||||
|
TokenClient client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFactory(url string,
|
type Factory struct {
|
||||||
proxy string,
|
notifierOptions
|
||||||
username string,
|
}
|
||||||
channel string,
|
|
||||||
token string,
|
// Option represents a functional option for configuring a notifier.
|
||||||
headers map[string]string,
|
type Option func(*notifierOptions)
|
||||||
certPool *x509.CertPool,
|
|
||||||
password string,
|
// WithProxyURL sets the proxy URL for the notifier.
|
||||||
providerUID string) *Factory {
|
func WithProxyURL(url string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.ProxyURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUsername sets the username for the notifier.
|
||||||
|
func WithUsername(username string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.Username = username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChannel sets the channel for the notifier.
|
||||||
|
func WithChannel(channel string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.Channel = channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithToken sets the token for the notifier.
|
||||||
|
func WithToken(token string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.Token = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHeaders sets the headers for the notifier.
|
||||||
|
func WithHeaders(headers map[string]string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.Headers = headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCertPool sets the certificate pool for the notifier.
|
||||||
|
func WithCertPool(certPool *x509.CertPool) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.CertPool = certPool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
o.Password = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCommitStatus sets the custom commit status for the notifier.
|
||||||
|
func WithCommitStatus(commitStatus string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.CommitStatus = commitStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithProviderName sets the provider name for the notifier.
|
||||||
|
func WithProviderName(name string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.ProviderName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithProviderNamespace sets the provider namespace for the notifier.
|
||||||
|
func WithProviderNamespace(namespace string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.ProviderNamespace = namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSecretData sets the secret data for the notifier.
|
||||||
|
func WithSecretData(data map[string][]byte) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.SecretData = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTokenCache sets the token cache for the notifier.
|
||||||
|
func WithTokenCache(cache *cache.TokenCache) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.TokenCache = cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTokenClient sets the token client for the notifier.
|
||||||
|
func WithTokenClient(kubeClient client.Client) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.TokenClient = kubeClient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceAccount sets the service account for the notifier.
|
||||||
|
func WithServiceAccount(serviceAccountName string) Option {
|
||||||
|
return func(o *notifierOptions) {
|
||||||
|
o.ServiceAccountName = serviceAccountName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
return &Factory{
|
return &Factory{
|
||||||
URL: url,
|
notifierOptions: options,
|
||||||
ProxyURL: proxy,
|
|
||||||
Channel: channel,
|
|
||||||
Username: username,
|
|
||||||
Token: token,
|
|
||||||
Headers: headers,
|
|
||||||
CertPool: certPool,
|
|
||||||
Password: password,
|
|
||||||
ProviderUID: providerUID,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Factory) Notifier(provider string) (Interface, error) {
|
func (f Factory) Notifier(provider string) (Interface, error) {
|
||||||
if f.URL == "" {
|
notifier, ok := notifiers[provider]
|
||||||
return &NopNotifier{}, nil
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("provider %s not supported", provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
var n Interface
|
return notifier(f.notifierOptions)
|
||||||
var err error
|
}
|
||||||
switch provider {
|
|
||||||
case apiv1.GenericProvider:
|
func genericNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewForwarder(f.URL, f.ProxyURL, f.Headers, f.CertPool, nil)
|
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, nil)
|
||||||
case apiv1.GenericHMACProvider:
|
}
|
||||||
n, err = NewForwarder(f.URL, f.ProxyURL, f.Headers, f.CertPool, []byte(f.Token))
|
|
||||||
case apiv1.SlackProvider:
|
func genericHMACNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewSlack(f.URL, f.ProxyURL, f.Token, f.CertPool, f.Username, f.Channel)
|
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, []byte(opts.Token))
|
||||||
case apiv1.DiscordProvider:
|
}
|
||||||
n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel)
|
|
||||||
case apiv1.RocketProvider:
|
func slackNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewRocket(f.URL, f.ProxyURL, f.CertPool, f.Username, f.Channel)
|
return NewSlack(opts.URL, opts.ProxyURL, opts.Token, opts.TLSConfig, opts.Username, opts.Channel)
|
||||||
case apiv1.MSTeamsProvider:
|
}
|
||||||
n, err = NewMSTeams(f.URL, f.ProxyURL, f.CertPool)
|
|
||||||
case apiv1.GitHubProvider:
|
func discordNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewGitHub(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
return NewDiscord(opts.URL, opts.ProxyURL, opts.Username, opts.Channel)
|
||||||
case apiv1.GitHubDispatchProvider:
|
}
|
||||||
n, err = NewGitHubDispatch(f.URL, f.Token, f.CertPool)
|
|
||||||
case apiv1.GitLabProvider:
|
func rocketNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewGitLab(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
return NewRocket(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Username, opts.Channel)
|
||||||
case apiv1.GiteaProvider:
|
}
|
||||||
n, err = NewGitea(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
|
||||||
case apiv1.BitbucketServerProvider:
|
func msteamsNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewBitbucketServer(f.ProviderUID, f.URL, f.Token, f.CertPool, f.Username, f.Password)
|
return NewMSTeams(opts.URL, opts.ProxyURL, opts.TLSConfig)
|
||||||
case apiv1.BitbucketProvider:
|
}
|
||||||
n, err = NewBitbucket(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
|
||||||
case apiv1.AzureDevOpsProvider:
|
func googleChatNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewAzureDevOps(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
return NewGoogleChat(opts.URL, opts.ProxyURL)
|
||||||
case apiv1.GoogleChatProvider:
|
}
|
||||||
n, err = NewGoogleChat(f.URL, f.ProxyURL)
|
|
||||||
case apiv1.GooglePubSubProvider:
|
func googlePubSubNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewGooglePubSub(f.URL, f.Channel, f.Token, f.Headers)
|
return NewGooglePubSub(opts.URL, opts.Channel, opts.Token, opts.Headers)
|
||||||
case apiv1.WebexProvider:
|
}
|
||||||
n, err = NewWebex(f.URL, f.ProxyURL, f.CertPool, f.Channel, f.Token)
|
|
||||||
case apiv1.SentryProvider:
|
func webexNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewSentry(f.CertPool, f.URL, f.Channel)
|
return NewWebex(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Channel, opts.Token)
|
||||||
case apiv1.AzureEventHubProvider:
|
}
|
||||||
n, err = NewAzureEventHub(f.URL, f.Token, f.Channel)
|
|
||||||
case apiv1.TelegramProvider:
|
func sentryNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewTelegram(f.Channel, f.Token)
|
return NewSentry(opts.CertPool, opts.URL, opts.Channel)
|
||||||
case apiv1.LarkProvider:
|
}
|
||||||
n, err = NewLark(f.URL)
|
|
||||||
case apiv1.Matrix:
|
func azureEventHubNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewMatrix(f.URL, f.Token, f.Channel, f.CertPool)
|
return NewAzureEventHub(opts.Context, opts.URL, opts.Token, opts.Channel, opts.ProxyURL, opts.ServiceAccountName, opts.ProviderName, opts.ProviderNamespace, opts.TokenClient, opts.TokenCache)
|
||||||
case apiv1.OpsgenieProvider:
|
}
|
||||||
n, err = NewOpsgenie(f.URL, f.ProxyURL, f.CertPool, f.Token)
|
|
||||||
case apiv1.AlertManagerProvider:
|
func telegramNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewAlertmanager(f.URL, f.ProxyURL, f.CertPool)
|
return NewTelegram(opts.ProxyURL, opts.Channel, opts.Token)
|
||||||
case apiv1.GrafanaProvider:
|
}
|
||||||
n, err = NewGrafana(f.URL, f.ProxyURL, f.Token, f.CertPool, f.Username, f.Password)
|
|
||||||
case apiv1.PagerDutyProvider:
|
func larkNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewPagerDuty(f.URL, f.ProxyURL, f.CertPool, f.Channel)
|
return NewLark(opts.URL)
|
||||||
case apiv1.DataDogProvider:
|
}
|
||||||
n, err = NewDataDog(f.URL, f.ProxyURL, f.CertPool, f.Token)
|
|
||||||
case apiv1.NATSProvider:
|
func matrixNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
n, err = NewNATS(f.URL, f.Channel, f.Username, f.Password)
|
return NewMatrix(opts.URL, opts.Token, opts.Channel, opts.TLSConfig)
|
||||||
default:
|
}
|
||||||
err = fmt.Errorf("provider %s not supported", provider)
|
|
||||||
}
|
func opsgenieNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewOpsgenie(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Token)
|
||||||
if err != nil {
|
}
|
||||||
n = &NopNotifier{}
|
|
||||||
}
|
func alertmanagerNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
return n, err
|
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.TLSConfig, opts.Username, opts.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pagerDutyNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewPagerDuty(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataDogNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewDataDog(opts.URL, opts.ProxyURL, opts.CertPool, opts.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func natsNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewNATS(opts.URL, opts.Channel, opts.Username, opts.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitHubNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
if opts.Token == "" && opts.Password != "" {
|
||||||
|
opts.Token = opts.Password
|
||||||
|
}
|
||||||
|
return NewGitHub(opts.CommitStatus, opts.URL, opts.Token, opts.CertPool, opts.ProxyURL, opts.ProviderName, opts.ProviderNamespace, opts.SecretData, opts.TokenCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitHubDispatchNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
if opts.Token == "" && opts.Password != "" {
|
||||||
|
opts.Token = opts.Password
|
||||||
|
}
|
||||||
|
return NewGitHubDispatch(opts.URL, opts.Token, opts.CertPool, opts.ProxyURL, opts.ProviderName, opts.ProviderNamespace, opts.SecretData, opts.TokenCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitLabNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
if opts.Token == "" && opts.Password != "" {
|
||||||
|
opts.Token = opts.Password
|
||||||
|
}
|
||||||
|
return NewGitLab(opts.CommitStatus, opts.URL, opts.Token, opts.CertPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func giteaNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
if opts.Token == "" && opts.Password != "" {
|
||||||
|
opts.Token = opts.Password
|
||||||
|
}
|
||||||
|
return NewGitea(opts.CommitStatus, opts.URL, opts.ProxyURL, opts.Token, opts.CertPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitbucketServerNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewBitbucketServer(opts.CommitStatus, opts.URL, opts.Token, opts.CertPool, opts.Username, opts.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitbucketNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewBitbucket(opts.CommitStatus, opts.URL, opts.Token, opts.CertPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func azureDevOpsNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||||
|
return NewAzureDevOps(opts.CommitStatus, opts.URL, opts.Token, opts.CertPool)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -37,14 +37,14 @@ const NotificationHeader = "gotk-component"
|
||||||
// Forwarder is an implementation of the notification Interface that posts the
|
// Forwarder is an implementation of the notification Interface that posts the
|
||||||
// body as an HTTP request using an optional proxy.
|
// body as an HTTP request using an optional proxy.
|
||||||
type Forwarder struct {
|
type Forwarder struct {
|
||||||
URL string
|
URL string
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
CertPool *x509.CertPool
|
TLSConfig *tls.Config
|
||||||
HMACKey []byte
|
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 {
|
if _, err := url.ParseRequestURI(hookURL); err != nil {
|
||||||
return nil, fmt.Errorf("invalid hook URL %s: %w", hookURL, err)
|
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{
|
return &Forwarder{
|
||||||
URL: hookURL,
|
URL: hookURL,
|
||||||
ProxyURL: proxyURL,
|
ProxyURL: proxyURL,
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
CertPool: certPool,
|
HMACKey: hmacKey,
|
||||||
HMACKey: hmacKey,
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,18 +77,28 @@ func (f *Forwarder) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
}
|
}
|
||||||
sig = fmt.Sprintf("sha256=%s", sign(eventJSON, f.HMACKey))
|
sig = fmt.Sprintf("sha256=%s", sign(eventJSON, f.HMACKey))
|
||||||
}
|
}
|
||||||
err := postMessage(ctx, f.URL, f.ProxyURL, f.CertPool, event, func(req *retryablehttp.Request) {
|
|
||||||
req.Header.Set(NotificationHeader, event.ReportingController)
|
|
||||||
for key, val := range f.Headers {
|
|
||||||
req.Header.Set(key, val)
|
|
||||||
}
|
|
||||||
if sig != "" {
|
|
||||||
req.Header.Set("X-Signature", sig)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
opts := []postOption{
|
||||||
|
withRequestModifier(func(req *retryablehttp.Request) {
|
||||||
|
req.Header.Set(NotificationHeader, event.ReportingController)
|
||||||
|
for key, val := range f.Headers {
|
||||||
|
req.Header.Set(key, val)
|
||||||
|
}
|
||||||
|
if sig != "" {
|
||||||
|
req.Header.Set("X-Signature", sig)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
if f.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(f.ProxyURL))
|
||||||
|
}
|
||||||
|
if f.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(f.TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(ctx, f.URL, event, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -41,13 +41,13 @@ func Fuzz_Forwarder(f *testing.F) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
var tlsConfig tls.Config
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||||
|
|
||||||
header := make(map[string]string)
|
header := make(map[string]string)
|
||||||
_ = fuzz.NewConsumer(seed).FuzzMap(&header)
|
_ = 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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,22 +34,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Gitea struct {
|
type Gitea struct {
|
||||||
BaseURL string
|
BaseURL string
|
||||||
Token string
|
Token string
|
||||||
Owner string
|
Owner string
|
||||||
Repo string
|
Repo string
|
||||||
ProviderUID string
|
CommitStatus string
|
||||||
Client *gitea.Client
|
Client *gitea.Client
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Interface = &Gitea{}
|
var _ Interface = &Gitea{}
|
||||||
|
|
||||||
func NewGitea(providerUID string, addr string, token string, certPool *x509.CertPool) (*Gitea, error) {
|
func NewGitea(commitStatus string, addr string, proxyURL string, token string, certPool *x509.CertPool) (*Gitea, error) {
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
return nil, errors.New("gitea token cannot be empty")
|
return nil, errors.New("gitea token cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should never happen
|
||||||
|
if commitStatus == "" {
|
||||||
|
return nil, errors.New("commit status cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
host, id, err := parseGitAddress(addr)
|
host, id, err := parseGitAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed parsing Git URL: %w", err)
|
return nil, fmt.Errorf("failed parsing Git URL: %w", err)
|
||||||
|
@ -64,33 +69,39 @@ func NewGitea(providerUID string, addr string, token string, certPool *x509.Cert
|
||||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
return nil, fmt.Errorf("invalid repository id %q", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := gitea.NewClient(host, gitea.SetToken(token))
|
tr := &http.Transport{}
|
||||||
|
if certPool != nil {
|
||||||
|
tr.TLSClientConfig = &tls.Config{
|
||||||
|
RootCAs: certPool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyURL != "" {
|
||||||
|
parsedProxyURL, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid proxy URL")
|
||||||
|
}
|
||||||
|
tr.Proxy = http.ProxyURL(parsedProxyURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := gitea.NewClient(host, gitea.SetToken(token), gitea.SetHTTPClient(&http.Client{Transport: tr}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed creating Gitea client: %w", err)
|
return nil, fmt.Errorf("failed creating Gitea client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if certPool != nil {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: certPool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client.SetHTTPClient(&http.Client{Transport: tr})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Gitea{
|
return &Gitea{
|
||||||
BaseURL: host,
|
BaseURL: host,
|
||||||
Token: token,
|
Token: token,
|
||||||
Owner: idComponents[0],
|
Owner: idComponents[0],
|
||||||
Repo: idComponents[1],
|
Repo: idComponents[1],
|
||||||
ProviderUID: providerUID,
|
CommitStatus: commitStatus,
|
||||||
Client: client,
|
Client: client,
|
||||||
Debug: os.Getenv("NOTIFIER_GITEA_DEBUG") == "true",
|
Debug: os.Getenv("NOTIFIER_GITEA_DEBUG") == "true",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {
|
func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
revString, ok := event.GetRevision()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing revision metadata")
|
return errors.New("missing revision metadata")
|
||||||
}
|
}
|
||||||
|
@ -104,7 +115,7 @@ func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, desc := formatNameAndDescription(event)
|
_, desc := formatNameAndDescription(event)
|
||||||
id := generateCommitStatusID(g.ProviderUID, event)
|
id := g.CommitStatus
|
||||||
|
|
||||||
status := gitea.CreateStatusOption{
|
status := gitea.CreateStatusOption{
|
||||||
State: state,
|
State: state,
|
||||||
|
|
|
@ -18,7 +18,9 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
testproxy "github.com/fluxcd/notification-controller/tests/proxy"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -31,10 +33,20 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newTestServer returns an HTTP server mimicking parts of Gitea's API so that tests don't
|
// newTestHTTPServer returns an HTTP server mimicking parts of Gitea's API so that tests don't
|
||||||
// need to rely on 3rd-party components to be available (like the try.gitea.io server).
|
// need to rely on 3rd-party components to be available (like the try.gitea.io server).
|
||||||
func newTestServer(t *testing.T) *httptest.Server {
|
func newTestHTTPServer(t *testing.T) *httptest.Server {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return httptest.NewServer(newGiteaStubHandler(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTestHTTPSServer returns an HTTPS server mimicking parts of Gitea's API so that tests don't
|
||||||
|
// need to rely on 3rd-party components to be available (like the try.gitea.io server).
|
||||||
|
func newTestHTTPSServer(t *testing.T) *httptest.Server {
|
||||||
|
return httptest.NewTLSServer(newGiteaStubHandler(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGiteaStubHandler(t *testing.T) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/api/v1/version":
|
case "/api/v1/version":
|
||||||
fmt.Fprintf(w, `{"version":"1.18.3"}`)
|
fmt.Fprintf(w, `{"version":"1.18.3"}`)
|
||||||
|
@ -42,18 +54,76 @@ func newTestServer(t *testing.T) *httptest.Server {
|
||||||
fmt.Fprintf(w, "[]")
|
fmt.Fprintf(w, "[]")
|
||||||
case "/api/v1/repos/foo/bar/statuses/69b59063470310ebbd88a9156325322a124e55a3":
|
case "/api/v1/repos/foo/bar/statuses/69b59063470310ebbd88a9156325322a124e55a3":
|
||||||
fmt.Fprintf(w, "{}")
|
fmt.Fprintf(w, "{}")
|
||||||
|
case "/api/v1/repos/foo/bar/commits/8a9156325322a124e55a369b59063470310ebbd8/statuses":
|
||||||
|
fmt.Fprintf(w, "[]")
|
||||||
|
case "/api/v1/repos/foo/bar/statuses/8a9156325322a124e55a369b59063470310ebbd8":
|
||||||
|
fmt.Fprintf(w, "{}")
|
||||||
default:
|
default:
|
||||||
t.Logf("unknown %s request at %s", r.Method, r.URL.Path)
|
t.Logf("unknown %s request at %s", r.Method, r.URL.Path)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
return srv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGiteaBasic(t *testing.T) {
|
func TestNewGiteaBasic(t *testing.T) {
|
||||||
srv := newTestServer(t)
|
srv := newTestHTTPServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
g, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", srv.URL+"/foo/bar", "foobar", nil)
|
g, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", "", "foobar", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, g.Owner, "foo")
|
||||||
|
assert.Equal(t, g.Repo, "bar")
|
||||||
|
assert.Equal(t, g.BaseURL, srv.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGiteaWithCertPool(t *testing.T) {
|
||||||
|
srv := newTestHTTPSServer(t)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
certPool.AddCert(srv.Certificate())
|
||||||
|
|
||||||
|
g, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", "", "foobar", certPool)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, g.Owner, "foo")
|
||||||
|
assert.Equal(t, g.Repo, "bar")
|
||||||
|
assert.Equal(t, g.BaseURL, srv.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGiteaNoCertificate(t *testing.T) {
|
||||||
|
srv := newTestHTTPSServer(t)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
|
||||||
|
_, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", "", "foobar", certPool)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorContains(t, err, "tls: failed to verify certificate: x509: certificate signed by unknown authority")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGiteaWithProxyURL(t *testing.T) {
|
||||||
|
srv := newTestHTTPServer(t)
|
||||||
|
defer srv.Close()
|
||||||
|
proxyAddr, _ := testproxy.New(t)
|
||||||
|
proxyURL := fmt.Sprintf("http://%s", proxyAddr)
|
||||||
|
|
||||||
|
g, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", proxyURL, "foobar", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, g.Owner, "foo")
|
||||||
|
assert.Equal(t, g.Repo, "bar")
|
||||||
|
assert.Equal(t, g.BaseURL, srv.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGiteaWithProxyURLAndCertPool(t *testing.T) {
|
||||||
|
srv := newTestHTTPSServer(t)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
certPool.AddCert(srv.Certificate())
|
||||||
|
|
||||||
|
proxyAddr, _ := testproxy.New(t)
|
||||||
|
proxyURL := fmt.Sprintf("http://%s", proxyAddr)
|
||||||
|
|
||||||
|
g, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", proxyURL, "foobar", certPool)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, g.Owner, "foo")
|
assert.Equal(t, g.Owner, "foo")
|
||||||
assert.Equal(t, g.Repo, "bar")
|
assert.Equal(t, g.Repo, "bar")
|
||||||
|
@ -61,44 +131,88 @@ func TestNewGiteaBasic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGiteaInvalidUrl(t *testing.T) {
|
func TestNewGiteaInvalidUrl(t *testing.T) {
|
||||||
srv := newTestServer(t)
|
srv := newTestHTTPServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
_, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", srv.URL+"/foo/bar/baz", "foobar", nil)
|
_, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar/baz", "", "foobar", nil)
|
||||||
assert.ErrorContains(t, err, "invalid repository id")
|
assert.ErrorContains(t, err, "invalid repository id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewGiteaInvalidProxyUrl(t *testing.T) {
|
||||||
|
_, err := NewGitea("kustomization/gitops-system/0c9c2e41", "/foo/bar", "wrong\nURL", "foobar", nil)
|
||||||
|
assert.ErrorContains(t, err, "invalid proxy URL")
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewGiteaEmptyToken(t *testing.T) {
|
func TestNewGiteaEmptyToken(t *testing.T) {
|
||||||
srv := newTestServer(t)
|
srv := newTestHTTPServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
_, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", srv.URL+"/foo/bar", "", nil)
|
_, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", "", "", nil)
|
||||||
assert.ErrorContains(t, err, "gitea token cannot be empty")
|
assert.ErrorContains(t, err, "gitea token cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitea_Post(t *testing.T) {
|
func TestNewGiteaEmptyCommitStatus(t *testing.T) {
|
||||||
srv := newTestServer(t)
|
srv := newTestHTTPServer(t)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
g, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", srv.URL+"/foo/bar", "foobar", nil)
|
_, err := NewGitea("", srv.URL+"/foo/bar", "", "foobar", nil)
|
||||||
|
assert.ErrorContains(t, err, "commit status cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitea_Post(t *testing.T) {
|
||||||
|
srv := newTestHTTPServer(t)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
g, err := NewGitea("kustomization/gitops-system/0c9c2e41", srv.URL+"/foo/bar", "", "foobar", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
event := eventv1.Event{
|
for _, tt := range []struct {
|
||||||
InvolvedObject: corev1.ObjectReference{
|
name string
|
||||||
Kind: "Kustomization",
|
event eventv1.Event
|
||||||
Namespace: "flux-system",
|
}{
|
||||||
Name: "podinfo-repo",
|
{
|
||||||
|
name: "revision key",
|
||||||
|
event: eventv1.Event{
|
||||||
|
InvolvedObject: corev1.ObjectReference{
|
||||||
|
Kind: "Kustomization",
|
||||||
|
Namespace: "flux-system",
|
||||||
|
Name: "podinfo-repo",
|
||||||
|
},
|
||||||
|
Severity: "info",
|
||||||
|
Timestamp: metav1.Time{
|
||||||
|
Time: time.Now(),
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
eventv1.MetaRevisionKey: "main@sha1:69b59063470310ebbd88a9156325322a124e55a3",
|
||||||
|
},
|
||||||
|
Message: "Service/podinfo/podinfo configured",
|
||||||
|
Reason: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Severity: "info",
|
{
|
||||||
Timestamp: metav1.Time{
|
name: "origin revision key",
|
||||||
Time: time.Now(),
|
event: eventv1.Event{
|
||||||
|
InvolvedObject: corev1.ObjectReference{
|
||||||
|
Kind: "Kustomization",
|
||||||
|
Namespace: "flux-system",
|
||||||
|
Name: "podinfo-repo",
|
||||||
|
},
|
||||||
|
Severity: "info",
|
||||||
|
Timestamp: metav1.Time{
|
||||||
|
Time: time.Now(),
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
eventv1.MetaRevisionKey: "main@sha1:69b59063470310ebbd88a9156325322a124e55a3",
|
||||||
|
eventv1.MetaOriginRevisionKey: "main@sha1:8a9156325322a124e55a369b59063470310ebbd8",
|
||||||
|
},
|
||||||
|
Message: "Service/podinfo/podinfo configured",
|
||||||
|
Reason: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Metadata: map[string]string{
|
} {
|
||||||
eventv1.MetaRevisionKey: "main@sha1:69b59063470310ebbd88a9156325322a124e55a3",
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
},
|
err := g.Post(context.Background(), tt.event)
|
||||||
Message: "Service/podinfo/podinfo configured",
|
assert.NoError(t, err)
|
||||||
Reason: "",
|
})
|
||||||
}
|
}
|
||||||
err = g.Post(context.Background(), event)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,74 +18,44 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/go-github/v53/github"
|
"github.com/google/go-github/v64/github"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
|
|
||||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitHub struct {
|
type GitHub struct {
|
||||||
Owner string
|
Owner string
|
||||||
Repo string
|
Repo string
|
||||||
ProviderUID string
|
CommitStatus string
|
||||||
Client *github.Client
|
Client *github.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitHub(providerUID string, addr string, token string, certPool *x509.CertPool) (*GitHub, error) {
|
func NewGitHub(commitStatus string, addr string, token string, certPool *x509.CertPool,
|
||||||
if len(token) == 0 {
|
proxyURL string, providerName string, providerNamespace string, secretData map[string][]byte,
|
||||||
return nil, errors.New("github token cannot be empty")
|
tokenCache *cache.TokenCache) (*GitHub, error) {
|
||||||
|
|
||||||
|
// this should never happen
|
||||||
|
if commitStatus == "" {
|
||||||
|
return nil, errors.New("commit status cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
host, id, err := parseGitAddress(addr)
|
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool,
|
||||||
|
proxyURL, providerName, providerNamespace, secretData, tokenCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseUrl, err := url.Parse(host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
comp := strings.Split(id, "/")
|
|
||||||
if len(comp) != 2 {
|
|
||||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
|
|
||||||
tc := oauth2.NewClient(context.Background(), ts)
|
|
||||||
client := github.NewClient(tc)
|
|
||||||
if baseUrl.Host != "github.com" {
|
|
||||||
if certPool != nil {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: certPool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
hc := &http.Client{Transport: tr}
|
|
||||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, hc)
|
|
||||||
tc = oauth2.NewClient(ctx, ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err = github.NewEnterpriseClient(host, host, tc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create enterprise GitHub client: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GitHub{
|
return &GitHub{
|
||||||
Owner: comp[0],
|
Owner: repoInfo.owner,
|
||||||
Repo: comp[1],
|
Repo: repoInfo.repo,
|
||||||
ProviderUID: providerUID,
|
CommitStatus: commitStatus,
|
||||||
Client: client,
|
Client: repoInfo.client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +66,7 @@ func (g *GitHub) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
revString, ok := event.GetRevision()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing revision metadata")
|
return errors.New("missing revision metadata")
|
||||||
}
|
}
|
||||||
|
@ -110,7 +80,7 @@ func (g *GitHub) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, desc := formatNameAndDescription(event)
|
_, desc := formatNameAndDescription(event)
|
||||||
id := generateCommitStatusID(g.ProviderUID, event)
|
id := g.CommitStatus
|
||||||
status := &github.RepoStatus{
|
status := &github.RepoStatus{
|
||||||
State: &state,
|
State: &state,
|
||||||
Context: &id,
|
Context: &id,
|
||||||
|
|
|
@ -18,19 +18,14 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
|
"github.com/fluxcd/pkg/cache"
|
||||||
|
|
||||||
"github.com/google/go-github/v53/github"
|
"github.com/google/go-github/v64/github"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitHubDispatch struct {
|
type GitHubDispatch struct {
|
||||||
|
@ -39,51 +34,20 @@ type GitHubDispatch struct {
|
||||||
Client *github.Client
|
Client *github.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitHubDispatch(addr string, token string, certPool *x509.CertPool) (*GitHubDispatch, error) {
|
func NewGitHubDispatch(addr string, token string, certPool *x509.CertPool, proxyURL string,
|
||||||
if len(token) == 0 {
|
providerName string, providerNamespace string, secretData map[string][]byte,
|
||||||
return nil, errors.New("github token cannot be empty")
|
tokenCache *cache.TokenCache) (*GitHubDispatch, error) {
|
||||||
}
|
|
||||||
|
|
||||||
host, id, err := parseGitAddress(addr)
|
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool,
|
||||||
|
proxyURL, providerName, providerNamespace, secretData, tokenCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseUrl, err := url.Parse(host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
comp := strings.Split(id, "/")
|
|
||||||
if len(comp) != 2 {
|
|
||||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
|
|
||||||
tc := oauth2.NewClient(context.Background(), ts)
|
|
||||||
client := github.NewClient(tc)
|
|
||||||
if baseUrl.Host != "github.com" {
|
|
||||||
if certPool != nil {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: certPool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
hc := &http.Client{Transport: tr}
|
|
||||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, hc)
|
|
||||||
tc = oauth2.NewClient(ctx, ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err = github.NewEnterpriseClient(host, host, tc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create enterprise GitHub client: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GitHubDispatch{
|
return &GitHubDispatch{
|
||||||
Owner: comp[0],
|
Owner: repoInfo.owner,
|
||||||
Repo: comp[1],
|
Repo: repoInfo.repo,
|
||||||
Client: client,
|
Client: repoInfo.client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ func Fuzz_GitHub_Dispatch(f *testing.F) {
|
||||||
var cert x509.CertPool
|
var cert x509.CertPool
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||||
|
|
||||||
dispatch, err := NewGitHubDispatch(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
dispatch, err := NewGitHubDispatch(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert, "", "", "", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,16 +18,23 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
|
authgithub "github.com/fluxcd/pkg/git/github"
|
||||||
|
"github.com/fluxcd/pkg/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewGitHubDispatchBasic(t *testing.T) {
|
func TestNewGitHubDispatchBasic(t *testing.T) {
|
||||||
g, err := NewGitHubDispatch("https://github.com/foo/bar", "foobar", nil)
|
g, err := NewGitHubDispatch("https://github.com/foo/bar", "foobar", nil, "", "", "", nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Owner, "foo")
|
assert.Equal(t, g.Owner, "foo")
|
||||||
assert.Equal(t, g.Repo, "bar")
|
assert.Equal(t, g.Repo, "bar")
|
||||||
|
@ -35,7 +42,7 @@ func TestNewGitHubDispatchBasic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEnterpriseGitHubDispatchBasic(t *testing.T) {
|
func TestNewEnterpriseGitHubDispatchBasic(t *testing.T) {
|
||||||
g, err := NewGitHubDispatch("https://foobar.com/foo/bar", "foobar", nil)
|
g, err := NewGitHubDispatch("https://foobar.com/foo/bar", "foobar", nil, "", "", "", nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Owner, "foo")
|
assert.Equal(t, g.Owner, "foo")
|
||||||
assert.Equal(t, g.Repo, "bar")
|
assert.Equal(t, g.Repo, "bar")
|
||||||
|
@ -43,17 +50,98 @@ func TestNewEnterpriseGitHubDispatchBasic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitHubDispatchInvalidUrl(t *testing.T) {
|
func TestNewGitHubDispatchInvalidUrl(t *testing.T) {
|
||||||
_, err := NewGitHubDispatch("https://github.com/foo/bar/baz", "foobar", nil)
|
_, err := NewGitHubDispatch("https://github.com/foo/bar/baz", "foobar", nil, "", "", "", nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitHubDispatchEmptyToken(t *testing.T) {
|
func TestNewGitHubDispatchEmptyToken(t *testing.T) {
|
||||||
_, err := NewGitHubDispatch("https://github.com/foo/bar", "", nil)
|
_, err := NewGitHubDispatch("https://github.com/foo/bar", "", nil, "", "", "", nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewGithubDispatchProvider(t *testing.T) {
|
||||||
|
appID := "123"
|
||||||
|
installationID := "456"
|
||||||
|
kp, _ := ssh.GenerateKeyPair(ssh.RSA_4096)
|
||||||
|
expiresAt := time.Now().UTC().Add(time.Hour)
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
secretData map[string][]byte
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil provider, no token",
|
||||||
|
wantErr: errors.New("github token or github app details must be specified"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with no github options",
|
||||||
|
secretData: map[string][]byte{},
|
||||||
|
wantErr: errors.New("github token or github app details must be specified"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with missing app ID in options ",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppInstallationID": []byte(installationID),
|
||||||
|
"githubAppPrivateKey": kp.PrivateKey,
|
||||||
|
},
|
||||||
|
wantErr: errors.New("github token or github app details must be specified"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with missing app installation ID in options ",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppID": []byte(appID),
|
||||||
|
"githubAppPrivateKey": kp.PrivateKey,
|
||||||
|
},
|
||||||
|
wantErr: errors.New("app installation ID must be provided to use github app authentication"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with missing app private key in options ",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppID": []byte(appID),
|
||||||
|
"githubAppInstallationID": []byte(installationID),
|
||||||
|
},
|
||||||
|
wantErr: errors.New("private key must be provided to use github app authentication"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with complete app authentication information",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppID": []byte(appID),
|
||||||
|
"githubAppInstallationID": []byte(installationID),
|
||||||
|
"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()
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGitHubDispatch_PostUpdate(t *testing.T) {
|
func TestGitHubDispatch_PostUpdate(t *testing.T) {
|
||||||
githubDispatch, err := NewGitHubDispatch("https://github.com/foo/bar", "foobar", nil)
|
githubDispatch, err := NewGitHubDispatch("https://github.com/foo/bar", "foobar", nil, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
event := testEvent()
|
event := testEvent()
|
||||||
|
|
|
@ -30,14 +30,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Fuzz_GitHub(f *testing.F) {
|
func Fuzz_GitHub(f *testing.F) {
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"success","description":""}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"success","description":""}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/"}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/"}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
|
f.Fuzz(func(t *testing.T, commitStatus, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(response)
|
w.Write(response)
|
||||||
io.Copy(io.Discard, r.Body)
|
io.Copy(io.Discard, r.Body)
|
||||||
|
@ -48,7 +48,7 @@ func Fuzz_GitHub(f *testing.F) {
|
||||||
var cert x509.CertPool
|
var cert x509.CertPool
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||||
|
|
||||||
github, err := NewGitHub(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
github, err := NewGitHub(commitStatus, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert, "", "foo", "bar", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
Copyright 2025 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package notifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gogithub "github.com/google/go-github/v64/github"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
"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
|
||||||
|
type repoInfo struct {
|
||||||
|
owner string
|
||||||
|
repo string
|
||||||
|
client *gogithub.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGitHubAppOptions constructs the github app authentication options.
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("error parsing proxy URL '%s': %w", proxy, err)
|
||||||
|
}
|
||||||
|
githubOpts = append(githubOpts, github.WithProxyURL(proxyURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(githubOpts) > 0 && tokenCache != nil {
|
||||||
|
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 *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
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := github.New(githubOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
appToken, err := client.GetToken(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token = appToken.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
host, id, err := parseGitAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
comp := strings.Split(id, "/")
|
||||||
|
if len(comp) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid repository id %q", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUrl, err := url.Parse(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
|
||||||
|
tc := oauth2.NewClient(context.Background(), ts)
|
||||||
|
client := gogithub.NewClient(tc)
|
||||||
|
if baseUrl.Host != "github.com" {
|
||||||
|
if certPool != nil {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
RootCAs: certPool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
hc := &http.Client{Transport: tr}
|
||||||
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, hc)
|
||||||
|
tc = oauth2.NewClient(ctx, ts)
|
||||||
|
}
|
||||||
|
client, err = gogithub.NewClient(tc).WithEnterpriseURLs(host, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create enterprise GitHub client: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &repoInfo{comp[0], comp[1], client}, nil
|
||||||
|
}
|
|
@ -17,38 +17,134 @@ limitations under the License.
|
||||||
package notifier
|
package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-github/v53/github"
|
authgithub "github.com/fluxcd/pkg/git/github"
|
||||||
|
"github.com/fluxcd/pkg/ssh"
|
||||||
|
|
||||||
|
"github.com/google/go-github/v64/github"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewGitHubBasic(t *testing.T) {
|
func TestNewGitHubBasic(t *testing.T) {
|
||||||
g, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar", "foobar", nil)
|
g, err := NewGitHub("kustomization/gitops-system/0c9c2e41", "https://github.com/foo/bar", "foobar", nil, "", "", "", nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Owner, "foo")
|
assert.Equal(t, g.Owner, "foo")
|
||||||
assert.Equal(t, g.Repo, "bar")
|
assert.Equal(t, g.Repo, "bar")
|
||||||
assert.Equal(t, g.Client.BaseURL.Host, "api.github.com")
|
assert.Equal(t, g.Client.BaseURL.Host, "api.github.com")
|
||||||
|
assert.Equal(t, g.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEmterpriseGitHubBasic(t *testing.T) {
|
func TestNewEmterpriseGitHubBasic(t *testing.T) {
|
||||||
g, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://foobar.com/foo/bar", "foobar", nil)
|
g, err := NewGitHub("kustomization/gitops-system/0c9c2e41", "https://foobar.com/foo/bar", "foobar", nil, "", "", "", nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Owner, "foo")
|
assert.Equal(t, g.Owner, "foo")
|
||||||
assert.Equal(t, g.Repo, "bar")
|
assert.Equal(t, g.Repo, "bar")
|
||||||
assert.Equal(t, g.Client.BaseURL.Host, "foobar.com")
|
assert.Equal(t, g.Client.BaseURL.Host, "foobar.com")
|
||||||
|
assert.Equal(t, g.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitHubInvalidUrl(t *testing.T) {
|
func TestNewGitHubInvalidUrl(t *testing.T) {
|
||||||
_, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar/baz", "foobar", nil)
|
_, err := NewGitHub("kustomization/gitops-system/0c9c2e41", "https://github.com/foo/bar/baz", "foobar", nil, "", "", "", nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitHubEmptyToken(t *testing.T) {
|
func TestNewGitHubEmptyToken(t *testing.T) {
|
||||||
_, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar", "", nil)
|
_, err := NewGitHub("kustomization/gitops-system/0c9c2e41", "https://github.com/foo/bar", "", nil, "", "", "", nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewGitHubEmptyCommitStatus(t *testing.T) {
|
||||||
|
_, err := NewGitHub("", "https://github.com/foo/bar", "foobar", nil, "", "", "", nil, nil)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGithubProvider(t *testing.T) {
|
||||||
|
appID := "123"
|
||||||
|
installationID := "456"
|
||||||
|
kp, _ := ssh.GenerateKeyPair(ssh.RSA_4096)
|
||||||
|
expiresAt := time.Now().UTC().Add(time.Hour)
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
secretData map[string][]byte
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil provider, no token",
|
||||||
|
wantErr: errors.New("github token or github app details must be specified"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with no github options",
|
||||||
|
secretData: map[string][]byte{},
|
||||||
|
wantErr: errors.New("github token or github app details must be specified"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with missing app ID in options ",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppInstallationID": []byte(installationID),
|
||||||
|
"githubAppPrivateKey": kp.PrivateKey,
|
||||||
|
},
|
||||||
|
wantErr: errors.New("github token or github app details must be specified"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with missing app installation ID in options ",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppID": []byte(appID),
|
||||||
|
"githubAppPrivateKey": kp.PrivateKey,
|
||||||
|
},
|
||||||
|
wantErr: errors.New("app installation ID must be provided to use github app authentication"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with missing app private key in options ",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppID": []byte(appID),
|
||||||
|
"githubAppInstallationID": []byte(installationID),
|
||||||
|
},
|
||||||
|
wantErr: errors.New("private key must be provided to use github app authentication"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider with complete app authentication information",
|
||||||
|
secretData: map[string][]byte{
|
||||||
|
"githubAppID": []byte(appID),
|
||||||
|
"githubAppInstallationID": []byte(installationID),
|
||||||
|
"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()
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDuplicateGithubStatus(t *testing.T) {
|
func TestDuplicateGithubStatus(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
|
|
@ -24,19 +24,19 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
gitlab "gitlab.com/gitlab-org/api/client-go"
|
||||||
|
|
||||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitLab struct {
|
type GitLab struct {
|
||||||
Id string
|
Id string
|
||||||
ProviderUID string
|
CommitStatus string
|
||||||
Client *gitlab.Client
|
Client *gitlab.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitLab(providerUID string, addr string, token string, certPool *x509.CertPool) (*GitLab, error) {
|
func NewGitLab(commitStatus string, addr string, token string, certPool *x509.CertPool) (*GitLab, error) {
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
return nil, errors.New("gitlab token cannot be empty")
|
return nil, errors.New("gitlab token cannot be empty")
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,11 @@ func NewGitLab(providerUID string, addr string, token string, certPool *x509.Cer
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should never happen
|
||||||
|
if commitStatus == "" {
|
||||||
|
return nil, errors.New("commit status cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
opts := []gitlab.ClientOptionFunc{gitlab.WithBaseURL(host)}
|
opts := []gitlab.ClientOptionFunc{gitlab.WithBaseURL(host)}
|
||||||
if certPool != nil {
|
if certPool != nil {
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
|
@ -62,9 +67,9 @@ func NewGitLab(providerUID string, addr string, token string, certPool *x509.Cer
|
||||||
}
|
}
|
||||||
|
|
||||||
gitlab := &GitLab{
|
gitlab := &GitLab{
|
||||||
Id: id,
|
Id: id,
|
||||||
ProviderUID: providerUID,
|
CommitStatus: commitStatus,
|
||||||
Client: client,
|
Client: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
return gitlab, nil
|
return gitlab, nil
|
||||||
|
@ -77,7 +82,7 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
revString, ok := event.GetRevision()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing revision metadata")
|
return errors.New("missing revision metadata")
|
||||||
}
|
}
|
||||||
|
@ -91,7 +96,7 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, desc := formatNameAndDescription(event)
|
_, desc := formatNameAndDescription(event)
|
||||||
id := generateCommitStatusID(g.ProviderUID, event)
|
id := g.CommitStatus
|
||||||
status := &gitlab.CommitStatus{
|
status := &gitlab.CommitStatus{
|
||||||
Name: id,
|
Name: id,
|
||||||
SHA: rev,
|
SHA: rev,
|
||||||
|
@ -99,7 +104,9 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
Description: desc,
|
Description: desc,
|
||||||
}
|
}
|
||||||
|
|
||||||
getOpt := &gitlab.GetCommitStatusesOptions{}
|
getOpt := &gitlab.GetCommitStatusesOptions{
|
||||||
|
Name: &status.Name,
|
||||||
|
}
|
||||||
statuses, _, err := g.Client.Commits.GetCommitStatuses(g.Id, rev, getOpt, gitlab.WithContext(ctx))
|
statuses, _, err := g.Client.Commits.GetCommitStatuses(g.Id, rev, getOpt, gitlab.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to list commit status: %s", err)
|
return fmt.Errorf("unable to list commit status: %s", err)
|
||||||
|
|
|
@ -30,12 +30,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Fuzz_GitLab(f *testing.F) {
|
func Fuzz_GitLab(f *testing.F) {
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
|
||||||
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[]`))
|
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[]`))
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
|
f.Fuzz(func(t *testing.T, commitStatus, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(response)
|
w.Write(response)
|
||||||
io.Copy(io.Discard, r.Body)
|
io.Copy(io.Discard, r.Body)
|
||||||
|
@ -46,7 +46,7 @@ func Fuzz_GitLab(f *testing.F) {
|
||||||
var cert x509.CertPool
|
var cert x509.CertPool
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||||
|
|
||||||
gitLab, err := NewGitLab(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
gitLab, err := NewGitLab(commitStatus, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewGitLabBasic(t *testing.T) {
|
func TestNewGitLabBasic(t *testing.T) {
|
||||||
g, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://gitlab.com/foo/bar", "foobar", nil)
|
g, err := NewGitLab("kustomization/gitops-system/0c9c2e41", "https://gitlab.com/foo/bar", "foobar", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Id, "foo/bar")
|
assert.Equal(t, g.Id, "foo/bar")
|
||||||
|
assert.Equal(t, g.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitLabSubgroups(t *testing.T) {
|
func TestNewGitLabSubgroups(t *testing.T) {
|
||||||
g, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://gitlab.com/foo/bar/baz", "foobar", nil)
|
g, err := NewGitLab("kustomization/gitops-system/0c9c2e41", "https://gitlab.com/foo/bar/baz", "foobar", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Id, "foo/bar/baz")
|
assert.Equal(t, g.Id, "foo/bar/baz")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitLabSelfHosted(t *testing.T) {
|
func TestNewGitLabSelfHosted(t *testing.T) {
|
||||||
g, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com/foo/bar", "foo:bar", nil)
|
g, err := NewGitLab("kustomization/gitops-system/0c9c2e41", "https://example.com/foo/bar", "foo:bar", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, g.Id, "foo/bar")
|
assert.Equal(t, g.Id, "foo/bar")
|
||||||
assert.Equal(t, g.Client.BaseURL().Host, "example.com")
|
assert.Equal(t, g.Client.BaseURL().Host, "example.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGitLabEmptyToken(t *testing.T) {
|
func TestNewGitLabEmptyToken(t *testing.T) {
|
||||||
_, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://gitlab.com/foo/bar", "", nil)
|
_, err := NewGitLab("kustomization/gitops-system/0c9c2e41", "https://gitlab.com/foo/bar", "", nil)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGitLabEmptyCommitStatus(t *testing.T) {
|
||||||
|
_, err := NewGitLab("", "https://gitlab.com/foo/bar", "foobar", nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,8 +142,12 @@ func (s *GoogleChat) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
Cards: []GoogleChatCard{card},
|
Cards: []GoogleChatCard{card},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := postMessage(ctx, s.URL, s.ProxyURL, nil, payload)
|
var opts []postOption
|
||||||
if err != nil {
|
if s.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(s.ProxyURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -28,15 +28,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Grafana struct {
|
type Grafana struct {
|
||||||
URL string
|
URL string
|
||||||
Token string
|
Token string
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
CertPool *x509.CertPool
|
TLSConfig *tls.Config
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphiteAnnotation represents a Grafana API annotation in Graphite format
|
// GraphitePayload represents a Grafana API annotation in Graphite format
|
||||||
type GraphitePayload struct {
|
type GraphitePayload struct {
|
||||||
When int64 `json:"when"` //optional unix timestamp (ms)
|
When int64 `json:"when"` //optional unix timestamp (ms)
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
|
@ -44,19 +44,19 @@ type GraphitePayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGrafana validates the Grafana URL and returns a Grafana object
|
// 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)
|
_, err := url.ParseRequestURI(URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Grafana URL %s", URL)
|
return nil, fmt.Errorf("invalid Grafana URL %s", URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Grafana{
|
return &Grafana{
|
||||||
URL: URL,
|
URL: URL,
|
||||||
ProxyURL: proxyURL,
|
ProxyURL: proxyURL,
|
||||||
Token: token,
|
Token: token,
|
||||||
CertPool: certPool,
|
Username: username,
|
||||||
Username: username,
|
Password: password,
|
||||||
Password: password,
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,23 +71,37 @@ func (g *Grafana) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
// add tag to filter on grafana
|
// add tag to filter on grafana
|
||||||
sfields = append(sfields, "flux", event.ReportingController)
|
sfields = append(sfields, "flux", event.ReportingController)
|
||||||
for k, v := range event.Metadata {
|
for k, v := range event.Metadata {
|
||||||
sfields = append(sfields, fmt.Sprintf("%s: %s", k, v))
|
key := strings.ReplaceAll(k, ":", "|")
|
||||||
|
value := strings.ReplaceAll(v, ":", "|")
|
||||||
|
sfields = append(sfields, fmt.Sprintf("%s: %s", key, value))
|
||||||
}
|
}
|
||||||
|
sfields = append(sfields, fmt.Sprintf("kind: %s", event.InvolvedObject.Kind))
|
||||||
|
sfields = append(sfields, fmt.Sprintf("name: %s", event.InvolvedObject.Name))
|
||||||
|
sfields = append(sfields, fmt.Sprintf("namespace: %s", event.InvolvedObject.Namespace))
|
||||||
payload := GraphitePayload{
|
payload := GraphitePayload{
|
||||||
When: event.Timestamp.Unix(),
|
When: event.Timestamp.Unix(),
|
||||||
Text: fmt.Sprintf("%s/%s.%s", strings.ToLower(event.InvolvedObject.Kind), event.InvolvedObject.Name, event.InvolvedObject.Namespace),
|
Text: fmt.Sprintf("%s/%s.%s", strings.ToLower(event.InvolvedObject.Kind), event.InvolvedObject.Name, event.InvolvedObject.Namespace),
|
||||||
Tags: sfields,
|
Tags: sfields,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := postMessage(ctx, g.URL, g.ProxyURL, g.CertPool, payload, func(request *retryablehttp.Request) {
|
opts := []postOption{
|
||||||
if (g.Username != "" && g.Password != "") && g.Token == "" {
|
withRequestModifier(func(req *retryablehttp.Request) {
|
||||||
request.Header.Add("Authorization", "Basic "+basicAuth(g.Username, g.Password))
|
if (g.Username != "" && g.Password != "") && g.Token == "" {
|
||||||
}
|
req.Header.Add("Authorization", "Basic "+basicAuth(g.Username, g.Password))
|
||||||
if g.Token != "" {
|
}
|
||||||
request.Header.Add("Authorization", "Bearer "+g.Token)
|
if g.Token != "" {
|
||||||
}
|
req.Header.Add("Authorization", "Bearer "+g.Token)
|
||||||
})
|
}
|
||||||
if err != nil {
|
}),
|
||||||
|
}
|
||||||
|
if g.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(g.ProxyURL))
|
||||||
|
}
|
||||||
|
if g.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(g.TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(ctx, g.URL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -43,10 +43,10 @@ func Fuzz_Grafana(f *testing.F) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
var tlsConfig tls.Config
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = 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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,9 @@ func TestGrafana_Post(t *testing.T) {
|
||||||
require.Equal(t, "flux", payload.Tags[0])
|
require.Equal(t, "flux", payload.Tags[0])
|
||||||
require.Equal(t, "source-controller", payload.Tags[1])
|
require.Equal(t, "source-controller", payload.Tags[1])
|
||||||
require.Equal(t, "test: metadata", payload.Tags[2])
|
require.Equal(t, "test: metadata", payload.Tags[2])
|
||||||
|
require.Equal(t, "kind: GitRepository", payload.Tags[3])
|
||||||
|
require.Equal(t, "name: webapp", payload.Tags[4])
|
||||||
|
require.Equal(t, "namespace: gitops-system", payload.Tags[5])
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
|
|
|
@ -109,5 +109,5 @@ func (l *Lark) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
Card: card,
|
Card: card,
|
||||||
}
|
}
|
||||||
|
|
||||||
return postMessage(ctx, l.URL, "", nil, payload)
|
return postMessage(ctx, l.URL, payload)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package notifier
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -15,10 +15,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Matrix struct {
|
type Matrix struct {
|
||||||
Token string
|
Token string
|
||||||
URL string
|
URL string
|
||||||
RoomId string
|
RoomId string
|
||||||
CertPool *x509.CertPool
|
TLSConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type MatrixPayload struct {
|
type MatrixPayload struct {
|
||||||
|
@ -26,17 +26,17 @@ type MatrixPayload struct {
|
||||||
MsgType string `json:"msgtype"`
|
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)
|
_, err := url.ParseRequestURI(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Matrix homeserver URL %s: '%w'", serverURL, err)
|
return nil, fmt.Errorf("invalid Matrix homeserver URL %s: '%w'", serverURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Matrix{
|
return &Matrix{
|
||||||
URL: serverURL,
|
URL: serverURL,
|
||||||
RoomId: roomId,
|
RoomId: roomId,
|
||||||
Token: token,
|
Token: token,
|
||||||
CertPool: certPool,
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +65,17 @@ func (m *Matrix) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
MsgType: "m.text",
|
MsgType: "m.text",
|
||||||
}
|
}
|
||||||
|
|
||||||
err = postMessage(ctx, fullURL, "", m.CertPool, payload, func(request *retryablehttp.Request) {
|
opts := []postOption{
|
||||||
request.Method = http.MethodPut
|
withRequestModifier(func(req *retryablehttp.Request) {
|
||||||
request.Header.Add("Authorization", "Bearer "+m.Token)
|
req.Method = http.MethodPut
|
||||||
})
|
req.Header.Add("Authorization", "Bearer "+m.Token)
|
||||||
if err != nil {
|
}),
|
||||||
|
}
|
||||||
|
if m.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(m.TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(ctx, fullURL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -27,10 +27,10 @@ func Fuzz_Matrix(f *testing.F) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
var tlsConfig tls.Config
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = 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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -22,6 +22,9 @@ import (
|
||||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OperationPost is the operation name used in cache event metrics
|
||||||
|
const OperationPost = "post"
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
Post(ctx context.Context, event eventv1.Event) error
|
Post(ctx context.Context, event eventv1.Event) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -28,10 +28,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Opsgenie struct {
|
type Opsgenie struct {
|
||||||
URL string
|
URL string
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
CertPool *x509.CertPool
|
TLSConfig *tls.Config
|
||||||
ApiKey string
|
ApiKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpsgenieAlert struct {
|
type OpsgenieAlert struct {
|
||||||
|
@ -40,7 +40,7 @@ type OpsgenieAlert struct {
|
||||||
Details map[string]string `json:"details"`
|
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)
|
_, err := url.ParseRequestURI(hookURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Opsgenie hook URL %s: '%w'", hookURL, err)
|
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{
|
return &Opsgenie{
|
||||||
URL: hookURL,
|
URL: hookURL,
|
||||||
ProxyURL: proxyURL,
|
ProxyURL: proxyURL,
|
||||||
CertPool: certPool,
|
ApiKey: token,
|
||||||
ApiKey: token,
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,18 +65,34 @@ func (s *Opsgenie) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var details = make(map[string]string)
|
||||||
|
|
||||||
|
if event.Metadata != nil {
|
||||||
|
details = event.Metadata
|
||||||
|
}
|
||||||
|
details["severity"] = event.Severity
|
||||||
|
|
||||||
payload := OpsgenieAlert{
|
payload := OpsgenieAlert{
|
||||||
Message: event.InvolvedObject.Kind + "/" + event.InvolvedObject.Name,
|
Message: event.InvolvedObject.Kind + "/" + event.InvolvedObject.Name,
|
||||||
Description: event.Message,
|
Description: event.Message,
|
||||||
Details: event.Metadata,
|
Details: details,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := postMessage(ctx, s.URL, s.ProxyURL, s.CertPool, payload, func(req *retryablehttp.Request) {
|
opts := []postOption{
|
||||||
req.Header.Set("Authorization", "GenieKey "+s.ApiKey)
|
withRequestModifier(func(req *retryablehttp.Request) {
|
||||||
})
|
req.Header.Set("Authorization", "GenieKey "+s.ApiKey)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
if s.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(s.ProxyURL))
|
||||||
|
}
|
||||||
|
if s.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(s.TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -42,10 +42,10 @@ func Fuzz_OpsGenie(f *testing.F) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
var tlsConfig tls.Config
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = 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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,9 +39,32 @@ func TestOpsgenie_Post(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
opsgenie, err := NewOpsgenie(ts.URL, "", nil, "token")
|
tests := []struct {
|
||||||
require.NoError(t, err)
|
name string
|
||||||
|
event func() v1beta1.Event
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test event",
|
||||||
|
event: testEvent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test event with empty metadata",
|
||||||
|
event: func() v1beta1.Event {
|
||||||
|
events := testEvent()
|
||||||
|
events.Metadata = nil
|
||||||
|
return events
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err = opsgenie.Post(context.TODO(), testEvent())
|
for _, tt := range tests {
|
||||||
require.NoError(t, err)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
opsgenie, err := NewOpsgenie(ts.URL, "", nil, "token")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = opsgenie.Post(context.TODO(), tt.event())
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
@ -33,10 +33,10 @@ type PagerDuty struct {
|
||||||
Endpoint string
|
Endpoint string
|
||||||
RoutingKey string
|
RoutingKey string
|
||||||
ProxyURL 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)
|
URL, err := url.ParseRequestURI(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid PagerDuty endpoint URL %q: '%w'", endpoint, err)
|
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,
|
Endpoint: URL.Scheme + "://" + URL.Host,
|
||||||
RoutingKey: routingKey,
|
RoutingKey: routingKey,
|
||||||
ProxyURL: proxyURL,
|
ProxyURL: proxyURL,
|
||||||
CertPool: certPool,
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,19 +54,36 @@ func (p *PagerDuty) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
if event.HasMetadata(eventv1.MetaCommitStatusKey, eventv1.MetaCommitStatusUpdateValue) || event.HasReason(meta.ProgressingReason) {
|
if event.HasMetadata(eventv1.MetaCommitStatusKey, eventv1.MetaCommitStatusUpdateValue) || event.HasReason(meta.ProgressingReason) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
e := toPagerDutyV2Event(event, p.RoutingKey)
|
|
||||||
err := postMessage(ctx, p.Endpoint+"/v2/enqueue", p.ProxyURL, p.CertPool, e)
|
var opts []postOption
|
||||||
if err != nil {
|
if p.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(p.ProxyURL))
|
||||||
|
}
|
||||||
|
if p.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(p.TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(
|
||||||
|
ctx,
|
||||||
|
p.Endpoint+"/v2/enqueue",
|
||||||
|
toPagerDutyV2Event(event, p.RoutingKey),
|
||||||
|
opts...,
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("failed sending event: %w", err)
|
return fmt.Errorf("failed sending event: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a change event for info events
|
// Send a change event for info events
|
||||||
if event.Severity == eventv1.EventSeverityInfo {
|
if event.Severity == eventv1.EventSeverityInfo {
|
||||||
ce := toPagerDutyChangeEvent(event, p.RoutingKey)
|
if err := postMessage(
|
||||||
err = postMessage(ctx, p.Endpoint+"/v2/change/enqueue", p.ProxyURL, p.CertPool, ce)
|
ctx,
|
||||||
if err != nil {
|
p.Endpoint+"/v2/change/enqueue",
|
||||||
|
toPagerDutyChangeEvent(event, p.RoutingKey),
|
||||||
|
opts...,
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("failed sending change event: %w", err)
|
return fmt.Errorf("failed sending change event: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -32,10 +32,10 @@ func Fuzz_PagerDuty(f *testing.F) {
|
||||||
ts := httptest.NewServer(mux)
|
ts := httptest.NewServer(mux)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
var tlsConfig tls.Config
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||||
|
|
||||||
pd, err := NewPagerDuty(ts.URL, "", &cert, routingKey)
|
pd, err := NewPagerDuty(ts.URL, "", &tlsConfig, routingKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -28,26 +28,26 @@ import (
|
||||||
|
|
||||||
// Rocket holds the hook URL
|
// Rocket holds the hook URL
|
||||||
type Rocket struct {
|
type Rocket struct {
|
||||||
URL string
|
URL string
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
Username string
|
Username string
|
||||||
Channel string
|
Channel string
|
||||||
CertPool *x509.CertPool
|
TLSConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRocket validates the Rocket URL and returns a Rocket object
|
// 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)
|
_, err := url.ParseRequestURI(hookURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Rocket hook URL %s: '%w'", hookURL, err)
|
return nil, fmt.Errorf("invalid Rocket hook URL %s: '%w'", hookURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Rocket{
|
return &Rocket{
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
URL: hookURL,
|
URL: hookURL,
|
||||||
ProxyURL: proxyURL,
|
ProxyURL: proxyURL,
|
||||||
Username: username,
|
Username: username,
|
||||||
CertPool: certPool,
|
TLSConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,8 +83,15 @@ func (s *Rocket) Post(ctx context.Context, event eventv1.Event) error {
|
||||||
|
|
||||||
payload.Attachments = []SlackAttachment{a}
|
payload.Attachments = []SlackAttachment{a}
|
||||||
|
|
||||||
err := postMessage(ctx, s.URL, s.ProxyURL, s.CertPool, payload)
|
var opts []postOption
|
||||||
if err != nil {
|
if s.ProxyURL != "" {
|
||||||
|
opts = append(opts, withProxy(s.ProxyURL))
|
||||||
|
}
|
||||||
|
if s.TLSConfig != nil {
|
||||||
|
opts = append(opts, withTLSConfig(s.TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postMessage(ctx, s.URL, payload, opts...); err != nil {
|
||||||
return fmt.Errorf("postMessage failed: %w", err)
|
return fmt.Errorf("postMessage failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -18,7 +18,6 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -42,10 +41,7 @@ func Fuzz_Rocket(f *testing.F) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
var cert x509.CertPool
|
rocket, err := NewRocket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", nil, username, channel)
|
||||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
|
||||||
|
|
||||||
rocket, err := NewRocket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, username, channel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue