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
|
||||
|
||||
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"
|
||||
directory: "/"
|
||||
labels: ["area/ci", "dependencies"]
|
||||
schedule:
|
||||
# By default, this will be on a monday.
|
||||
interval: "weekly"
|
||||
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:
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
|
|
|
@ -13,3 +13,18 @@
|
|||
- name: backport:release/v1.1.x
|
||||
description: To be backported to release/v1.1.x
|
||||
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))
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- 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
|
||||
with:
|
||||
# Use token to allow workflows to be triggered for the created PR
|
||||
|
|
|
@ -11,11 +11,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
|
|
|
@ -12,14 +12,14 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- 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
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
id: cache
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
|
@ -27,18 +27,16 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-buildx-ghcache-
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Setup Kubernetes
|
||||
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
||||
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
|
||||
with:
|
||||
version: v0.20.0
|
||||
cluster_name: kind
|
||||
node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Run tests
|
||||
|
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
packages: write # for pushing and signing container images.
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Prepare
|
||||
|
@ -42,24 +42,24 @@ jobs:
|
|||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||
- 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
|
||||
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
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
- name: Generate images meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
with:
|
||||
images: |
|
||||
fluxcd/${{ env.CONTROLLER }}
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
|||
type=raw,value=${{ steps.prep.outputs.VERSION }}
|
||||
- name: Publish images
|
||||
id: build-push
|
||||
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
with:
|
||||
sbom: true
|
||||
provenance: true
|
||||
|
@ -79,7 +79,7 @@ jobs:
|
|||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
||||
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
||||
- name: Sign images
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: 1
|
||||
|
@ -92,14 +92,14 @@ jobs:
|
|||
mkdir -p config/release
|
||||
kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.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
|
||||
id: run-goreleaser
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --skip-validate
|
||||
args: release --clean --skip=validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Generate SLSA metadata
|
||||
|
@ -123,7 +123,7 @@ jobs:
|
|||
id-token: write # for creating OIDC tokens for signing.
|
||||
contents: write # for uploading attestations to GitHub releases.
|
||||
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:
|
||||
provenance-name: "provenance.intoto.jsonl"
|
||||
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
||||
|
@ -136,7 +136,7 @@ jobs:
|
|||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
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:
|
||||
image: ${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
|
@ -151,7 +151,7 @@ jobs:
|
|||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
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:
|
||||
image: ghcr.io/${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
|
|
|
@ -18,9 +18,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- 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:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
|
@ -31,22 +31,22 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
with:
|
||||
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://codeql.github.com/codeql-query-help/go/
|
||||
queries: security-and-quality
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
- 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:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
|
||||
with:
|
||||
# Configuration 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/).
|
||||
|
||||
changelog:
|
||||
skip: true
|
||||
disable: true
|
||||
|
||||
checksum:
|
||||
extra_files:
|
||||
|
|
382
CHANGELOG.md
382
CHANGELOG.md
|
@ -2,6 +2,388 @@
|
|||
|
||||
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
|
||||
|
||||
**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
|
||||
|
||||
Prerequisites:
|
||||
- Go >= 1.17
|
||||
- Go >= 1.24
|
||||
|
||||
You can run the test suite by simply doing:
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
ARG GO_VERSION=1.20
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GO_VERSION=1.24
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
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 --from=xx / /
|
||||
|
@ -30,7 +30,7 @@ COPY internal/ internal/
|
|||
ENV CGO_ENABLED=0
|
||||
RUN xx-go build -trimpath -a -o notification-controller main.go
|
||||
|
||||
FROM alpine:3.18
|
||||
FROM alpine:3.21
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
|
|
|
@ -7,4 +7,6 @@ as listed in
|
|||
|
||||
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
|
||||
# Produce CRDs that work back to Kubernetes 1.16
|
||||
CRD_OPTIONS ?= crd:crdVersions=v1
|
||||
SOURCE_VER ?= v1.1.2
|
||||
SOURCE_VER ?= v1.2.4
|
||||
|
||||
# Repository root based on Git metadata
|
||||
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(REPOSITORY_ROOT)/build
|
||||
|
||||
# API (doc) generation utilities
|
||||
CONTROLLER_GEN_VERSION ?= v0.12.0
|
||||
CONTROLLER_GEN_VERSION ?= v0.16.1
|
||||
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
|
||||
|
||||
# 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
|
||||
tidy:
|
||||
cd api; rm -f go.sum; go mod tidy -compat=1.20
|
||||
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.24
|
||||
|
||||
# Run go fmt against code
|
||||
fmt:
|
||||
|
@ -153,13 +153,13 @@ fuzz-native:
|
|||
./tests/fuzz/native_go_run.sh
|
||||
|
||||
# Find or download controller-gen
|
||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
||||
CONTROLLER_GEN = $(GOBIN)/controller-gen
|
||||
.PHONY: controller-gen
|
||||
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))
|
||||
|
||||
# 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
|
||||
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))
|
||||
|
@ -171,7 +171,7 @@ install-envtest: setup-envtest
|
|||
mkdir -p ${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
|
||||
setup-envtest: ## Download envtest-setup locally if necessary.
|
||||
$(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 ;\
|
||||
go mod init tmp ;\
|
||||
echo "Downloading $(2)" ;\
|
||||
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
|
||||
GOBIN=$(GOBIN) go install $(2) ;\
|
||||
rm -rf $$TMP_DIR ;\
|
||||
}
|
||||
endef
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
[](https://github.com/fluxcd/notification-controller/releases)
|
||||
|
||||
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).
|
||||
|
||||

|
||||
|
|
32
api/go.mod
32
api/go.mod
|
@ -1,31 +1,35 @@
|
|||
module github.com/fluxcd/notification-controller/api
|
||||
|
||||
go 1.20
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/meta v1.2.0
|
||||
k8s.io/apimachinery v0.28.4
|
||||
sigs.k8s.io/controller-runtime v0.16.3
|
||||
github.com/fluxcd/pkg/apis/meta v1.17.0
|
||||
k8s.io/apimachinery v0.33.2
|
||||
sigs.k8s.io/controller-runtime v0.21.0
|
||||
)
|
||||
|
||||
// Fix CVE-2022-28948
|
||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
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/google/gofuzz v1.2.0 // 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/reflect2 v1.0.2 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // 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/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fluxcd/pkg/apis/meta v1.2.0 h1:O766PzGAdMdQKybSflGL8oV0+GgCNIkdsxfalRyzeO8=
|
||||
github.com/fluxcd/pkg/apis/meta v1.2.0/go.mod h1:fU/Az9AoVyIxC0oI4ihG0NVMNnvrcCzdEym3wxjIQsc=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/fluxcd/pkg/apis/meta v1.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E=
|
||||
github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
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/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.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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
@ -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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
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/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
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/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -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-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.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.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-20191011141410-1b5146add898/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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
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=
|
||||
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
|
||||
k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
|
||||
k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
|
||||
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
|
||||
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
|
||||
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
|
||||
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
|
||||
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/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"
|
||||
NexusReceiver string = "nexus"
|
||||
ACRReceiver string = "acr"
|
||||
CDEventsReceiver string = "cdevents"
|
||||
)
|
||||
|
||||
// ReceiverSpec defines the desired state of the Receiver.
|
||||
type ReceiverSpec struct {
|
||||
// Type of webhook sender, used to determine
|
||||
// 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
|
||||
Type string `json:"type"`
|
||||
|
||||
|
@ -66,6 +67,16 @@ type ReceiverSpec struct {
|
|||
// +required
|
||||
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
|
||||
// to validate the payload authenticity.
|
||||
// +required
|
||||
|
@ -122,7 +133,6 @@ func (in *Receiver) GetInterval() time.Duration {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
|
|
|
@ -31,13 +31,13 @@ type CrossNamespaceObjectReference struct {
|
|||
// Name of the referent
|
||||
// If multiple resources are targeted `*` may be set.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=53
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// Namespace of the referent
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=53
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +kubebuilder:validation:Optional
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
|
|
@ -67,7 +67,6 @@ type AlertStatus struct {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="v1beta1 Alert is deprecated, upgrade to v1beta3"
|
||||
|
|
|
@ -110,7 +110,6 @@ type ProviderStatus struct {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="v1beta1 Provider is deprecated, upgrade to v1beta3"
|
||||
|
|
|
@ -97,7 +97,6 @@ func (in *Receiver) SetConditions(conditions []metav1.Condition) {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="v1beta1 Receiver is deprecated, upgrade to v1"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
|
|
@ -88,7 +88,6 @@ type AlertStatus struct {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="v1beta2 Alert is deprecated, upgrade to v1beta3"
|
||||
|
|
|
@ -131,7 +131,6 @@ type ProviderStatus struct {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="v1beta2 Provider is deprecated, upgrade to v1beta3"
|
||||
|
|
|
@ -129,7 +129,6 @@ func (in *Receiver) GetInterval() time.Duration {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="v1beta2 Receiver is deprecated, upgrade to v1"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
|
|
@ -64,8 +64,11 @@ type AlertSpec struct {
|
|||
ExclusionList []string `json:"exclusionList,omitempty"`
|
||||
|
||||
// Summary holds a short description of the impact and affected cluster.
|
||||
// Deprecated: Use EventMetadata instead.
|
||||
//
|
||||
// +kubebuilder:validation:MaxLength:=255
|
||||
// +optional
|
||||
// +deprecated
|
||||
Summary string `json:"summary,omitempty"`
|
||||
|
||||
// Suspend tells the controller to suspend subsequent
|
||||
|
@ -75,7 +78,6 @@ type AlertSpec struct {
|
|||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
||||
|
|
|
@ -55,12 +55,22 @@ const (
|
|||
)
|
||||
|
||||
// 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 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
|
||||
// +required
|
||||
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.
|
||||
// +kubebuilder:validation:MaxLength:=2048
|
||||
// +optional
|
||||
|
@ -87,33 +97,60 @@ type ProviderSpec struct {
|
|||
Timeout *metav1.Duration `json:"timeout,omitempty"`
|
||||
|
||||
// Proxy the HTTP/S address of the proxy server.
|
||||
// Deprecated: Use ProxySecretRef instead. Will be removed in v1.
|
||||
// +kubebuilder:validation:Pattern="^(http|https)://.*$"
|
||||
// +kubebuilder:validation:MaxLength:=2048
|
||||
// +kubebuilder:validation:Optional
|
||||
// +optional
|
||||
Proxy string `json:"proxy,omitempty"`
|
||||
|
||||
// ProxySecretRef specifies the Secret containing the proxy configuration
|
||||
// for this Provider. The Secret should contain an 'address' key with the
|
||||
// HTTP/S address of the proxy server. Optional 'username' and 'password'
|
||||
// keys can be provided for proxy authentication.
|
||||
// +optional
|
||||
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`
|
||||
|
||||
// SecretRef specifies the Secret containing the authentication
|
||||
// credentials for this Provider.
|
||||
// +optional
|
||||
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
|
||||
// CertSecretRef specifies the Secret containing
|
||||
// a PEM-encoded CA certificate (in the `ca.crt` key).
|
||||
// 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.
|
||||
// +optional
|
||||
ServiceAccountName string `json:"serviceAccountName,omitempty"`
|
||||
|
||||
// CertSecretRef specifies the Secret containing TLS certificates
|
||||
// for secure communication.
|
||||
//
|
||||
// Note: Support for the `caFile` key has
|
||||
// been deprecated.
|
||||
// Supported configurations:
|
||||
// - CA-only: Server authentication (provide ca.crt only)
|
||||
// - mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
|
||||
// - Client-only: Client authentication with system CA (provide tls.crt + tls.key only)
|
||||
//
|
||||
// Legacy keys "caFile", "certFile", "keyFile" are supported but deprecated. Use "ca.crt", "tls.crt", "tls.key" instead.
|
||||
//
|
||||
// +optional
|
||||
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
||||
|
||||
// Suspend tells the controller to suspend subsequent
|
||||
// events handling for this Provider.
|
||||
// +optional
|
||||
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:Namespaced
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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.
|
||||
func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) {
|
||||
*out = *in
|
||||
if in.Interval != nil {
|
||||
in, out := &in.Interval, &out.Interval
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
in, out := &in.Timeout, &out.Timeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.ProxySecretRef != nil {
|
||||
in, out := &in.ProxySecretRef, &out.ProxySecretRef
|
||||
*out = new(meta.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(meta.LocalObjectReference)
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: alerts.notification.toolkit.fluxcd.io
|
||||
spec:
|
||||
group: notification.toolkit.fluxcd.io
|
||||
|
@ -32,14 +32,19 @@ spec:
|
|||
description: Alert is the Schema for the alerts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. 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'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
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
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. 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'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
|
@ -49,7 +54,8 @@ spec:
|
|||
properties:
|
||||
eventSeverity:
|
||||
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.
|
||||
enum:
|
||||
- info
|
||||
|
@ -58,8 +64,9 @@ spec:
|
|||
eventSources:
|
||||
description: Filter events based on the involved objects.
|
||||
items:
|
||||
description: CrossNamespaceObjectReference contains enough information
|
||||
to let you locate the typed referenced object at cluster level
|
||||
description: |-
|
||||
CrossNamespaceObjectReference contains enough information to let you locate the
|
||||
typed referenced object at cluster level
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent
|
||||
|
@ -81,11 +88,10 @@ spec:
|
|||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: MatchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
description: |-
|
||||
MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
name:
|
||||
description: Name of the referent
|
||||
|
@ -98,6 +104,7 @@ spec:
|
|||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
|
@ -120,8 +127,9 @@ spec:
|
|||
description: Short description of the impact and affected cluster.
|
||||
type: string
|
||||
suspend:
|
||||
description: This flag tells the controller to suspend subsequent
|
||||
events dispatching. Defaults to false.
|
||||
description: |-
|
||||
This flag tells the controller to suspend subsequent events dispatching.
|
||||
Defaults to false.
|
||||
type: boolean
|
||||
required:
|
||||
- eventSources
|
||||
|
@ -134,43 +142,35 @@ spec:
|
|||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
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 }"
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
|
@ -185,10 +185,6 @@ spec:
|
|||
type: string
|
||||
type:
|
||||
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
|
||||
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
|
||||
|
@ -228,14 +224,19 @@ spec:
|
|||
description: Alert is the Schema for the alerts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. 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'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
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
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. 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'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
|
@ -246,27 +247,30 @@ spec:
|
|||
eventMetadata:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 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 would override one already
|
||||
present on the original event as generated by the emitter, then
|
||||
the override doesn't happen, i.e. the original value is preserved,
|
||||
and an info log is printed.
|
||||
description: |-
|
||||
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
|
||||
would override one already present on the original event as generated by the emitter,
|
||||
then the override doesn't happen, i.e. the original value is preserved, and an info
|
||||
log is printed.
|
||||
type: object
|
||||
eventSeverity:
|
||||
default: info
|
||||
description: EventSeverity specifies how to filter events based on
|
||||
severity. If set to 'info' no events will be filtered.
|
||||
description: |-
|
||||
EventSeverity specifies how to filter events based on severity.
|
||||
If set to 'info' no events will be filtered.
|
||||
enum:
|
||||
- info
|
||||
- error
|
||||
type: string
|
||||
eventSources:
|
||||
description: EventSources specifies how to filter events based on
|
||||
the involved object kind, name and namespace.
|
||||
description: |-
|
||||
EventSources specifies how to filter events based
|
||||
on the involved object kind, name and namespace.
|
||||
items:
|
||||
description: CrossNamespaceObjectReference contains enough information
|
||||
to let you locate the typed referenced object at cluster level
|
||||
description: |-
|
||||
CrossNamespaceObjectReference contains enough information to let you locate the
|
||||
typed referenced object at cluster level
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent
|
||||
|
@ -288,21 +292,22 @@ spec:
|
|||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: MatchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed. MatchLabels requires the name to be set to `*`.
|
||||
description: |-
|
||||
MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
MatchLabels requires the name to be set to `*`.
|
||||
type: object
|
||||
name:
|
||||
description: Name of the referent If multiple resources are
|
||||
targeted `*` may be set.
|
||||
maxLength: 53
|
||||
description: |-
|
||||
Name of the referent
|
||||
If multiple resources are targeted `*` may be set.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the referent
|
||||
maxLength: 53
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
|
@ -311,13 +316,15 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
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.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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.
|
||||
items:
|
||||
type: string
|
||||
|
@ -338,8 +345,9 @@ spec:
|
|||
maxLength: 255
|
||||
type: string
|
||||
suspend:
|
||||
description: Suspend tells the controller to suspend subsequent events
|
||||
handling for this Alert.
|
||||
description: |-
|
||||
Suspend tells the controller to suspend subsequent
|
||||
events handling for this Alert.
|
||||
type: boolean
|
||||
required:
|
||||
- eventSources
|
||||
|
@ -353,43 +361,35 @@ spec:
|
|||
conditions:
|
||||
description: Conditions holds the conditions for the Alert.
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
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 }"
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
|
@ -404,10 +404,6 @@ spec:
|
|||
type: string
|
||||
type:
|
||||
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
|
||||
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
|
||||
|
@ -420,9 +416,10 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
lastHandledReconcileAt:
|
||||
description: LastHandledReconcileAt holds the value of the most recent
|
||||
reconcile request value, so a change of the annotation value can
|
||||
be detected.
|
||||
description: |-
|
||||
LastHandledReconcileAt holds the value of the most recent
|
||||
reconcile request value, so a change of the annotation value
|
||||
can be detected.
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: ObservedGeneration is the last observed generation.
|
||||
|
@ -444,14 +441,19 @@ spec:
|
|||
description: Alert is the Schema for the alerts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. 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'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
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
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. 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'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
|
@ -462,27 +464,30 @@ spec:
|
|||
eventMetadata:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 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 would override one already
|
||||
present on the original event as generated by the emitter, then
|
||||
the override doesn't happen, i.e. the original value is preserved,
|
||||
and an info log is printed.
|
||||
description: |-
|
||||
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
|
||||
would override one already present on the original event as generated by the emitter,
|
||||
then the override doesn't happen, i.e. the original value is preserved, and an info
|
||||
log is printed.
|
||||
type: object
|
||||
eventSeverity:
|
||||
default: info
|
||||
description: EventSeverity specifies how to filter events based on
|
||||
severity. If set to 'info' no events will be filtered.
|
||||
description: |-
|
||||
EventSeverity specifies how to filter events based on severity.
|
||||
If set to 'info' no events will be filtered.
|
||||
enum:
|
||||
- info
|
||||
- error
|
||||
type: string
|
||||
eventSources:
|
||||
description: EventSources specifies how to filter events based on
|
||||
the involved object kind, name and namespace.
|
||||
description: |-
|
||||
EventSources specifies how to filter events based
|
||||
on the involved object kind, name and namespace.
|
||||
items:
|
||||
description: CrossNamespaceObjectReference contains enough information
|
||||
to let you locate the typed referenced object at cluster level
|
||||
description: |-
|
||||
CrossNamespaceObjectReference contains enough information to let you locate the
|
||||
typed referenced object at cluster level
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent
|
||||
|
@ -504,21 +509,22 @@ spec:
|
|||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: MatchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed. MatchLabels requires the name to be set to `*`.
|
||||
description: |-
|
||||
MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
MatchLabels requires the name to be set to `*`.
|
||||
type: object
|
||||
name:
|
||||
description: Name of the referent If multiple resources are
|
||||
targeted `*` may be set.
|
||||
maxLength: 53
|
||||
description: |-
|
||||
Name of the referent
|
||||
If multiple resources are targeted `*` may be set.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the referent
|
||||
maxLength: 53
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
|
@ -527,13 +533,15 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
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.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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.
|
||||
items:
|
||||
type: string
|
||||
|
@ -549,13 +557,15 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
summary:
|
||||
description: Summary holds a short description of the impact and affected
|
||||
cluster.
|
||||
description: |-
|
||||
Summary holds a short description of the impact and affected cluster.
|
||||
Deprecated: Use EventMetadata instead.
|
||||
maxLength: 255
|
||||
type: string
|
||||
suspend:
|
||||
description: Suspend tells the controller to suspend subsequent events
|
||||
handling for this Alert.
|
||||
description: |-
|
||||
Suspend tells the controller to suspend subsequent
|
||||
events handling for this Alert.
|
||||
type: boolean
|
||||
required:
|
||||
- eventSources
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: providers.notification.toolkit.fluxcd.io
|
||||
spec:
|
||||
group: notification.toolkit.fluxcd.io
|
||||
|
@ -32,14 +32,19 @@ spec:
|
|||
description: Provider is the Schema for the providers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. 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'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
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
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. 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'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
|
@ -51,7 +56,8 @@ spec:
|
|||
pattern: ^(http|https)://
|
||||
type: string
|
||||
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`)
|
||||
properties:
|
||||
name:
|
||||
|
@ -68,7 +74,8 @@ spec:
|
|||
pattern: ^(http|https)://
|
||||
type: string
|
||||
secretRef:
|
||||
description: Secret reference containing the provider webhook URL
|
||||
description: |-
|
||||
Secret reference containing the provider webhook URL
|
||||
using "address" as data key
|
||||
properties:
|
||||
name:
|
||||
|
@ -78,8 +85,9 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
suspend:
|
||||
description: This flag tells the controller to suspend subsequent
|
||||
events handling. Defaults to false.
|
||||
description: |-
|
||||
This flag tells the controller to suspend subsequent events handling.
|
||||
Defaults to false.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout for sending alerts to the provider.
|
||||
|
@ -123,43 +131,35 @@ spec:
|
|||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
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 }"
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
|
@ -174,10 +174,6 @@ spec:
|
|||
type: string
|
||||
type:
|
||||
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
|
||||
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
|
||||
|
@ -217,14 +213,19 @@ spec:
|
|||
description: Provider is the Schema for the providers API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. 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'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
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
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. 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'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
|
@ -232,17 +233,20 @@ spec:
|
|||
description: ProviderSpec defines the desired state of the Provider.
|
||||
properties:
|
||||
address:
|
||||
description: Address specifies the endpoint, in a generic sense, to
|
||||
where alerts are sent. What kind of endpoint depends on the specific
|
||||
Provider type being used. For the generic Provider, for example,
|
||||
this is an HTTP/S address. For other Provider types this could be
|
||||
a project ID or a namespace.
|
||||
description: |-
|
||||
Address specifies the endpoint, in a generic sense, to where alerts are sent.
|
||||
What kind of endpoint depends on the specific Provider type being used.
|
||||
For the generic Provider, for example, this is an HTTP/S address.
|
||||
For other Provider types this could be a project ID or a namespace.
|
||||
maxLength: 2048
|
||||
type: string
|
||||
certSecretRef:
|
||||
description: "CertSecretRef specifies the Secret containing a PEM-encoded
|
||||
CA certificate (in the `ca.crt` key). \n Note: Support for the `caFile`
|
||||
key has been deprecated."
|
||||
description: |-
|
||||
CertSecretRef specifies the Secret containing
|
||||
a PEM-encoded CA certificate (in the `ca.crt` key).
|
||||
|
||||
Note: Support for the `caFile` key has
|
||||
been deprecated.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
|
@ -266,7 +270,8 @@ spec:
|
|||
pattern: ^(http|https)://.*$
|
||||
type: string
|
||||
secretRef:
|
||||
description: SecretRef specifies the Secret containing the authentication
|
||||
description: |-
|
||||
SecretRef specifies the Secret containing the authentication
|
||||
credentials for this Provider.
|
||||
properties:
|
||||
name:
|
||||
|
@ -276,8 +281,9 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
suspend:
|
||||
description: Suspend tells the controller to suspend subsequent events
|
||||
handling for this Provider.
|
||||
description: |-
|
||||
Suspend tells the controller to suspend subsequent
|
||||
events handling for this Provider.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout for sending alerts to the Provider.
|
||||
|
@ -328,43 +334,35 @@ spec:
|
|||
conditions:
|
||||
description: Conditions holds the conditions for the Provider.
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
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 }"
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
|
@ -379,10 +377,6 @@ spec:
|
|||
type: string
|
||||
type:
|
||||
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
|
||||
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
|
||||
|
@ -395,9 +389,10 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
lastHandledReconcileAt:
|
||||
description: LastHandledReconcileAt holds the value of the most recent
|
||||
reconcile request value, so a change of the annotation value can
|
||||
be detected.
|
||||
description: |-
|
||||
LastHandledReconcileAt holds the value of the most recent
|
||||
reconcile request value, so a change of the annotation value
|
||||
can be detected.
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: ObservedGeneration is the last reconciled generation.
|
||||
|
@ -419,14 +414,19 @@ spec:
|
|||
description: Provider is the Schema for the providers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. 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'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
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
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. 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'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
|
@ -434,17 +434,24 @@ spec:
|
|||
description: ProviderSpec defines the desired state of the Provider.
|
||||
properties:
|
||||
address:
|
||||
description: Address specifies the endpoint, in a generic sense, to
|
||||
where alerts are sent. What kind of endpoint depends on the specific
|
||||
Provider type being used. For the generic Provider, for example,
|
||||
this is an HTTP/S address. For other Provider types this could be
|
||||
a project ID or a namespace.
|
||||
description: |-
|
||||
Address specifies the endpoint, in a generic sense, to where alerts are sent.
|
||||
What kind of endpoint depends on the specific Provider type being used.
|
||||
For the generic Provider, for example, this is an HTTP/S address.
|
||||
For other Provider types this could be a project ID or a namespace.
|
||||
maxLength: 2048
|
||||
type: string
|
||||
certSecretRef:
|
||||
description: "CertSecretRef specifies the Secret containing a PEM-encoded
|
||||
CA certificate (in the `ca.crt` key). \n Note: Support for the `caFile`
|
||||
key has been deprecated."
|
||||
description: |-
|
||||
CertSecretRef specifies the Secret containing TLS certificates
|
||||
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:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
|
@ -457,13 +464,43 @@ spec:
|
|||
should be posted.
|
||||
maxLength: 2048
|
||||
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:
|
||||
description: Proxy the HTTP/S address of the proxy server.
|
||||
description: |-
|
||||
Proxy the HTTP/S address of the proxy server.
|
||||
Deprecated: Use ProxySecretRef instead. Will be removed in v1.
|
||||
maxLength: 2048
|
||||
pattern: ^(http|https)://.*$
|
||||
type: string
|
||||
proxySecretRef:
|
||||
description: |-
|
||||
ProxySecretRef specifies the Secret containing the proxy configuration
|
||||
for this Provider. The Secret should contain an 'address' key with the
|
||||
HTTP/S address of the proxy server. Optional 'username' and 'password'
|
||||
keys can be provided for proxy authentication.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
secretRef:
|
||||
description: SecretRef specifies the Secret containing the authentication
|
||||
description: |-
|
||||
SecretRef specifies the Secret containing the authentication
|
||||
credentials for this Provider.
|
||||
properties:
|
||||
name:
|
||||
|
@ -472,9 +509,17 @@ spec:
|
|||
required:
|
||||
- name
|
||||
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:
|
||||
description: Suspend tells the controller to suspend subsequent events
|
||||
handling for this Provider.
|
||||
description: |-
|
||||
Suspend tells the controller to suspend subsequent
|
||||
events handling for this Provider.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout for sending alerts to the Provider.
|
||||
|
@ -518,6 +563,12 @@ spec:
|
|||
required:
|
||||
- type
|
||||
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
|
||||
served: true
|
||||
storage: true
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,4 +6,4 @@ resources:
|
|||
images:
|
||||
- name: fluxcd/notification-controller
|
||||
newName: fluxcd/notification-controller
|
||||
newTag: v1.1.0
|
||||
newTag: v1.6.0
|
||||
|
|
|
@ -19,6 +19,12 @@ rules:
|
|||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts/token
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- image.fluxcd.io
|
||||
resources:
|
||||
|
@ -39,29 +45,7 @@ rules:
|
|||
- notification.toolkit.fluxcd.io
|
||||
resources:
|
||||
- alerts
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- notification.toolkit.fluxcd.io
|
||||
resources:
|
||||
- providers
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- notification.toolkit.fluxcd.io
|
||||
resources:
|
||||
- receivers
|
||||
verbs:
|
||||
- create
|
||||
|
@ -83,53 +67,8 @@ rules:
|
|||
- source.fluxcd.io
|
||||
resources:
|
||||
- buckets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- buckets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- gitrepositories
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- gitrepositories/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- helmrepositories
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- helmrepositories/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- ocirepositories
|
||||
verbs:
|
||||
- get
|
||||
|
@ -140,6 +79,9 @@ rules:
|
|||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
- buckets/status
|
||||
- gitrepositories/status
|
||||
- helmrepositories/status
|
||||
- ocirepositories/status
|
||||
verbs:
|
||||
- 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>
|
||||
<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>
|
||||
<em>
|
||||
<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>
|
||||
<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>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
|
|
|
@ -161,7 +161,8 @@ string
|
|||
</td>
|
||||
<td>
|
||||
<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>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -254,6 +255,21 @@ string
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<em>
|
||||
string
|
||||
|
@ -314,7 +330,25 @@ string
|
|||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Proxy the HTTP/S address of the proxy server.</p>
|
||||
<p>Proxy the HTTP/S address of the proxy server.
|
||||
Deprecated: Use ProxySecretRef instead. Will be removed in v1.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>proxySecretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ProxySecretRef specifies the Secret containing the proxy configuration
|
||||
for this Provider. The Secret should contain an ‘address’ key with the
|
||||
HTTP/S address of the proxy server. Optional ‘username’ and ‘password’
|
||||
keys can be provided for proxy authentication.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -334,6 +368,21 @@ credentials for this Provider.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<em>
|
||||
<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>
|
||||
<em>(Optional)</em>
|
||||
<p>CertSecretRef specifies the Secret containing
|
||||
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
|
||||
<p>Note: Support for the <code>caFile</code> key has
|
||||
been deprecated.</p>
|
||||
<p>CertSecretRef specifies the Secret containing TLS certificates
|
||||
for secure communication.</p>
|
||||
<p>Supported configurations:
|
||||
- CA-only: Server authentication (provide ca.crt only)
|
||||
- mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
|
||||
- Client-only: Client authentication with system CA (provide tls.crt + tls.key only)</p>
|
||||
<p>Legacy keys “caFile”, “certFile”, “keyFile” are supported but deprecated. Use “ca.crt”, “tls.crt”, “tls.key” instead.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -362,6 +414,22 @@ bool
|
|||
events handling for this Provider.</p>
|
||||
</td>
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -477,7 +545,8 @@ string
|
|||
</td>
|
||||
<td>
|
||||
<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>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -527,6 +596,21 @@ string
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<em>
|
||||
string
|
||||
|
@ -587,7 +671,25 @@ string
|
|||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Proxy the HTTP/S address of the proxy server.</p>
|
||||
<p>Proxy the HTTP/S address of the proxy server.
|
||||
Deprecated: Use ProxySecretRef instead. Will be removed in v1.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>proxySecretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ProxySecretRef specifies the Secret containing the proxy configuration
|
||||
for this Provider. The Secret should contain an ‘address’ key with the
|
||||
HTTP/S address of the proxy server. Optional ‘username’ and ‘password’
|
||||
keys can be provided for proxy authentication.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -607,6 +709,21 @@ credentials for this Provider.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<em>
|
||||
<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>
|
||||
<em>(Optional)</em>
|
||||
<p>CertSecretRef specifies the Secret containing
|
||||
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
|
||||
<p>Note: Support for the <code>caFile</code> key has
|
||||
been deprecated.</p>
|
||||
<p>CertSecretRef specifies the Secret containing TLS certificates
|
||||
for secure communication.</p>
|
||||
<p>Supported configurations:
|
||||
- CA-only: Server authentication (provide ca.crt only)
|
||||
- mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key)
|
||||
- Client-only: Client authentication with system CA (provide tls.crt + tls.key only)</p>
|
||||
<p>Legacy keys “caFile”, “certFile”, “keyFile” are supported but deprecated. Use “ca.crt”, “tls.crt”, “tls.key” instead.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -635,6 +755,22 @@ bool
|
|||
events handling for this Provider.</p>
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -134,6 +134,7 @@ handle the incoming webhook request.
|
|||
| [Nexus](#nexus) | `nexus` | ❌ |
|
||||
| [Azure Container Registry](#acr) | `acr` | ❌ |
|
||||
| [Google Container Registry](#gcr) | `gcr` | ❌ |
|
||||
| [CDEvents](#cdevents) | `cdevents` | ✅ |
|
||||
|
||||
#### Generic
|
||||
|
||||
|
@ -613,6 +614,35 @@ spec:
|
|||
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
|
||||
|
||||
`.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
|
||||
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
|
||||
|
||||
`.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
|
||||
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
|
||||
as the [Alert](alerts.md) object, preventing tenants from triggering
|
||||
as the Receiver object, preventing tenants from triggering
|
||||
reconciliations to another tenant's resources.
|
||||
|
||||
### Public Ingress considerations
|
||||
|
|
|
@ -368,7 +368,8 @@ and use `https://api.telegram.org/` as the api url.
|
|||
--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)
|
||||
|
||||
```yaml
|
||||
|
@ -379,7 +380,7 @@ metadata:
|
|||
namespace: flux-system
|
||||
spec:
|
||||
type: telegram
|
||||
channel: "@fluxtest" # or "-1557265138" (channel id)
|
||||
channel: "@fluxtest" # or "-1557265138" (channel id) or "-1552289257:1" (forum chat id with topic id)
|
||||
secretRef:
|
||||
name: telegram-token
|
||||
```
|
||||
|
|
|
@ -447,7 +447,7 @@ metadata:
|
|||
stringData:
|
||||
token: <DataDog API Key>
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta1
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
kind: Alert
|
||||
metadata:
|
||||
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.
|
||||
|
||||
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
|
||||
the username (`@username`) of the target channel.
|
||||
This can be a unique identifier (`-1234567890`) for the target chat,
|
||||
a unique identifier with the topic identifier (`-1234567890:1`) for the forum chat,
|
||||
or the username (`@username`) of the target channel.
|
||||
|
||||
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
|
||||
or [TLS certificates](#tls-certificates).
|
||||
|
@ -586,7 +587,7 @@ metadata:
|
|||
spec:
|
||||
type: telegram
|
||||
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:
|
||||
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 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
|
||||
|
||||
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
|
||||
namespace: flux-system
|
||||
spec:
|
||||
summary: "Cluster addons impacted in us-east-2"
|
||||
providerRef:
|
||||
name: slack-bot
|
||||
eventMetadata:
|
||||
summary: Cluster addons impacted
|
||||
env: prod
|
||||
cluster: my-cluster
|
||||
region: us-east-2
|
||||
eventSeverity: error
|
||||
eventSources:
|
||||
- kind: GitRepository
|
||||
|
@ -51,7 +55,7 @@ In the above example:
|
|||
all GitRepositories and Kustomizations in the `flux-system` namespace.
|
||||
- When an event with severity `error` is received, the controller posts
|
||||
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`.
|
||||
|
||||
|
@ -78,10 +82,15 @@ An Alert also needs a
|
|||
|
||||
### Summary
|
||||
|
||||
`.spec.summary` is an optional field to specify a short description of the
|
||||
impact and affected cluster.
|
||||
`.spec.summary` is an optional field to specify a short description of the impact.
|
||||
|
||||
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
|
||||
|
||||
|
@ -146,10 +155,11 @@ preventing tenants from subscribing to another tenant's events.
|
|||
### Event metadata
|
||||
|
||||
`.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
|
||||
would override one already present on the original event as generated by the emitter,
|
||||
then the override doesn't happen, i.e. the original value is preserved, and an info
|
||||
log is printed.
|
||||
the controller. This can be used for enhancing the context of the event, e.g. with
|
||||
cluster-level information.
|
||||
|
||||
For all the event metadata sources and their precedence order, please refer to
|
||||
[Event metadata from object annotations](#event-metadata-from-object-annotations).
|
||||
|
||||
#### Example
|
||||
|
||||
|
@ -168,9 +178,68 @@ spec:
|
|||
inclusionList:
|
||||
- ".*succeeded.*"
|
||||
eventMetadata:
|
||||
app.kubernetes.io/env: "production"
|
||||
app.kubernetes.io/cluster: "my-cluster"
|
||||
app.kubernetes.io/region: "us-east-1"
|
||||
env: production
|
||||
cluster: my-cluster
|
||||
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
|
||||
|
|
|
@ -11,7 +11,7 @@ install or upgrade [Flagger](https://github.com/fluxcd/flagger).
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: slack-bot
|
||||
|
@ -23,7 +23,7 @@ spec:
|
|||
secretRef:
|
||||
name: slack-bot-token
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Alert
|
||||
metadata:
|
||||
name: slack
|
||||
|
@ -109,7 +109,9 @@ The supported alerting providers are:
|
|||
| [WebEx](#webex) | `webex` |
|
||||
| [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 |
|
||||
|--------------------------------------------------------------|-------------------|
|
||||
|
@ -133,7 +135,7 @@ for example:
|
|||
```json
|
||||
{
|
||||
"involvedObject": {
|
||||
"apiVersion": "kustomize.toolkit.fluxcd.io/v1beta2",
|
||||
"apiVersion": "kustomize.toolkit.fluxcd.io/v1",
|
||||
"kind": "Kustomization",
|
||||
"name": "webapp",
|
||||
"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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -299,7 +301,7 @@ by adding the integration to each channel.
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: slack
|
||||
|
@ -328,7 +330,7 @@ and a `slack` Provider with a [Secret reference](#address-example).
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: slack
|
||||
|
@ -350,27 +352,30 @@ stringData:
|
|||
##### Microsoft Teams
|
||||
|
||||
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
|
||||
[connector message](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#example-of-connector-message),
|
||||
with the metadata attached as facts, and the involved object as summary.
|
||||
**Note:** If the Address host contains the suffix `.webhook.office.com`, the controller will imply that
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
This Provider type supports the configuration of a [proxy URL](#https-proxy)
|
||||
and/or [TLS certificates](#tls-certificates), but lacks support for
|
||||
and/or [certificate secret reference](#certificate-secret-reference), but lacks support for
|
||||
configuring a [Channel](#channel). This can be configured during the
|
||||
creation of the incoming webhook in Microsoft Teams.
|
||||
creation of the Incoming Webhook Workflow in Microsoft Teams.
|
||||
|
||||
###### Microsoft Teams example
|
||||
|
||||
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),
|
||||
and a `msteams` Provider with a [Secret reference](#address-example).
|
||||
`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 an `msteams` Provider with a [Secret reference](#secret-reference).
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: msteams
|
||||
|
@ -386,7 +391,7 @@ metadata:
|
|||
name: msteams-webhook
|
||||
namespace: default
|
||||
stringData:
|
||||
address: "https://xxx.webhook.office.com/..."
|
||||
address: https://prod-xxx.yyy.logic.azure.com:443/workflows/zzz/triggers/manual/paths/invoke?...
|
||||
```
|
||||
|
||||
##### 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).
|
||||
|
||||
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.
|
||||
|
||||
|
@ -410,7 +415,7 @@ set to a [DataDog API key](https://docs.datadoghq.com/account_management/api-app
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: datadog
|
||||
|
@ -429,7 +434,7 @@ metadata:
|
|||
stringData:
|
||||
token: <DataDog API Key>
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta1
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Alert
|
||||
metadata:
|
||||
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).
|
||||
|
||||
This Provider type supports the configuration of a [proxy URL](#https-proxy)
|
||||
and/or [TLS certificates](#tls-certificates), but lacks support for
|
||||
and/or [certificate secret reference](#certificate-secret-reference), but lacks support for
|
||||
configuring a [Channel](#channel). This can be configured [during the creation
|
||||
of the address](https://discord.com/developers/docs/resources/webhook#create-webhook)
|
||||
|
||||
|
@ -466,7 +471,7 @@ and a `discord` Provider with a [Secret reference](#secret-reference).
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: discord
|
||||
|
@ -502,7 +507,7 @@ The Provider's [Channel](#channel) is used to set the `environment` on the
|
|||
Sentry client.
|
||||
|
||||
This Provider type supports the configuration of
|
||||
[TLS certificates](#tls-certificates).
|
||||
[certificate secret reference](#certificate-secret-reference).
|
||||
|
||||
###### Sentry example
|
||||
|
||||
|
@ -512,7 +517,7 @@ and a `sentry` Provider with a [Secret reference](#secret-reference).
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: sentry
|
||||
|
@ -539,36 +544,37 @@ stringData:
|
|||
##### Telegram
|
||||
|
||||
When `.spec.type` is set to `telegram`, the controller will send a payload for
|
||||
an [Event](events.md#event-structure) to the provided Telegram [Address](#address).
|
||||
an [Event](events.md#event-structure) to the Telegram Bot API.
|
||||
|
||||
The Event will be formatted into a message string, with the metadata attached
|
||||
as a list of key-value pairs.
|
||||
|
||||
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
|
||||
the username (`@username`) of the target channel.
|
||||
This can be a unique identifier (`-1234567890`) for the target chat,
|
||||
a unique identifier with the topic identifier (`-1234567890:1`) for the forum chat,
|
||||
or the username (`@username`) of the target channel.
|
||||
|
||||
This Provider type does not support the configuration of a [proxy URL](#https-proxy)
|
||||
or [TLS certificates](#tls-certificates).
|
||||
This Provider type does not support the configuration of a [certificate secret reference](#certificate-secret-reference).
|
||||
|
||||
**Note:** The Telegram provider always ignores the `address` field and uses the default
|
||||
Telegram Bot API endpoint (`https://api.telegram.org`).
|
||||
|
||||
###### Telegram example
|
||||
|
||||
To configure a Provider for Telegram, create a Secret with [the `token`](#token-example)
|
||||
obtained from [the BotFather](https://core.telegram.org/bots#how-do-i-create-a-bot),
|
||||
and a `telegram` Provider with a [Secret reference](#secret-reference), and the
|
||||
`address` set to `https://api.telegram.org`.
|
||||
and a `telegram` Provider with a [Secret reference](#secret-reference).
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: telegram
|
||||
namespace: default
|
||||
spec:
|
||||
type: telegram
|
||||
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:
|
||||
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).
|
||||
|
||||
```yaml
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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).
|
||||
|
||||
```yaml
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
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).
|
||||
|
||||
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
|
||||
|
||||
|
@ -664,7 +670,7 @@ and a `rocket` Provider with a [Secret reference](#secret-reference).
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: rocket
|
||||
|
@ -694,7 +700,7 @@ and a `googlechat` Provider with a [Secret reference](#secret-reference).
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
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).
|
||||
|
||||
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
|
||||
|
||||
|
@ -748,7 +754,7 @@ YAML string-to-string dictionary, and a `googlepubsub` Provider with the associa
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -793,7 +799,7 @@ and a `opsgenie` Provider with a [Secret reference](#secret-reference) and the
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -842,7 +848,7 @@ When adding an integration for a service on PagerDuty, it is recommended to use
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: pagerduty
|
||||
|
@ -857,7 +863,7 @@ only those sources you want to trigger an incident for that service. For example
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Alert
|
||||
metadata:
|
||||
name: my-service-pagerduty
|
||||
|
@ -878,9 +884,10 @@ an [Event](events.md#event-structure) to the provided Prometheus Alertmanager
|
|||
[Address](#address).
|
||||
|
||||
The Event will be formatted into a `firing` [Prometheus Alertmanager
|
||||
alert](https://prometheus.io/docs/alerting/latest/notifications/#alert),
|
||||
with the metadata added to the `labels` fields, and the `message` (and optional
|
||||
`.metadata.summary`) added as annotations.
|
||||
alert](https://prometheus.io/docs/alerting/latest/notifications/#alert), with
|
||||
the metadata added to the `labels` fields, and the `message` (and optional
|
||||
`.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:
|
||||
|
||||
|
@ -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` |
|
||||
| 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 |
|
||||
| kind | The kind 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 |
|
||||
|
||||
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)
|
||||
and [TLS certificates](#tls-certificates).
|
||||
and [certificate secret reference](#certificate-secret-reference).
|
||||
|
||||
###### Prometheus Alertmanager example
|
||||
|
||||
To configure a Provider for Prometheus Alertmanager, create a Secret with [the
|
||||
`address`](#address-example) set to the Prometheus Alertmanager [HTTP API
|
||||
To configure a Provider for Prometheus Alertmanager, authentication can be done using either Basic Authentication or a Bearer Token.
|
||||
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)
|
||||
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).
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: alertmanager
|
||||
|
@ -923,7 +947,33 @@ metadata:
|
|||
name: alertmanager-address
|
||||
namespace: default
|
||||
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
|
||||
|
@ -938,7 +988,7 @@ The [Channel](#channel) is used to set the ID of the room to send the message
|
|||
to.
|
||||
|
||||
This Provider type does support the configuration of a [proxy URL](#https-proxy)
|
||||
and [TLS certificates](#tls-certificates).
|
||||
and [certificate secret reference](#certificate-secret-reference).
|
||||
|
||||
###### Webex example
|
||||
|
||||
|
@ -953,7 +1003,7 @@ controller.
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: webex
|
||||
|
@ -1047,7 +1097,7 @@ credentials for the provider API.
|
|||
The Kubernetes secret can have any of the following keys:
|
||||
|
||||
- `address` - overrides `.spec.address`
|
||||
- `proxy` - overrides `.spec.proxy`
|
||||
- `proxy` - overrides `.spec.proxy` (deprecated, use `.spec.proxySecretRef` instead. **Support for this key will be removed in v1**)
|
||||
- `token` - used for authentication
|
||||
- `username` - overrides `.spec.username`
|
||||
- `headers` - HTTP headers values included in the POST request
|
||||
|
@ -1105,7 +1155,7 @@ stringData:
|
|||
#### Proxy auth example
|
||||
|
||||
Some networks need to use an authenticated proxy to access external services.
|
||||
Therefore, the proxy address can be stored as a secret to hide parameters like the username and password:
|
||||
The recommended approach is to use `.spec.proxySecretRef` with a dedicated Secret:
|
||||
|
||||
```yaml
|
||||
---
|
||||
|
@ -1114,15 +1164,56 @@ kind: Secret
|
|||
metadata:
|
||||
name: my-provider-proxy
|
||||
namespace: default
|
||||
stringData:
|
||||
address: "http://proxy_url:proxy_port"
|
||||
username: "proxy_username"
|
||||
password: "proxy_password"
|
||||
```
|
||||
|
||||
**Legacy approach (deprecated):**
|
||||
The proxy address can also be stored in the main secret to hide parameters like the username and password:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: my-provider-proxy-legacy
|
||||
namespace: default
|
||||
stringData:
|
||||
proxy: "http://username:password@proxy_url:proxy_port"
|
||||
```
|
||||
|
||||
### TLS certificates
|
||||
### Certificate secret reference
|
||||
|
||||
`.spec.certSecretRef` is an optional field to specify a name reference to a
|
||||
Secret in the same namespace as the Provider, containing the TLS CA certificate.
|
||||
The secret must be of type `kubernetes.io/tls` or `Opaque`.
|
||||
Secret in the same namespace as the Provider, containing TLS certificates for
|
||||
secure communication. The secret must be of type `kubernetes.io/tls` or `Opaque`.
|
||||
|
||||
#### Supported configurations
|
||||
|
||||
- **CA-only**: Server authentication (provide `ca.crt` only)
|
||||
- **mTLS**: Client certificate authentication (provide `tls.crt` + `tls.key`, optionally with `ca.crt`)
|
||||
|
||||
#### Providers supporting client certificate authentication
|
||||
|
||||
The following webhook-based providers support client certificate authentication:
|
||||
|
||||
| Provider Type | Description |
|
||||
|---------------------|--------------------------------|
|
||||
| `alertmanager` | Prometheus Alertmanager |
|
||||
| `discord` | Discord webhooks |
|
||||
| `forwarder` | Generic forwarder |
|
||||
| `grafana` | Grafana annotations API |
|
||||
| `matrix` | Matrix rooms |
|
||||
| `msteams` | Microsoft Teams |
|
||||
| `opsgenie` | Opsgenie alerts |
|
||||
| `pagerduty` | PagerDuty events |
|
||||
| `rocket` | Rocket.Chat |
|
||||
| `slack` | Slack API |
|
||||
| `webex` | Webex messages |
|
||||
|
||||
Support for client certificate authentication is being expanded to additional providers over time.
|
||||
|
||||
#### Example
|
||||
|
||||
|
@ -1131,7 +1222,7 @@ using a self-signed TLS certificate, set the `ca.crt` like so:
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: my-webhook
|
||||
|
@ -1160,10 +1251,25 @@ the controller will log a deprecation warning.
|
|||
### HTTP/S proxy
|
||||
|
||||
`.spec.proxy` is an optional field to specify an HTTP/S proxy address.
|
||||
**Warning:** This field is deprecated, use `.spec.proxySecretRef` instead. **Support for this field will be removed in v1.**
|
||||
|
||||
`.spec.proxySecretRef` is an optional field to specify a name reference to a
|
||||
Secret in the same namespace as the Provider, containing the proxy configuration.
|
||||
The Secret should contain an `address` key with the HTTP/S address of the proxy server.
|
||||
Optional `username` and `password` keys can be provided for proxy authentication.
|
||||
|
||||
If the proxy address contains sensitive information such as basic auth credentials, it is
|
||||
recommended to store the proxy in the Kubernetes secret referenced by `.spec.secretRef.name`.
|
||||
When the referenced Secret contains a `proxy` key, the `.spec.proxy` value is ignored.
|
||||
recommended to use `.spec.proxySecretRef` instead of `.spec.proxy`.
|
||||
When `.spec.proxySecretRef` is specified, both `.spec.proxy` and the `proxy` key from
|
||||
`.spec.secretRef` are ignored.
|
||||
|
||||
### Timeout
|
||||
|
||||
`.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
|
||||
|
||||
|
@ -1201,7 +1307,7 @@ kubectl create secret generic grafana-token \
|
|||
Create a provider of type `grafana` and reference the `grafana-token` secret:
|
||||
|
||||
```yaml
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: grafana
|
||||
|
@ -1213,6 +1319,11 @@ spec:
|
|||
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
|
||||
|
||||
The `githubdispatch` provider generates GitHub events of type
|
||||
|
@ -1228,7 +1339,7 @@ The request includes the `event_type` and `client_payload` fields:
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: github-dispatch
|
||||
|
@ -1237,14 +1348,16 @@ spec:
|
|||
type: githubdispatch
|
||||
address: https://github.com/stefanprodan/podinfo
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -1253,12 +1366,19 @@ The provider requires a secret in the same format, with the personal access toke
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: api-token
|
||||
name: auth-secret
|
||||
namespace: default
|
||||
data:
|
||||
stringData:
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta3
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
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:
|
||||
|
||||
```yaml
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Alert
|
||||
metadata:
|
||||
name: github-dispatch
|
||||
|
@ -1321,7 +1441,7 @@ spec:
|
|||
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:
|
||||
|
||||
```yaml
|
||||
|
@ -1340,13 +1460,62 @@ jobs:
|
|||
|
||||
### Azure Event Hub
|
||||
|
||||
The Azure Event Hub supports two authentication methods, [JWT](https://docs.microsoft.com/en-us/azure/event-hubs/authenticate-application)
|
||||
and [SAS](https://docs.microsoft.com/en-us/azure/event-hubs/authorize-access-shared-access-signature) based.
|
||||
The Azure Event Hub provider supports the following authentication methods,
|
||||
- [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
|
||||
|
||||
In JWT we use 3 input values. Channel, token and address.
|
||||
We perform the following translation to match we the data we need to communicate with Azure Event Hub.
|
||||
In JWT we use 3 input values. Channel, token and address. We perform the
|
||||
following translation to match we the data we need to communicate with Azure
|
||||
Event Hub.
|
||||
|
||||
- channel = Azure Event Hub namespace
|
||||
- address = Azure Event Hub name
|
||||
|
@ -1354,7 +1523,7 @@ We perform the following translation to match we the data we need to communicate
|
|||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: azure
|
||||
|
@ -1376,11 +1545,13 @@ stringData:
|
|||
```
|
||||
|
||||
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
|
||||
event-hub you can follow most of the bellow commands. You will need to provide the `client_secret` that you got
|
||||
when generating the Azure Enterprise Application.
|
||||
If you want to make a easy test assuming that you have setup a Azure Enterprise
|
||||
application and you called it event-hub you can follow most of the bellow
|
||||
commands. You will need to provide the `client_secret` that you got when
|
||||
generating the Azure Enterprise Application.
|
||||
|
||||
```shell
|
||||
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
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: azure
|
||||
|
@ -1423,8 +1594,8 @@ stringData:
|
|||
```
|
||||
|
||||
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
|
||||
you the default Root SAS, which is NOT supposed to be used in production.
|
||||
able to use a similar command to get your connection string. This will give you
|
||||
the default Root SAS, which is NOT supposed to be used in production.
|
||||
|
||||
```shell
|
||||
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`.
|
||||
|
||||
```yaml
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: github-status
|
||||
|
@ -1461,7 +1632,7 @@ spec:
|
|||
secretRef:
|
||||
name: github-token
|
||||
---
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
||||
kind: Alert
|
||||
metadata:
|
||||
name: github-status
|
||||
|
@ -1476,10 +1647,16 @@ spec:
|
|||
|
||||
#### GitHub
|
||||
|
||||
When `.spec.type` is set to `github`, the referenced 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).
|
||||
When `.spec.type` is set to `github`, the referenced secret can contain a
|
||||
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:
|
||||
|
||||
|
@ -1487,12 +1664,21 @@ You can create the secret with `kubectl` like this:
|
|||
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
|
||||
|
||||
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:
|
||||
|
||||
|
@ -1500,6 +1686,10 @@ You can create the secret with `kubectl` like this:
|
|||
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
|
||||
|
||||
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 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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
go 1.20
|
||||
go 1.24.0
|
||||
|
||||
replace github.com/fluxcd/notification-controller/api => ./api
|
||||
|
||||
require (
|
||||
cloud.google.com/go/pubsub v1.33.0
|
||||
code.gitea.io/sdk/gitea v0.17.0
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
|
||||
cloud.google.com/go/pubsub v1.49.0
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
|
||||
github.com/Azure/azure-amqp-common-go/v4 v4.2.0
|
||||
github.com/Azure/azure-event-hubs-go/v3 v3.6.1
|
||||
github.com/DataDog/datadog-api-client-go/v2 v2.19.0
|
||||
github.com/PagerDuty/go-pagerduty v1.7.0
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/fluxcd/cli-utils v0.36.0-flux.1
|
||||
github.com/fluxcd/notification-controller/api v1.1.0
|
||||
github.com/fluxcd/pkg/apis/event v0.6.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.2.0
|
||||
github.com/fluxcd/pkg/git v0.16.0
|
||||
github.com/fluxcd/pkg/masktoken v0.2.0
|
||||
github.com/fluxcd/pkg/runtime v0.43.0
|
||||
github.com/fluxcd/pkg/ssa v0.35.0
|
||||
github.com/getsentry/sentry-go v0.25.0
|
||||
github.com/go-logr/logr v1.3.0
|
||||
github.com/google/go-github/v53 v53.2.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
||||
github.com/ktrysmt/go-bitbucket v0.9.72
|
||||
github.com/Azure/azure-event-hubs-go/v3 v3.6.2
|
||||
github.com/DataDog/datadog-api-client-go/v2 v2.42.0
|
||||
github.com/PagerDuty/go-pagerduty v1.8.0
|
||||
github.com/cdevents/sdk-go v0.4.1
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/elazarl/goproxy v1.7.2
|
||||
github.com/fluxcd/cli-utils v0.36.0-flux.14
|
||||
github.com/fluxcd/notification-controller/api v1.6.0
|
||||
github.com/fluxcd/pkg/apis/event v0.18.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.17.0
|
||||
github.com/fluxcd/pkg/auth v0.21.0
|
||||
github.com/fluxcd/pkg/cache v0.10.0
|
||||
github.com/fluxcd/pkg/git v0.34.0
|
||||
github.com/fluxcd/pkg/masktoken v0.7.0
|
||||
github.com/fluxcd/pkg/runtime v0.69.0
|
||||
github.com/fluxcd/pkg/ssa v0.51.0
|
||||
github.com/fluxcd/pkg/ssh v0.20.0
|
||||
github.com/getsentry/sentry-go v0.34.1
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/google/cel-go v0.25.0
|
||||
github.com/google/go-github/v64 v64.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8
|
||||
github.com/ktrysmt/go-bitbucket v0.9.86
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1
|
||||
github.com/nats-io/nats.go v1.31.0
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/sethvargo/go-limiter v0.7.2
|
||||
github.com/slok/go-http-metrics v0.11.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/whilp/git-urls v1.0.0
|
||||
github.com/xanzy/go-gitlab v0.94.0
|
||||
golang.org/x/oauth2 v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/api v0.152.0
|
||||
k8s.io/api v0.28.4
|
||||
k8s.io/apimachinery v0.28.4
|
||||
k8s.io/client-go v0.28.4
|
||||
k8s.io/utils v0.0.0-20231127182322-b307cd553661
|
||||
sigs.k8s.io/controller-runtime v0.16.3
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
github.com/nats-io/nats.go v1.43.0
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/sethvargo/go-limiter v1.0.0
|
||||
github.com/slok/go-http-metrics v0.13.0
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
gitlab.com/gitlab-org/api/client-go v0.134.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/text v0.27.0
|
||||
google.golang.org/api v0.241.0
|
||||
k8s.io/api v0.33.2
|
||||
k8s.io/apimachinery v0.33.2
|
||||
k8s.io/client-go v0.33.2
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/controller-runtime v0.21.0
|
||||
sigs.k8s.io/yaml v1.5.0
|
||||
)
|
||||
|
||||
// Fix CVE-2022-28948
|
||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.10 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/go-amqp v1.0.0 // indirect
|
||||
cel.dev/expr v0.23.1 // indirect
|
||||
cloud.google.com/go v0.120.0 // indirect
|
||||
cloud.google.com/go/auth v0.16.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
github.com/42wim/httpsig v1.2.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect
|
||||
github.com/Azure/go-amqp v1.3.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // 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/to v0.4.0 // 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/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/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/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/cloudflare/circl v1.3.6 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.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/devigned/tab v0.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/docker/cli v28.2.2+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.11.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-logr/zapr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.7.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/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/hashicorp/go-version v1.7.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/jpillora/backoff v1.0.0 // 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/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // 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/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/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.17.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/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
|
||||
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/zap v1.25.0 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.28.4 // indirect
|
||||
k8s.io/cli-runtime v0.28.4 // indirect
|
||||
k8s.io/component-base v0.28.4 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e // indirect
|
||||
k8s.io/kubectl v0.28.4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.15.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.15.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
||||
k8s.io/cli-runtime v0.33.2 // indirect
|
||||
k8s.io/component-base v0.33.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 // indirect
|
||||
k8s.io/kubectl v0.33.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.20.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.0 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||
)
|
||||
|
|
|
@ -19,7 +19,6 @@ package controller
|
|||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
|
@ -28,12 +27,16 @@ import (
|
|||
|
||||
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"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="",resources=secrets,verbs=get;list;watch
|
||||
// +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
|
||||
// Provider.
|
||||
|
@ -41,40 +44,21 @@ type ProviderReconciler struct {
|
|||
client.Client
|
||||
kuberecorder.EventRecorder
|
||||
|
||||
ControllerName string
|
||||
TokenCache *cache.TokenCache
|
||||
}
|
||||
|
||||
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&apiv1beta3.Provider{}, builder.WithPredicates(finalizerPredicate{})).
|
||||
For(&apiv1beta3.Provider{}, builder.WithPredicates(providerPredicate{})).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
obj := &apiv1beta3.Provider{}
|
||||
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
|
@ -86,11 +70,29 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
|
|||
}
|
||||
}()
|
||||
|
||||
// Remove the notification-controller finalizer.
|
||||
controllerutil.RemoveFinalizer(obj, apiv1.NotificationFinalizer)
|
||||
// Examine if the object is under deletion.
|
||||
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
return r.reconcileDelete(obj)
|
||||
}
|
||||
|
||||
log.Info("removed finalizer from Provider to migrate to static Provider")
|
||||
r.Event(obj, corev1.EventTypeNormal, "Migration", "removed finalizer from Provider to migrate to static Provider")
|
||||
// Add finalizer if it doesn't exist.
|
||||
if !controllerutil.ContainsFinalizer(obj, apiv1.NotificationFinalizer) {
|
||||
controllerutil.AddFinalizer(obj, apiv1.NotificationFinalizer)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Remove finalizer at create.
|
||||
|
||||
provider.ObjectMeta.Finalizers = append(provider.ObjectMeta.Finalizers, "foo.bar", apiv1.NotificationFinalizer)
|
||||
// Create without finalizer.
|
||||
provider.Spec = apiv1beta3.ProviderSpec{
|
||||
Type: "slack",
|
||||
Type: "generic",
|
||||
}
|
||||
g.Expect(testEnv.Create(ctx, provider)).ToNot(HaveOccurred())
|
||||
|
||||
// Should eventually have finalizer.
|
||||
g.Eventually(func() bool {
|
||||
_ = testEnv.Get(ctx, providerKey, provider)
|
||||
return !controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
||||
return controllerutil.ContainsFinalizer(provider, apiv1.NotificationFinalizer)
|
||||
}, timeout, time.Second).Should(BeTrue())
|
||||
|
||||
// Remove finalizer at update.
|
||||
|
||||
// Remove finalizer.
|
||||
patchHelper, err := patch.NewHelper(provider, testEnv.Client)
|
||||
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())
|
||||
|
||||
// Should eventually have finalizer again.
|
||||
g.Eventually(func() bool {
|
||||
_ = 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)
|
||||
}, timeout).Should(BeTrue())
|
||||
}, timeout, time.Second).Should(BeTrue())
|
||||
|
||||
// Delete the object and verify.
|
||||
g.Expect(testEnv.Delete(ctx, provider)).ToNot(HaveOccurred())
|
||||
|
@ -117,3 +85,85 @@ func TestProviderReconciler(t *testing.T) {
|
|||
return false
|
||||
}, 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"
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"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/runtime/conditions"
|
||||
|
@ -54,7 +55,7 @@ type ReceiverReconciler struct {
|
|||
}
|
||||
|
||||
type ReceiverReconcilerOptions struct {
|
||||
RateLimiter ratelimiter.RateLimiter
|
||||
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
|
||||
}
|
||||
|
||||
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.
|
||||
r.Metrics.RecordReadiness(ctx, obj)
|
||||
r.Metrics.RecordDuration(ctx, obj, reconcileStart)
|
||||
r.Metrics.RecordSuspend(ctx, obj, obj.Spec.Suspend)
|
||||
|
||||
// Emit warning event if the reconciliation failed.
|
||||
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
|
||||
// produces an 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.
|
||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "Reconciliation in progress")
|
||||
|
||||
token, err := r.token(ctx, obj)
|
||||
if err != nil {
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, apiv1.TokenNotFoundReason, err.Error())
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, apiv1.TokenNotFoundReason, "%s", err)
|
||||
obj.Status.WebhookPath = ""
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
webhookPath := obj.GetWebhookPath(token)
|
||||
msg := fmt.Sprintf("Receiver initialized for path: %s", webhookPath)
|
||||
|
||||
// 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 {
|
||||
obj.Status.WebhookPath = webhookPath
|
||||
ctrl.LoggerFrom(ctx).Info(msg)
|
||||
log.Info(msg)
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
|
||||
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) {
|
||||
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
|
||||
// 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{
|
||||
Recorder: prommetrics.NewRecorder(prommetrics.Config{
|
||||
Prefix: "gotk_receiver",
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
ssautil "github.com/fluxcd/pkg/ssa/utils"
|
||||
|
||||
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||
apiv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||
|
@ -81,9 +82,8 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
if err := (&ProviderReconciler{
|
||||
Client: testEnv,
|
||||
ControllerName: controllerName,
|
||||
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
||||
Client: testEnv,
|
||||
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
||||
}).SetupWithManager(testEnv); err != nil {
|
||||
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)
|
||||
|
||||
object, err := ssa.ReadObject(strings.NewReader(yml))
|
||||
object, err := ssautil.ReadObject(strings.NewReader(yml))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@ limitations under the License.
|
|||
// and their default states.
|
||||
package features
|
||||
|
||||
import feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
import (
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
|
||||
|
@ -35,6 +38,10 @@ var features = map[string]bool{
|
|||
CacheSecretsAndConfigMaps: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
auth.SetFeatureGates(features)
|
||||
}
|
||||
|
||||
// FeatureGates contains a list of all supported feature gates and
|
||||
// their default values.
|
||||
func FeatureGates() map[string]bool {
|
||||
|
|
|
@ -18,10 +18,13 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
|
@ -29,27 +32,59 @@ import (
|
|||
)
|
||||
|
||||
type Alertmanager struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
CertPool *x509.CertPool
|
||||
URL string
|
||||
ProxyURL string
|
||||
TLSConfig *tls.Config
|
||||
Token string
|
||||
}
|
||||
|
||||
type AlertManagerAlert struct {
|
||||
Status string `json:"status"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Alertmanager URL %s: '%w'", hookURL, err)
|
||||
}
|
||||
|
||||
return &Alertmanager{
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
CertPool: certPool,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Token: token,
|
||||
TLSConfig: tlsConfig,
|
||||
}, 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["severity"] = event.Severity
|
||||
labels["reason"] = event.Reason
|
||||
labels["timestamp"] = event.Timestamp.String()
|
||||
|
||||
labels["kind"] = event.InvolvedObject.Kind
|
||||
labels["name"] = event.InvolvedObject.Name
|
||||
labels["namespace"] = event.InvolvedObject.Namespace
|
||||
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{
|
||||
{
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
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 nil
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -43,10 +43,10 @@ func Fuzz_AlertManager(f *testing.F) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
var tlsConfig tls.Config
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||
|
||||
alertmanager, err := NewAlertmanager(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert)
|
||||
alertmanager, err := NewAlertmanager(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &tlsConfig, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func TestAlertmanager_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
alertmanager, err := NewAlertmanager(ts.URL, "", nil)
|
||||
alertmanager, err := NewAlertmanager(ts.URL, "", nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = alertmanager.Post(context.TODO(), testEvent())
|
||||
|
|
|
@ -40,14 +40,14 @@ type azureDevOpsClient interface {
|
|||
|
||||
// AzureDevOps is an Azure DevOps notifier.
|
||||
type AzureDevOps struct {
|
||||
Project string
|
||||
Repo string
|
||||
ProviderUID string
|
||||
Client azureDevOpsClient
|
||||
Project string
|
||||
Repo string
|
||||
CommitStatus string
|
||||
Client azureDevOpsClient
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
// this should never happen
|
||||
if commitStatus == "" {
|
||||
return nil, errors.New("commit status cannot be empty")
|
||||
}
|
||||
|
||||
comp := strings.Split(id, "/")
|
||||
if len(comp) != 4 {
|
||||
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,
|
||||
}
|
||||
return &AzureDevOps{
|
||||
Project: proj,
|
||||
Repo: repo,
|
||||
ProviderUID: providerUID,
|
||||
Client: gitClient,
|
||||
Project: proj,
|
||||
Repo: repo,
|
||||
CommitStatus: commitStatus,
|
||||
Client: gitClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -91,7 +96,7 @@ func (a AzureDevOps) Post(ctx context.Context, event eventv1.Event) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
||||
revString, ok := event.GetRevision()
|
||||
if !ok {
|
||||
return errors.New("missing revision metadata")
|
||||
}
|
||||
|
@ -108,7 +113,7 @@ func (a AzureDevOps) Post(ctx context.Context, event eventv1.Event) error {
|
|||
g := commitStatusGenre(event)
|
||||
|
||||
_, desc := formatNameAndDescription(event)
|
||||
id := generateCommitStatusID(a.ProviderUID, event)
|
||||
id := a.CommitStatus
|
||||
createArgs := git.CreateCommitStatusArgs{
|
||||
Project: &a.Project,
|
||||
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"}]}`
|
||||
|
||||
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("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("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "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", "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", "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":0,"value":[]}`))
|
||||
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) {
|
||||
if strings.HasSuffix(r.URL.Path, "_apis") {
|
||||
w.Write([]byte(apiLocations))
|
||||
|
@ -54,7 +54,7 @@ func Fuzz_AzureDevOps(f *testing.F) {
|
|||
var cert x509.CertPool
|
||||
_ = 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 {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -27,19 +27,24 @@ import (
|
|||
)
|
||||
|
||||
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.Equal(t, a.Project, "bar")
|
||||
assert.Equal(t, a.Repo, "baz")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
event: eventv1.Event{
|
||||
|
@ -132,7 +165,7 @@ func TestAzureDevOps_Post(t *testing.T) {
|
|||
|
||||
for _, tt := range postTests {
|
||||
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{}
|
||||
a.Client = fakeClient
|
||||
assert.Nil(t, err)
|
||||
|
|
|
@ -17,10 +17,20 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"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"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
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
|
||||
|
@ -29,21 +39,33 @@ type AzureEventHub struct {
|
|||
}
|
||||
|
||||
// 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 err error
|
||||
|
||||
// token should only be defined if JWT is used
|
||||
if token != "" {
|
||||
hub, err = newJWTHub(endpointURL, token, eventHubNamespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create a eventhub using JWT %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := validateAuthOptions(endpointURL, token, serviceAccountName); err != nil {
|
||||
return nil, fmt.Errorf("invalid authentication options: %v", err)
|
||||
}
|
||||
|
||||
if isSASAuth(endpointURL) {
|
||||
hub, err = newSASHub(endpointURL)
|
||||
if err != nil {
|
||||
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{
|
||||
|
@ -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
|
||||
func (j *PureJWT) GetToken(uri string) (*auth.Token, error) {
|
||||
return &auth.Token{
|
||||
TokenType: auth.CBSTokenTypeJWT,
|
||||
func (j *PureJWT) GetToken(uri string) (*azauth.Token, error) {
|
||||
return &azauth.Token{
|
||||
TokenType: azauth.CBSTokenTypeJWT,
|
||||
Token: j.jwt,
|
||||
Expiry: "",
|
||||
}, nil
|
||||
|
@ -116,3 +138,75 @@ func newSASHub(address string) (*eventhub.Hub, error) {
|
|||
|
||||
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.
|
||||
type Bitbucket struct {
|
||||
Owner string
|
||||
Repo string
|
||||
ProviderUID string
|
||||
Client *bitbucket.Client
|
||||
Owner string
|
||||
Repo string
|
||||
CommitStatus string
|
||||
Client *bitbucket.Client
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -77,10 +82,10 @@ func NewBitbucket(providerUID string, addr string, token string, certPool *x509.
|
|||
}
|
||||
|
||||
return &Bitbucket{
|
||||
Owner: owner,
|
||||
Repo: repo,
|
||||
ProviderUID: providerUID,
|
||||
Client: client,
|
||||
Owner: owner,
|
||||
Repo: repo,
|
||||
CommitStatus: commitStatus,
|
||||
Client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -91,7 +96,7 @@ func (b Bitbucket) Post(ctx context.Context, event eventv1.Event) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
||||
revString, ok := event.GetRevision()
|
||||
if !ok {
|
||||
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)
|
||||
id := generateCommitStatusID(b.ProviderUID, event)
|
||||
id := b.CommitStatus
|
||||
// key has a limitation of 40 characters in bitbucket api
|
||||
key := sha1String(id)
|
||||
|
||||
|
|
|
@ -31,10 +31,10 @@ import (
|
|||
)
|
||||
|
||||
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("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", "info", []byte{}, []byte(`{"state":"SUCCESSFUL","description":"","key":"","name":"","url":""}`))
|
||||
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) {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
w.Write(response)
|
||||
|
@ -45,7 +45,7 @@ func Fuzz_Bitbucket(f *testing.F) {
|
|||
var cert x509.CertPool
|
||||
_ = 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 {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,18 +23,24 @@ import (
|
|||
)
|
||||
|
||||
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.Equal(t, b.Owner, "foo")
|
||||
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) {
|
||||
_, 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -37,11 +37,9 @@ import (
|
|||
|
||||
// BitbucketServer is a notifier for BitBucket Server and Data Center.
|
||||
type BitbucketServer struct {
|
||||
ProjectKey string
|
||||
RepositorySlug string
|
||||
ProviderUID string
|
||||
CommitStatus string
|
||||
Url *url.URL
|
||||
ProviderAddress string
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
Token string
|
||||
|
@ -49,8 +47,10 @@ type BitbucketServer struct {
|
|||
}
|
||||
|
||||
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"
|
||||
bbServerSourceCodeMgmtString = "/scm/"
|
||||
)
|
||||
|
||||
type bbServerBuildStatus struct {
|
||||
|
@ -81,18 +81,16 @@ type bbServerBuildStatusSetRequest struct {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
hst, id, err := parseBitbucketServerGitAddress(addr)
|
||||
func NewBitbucketServer(commitStatus string, addr string, token string, certPool *x509.CertPool, username string, password string) (*BitbucketServer, error) {
|
||||
url, err := parseBitbucketServerGitAddress(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comp := strings.Split(id, "/")
|
||||
if len(comp) != 2 {
|
||||
return nil, fmt.Errorf("invalid repository id %q", id)
|
||||
// this should never happen
|
||||
if commitStatus == "" {
|
||||
return nil, errors.New("commit status cannot be empty")
|
||||
}
|
||||
projectkey := comp[0]
|
||||
reposlug := comp[1]
|
||||
|
||||
httpClient := retryablehttp.NewClient()
|
||||
if certPool != nil {
|
||||
|
@ -114,10 +112,8 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool
|
|||
}
|
||||
|
||||
return &BitbucketServer{
|
||||
ProjectKey: projectkey,
|
||||
RepositorySlug: reposlug,
|
||||
ProviderUID: providerUID,
|
||||
Host: hst,
|
||||
CommitStatus: commitStatus,
|
||||
Url: url,
|
||||
ProviderAddress: addr,
|
||||
Token: token,
|
||||
Username: username,
|
||||
|
@ -132,7 +128,7 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error {
|
|||
if event.HasReason(meta.ProgressingReason) {
|
||||
return nil
|
||||
}
|
||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
||||
revString, ok := event.GetRevision()
|
||||
if !ok {
|
||||
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 = 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 := sha1String(id)
|
||||
|
||||
u := b.Host + b.createApiPath(rev)
|
||||
dupe, err := b.duplicateBitbucketServerStatus(ctx, rev, state, name, desc, id, key, u)
|
||||
u := b.Url.JoinPath(b.createBuildPath(rev)).String()
|
||||
dupe, err := b.duplicateBitbucketServerStatus(ctx, state, name, desc, key, u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get existing commit status: %w", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet, key, rev)
|
||||
req, err := b.prepareCommonRequest(ctx, u, nil, http.MethodGet)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
}
|
||||
if isError(d) && d.StatusCode != http.StatusNotFound {
|
||||
if d != nil && isError(d) && d.StatusCode != http.StatusNotFound {
|
||||
defer d.Body.Close()
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
j := &bbServerBuildStatusSetRequest{
|
||||
Key: key,
|
||||
|
@ -235,7 +231,7 @@ func (b BitbucketServer) postBuildStatus(ctx context.Context, rev, state, name,
|
|||
}
|
||||
|
||||
//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 {
|
||||
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
|
||||
}
|
||||
|
||||
func (b BitbucketServer) createApiPath(rev string) string {
|
||||
return fmt.Sprintf(bbServerEndPointTmpl, b.ProjectKey, b.RepositorySlug, rev)
|
||||
func (b BitbucketServer) createBuildPath(rev string) string {
|
||||
return fmt.Sprintf(bbServerEndPointBuildsTmpl, rev)
|
||||
}
|
||||
|
||||
func parseBitbucketServerGitAddress(s string) (string, string, error) {
|
||||
host, id, err := parseGitAddress(s)
|
||||
func parseBitbucketServerGitAddress(s string) (*url.URL, error) {
|
||||
u, err := url.Parse(s)
|
||||
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
|
||||
id = strings.TrimPrefix(id, "scm/")
|
||||
return host, id, nil
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return nil, fmt.Errorf("could not parse git address: unsupported scheme type in address: %s. Must be http or https", u.Scheme)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not prepare request: %w", err)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
|
@ -33,33 +34,91 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestNewBitbucketServerBasic(t *testing.T) {
|
||||
b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword")
|
||||
func TestNewBitbucketServerBasicNoContext(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)
|
||||
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 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) {
|
||||
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.Equal(t, b.Token, "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP")
|
||||
}
|
||||
|
||||
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.Equal(t, err.Error(), "invalid credentials, expected to be one of username/password or API Token")
|
||||
}
|
||||
|
||||
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.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) {
|
||||
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)
|
||||
|
||||
//Validate missing revision
|
||||
|
@ -70,8 +129,14 @@ func TestPostBitbucketServerMissingRevision(t *testing.T) {
|
|||
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) {
|
||||
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)
|
||||
|
||||
//Validate extract commit hash
|
||||
|
@ -84,7 +149,7 @@ func TestPostBitbucketServerBadCommitHash(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)
|
||||
|
||||
//Validate conversion to bitbucket state
|
||||
|
@ -123,13 +188,14 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
password string
|
||||
token string
|
||||
event eventv1.Event
|
||||
provideruid string
|
||||
commitStatus string
|
||||
key string
|
||||
uriHash string
|
||||
}{
|
||||
{
|
||||
name: "Validate Token Auth ",
|
||||
token: "goodtoken",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
name: "Validate Token Auth ",
|
||||
token: "goodtoken",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
headers: map[string]string{
|
||||
"Authorization": "Bearer goodtoken",
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -138,15 +204,30 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("info", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
{
|
||||
name: "Validate Basic Auth and Post State=Successful",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
name: "Event with origin revision",
|
||||
token: "goodtoken",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
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{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -155,15 +236,14 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("info", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
{
|
||||
name: "Validate Post State=Failed",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
name: "Validate Post State=Failed",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
headers: map[string]string{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -172,9 +252,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
{
|
||||
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",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
headers: map[string]string{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -191,9 +270,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
{
|
||||
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",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
headers: map[string]string{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -210,9 +288,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
{
|
||||
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",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
headers: map[string]string{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -229,15 +306,14 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
{
|
||||
name: "Validate duplicate commit status successful match",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
||||
name: "Validate duplicate commit status successful match",
|
||||
username: "hello",
|
||||
password: "password",
|
||||
commitStatus: "kustomization/gitops-system/0c9c2e41",
|
||||
headers: map[string]string{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")),
|
||||
"x-atlassian-token": "no-check",
|
||||
|
@ -246,9 +322,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
event: generateTestEventKustomization("info", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}),
|
||||
key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
}))),
|
||||
key: sha1String("kustomization/gitops-system/0c9c2e41"),
|
||||
uriHash: "5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -262,7 +337,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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
|
||||
if r.Method == http.MethodGet {
|
||||
|
@ -281,7 +357,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
jsondata, _ := json.Marshal(&bbServerBuildStatus{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
Key: sha1String(generateCommitStatusID(tt.provideruid, tt.event)),
|
||||
Key: sha1String(tt.commitStatus),
|
||||
State: "SUCCESSFUL",
|
||||
Url: "https://example.com:7990/scm/projectfoo/repobar.git",
|
||||
})
|
||||
|
@ -364,7 +440,7 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) {
|
|||
}
|
||||
}))
|
||||
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)
|
||||
err = c.Post(context.TODO(), tt.event)
|
||||
if tt.testFailReason == "" {
|
||||
|
|
|
@ -19,91 +19,130 @@ package notifier
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
httpClient := retryablehttp.NewClient()
|
||||
if certPool != nil {
|
||||
httpClient.HTTPClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: certPool,
|
||||
},
|
||||
}
|
||||
}
|
||||
type postOption func(*postOptions)
|
||||
|
||||
if proxy != "" {
|
||||
proxyURL, err := url.Parse(proxy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse proxy URL '%s', error: %w", proxy, err)
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if certPool != nil {
|
||||
tlsConfig = &tls.Config{
|
||||
RootCAs: certPool,
|
||||
func postMessage(ctx context.Context, address string, payload interface{}, opts ...postOption) error {
|
||||
options := &postOptions{
|
||||
// Default validateResponse function verifies that the response status code is 200, 202 or 201.
|
||||
responseValidator: func(statusCode int, body []byte) error {
|
||||
if statusCode == http.StatusOK ||
|
||||
statusCode == http.StatusAccepted ||
|
||||
statusCode == http.StatusCreated {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
httpClient.HTTPClient.Transport = &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
TLSClientConfig: tlsConfig,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 15 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
|
||||
}
|
||||
|
||||
return fmt.Errorf("request failed with status code %d, %s", statusCode, string(body))
|
||||
},
|
||||
}
|
||||
|
||||
httpClient.HTTPClient.Timeout = 15 * time.Second
|
||||
httpClient.RetryWaitMin = 2 * time.Second
|
||||
httpClient.RetryWaitMax = 30 * time.Second
|
||||
httpClient.RetryMax = 4
|
||||
httpClient.Logger = nil
|
||||
for _, o := range opts {
|
||||
o(options)
|
||||
}
|
||||
|
||||
httpClient, err := newHTTPClient(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
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 {
|
||||
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")
|
||||
for _, o := range reqOpts {
|
||||
o(req)
|
||||
if options.requestModifier != nil {
|
||||
options.requestModifier(req)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
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 {
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read response body, %s", err)
|
||||
}
|
||||
return fmt.Errorf("request failed with status code %d, %s", resp.StatusCode, string(b))
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if err := options.responseValidator(resp.StatusCode, body); err != nil {
|
||||
return fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
corev1 "k8s.io/api/core/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"])
|
||||
}))
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, err := io.ReadAll(r.Body)
|
||||
|
@ -65,10 +81,43 @@ func Test_postSelfSignedCert(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
certpool := x509.NewCertPool()
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return eventv1.Event{
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
|
|
|
@ -90,8 +90,12 @@ func (s *Discord) Post(ctx context.Context, event eventv1.Event) error {
|
|||
|
||||
payload.Attachments = []SlackAttachment{a}
|
||||
|
||||
err := postMessage(ctx, s.URL, s.ProxyURL, nil, payload)
|
||||
if err != nil {
|
||||
var opts []postOption
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,114 +17,339 @@ limitations under the License.
|
|||
package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
|
||||
apiv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||
)
|
||||
|
||||
type Factory struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
Token string
|
||||
Headers map[string]string
|
||||
CertPool *x509.CertPool
|
||||
Password string
|
||||
ProviderUID string
|
||||
var (
|
||||
// notifiers is a map of notifier names to factory functions.
|
||||
notifiers = notifierMap{
|
||||
// GenericProvider is the default notifier
|
||||
apiv1.GenericProvider: genericNotifierFunc,
|
||||
apiv1.GenericHMACProvider: genericHMACNotifierFunc,
|
||||
apiv1.SlackProvider: slackNotifierFunc,
|
||||
apiv1.DiscordProvider: discordNotifierFunc,
|
||||
apiv1.RocketProvider: rocketNotifierFunc,
|
||||
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,
|
||||
proxy string,
|
||||
username string,
|
||||
channel string,
|
||||
token string,
|
||||
headers map[string]string,
|
||||
certPool *x509.CertPool,
|
||||
password string,
|
||||
providerUID string) *Factory {
|
||||
type Factory struct {
|
||||
notifierOptions
|
||||
}
|
||||
|
||||
// Option represents a functional option for configuring a notifier.
|
||||
type Option func(*notifierOptions)
|
||||
|
||||
// WithProxyURL sets the proxy URL for the notifier.
|
||||
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{
|
||||
URL: url,
|
||||
ProxyURL: proxy,
|
||||
Channel: channel,
|
||||
Username: username,
|
||||
Token: token,
|
||||
Headers: headers,
|
||||
CertPool: certPool,
|
||||
Password: password,
|
||||
ProviderUID: providerUID,
|
||||
notifierOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (f Factory) Notifier(provider string) (Interface, error) {
|
||||
if f.URL == "" {
|
||||
return &NopNotifier{}, nil
|
||||
notifier, ok := notifiers[provider]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("provider %s not supported", provider)
|
||||
}
|
||||
|
||||
var n Interface
|
||||
var err error
|
||||
switch provider {
|
||||
case apiv1.GenericProvider:
|
||||
n, err = NewForwarder(f.URL, f.ProxyURL, f.Headers, f.CertPool, nil)
|
||||
case apiv1.GenericHMACProvider:
|
||||
n, err = NewForwarder(f.URL, f.ProxyURL, f.Headers, f.CertPool, []byte(f.Token))
|
||||
case apiv1.SlackProvider:
|
||||
n, err = NewSlack(f.URL, f.ProxyURL, f.Token, f.CertPool, f.Username, f.Channel)
|
||||
case apiv1.DiscordProvider:
|
||||
n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel)
|
||||
case apiv1.RocketProvider:
|
||||
n, err = NewRocket(f.URL, f.ProxyURL, f.CertPool, f.Username, f.Channel)
|
||||
case apiv1.MSTeamsProvider:
|
||||
n, err = NewMSTeams(f.URL, f.ProxyURL, f.CertPool)
|
||||
case apiv1.GitHubProvider:
|
||||
n, err = NewGitHub(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
||||
case apiv1.GitHubDispatchProvider:
|
||||
n, err = NewGitHubDispatch(f.URL, f.Token, f.CertPool)
|
||||
case apiv1.GitLabProvider:
|
||||
n, err = NewGitLab(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
||||
case apiv1.GiteaProvider:
|
||||
n, err = NewGitea(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
||||
case apiv1.BitbucketServerProvider:
|
||||
n, err = NewBitbucketServer(f.ProviderUID, f.URL, f.Token, f.CertPool, f.Username, f.Password)
|
||||
case apiv1.BitbucketProvider:
|
||||
n, err = NewBitbucket(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
||||
case apiv1.AzureDevOpsProvider:
|
||||
n, err = NewAzureDevOps(f.ProviderUID, f.URL, f.Token, f.CertPool)
|
||||
case apiv1.GoogleChatProvider:
|
||||
n, err = NewGoogleChat(f.URL, f.ProxyURL)
|
||||
case apiv1.GooglePubSubProvider:
|
||||
n, err = NewGooglePubSub(f.URL, f.Channel, f.Token, f.Headers)
|
||||
case apiv1.WebexProvider:
|
||||
n, err = NewWebex(f.URL, f.ProxyURL, f.CertPool, f.Channel, f.Token)
|
||||
case apiv1.SentryProvider:
|
||||
n, err = NewSentry(f.CertPool, f.URL, f.Channel)
|
||||
case apiv1.AzureEventHubProvider:
|
||||
n, err = NewAzureEventHub(f.URL, f.Token, f.Channel)
|
||||
case apiv1.TelegramProvider:
|
||||
n, err = NewTelegram(f.Channel, f.Token)
|
||||
case apiv1.LarkProvider:
|
||||
n, err = NewLark(f.URL)
|
||||
case apiv1.Matrix:
|
||||
n, err = NewMatrix(f.URL, f.Token, f.Channel, f.CertPool)
|
||||
case apiv1.OpsgenieProvider:
|
||||
n, err = NewOpsgenie(f.URL, f.ProxyURL, f.CertPool, f.Token)
|
||||
case apiv1.AlertManagerProvider:
|
||||
n, err = NewAlertmanager(f.URL, f.ProxyURL, f.CertPool)
|
||||
case apiv1.GrafanaProvider:
|
||||
n, err = NewGrafana(f.URL, f.ProxyURL, f.Token, f.CertPool, f.Username, f.Password)
|
||||
case apiv1.PagerDutyProvider:
|
||||
n, err = NewPagerDuty(f.URL, f.ProxyURL, f.CertPool, f.Channel)
|
||||
case apiv1.DataDogProvider:
|
||||
n, err = NewDataDog(f.URL, f.ProxyURL, f.CertPool, f.Token)
|
||||
case apiv1.NATSProvider:
|
||||
n, err = NewNATS(f.URL, f.Channel, f.Username, f.Password)
|
||||
default:
|
||||
err = fmt.Errorf("provider %s not supported", provider)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
n = &NopNotifier{}
|
||||
}
|
||||
return n, err
|
||||
return notifier(f.notifierOptions)
|
||||
}
|
||||
|
||||
func genericNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, nil)
|
||||
}
|
||||
|
||||
func genericHMACNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewForwarder(opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, []byte(opts.Token))
|
||||
}
|
||||
|
||||
func slackNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewSlack(opts.URL, opts.ProxyURL, opts.Token, opts.TLSConfig, opts.Username, opts.Channel)
|
||||
}
|
||||
|
||||
func discordNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewDiscord(opts.URL, opts.ProxyURL, opts.Username, opts.Channel)
|
||||
}
|
||||
|
||||
func rocketNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewRocket(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Username, opts.Channel)
|
||||
}
|
||||
|
||||
func msteamsNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewMSTeams(opts.URL, opts.ProxyURL, opts.TLSConfig)
|
||||
}
|
||||
|
||||
func googleChatNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewGoogleChat(opts.URL, opts.ProxyURL)
|
||||
}
|
||||
|
||||
func googlePubSubNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewGooglePubSub(opts.URL, opts.Channel, opts.Token, opts.Headers)
|
||||
}
|
||||
|
||||
func webexNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewWebex(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Channel, opts.Token)
|
||||
}
|
||||
|
||||
func sentryNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewSentry(opts.CertPool, opts.URL, opts.Channel)
|
||||
}
|
||||
|
||||
func azureEventHubNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewAzureEventHub(opts.Context, opts.URL, opts.Token, opts.Channel, opts.ProxyURL, opts.ServiceAccountName, opts.ProviderName, opts.ProviderNamespace, opts.TokenClient, opts.TokenCache)
|
||||
}
|
||||
|
||||
func telegramNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewTelegram(opts.ProxyURL, opts.Channel, opts.Token)
|
||||
}
|
||||
|
||||
func larkNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewLark(opts.URL)
|
||||
}
|
||||
|
||||
func matrixNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewMatrix(opts.URL, opts.Token, opts.Channel, opts.TLSConfig)
|
||||
}
|
||||
|
||||
func opsgenieNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
return NewOpsgenie(opts.URL, opts.ProxyURL, opts.TLSConfig, opts.Token)
|
||||
}
|
||||
|
||||
func alertmanagerNotifierFunc(opts notifierOptions) (Interface, error) {
|
||||
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"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -37,14 +37,14 @@ const NotificationHeader = "gotk-component"
|
|||
// Forwarder is an implementation of the notification Interface that posts the
|
||||
// body as an HTTP request using an optional proxy.
|
||||
type Forwarder struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Headers map[string]string
|
||||
CertPool *x509.CertPool
|
||||
HMACKey []byte
|
||||
URL string
|
||||
ProxyURL string
|
||||
Headers map[string]string
|
||||
TLSConfig *tls.Config
|
||||
HMACKey []byte
|
||||
}
|
||||
|
||||
func NewForwarder(hookURL string, proxyURL string, headers map[string]string, certPool *x509.CertPool, hmacKey []byte) (*Forwarder, error) {
|
||||
func NewForwarder(hookURL string, proxyURL string, headers map[string]string, tlsConfig *tls.Config, hmacKey []byte) (*Forwarder, error) {
|
||||
if _, err := url.ParseRequestURI(hookURL); err != nil {
|
||||
return nil, fmt.Errorf("invalid hook URL %s: %w", hookURL, err)
|
||||
}
|
||||
|
@ -54,11 +54,11 @@ func NewForwarder(hookURL string, proxyURL string, headers map[string]string, ce
|
|||
}
|
||||
|
||||
return &Forwarder{
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Headers: headers,
|
||||
CertPool: certPool,
|
||||
HMACKey: hmacKey,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Headers: headers,
|
||||
HMACKey: hmacKey,
|
||||
TLSConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -77,18 +77,28 @@ func (f *Forwarder) Post(ctx context.Context, event eventv1.Event) error {
|
|||
}
|
||||
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 nil
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -41,13 +41,13 @@ func Fuzz_Forwarder(f *testing.F) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
var tlsConfig tls.Config
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||
|
||||
header := make(map[string]string)
|
||||
_ = fuzz.NewConsumer(seed).FuzzMap(&header)
|
||||
|
||||
forwarder, err := NewForwarder(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", header, &cert, hmacKey)
|
||||
forwarder, err := NewForwarder(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", header, &tlsConfig, hmacKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -34,22 +34,27 @@ import (
|
|||
)
|
||||
|
||||
type Gitea struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
Owner string
|
||||
Repo string
|
||||
ProviderUID string
|
||||
Client *gitea.Client
|
||||
Debug bool
|
||||
BaseURL string
|
||||
Token string
|
||||
Owner string
|
||||
Repo string
|
||||
CommitStatus string
|
||||
Client *gitea.Client
|
||||
Debug bool
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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{
|
||||
BaseURL: host,
|
||||
Token: token,
|
||||
Owner: idComponents[0],
|
||||
Repo: idComponents[1],
|
||||
ProviderUID: providerUID,
|
||||
Client: client,
|
||||
Debug: os.Getenv("NOTIFIER_GITEA_DEBUG") == "true",
|
||||
BaseURL: host,
|
||||
Token: token,
|
||||
Owner: idComponents[0],
|
||||
Repo: idComponents[1],
|
||||
CommitStatus: commitStatus,
|
||||
Client: client,
|
||||
Debug: os.Getenv("NOTIFIER_GITEA_DEBUG") == "true",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {
|
||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
||||
revString, ok := event.GetRevision()
|
||||
if !ok {
|
||||
return errors.New("missing revision metadata")
|
||||
}
|
||||
|
@ -104,7 +115,7 @@ func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {
|
|||
}
|
||||
|
||||
_, desc := formatNameAndDescription(event)
|
||||
id := generateCommitStatusID(g.ProviderUID, event)
|
||||
id := g.CommitStatus
|
||||
|
||||
status := gitea.CreateStatusOption{
|
||||
State: state,
|
||||
|
|
|
@ -18,7 +18,9 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
testproxy "github.com/fluxcd/notification-controller/tests/proxy"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -31,10 +33,20 @@ import (
|
|||
"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).
|
||||
func newTestServer(t *testing.T) *httptest.Server {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
func newTestHTTPServer(t *testing.T) *httptest.Server {
|
||||
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 {
|
||||
case "/api/v1/version":
|
||||
fmt.Fprintf(w, `{"version":"1.18.3"}`)
|
||||
|
@ -42,18 +54,76 @@ func newTestServer(t *testing.T) *httptest.Server {
|
|||
fmt.Fprintf(w, "[]")
|
||||
case "/api/v1/repos/foo/bar/statuses/69b59063470310ebbd88a9156325322a124e55a3":
|
||||
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:
|
||||
t.Logf("unknown %s request at %s", r.Method, r.URL.Path)
|
||||
}
|
||||
}))
|
||||
return srv
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewGiteaBasic(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
srv := newTestHTTPServer(t)
|
||||
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.Equal(t, g.Owner, "foo")
|
||||
assert.Equal(t, g.Repo, "bar")
|
||||
|
@ -61,44 +131,88 @@ func TestNewGiteaBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewGiteaInvalidUrl(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
srv := newTestHTTPServer(t)
|
||||
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")
|
||||
}
|
||||
|
||||
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) {
|
||||
srv := newTestServer(t)
|
||||
srv := newTestHTTPServer(t)
|
||||
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")
|
||||
}
|
||||
|
||||
func TestGitea_Post(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
func TestNewGiteaEmptyCommitStatus(t *testing.T) {
|
||||
srv := newTestHTTPServer(t)
|
||||
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)
|
||||
|
||||
event := eventv1.Event{
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
Kind: "Kustomization",
|
||||
Namespace: "flux-system",
|
||||
Name: "podinfo-repo",
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
event eventv1.Event
|
||||
}{
|
||||
{
|
||||
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{
|
||||
Time: time.Now(),
|
||||
{
|
||||
name: "origin 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",
|
||||
eventv1.MetaOriginRevisionKey: "main@sha1:8a9156325322a124e55a369b59063470310ebbd8",
|
||||
},
|
||||
Message: "Service/podinfo/podinfo configured",
|
||||
Reason: "",
|
||||
},
|
||||
},
|
||||
Metadata: map[string]string{
|
||||
eventv1.MetaRevisionKey: "main@sha1:69b59063470310ebbd88a9156325322a124e55a3",
|
||||
},
|
||||
Message: "Service/podinfo/podinfo configured",
|
||||
Reason: "",
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := g.Post(context.Background(), tt.event)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
err = g.Post(context.Background(), event)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -18,74 +18,44 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v53/github"
|
||||
"golang.org/x/oauth2"
|
||||
"github.com/google/go-github/v64/github"
|
||||
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
)
|
||||
|
||||
type GitHub struct {
|
||||
Owner string
|
||||
Repo string
|
||||
ProviderUID string
|
||||
Client *github.Client
|
||||
Owner string
|
||||
Repo string
|
||||
CommitStatus string
|
||||
Client *github.Client
|
||||
}
|
||||
|
||||
func NewGitHub(providerUID string, addr string, token string, certPool *x509.CertPool) (*GitHub, error) {
|
||||
if len(token) == 0 {
|
||||
return nil, errors.New("github token cannot be empty")
|
||||
func NewGitHub(commitStatus string, addr string, token string, certPool *x509.CertPool,
|
||||
proxyURL string, providerName string, providerNamespace string, secretData map[string][]byte,
|
||||
tokenCache *cache.TokenCache) (*GitHub, error) {
|
||||
|
||||
// this should never happen
|
||||
if commitStatus == "" {
|
||||
return nil, errors.New("commit status cannot be empty")
|
||||
}
|
||||
|
||||
host, id, err := parseGitAddress(addr)
|
||||
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool,
|
||||
proxyURL, providerName, providerNamespace, secretData, tokenCache)
|
||||
if err != nil {
|
||||
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{
|
||||
Owner: comp[0],
|
||||
Repo: comp[1],
|
||||
ProviderUID: providerUID,
|
||||
Client: client,
|
||||
Owner: repoInfo.owner,
|
||||
Repo: repoInfo.repo,
|
||||
CommitStatus: commitStatus,
|
||||
Client: repoInfo.client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -96,7 +66,7 @@ func (g *GitHub) Post(ctx context.Context, event eventv1.Event) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
||||
revString, ok := event.GetRevision()
|
||||
if !ok {
|
||||
return errors.New("missing revision metadata")
|
||||
}
|
||||
|
@ -110,7 +80,7 @@ func (g *GitHub) Post(ctx context.Context, event eventv1.Event) error {
|
|||
}
|
||||
|
||||
_, desc := formatNameAndDescription(event)
|
||||
id := generateCommitStatusID(g.ProviderUID, event)
|
||||
id := g.CommitStatus
|
||||
status := &github.RepoStatus{
|
||||
State: &state,
|
||||
Context: &id,
|
||||
|
|
|
@ -18,19 +18,14 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
|
||||
"github.com/google/go-github/v53/github"
|
||||
"golang.org/x/oauth2"
|
||||
"github.com/google/go-github/v64/github"
|
||||
)
|
||||
|
||||
type GitHubDispatch struct {
|
||||
|
@ -39,51 +34,20 @@ type GitHubDispatch struct {
|
|||
Client *github.Client
|
||||
}
|
||||
|
||||
func NewGitHubDispatch(addr string, token string, certPool *x509.CertPool) (*GitHubDispatch, error) {
|
||||
if len(token) == 0 {
|
||||
return nil, errors.New("github token cannot be empty")
|
||||
}
|
||||
func NewGitHubDispatch(addr string, token string, certPool *x509.CertPool, proxyURL string,
|
||||
providerName string, providerNamespace string, secretData map[string][]byte,
|
||||
tokenCache *cache.TokenCache) (*GitHubDispatch, error) {
|
||||
|
||||
host, id, err := parseGitAddress(addr)
|
||||
repoInfo, err := getRepoInfoAndGithubClient(addr, token, certPool,
|
||||
proxyURL, providerName, providerNamespace, secretData, tokenCache)
|
||||
if err != nil {
|
||||
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{
|
||||
Owner: comp[0],
|
||||
Repo: comp[1],
|
||||
Client: client,
|
||||
Owner: repoInfo.owner,
|
||||
Repo: repoInfo.repo,
|
||||
Client: repoInfo.client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ func Fuzz_GitHub_Dispatch(f *testing.F) {
|
|||
var cert x509.CertPool
|
||||
_ = 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 {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,16 +18,23 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
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) {
|
||||
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.Equal(t, g.Owner, "foo")
|
||||
assert.Equal(t, g.Repo, "bar")
|
||||
|
@ -35,7 +42,7 @@ func TestNewGitHubDispatchBasic(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.Equal(t, g.Owner, "foo")
|
||||
assert.Equal(t, g.Repo, "bar")
|
||||
|
@ -43,17 +50,98 @@ func TestNewEnterpriseGitHubDispatchBasic(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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
|
||||
event := testEvent()
|
||||
|
|
|
@ -30,14 +30,14 @@ import (
|
|||
)
|
||||
|
||||
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("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "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("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "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("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", "error", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
|
||||
f.Add("kustomization/gitops-system/0c9c2e41", "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":"failure","description":""}]`))
|
||||
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/"}]`))
|
||||
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "", []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) {
|
||||
w.Write(response)
|
||||
io.Copy(io.Discard, r.Body)
|
||||
|
@ -48,7 +48,7 @@ func Fuzz_GitHub(f *testing.F) {
|
|||
var cert x509.CertPool
|
||||
_ = 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 {
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"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"
|
||||
)
|
||||
|
||||
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.Equal(t, g.Owner, "foo")
|
||||
assert.Equal(t, g.Repo, "bar")
|
||||
assert.Equal(t, g.Client.BaseURL.Host, "api.github.com")
|
||||
assert.Equal(t, g.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||
}
|
||||
|
||||
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.Equal(t, g.Owner, "foo")
|
||||
assert.Equal(t, g.Repo, "bar")
|
||||
assert.Equal(t, g.Client.BaseURL.Host, "foobar.com")
|
||||
assert.Equal(t, g.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
@ -24,19 +24,19 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/xanzy/go-gitlab"
|
||||
gitlab "gitlab.com/gitlab-org/api/client-go"
|
||||
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
type GitLab struct {
|
||||
Id string
|
||||
ProviderUID string
|
||||
Client *gitlab.Client
|
||||
Id string
|
||||
CommitStatus string
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// this should never happen
|
||||
if commitStatus == "" {
|
||||
return nil, errors.New("commit status cannot be empty")
|
||||
}
|
||||
|
||||
opts := []gitlab.ClientOptionFunc{gitlab.WithBaseURL(host)}
|
||||
if certPool != nil {
|
||||
tr := &http.Transport{
|
||||
|
@ -62,9 +67,9 @@ func NewGitLab(providerUID string, addr string, token string, certPool *x509.Cer
|
|||
}
|
||||
|
||||
gitlab := &GitLab{
|
||||
Id: id,
|
||||
ProviderUID: providerUID,
|
||||
Client: client,
|
||||
Id: id,
|
||||
CommitStatus: commitStatus,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
return gitlab, nil
|
||||
|
@ -77,7 +82,7 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
|
||||
revString, ok := event.GetRevision()
|
||||
if !ok {
|
||||
return errors.New("missing revision metadata")
|
||||
}
|
||||
|
@ -91,7 +96,7 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
|
|||
}
|
||||
|
||||
_, desc := formatNameAndDescription(event)
|
||||
id := generateCommitStatusID(g.ProviderUID, event)
|
||||
id := g.CommitStatus
|
||||
status := &gitlab.CommitStatus{
|
||||
Name: id,
|
||||
SHA: rev,
|
||||
|
@ -99,7 +104,9 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
|
|||
Description: desc,
|
||||
}
|
||||
|
||||
getOpt := &gitlab.GetCommitStatusesOptions{}
|
||||
getOpt := &gitlab.GetCommitStatusesOptions{
|
||||
Name: &status.Name,
|
||||
}
|
||||
statuses, _, err := g.Client.Commits.GetCommitStatuses(g.Id, rev, getOpt, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to list commit status: %s", err)
|
||||
|
|
|
@ -30,12 +30,12 @@ import (
|
|||
)
|
||||
|
||||
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("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "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("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[]`))
|
||||
f.Add("kustomization/gitops-system/0c9c2e41", "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", "info", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
|
||||
f.Add("kustomization/gitops-system/0c9c2e41", "token", "org/repo", "revision/abce1", "info", "Progressing", []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) {
|
||||
w.Write(response)
|
||||
io.Copy(io.Discard, r.Body)
|
||||
|
@ -46,7 +46,7 @@ func Fuzz_GitLab(f *testing.F) {
|
|||
var cert x509.CertPool
|
||||
_ = 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 {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,25 +23,31 @@ import (
|
|||
)
|
||||
|
||||
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.Equal(t, g.Id, "foo/bar")
|
||||
assert.Equal(t, g.CommitStatus, "kustomization/gitops-system/0c9c2e41")
|
||||
}
|
||||
|
||||
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.Equal(t, g.Id, "foo/bar/baz")
|
||||
}
|
||||
|
||||
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.Equal(t, g.Id, "foo/bar")
|
||||
assert.Equal(t, g.Client.BaseURL().Host, "example.com")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -142,8 +142,12 @@ func (s *GoogleChat) Post(ctx context.Context, event eventv1.Event) error {
|
|||
Cards: []GoogleChatCard{card},
|
||||
}
|
||||
|
||||
err := postMessage(ctx, s.URL, s.ProxyURL, nil, payload)
|
||||
if err != nil {
|
||||
var opts []postOption
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -28,15 +28,15 @@ import (
|
|||
)
|
||||
|
||||
type Grafana struct {
|
||||
URL string
|
||||
Token string
|
||||
ProxyURL string
|
||||
CertPool *x509.CertPool
|
||||
Username string
|
||||
Password string
|
||||
URL string
|
||||
Token string
|
||||
ProxyURL string
|
||||
TLSConfig *tls.Config
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// GraphiteAnnotation represents a Grafana API annotation in Graphite format
|
||||
// GraphitePayload represents a Grafana API annotation in Graphite format
|
||||
type GraphitePayload struct {
|
||||
When int64 `json:"when"` //optional unix timestamp (ms)
|
||||
Text string `json:"text"`
|
||||
|
@ -44,19 +44,19 @@ type GraphitePayload struct {
|
|||
}
|
||||
|
||||
// NewGrafana validates the Grafana URL and returns a Grafana object
|
||||
func NewGrafana(URL string, proxyURL string, token string, certPool *x509.CertPool, username string, password string) (*Grafana, error) {
|
||||
func NewGrafana(URL string, proxyURL string, token string, tlsConfig *tls.Config, username string, password string) (*Grafana, error) {
|
||||
_, err := url.ParseRequestURI(URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Grafana URL %s", URL)
|
||||
}
|
||||
|
||||
return &Grafana{
|
||||
URL: URL,
|
||||
ProxyURL: proxyURL,
|
||||
Token: token,
|
||||
CertPool: certPool,
|
||||
Username: username,
|
||||
Password: password,
|
||||
URL: URL,
|
||||
ProxyURL: proxyURL,
|
||||
Token: token,
|
||||
Username: username,
|
||||
Password: password,
|
||||
TLSConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -71,23 +71,37 @@ func (g *Grafana) Post(ctx context.Context, event eventv1.Event) error {
|
|||
// add tag to filter on grafana
|
||||
sfields = append(sfields, "flux", event.ReportingController)
|
||||
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{
|
||||
When: event.Timestamp.Unix(),
|
||||
Text: fmt.Sprintf("%s/%s.%s", strings.ToLower(event.InvolvedObject.Kind), event.InvolvedObject.Name, event.InvolvedObject.Namespace),
|
||||
Tags: sfields,
|
||||
}
|
||||
|
||||
err := postMessage(ctx, g.URL, g.ProxyURL, g.CertPool, payload, func(request *retryablehttp.Request) {
|
||||
if (g.Username != "" && g.Password != "") && g.Token == "" {
|
||||
request.Header.Add("Authorization", "Basic "+basicAuth(g.Username, g.Password))
|
||||
}
|
||||
if g.Token != "" {
|
||||
request.Header.Add("Authorization", "Bearer "+g.Token)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
opts := []postOption{
|
||||
withRequestModifier(func(req *retryablehttp.Request) {
|
||||
if (g.Username != "" && g.Password != "") && g.Token == "" {
|
||||
req.Header.Add("Authorization", "Basic "+basicAuth(g.Username, g.Password))
|
||||
}
|
||||
if g.Token != "" {
|
||||
req.Header.Add("Authorization", "Bearer "+g.Token)
|
||||
}
|
||||
}),
|
||||
}
|
||||
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 nil
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -43,10 +43,10 @@ func Fuzz_Grafana(f *testing.F) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
var tlsConfig tls.Config
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||
|
||||
grafana, err := NewGrafana(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", token, &cert, username, password)
|
||||
grafana, err := NewGrafana(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", token, &tlsConfig, username, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ func TestGrafana_Post(t *testing.T) {
|
|||
require.Equal(t, "flux", payload.Tags[0])
|
||||
require.Equal(t, "source-controller", payload.Tags[1])
|
||||
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()
|
||||
|
||||
|
|
|
@ -109,5 +109,5 @@ func (l *Lark) Post(ctx context.Context, event eventv1.Event) error {
|
|||
Card: card,
|
||||
}
|
||||
|
||||
return postMessage(ctx, l.URL, "", nil, payload)
|
||||
return postMessage(ctx, l.URL, payload)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package notifier
|
|||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -15,10 +15,10 @@ import (
|
|||
)
|
||||
|
||||
type Matrix struct {
|
||||
Token string
|
||||
URL string
|
||||
RoomId string
|
||||
CertPool *x509.CertPool
|
||||
Token string
|
||||
URL string
|
||||
RoomId string
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
type MatrixPayload struct {
|
||||
|
@ -26,17 +26,17 @@ type MatrixPayload struct {
|
|||
MsgType string `json:"msgtype"`
|
||||
}
|
||||
|
||||
func NewMatrix(serverURL, token, roomId string, certPool *x509.CertPool) (*Matrix, error) {
|
||||
func NewMatrix(serverURL, token, roomId string, tlsConfig *tls.Config) (*Matrix, error) {
|
||||
_, err := url.ParseRequestURI(serverURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Matrix homeserver URL %s: '%w'", serverURL, err)
|
||||
}
|
||||
|
||||
return &Matrix{
|
||||
URL: serverURL,
|
||||
RoomId: roomId,
|
||||
Token: token,
|
||||
CertPool: certPool,
|
||||
URL: serverURL,
|
||||
RoomId: roomId,
|
||||
Token: token,
|
||||
TLSConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -65,11 +65,17 @@ func (m *Matrix) Post(ctx context.Context, event eventv1.Event) error {
|
|||
MsgType: "m.text",
|
||||
}
|
||||
|
||||
err = postMessage(ctx, fullURL, "", m.CertPool, payload, func(request *retryablehttp.Request) {
|
||||
request.Method = http.MethodPut
|
||||
request.Header.Add("Authorization", "Bearer "+m.Token)
|
||||
})
|
||||
if err != nil {
|
||||
opts := []postOption{
|
||||
withRequestModifier(func(req *retryablehttp.Request) {
|
||||
req.Method = http.MethodPut
|
||||
req.Header.Add("Authorization", "Bearer "+m.Token)
|
||||
}),
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -27,10 +27,10 @@ func Fuzz_Matrix(f *testing.F) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
var tlsConfig tls.Config
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||
|
||||
matrix, err := NewMatrix(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, roomId, &cert)
|
||||
matrix, err := NewMatrix(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, roomId, &tlsConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
// OperationPost is the operation name used in cache event metrics
|
||||
const OperationPost = "post"
|
||||
|
||||
type Interface interface {
|
||||
Post(ctx context.Context, event eventv1.Event) error
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -28,10 +28,10 @@ import (
|
|||
)
|
||||
|
||||
type Opsgenie struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
CertPool *x509.CertPool
|
||||
ApiKey string
|
||||
URL string
|
||||
ProxyURL string
|
||||
TLSConfig *tls.Config
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
type OpsgenieAlert struct {
|
||||
|
@ -40,7 +40,7 @@ type OpsgenieAlert struct {
|
|||
Details map[string]string `json:"details"`
|
||||
}
|
||||
|
||||
func NewOpsgenie(hookURL string, proxyURL string, certPool *x509.CertPool, token string) (*Opsgenie, error) {
|
||||
func NewOpsgenie(hookURL string, proxyURL string, tlsConfig *tls.Config, token string) (*Opsgenie, error) {
|
||||
_, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Opsgenie hook URL %s: '%w'", hookURL, err)
|
||||
|
@ -51,10 +51,10 @@ func NewOpsgenie(hookURL string, proxyURL string, certPool *x509.CertPool, token
|
|||
}
|
||||
|
||||
return &Opsgenie{
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
CertPool: certPool,
|
||||
ApiKey: token,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
ApiKey: token,
|
||||
TLSConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -65,18 +65,34 @@ func (s *Opsgenie) Post(ctx context.Context, event eventv1.Event) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var details = make(map[string]string)
|
||||
|
||||
if event.Metadata != nil {
|
||||
details = event.Metadata
|
||||
}
|
||||
details["severity"] = event.Severity
|
||||
|
||||
payload := OpsgenieAlert{
|
||||
Message: event.InvolvedObject.Kind + "/" + event.InvolvedObject.Name,
|
||||
Description: event.Message,
|
||||
Details: event.Metadata,
|
||||
Details: details,
|
||||
}
|
||||
|
||||
err := postMessage(ctx, s.URL, s.ProxyURL, s.CertPool, payload, func(req *retryablehttp.Request) {
|
||||
req.Header.Set("Authorization", "GenieKey "+s.ApiKey)
|
||||
})
|
||||
opts := []postOption{
|
||||
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 nil
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -42,10 +42,10 @@ func Fuzz_OpsGenie(f *testing.F) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
var tlsConfig tls.Config
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||
|
||||
opsgenie, err := NewOpsgenie(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, token)
|
||||
opsgenie, err := NewOpsgenie(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &tlsConfig, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -38,9 +39,32 @@ func TestOpsgenie_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
opsgenie, err := NewOpsgenie(ts.URL, "", nil, "token")
|
||||
require.NoError(t, err)
|
||||
tests := []struct {
|
||||
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())
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
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 (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
@ -33,10 +33,10 @@ type PagerDuty struct {
|
|||
Endpoint string
|
||||
RoutingKey string
|
||||
ProxyURL string
|
||||
CertPool *x509.CertPool
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
func NewPagerDuty(endpoint string, proxyURL string, certPool *x509.CertPool, routingKey string) (*PagerDuty, error) {
|
||||
func NewPagerDuty(endpoint string, proxyURL string, tlsConfig *tls.Config, routingKey string) (*PagerDuty, error) {
|
||||
URL, err := url.ParseRequestURI(endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PagerDuty endpoint URL %q: '%w'", endpoint, err)
|
||||
|
@ -45,7 +45,7 @@ func NewPagerDuty(endpoint string, proxyURL string, certPool *x509.CertPool, rou
|
|||
Endpoint: URL.Scheme + "://" + URL.Host,
|
||||
RoutingKey: routingKey,
|
||||
ProxyURL: proxyURL,
|
||||
CertPool: certPool,
|
||||
TLSConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
return nil
|
||||
}
|
||||
e := toPagerDutyV2Event(event, p.RoutingKey)
|
||||
err := postMessage(ctx, p.Endpoint+"/v2/enqueue", p.ProxyURL, p.CertPool, e)
|
||||
if err != nil {
|
||||
|
||||
var opts []postOption
|
||||
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)
|
||||
}
|
||||
|
||||
// Send a change event for info events
|
||||
if event.Severity == eventv1.EventSeverityInfo {
|
||||
ce := toPagerDutyChangeEvent(event, p.RoutingKey)
|
||||
err = postMessage(ctx, p.Endpoint+"/v2/change/enqueue", p.ProxyURL, p.CertPool, ce)
|
||||
if err != nil {
|
||||
if err := postMessage(
|
||||
ctx,
|
||||
p.Endpoint+"/v2/change/enqueue",
|
||||
toPagerDutyChangeEvent(event, p.RoutingKey),
|
||||
opts...,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed sending change event: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -32,10 +32,10 @@ func Fuzz_PagerDuty(f *testing.F) {
|
|||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
var tlsConfig tls.Config
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&tlsConfig)
|
||||
|
||||
pd, err := NewPagerDuty(ts.URL, "", &cert, routingKey)
|
||||
pd, err := NewPagerDuty(ts.URL, "", &tlsConfig, routingKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -28,26 +28,26 @@ import (
|
|||
|
||||
// Rocket holds the hook URL
|
||||
type Rocket struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
CertPool *x509.CertPool
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// NewRocket validates the Rocket URL and returns a Rocket object
|
||||
func NewRocket(hookURL string, proxyURL string, certPool *x509.CertPool, username string, channel string) (*Rocket, error) {
|
||||
func NewRocket(hookURL string, proxyURL string, tlsConfig *tls.Config, username string, channel string) (*Rocket, error) {
|
||||
_, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Rocket hook URL %s: '%w'", hookURL, err)
|
||||
}
|
||||
|
||||
return &Rocket{
|
||||
Channel: channel,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Username: username,
|
||||
CertPool: certPool,
|
||||
Channel: channel,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Username: username,
|
||||
TLSConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -83,8 +83,15 @@ func (s *Rocket) Post(ctx context.Context, event eventv1.Event) error {
|
|||
|
||||
payload.Attachments = []SlackAttachment{a}
|
||||
|
||||
err := postMessage(ctx, s.URL, s.ProxyURL, s.CertPool, payload)
|
||||
if err != nil {
|
||||
var opts []postOption
|
||||
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 nil
|
||||
|
|
|
@ -18,7 +18,6 @@ package notifier
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -42,10 +41,7 @@ func Fuzz_Rocket(f *testing.F) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
var cert x509.CertPool
|
||||
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
|
||||
|
||||
rocket, err := NewRocket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &cert, username, channel)
|
||||
rocket, err := NewRocket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", nil, username, channel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue