From cbc5c736f42666134f58b487dea8dcbc43bdf332 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Tue, 24 Feb 2026 13:24:55 +0000 Subject: [PATCH 01/48] Add backport label for Flux 2.8 Signed-off-by: Matheus Pimenta --- .github/labels.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/labels.yaml b/.github/labels.yaml index e5fcef61..5821ac39 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -44,12 +44,12 @@ description: Feature request proposals in the RFC format color: '#D621C3' aliases: ['area/RFC'] -- name: backport:release/v2.5.x - description: To be backported to release/v2.5.x - color: '#ffd700' - name: backport:release/v2.6.x description: To be backported to release/v2.6.x color: '#ffd700' - name: backport:release/v2.7.x description: To be backported to release/v2.7.x color: '#ffd700' +- name: backport:release/v2.8.x + description: To be backported to release/v2.8.x + color: '#ffd700' From 2666eaf8fc1d4d39e9c86aa12e5e4a7744f1a3ec Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Wed, 25 Feb 2026 10:16:31 +0000 Subject: [PATCH 02/48] Remove no longer needed workaround for Flux 2.8 Signed-off-by: Matheus Pimenta --- .github/workflows/backport.yaml | 2 +- .github/workflows/scan.yaml | 2 +- .github/workflows/sync-labels.yaml | 2 +- .github/workflows/upgrade-fluxcd-pkg.yaml | 13 +------------ 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index c6670b35..e76969e8 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -8,6 +8,6 @@ jobs: permissions: contents: write # for reading and creating branches. pull-requests: write # for creating pull requests against release branches. - uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.8.0 + uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.9.0 secrets: github-token: ${{ secrets.BOT_GITHUB_TOKEN }} diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index 7e16d1fc..05068600 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -13,7 +13,7 @@ jobs: permissions: contents: read # for reading the repository code. security-events: write # for uploading the CodeQL analysis results. - uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.8.0 + uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.9.0 secrets: github-token: ${{ secrets.GITHUB_TOKEN }} fossa-token: ${{ secrets.FOSSA_TOKEN }} diff --git a/.github/workflows/sync-labels.yaml b/.github/workflows/sync-labels.yaml index 2c14aefd..65229b18 100644 --- a/.github/workflows/sync-labels.yaml +++ b/.github/workflows/sync-labels.yaml @@ -12,6 +12,6 @@ jobs: permissions: contents: read # for reading the labels file. issues: write # for creating and updating labels. - uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.8.0 + uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.9.0 secrets: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/upgrade-fluxcd-pkg.yaml b/.github/workflows/upgrade-fluxcd-pkg.yaml index 27209b6b..659fd30a 100644 --- a/.github/workflows/upgrade-fluxcd-pkg.yaml +++ b/.github/workflows/upgrade-fluxcd-pkg.yaml @@ -2,20 +2,9 @@ name: upgrade-fluxcd-pkg on: workflow_dispatch: - inputs: - pre-release-pkg: - description: >- - Temporary flag for Flux 2.8: use the flux/v2.8.x pkg branch for main branches - because the pkg release branch was cut before the Flux distribution release. - Remove this input once Flux 2.8.0 is released. - required: false - default: false - type: boolean jobs: upgrade-fluxcd-pkg: - uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.8.0 - with: - pre-release-pkg: ${{ inputs.pre-release-pkg }} + uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.9.0 secrets: github-token: ${{ secrets.BOT_GITHUB_TOKEN }} From ab4bbffa5b824c1f341dbdfafce144c924ea69c0 Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Fri, 27 Feb 2026 09:08:54 +0000 Subject: [PATCH 03/48] Update toolkit components - helm-controller to v1.5.1 https://github.com/fluxcd/helm-controller/blob/v1.5.1/CHANGELOG.md - kustomize-controller to v1.8.1 https://github.com/fluxcd/kustomize-controller/blob/v1.8.1/CHANGELOG.md - notification-controller to v1.8.1 https://github.com/fluxcd/notification-controller/blob/v1.8.1/CHANGELOG.md Signed-off-by: GitHub --- go.mod | 6 +++--- go.sum | 12 ++++++------ manifests/bases/helm-controller/kustomization.yaml | 4 ++-- .../bases/kustomize-controller/kustomization.yaml | 4 ++-- .../bases/notification-controller/kustomization.yaml | 4 ++-- manifests/crds/kustomization.yaml | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 7e94c1b8..d4bae782 100644 --- a/go.mod +++ b/go.mod @@ -12,11 +12,11 @@ require ( github.com/distribution/distribution/v3 v3.0.0 github.com/fluxcd/cli-utils v0.37.1-flux.1 github.com/fluxcd/go-git-providers v0.26.0 - github.com/fluxcd/helm-controller/api v1.5.0 + github.com/fluxcd/helm-controller/api v1.5.1 github.com/fluxcd/image-automation-controller/api v1.1.0 github.com/fluxcd/image-reflector-controller/api v1.1.0 - github.com/fluxcd/kustomize-controller/api v1.8.0 - github.com/fluxcd/notification-controller/api v1.8.0 + github.com/fluxcd/kustomize-controller/api v1.8.1 + github.com/fluxcd/notification-controller/api v1.8.1 github.com/fluxcd/pkg/apis/event v0.24.0 github.com/fluxcd/pkg/apis/meta v1.25.0 github.com/fluxcd/pkg/auth v0.38.2 diff --git a/go.sum b/go.sum index cc0d0690..3fcdd17a 100644 --- a/go.sum +++ b/go.sum @@ -174,16 +174,16 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw= github.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo= -github.com/fluxcd/helm-controller/api v1.5.0 h1:M82IuZxDiwTinwq9ASBQ4VjPP0dPJwW576GZZk3mqdY= -github.com/fluxcd/helm-controller/api v1.5.0/go.mod h1:Yr0y7GKizbvQQGK5wBX6sGCZrTY86AN9n1PNEsji2XE= +github.com/fluxcd/helm-controller/api v1.5.1 h1:yraWl0ImzO4yIy/N5d9i54N+OZxKuFZqjed8wrIjy8U= +github.com/fluxcd/helm-controller/api v1.5.1/go.mod h1:Yr0y7GKizbvQQGK5wBX6sGCZrTY86AN9n1PNEsji2XE= github.com/fluxcd/image-automation-controller/api v1.1.0 h1:CLPNHQskX0falo4s1suG1ztUe9IGaY9q5ntcz5Fxt9A= github.com/fluxcd/image-automation-controller/api v1.1.0/go.mod h1:dIpTDlWgUfjvdGZCNfe8Ht9sCiHwRbJDoIbwfLQ56wc= github.com/fluxcd/image-reflector-controller/api v1.1.0 h1:7TtE9DrCnlH1Wn3R3UKXJHNhX/FgS0ejdjFKHzl+XHs= github.com/fluxcd/image-reflector-controller/api v1.1.0/go.mod h1:hLGsqTv3RydJXaApmN+ZtIOHNxlUdmuOJl323x6dsPE= -github.com/fluxcd/kustomize-controller/api v1.8.0 h1:NqDgjqUwotXaHhvd5z46xOhe1O/NPzycXExbuHRmt38= -github.com/fluxcd/kustomize-controller/api v1.8.0/go.mod h1:+ZJB/dIGbnSzZ5J/kiJ8n0USmLNAjfeZU6Xfra0oMZA= -github.com/fluxcd/notification-controller/api v1.8.0 h1:KF0+Fq8WVtmUUnj66ymPBo11/ZmSrVHES3toJojJ1CA= -github.com/fluxcd/notification-controller/api v1.8.0/go.mod h1:tGlTJS+hSLbgQm1L78hl6N3iWbTerifh1V5Qm8we4Zo= +github.com/fluxcd/kustomize-controller/api v1.8.1 h1:Pe5+sV1i1EwfK5TA4ogYX6YJ6ADJaETmG58WYieRkG4= +github.com/fluxcd/kustomize-controller/api v1.8.1/go.mod h1:+ZJB/dIGbnSzZ5J/kiJ8n0USmLNAjfeZU6Xfra0oMZA= +github.com/fluxcd/notification-controller/api v1.8.1 h1:tBg5QrXsVAdMEsV/oq3gqApdRDwcO9gyc6plDf/3QGI= +github.com/fluxcd/notification-controller/api v1.8.1/go.mod h1:tGlTJS+hSLbgQm1L78hl6N3iWbTerifh1V5Qm8we4Zo= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/event v0.24.0 h1:WVPf0FrJ5JExRDDGoo4W0jZgHZt0n4E48/e8b3TSmkA= diff --git a/manifests/bases/helm-controller/kustomization.yaml b/manifests/bases/helm-controller/kustomization.yaml index 968d7faf..97df865a 100644 --- a/manifests/bases/helm-controller/kustomization.yaml +++ b/manifests/bases/helm-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.0/helm-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.0/helm-controller.deployment.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.1/helm-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.1/helm-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index 51dca314..15238b0e 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.0/kustomize-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.0/kustomize-controller.deployment.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.1/kustomize-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.1/kustomize-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/notification-controller/kustomization.yaml b/manifests/bases/notification-controller/kustomization.yaml index 78e1f44f..2a763063 100644 --- a/manifests/bases/notification-controller/kustomization.yaml +++ b/manifests/bases/notification-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.0/notification-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.0/notification-controller.deployment.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.1/notification-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.1/notification-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index 22b7db26..a871eb29 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -2,9 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - https://github.com/fluxcd/source-controller/releases/download/v1.8.0/source-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.0/kustomize-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.0/helm-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.0/notification-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.1/kustomize-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.1/helm-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.1/notification-controller.crds.yaml - https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.0/image-reflector-controller.crds.yaml - https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.0/image-automation-controller.crds.yaml - https://github.com/fluxcd/source-watcher/releases/download/v2.1.0/source-watcher.crds.yaml From 1516761fc8770879b3a44c77a498d2b92025ca03 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Fri, 27 Feb 2026 12:35:51 +0000 Subject: [PATCH 04/48] Add missing things to release notes template Signed-off-by: Matheus Pimenta --- docs/release/release-notes-template.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release/release-notes-template.md b/docs/release/release-notes-template.md index 2a05d5c5..5ad01f56 100644 --- a/docs/release/release-notes-template.md +++ b/docs/release/release-notes-template.md @@ -26,6 +26,8 @@ The following template can be used for the GitHub release page: +ℹ️ Please follow the [Upgrade Procedure for Flux v2.7+](https://github.com/fluxcd/flux2/discussions/5572) for a smooth upgrade from Flux v2.6 to the latest version. + ### Fixes and improvements @@ -36,7 +38,7 @@ The following template can be used for the GitHub release page: ## Components changelog -- -controller [v](https://github.com/fluxcd/-controller/blob//CHANGELOG.md +- -controller [v](https://github.com/fluxcd/-controller/blob//CHANGELOG.md) ## CLI changelog From 5f3098477e3536a5e70e449c57eeea88d43e89d3 Mon Sep 17 00:00:00 2001 From: Gagan H R Date: Wed, 11 Mar 2026 21:40:14 +0530 Subject: [PATCH 05/48] ci: add top-level permissions to upgrade-fluxcd-pkg workflow Add explicit top-level `permissions: contents: read` to the upgrade-fluxcd-pkg workflow to follow the principle of least privilege and fix the OpenSSF Scorecard Token-Permissions warning. Signed-off-by: Gagan H R --- .github/workflows/upgrade-fluxcd-pkg.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/upgrade-fluxcd-pkg.yaml b/.github/workflows/upgrade-fluxcd-pkg.yaml index 659fd30a..c96199d1 100644 --- a/.github/workflows/upgrade-fluxcd-pkg.yaml +++ b/.github/workflows/upgrade-fluxcd-pkg.yaml @@ -3,6 +3,9 @@ name: upgrade-fluxcd-pkg on: workflow_dispatch: +permissions: + contents: read + jobs: upgrade-fluxcd-pkg: uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.9.0 From b60dfbe970bb27b3254fda2825e1bc722e75f46d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:46:48 +0000 Subject: [PATCH 06/48] build(deps): bump the ci group across 1 directory with 11 updates Bumps the ci group with 11 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/setup-go](https://github.com/actions/setup-go) | `6.2.0` | `6.3.0` | | [replicatedhq/replicated-actions](https://github.com/replicatedhq/replicated-actions) | `1.19.0` | `1.20.0` | | [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) | `3.1.2` | `4.0.0` | | [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `3.7.0` | `4.0.0` | | [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3.12.0` | `4.0.0` | | [docker/login-action](https://github.com/docker/login-action) | `3.7.0` | `4.0.0` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `6.0.0` | `7.0.0` | | [github/codeql-action](https://github.com/github/codeql-action) | `4.32.4` | `4.32.6` | | [anchore/sbom-action](https://github.com/anchore/sbom-action) | `0.22.2` | `0.23.1` | | [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) | `4.0.0` | `4.1.0` | | [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `6.4.0` | `7.0.0` | Updates `actions/setup-go` from 6.2.0 to 6.3.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417) Updates `replicatedhq/replicated-actions` from 1.19.0 to 1.20.0 - [Release notes](https://github.com/replicatedhq/replicated-actions/releases) - [Commits](https://github.com/replicatedhq/replicated-actions/compare/49b440dabd7e0e868cbbabda5cfc0d8332a279fa...1abb33f5274580b14f49f2a12d819df7920e4d9b) Updates `hashicorp/setup-terraform` from 3.1.2 to 4.0.0 - [Release notes](https://github.com/hashicorp/setup-terraform/releases) - [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/setup-terraform/compare/b9cd54a3c349d3f38e8881555d616ced269862dd...5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85) Updates `docker/setup-qemu-action` from 3.7.0 to 4.0.0 - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/c7c53464625b32c7a7e944ae62b3e17d2b600130...ce360397dd3f832beb865e1373c09c0e9f86d70a) Updates `docker/setup-buildx-action` from 3.12.0 to 4.0.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/8d2750c68a42422c14e847fe6c8ac0403b4cbd6f...4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd) Updates `docker/login-action` from 3.7.0 to 4.0.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/c94ce9fb468520275223c153574b00df6fe4bcc9...b45d80f862d83dbcd57f89517bcf500b2ab88fb2) Updates `actions/upload-artifact` from 6.0.0 to 7.0.0 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/b7c566a772e6b6bfb58ed0dc250532a479d7789f...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f) Updates `github/codeql-action` from 4.32.4 to 4.32.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...0d579ffd059c29b07949a3cce3983f0780820c98) Updates `anchore/sbom-action` from 0.22.2 to 0.23.1 - [Release notes](https://github.com/anchore/sbom-action/releases) - [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md) - [Commits](https://github.com/anchore/sbom-action/compare/28d71544de8eaf1b958d335707167c5f783590ad...57aae528053a48a3f6235f2d9461b05fbcb7366d) Updates `sigstore/cosign-installer` from 4.0.0 to 4.1.0 - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/faadad0cce49287aee09b3a48701e75088a2c6ad...ba7bc0a3fef59531c69a25acd34668d6d3fe6f22) Updates `goreleaser/goreleaser-action` from 6.4.0 to 7.0.0 - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/e435ccd777264be153ace6237001ef4d979d3a7a...ec59f474b9834571250b370d4735c50f8e2d1e29) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ci - dependency-name: replicatedhq/replicated-actions dependency-version: 1.20.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ci - dependency-name: hashicorp/setup-terraform dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: ci - dependency-name: docker/setup-qemu-action dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: ci - dependency-name: docker/setup-buildx-action dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: ci - dependency-name: docker/login-action dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: ci - dependency-name: actions/upload-artifact dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: ci - dependency-name: github/codeql-action dependency-version: 4.32.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ci - dependency-name: anchore/sbom-action dependency-version: 0.23.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ci - dependency-name: sigstore/cosign-installer dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ci - dependency-name: goreleaser/goreleaser-action dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: ci ... Signed-off-by: dependabot[bot] --- .github/workflows/conformance.yaml | 14 +++++++------- .github/workflows/e2e-azure.yaml | 4 ++-- .github/workflows/e2e-bootstrap.yaml | 2 +- .github/workflows/e2e-gcp.yaml | 10 +++++----- .github/workflows/e2e.yaml | 2 +- .github/workflows/ossf.yaml | 4 ++-- .github/workflows/release.yaml | 22 +++++++++++----------- .github/workflows/update.yaml | 2 +- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 463c518c..9b66001f 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -25,7 +25,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: | @@ -82,7 +82,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: | @@ -107,7 +107,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} - name: Create cluster id: create-cluster - uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0 + uses: replicatedhq/replicated-actions/create-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0 with: api-token: ${{ secrets.REPLICATED_API_TOKEN }} kubernetes-distribution: "k3s" @@ -150,7 +150,7 @@ jobs: kubectl delete ns flux-system --wait - name: Delete cluster if: ${{ always() }} - uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0 + uses: replicatedhq/replicated-actions/remove-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0 continue-on-error: true with: api-token: ${{ secrets.REPLICATED_API_TOKEN }} @@ -174,7 +174,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: | @@ -199,7 +199,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} - name: Create cluster id: create-cluster - uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0 + uses: replicatedhq/replicated-actions/create-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0 with: api-token: ${{ secrets.REPLICATED_API_TOKEN }} kubernetes-distribution: "openshift" @@ -240,7 +240,7 @@ jobs: kubectl delete ns flux-system --wait - name: Delete cluster if: ${{ always() }} - uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0 + uses: replicatedhq/replicated-actions/remove-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0 continue-on-error: true with: api-token: ${{ secrets.REPLICATED_API_TOKEN }} diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index 5c8b4238..ef6458e3 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -31,12 +31,12 @@ jobs: - name: CheckoutD uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: 1.26.x cache-dependency-path: tests/integration/go.sum - name: Setup Terraform - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 + uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0 - name: Setup Flux CLI run: make build working-directory: ./ diff --git a/.github/workflows/e2e-bootstrap.yaml b/.github/workflows/e2e-bootstrap.yaml index 5f8f92d7..1ffd3d08 100644 --- a/.github/workflows/e2e-bootstrap.yaml +++ b/.github/workflows/e2e-bootstrap.yaml @@ -19,7 +19,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: 1.26.x cache-dependency-path: | diff --git a/.github/workflows/e2e-gcp.yaml b/.github/workflows/e2e-gcp.yaml index a69d1c57..934d2048 100644 --- a/.github/workflows/e2e-gcp.yaml +++ b/.github/workflows/e2e-gcp.yaml @@ -31,12 +31,12 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: 1.26.x cache-dependency-path: tests/integration/go.sum - name: Setup Terraform - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 + uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0 - name: Setup Flux CLI run: make build working-directory: ./ @@ -56,11 +56,11 @@ jobs: - name: Setup gcloud uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 - name: Setup QEMU - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Setup Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Log into us-central1-docker.pkg.dev - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: us-central1-docker.pkg.dev username: oauth2accesstoken diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 69c7a789..01a5de5d 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -25,7 +25,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: 1.26.x cache-dependency-path: | diff --git a/.github/workflows/ossf.yaml b/.github/workflows/ossf.yaml index bcc211e4..fc2919ab 100644 --- a/.github/workflows/ossf.yaml +++ b/.github/workflows/ossf.yaml @@ -28,12 +28,12 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} publish_results: true - name: Upload artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif retention-days: 5 - name: Upload SARIF results - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: results.sarif diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 31b4422a..bba797bd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,31 +26,31 @@ jobs: - name: Unshallow run: git fetch --prune --unshallow - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: 1.26.x cache: false - name: Setup QEMU - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Setup Docker Buildx id: buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Setup Syft - uses: anchore/sbom-action/download-syft@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2 + uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1 - name: Setup Cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 with: cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3 - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main - name: Login to GitHub Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: fluxcdbot password: ${{ secrets.GITHUB_TOKEN }} - name: Login to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: fluxcdbot password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} @@ -72,7 +72,7 @@ jobs: tar -czvf ./output/crd-schemas.tar.gz -C schemas . - name: Run GoReleaser id: run-goreleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: version: latest args: release --skip=validate @@ -116,13 +116,13 @@ jobs: VERSION=$(flux version --client | awk '{ print $NF }') echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Login to GHCR - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: fluxcdbot password: ${{ secrets.GITHUB_TOKEN }} - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: fluxcdbot password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} @@ -150,7 +150,7 @@ jobs: --path="./flux-system" \ --source=${{ github.repositoryUrl }} \ --revision="${{ github.ref_name }}@sha1:${{ github.sha }}" - - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + - uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 with: cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3 - name: Sign manifests diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml index 3069fede..c92290fc 100644 --- a/.github/workflows/update.yaml +++ b/.github/workflows/update.yaml @@ -18,7 +18,7 @@ jobs: - name: Check out code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: 1.26.x cache-dependency-path: | From 64808a0eac2d46829dd5c5a14d1dc026bbd0fd7f Mon Sep 17 00:00:00 2001 From: matheuscscp <2975506+matheuscscp@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:23:07 +0000 Subject: [PATCH 07/48] Update fluxcd/pkg dependencies Signed-off-by: GitHub --- go.mod | 103 +++++++++--------- go.sum | 218 +++++++++++++++++++-------------------- tests/integration/go.mod | 24 ++--- tests/integration/go.sum | 52 +++++----- 4 files changed, 197 insertions(+), 200 deletions(-) diff --git a/go.mod b/go.mod index d4bae782..46cba866 100644 --- a/go.mod +++ b/go.mod @@ -10,27 +10,27 @@ require ( github.com/ProtonMail/go-crypto v1.3.0 github.com/cyphar/filepath-securejoin v0.6.1 github.com/distribution/distribution/v3 v3.0.0 - github.com/fluxcd/cli-utils v0.37.1-flux.1 + github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/go-git-providers v0.26.0 github.com/fluxcd/helm-controller/api v1.5.1 github.com/fluxcd/image-automation-controller/api v1.1.0 github.com/fluxcd/image-reflector-controller/api v1.1.0 github.com/fluxcd/kustomize-controller/api v1.8.1 github.com/fluxcd/notification-controller/api v1.8.1 - github.com/fluxcd/pkg/apis/event v0.24.0 - github.com/fluxcd/pkg/apis/meta v1.25.0 - github.com/fluxcd/pkg/auth v0.38.2 - github.com/fluxcd/pkg/chartutil v1.22.0 + github.com/fluxcd/pkg/apis/event v0.25.0 + github.com/fluxcd/pkg/apis/meta v1.26.0 + github.com/fluxcd/pkg/auth v0.40.0 + github.com/fluxcd/pkg/chartutil v1.23.0 github.com/fluxcd/pkg/envsubst v1.5.0 - github.com/fluxcd/pkg/git v0.43.0 - github.com/fluxcd/pkg/kustomize v1.27.0 - github.com/fluxcd/pkg/oci v0.60.0 - github.com/fluxcd/pkg/runtime v0.100.1 + github.com/fluxcd/pkg/git v0.46.0 + github.com/fluxcd/pkg/kustomize v1.28.0 + github.com/fluxcd/pkg/oci v0.63.0 + github.com/fluxcd/pkg/runtime v0.103.0 github.com/fluxcd/pkg/sourceignore v0.17.0 - github.com/fluxcd/pkg/ssa v0.67.1 + github.com/fluxcd/pkg/ssa v0.70.0 github.com/fluxcd/pkg/ssh v0.24.0 github.com/fluxcd/pkg/tar v0.17.0 - github.com/fluxcd/pkg/version v0.12.0 + github.com/fluxcd/pkg/version v0.14.0 github.com/fluxcd/source-controller/api v1.8.0 github.com/fluxcd/source-watcher/api/v2 v2.1.0 github.com/go-git/go-git/v5 v5.16.5 @@ -53,13 +53,13 @@ require ( golang.org/x/crypto v0.48.0 golang.org/x/term v0.40.0 golang.org/x/text v0.34.0 - k8s.io/api v0.35.0 - k8s.io/apiextensions-apiserver v0.35.0 - k8s.io/apimachinery v0.35.0 - k8s.io/cli-runtime v0.35.0 - k8s.io/client-go v0.35.0 - k8s.io/kubectl v0.35.0 - sigs.k8s.io/controller-runtime v0.23.1 + k8s.io/api v0.35.2 + k8s.io/apiextensions-apiserver v0.35.2 + k8s.io/apimachinery v0.35.2 + k8s.io/cli-runtime v0.35.2 + k8s.io/client-go v0.35.2 + k8s.io/kubectl v0.35.2 + sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/kustomize/api v0.21.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 @@ -107,7 +107,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect @@ -115,7 +115,7 @@ require ( github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.1.5+incompatible // indirect + github.com/docker/cli v29.2.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -128,7 +128,7 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect - github.com/fluxcd/pkg/apis/kustomize v1.15.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.16.0 // indirect github.com/fluxcd/pkg/cache v0.13.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -158,9 +158,8 @@ require ( github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect @@ -199,9 +198,9 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/otlptranslator v0.0.2 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/otlptranslator v1.0.0 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect @@ -219,28 +218,28 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect gitlab.com/gitlab-org/api/client-go v1.29.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect - go.opentelemetry.io/otel/log v0.14.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.8.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect + go.opentelemetry.io/otel/log v0.16.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.50.0 // indirect @@ -250,8 +249,8 @@ require ( golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/api v0.261.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -259,8 +258,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - helm.sh/helm/v4 v4.1.1 // indirect - k8s.io/component-base v0.35.0 // indirect + helm.sh/helm/v4 v4.1.3 // indirect + k8s.io/component-base v0.35.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect diff --git a/go.sum b/go.sum index 3fcdd17a..ac0034b2 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= @@ -142,8 +142,8 @@ github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao= -github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= +github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= @@ -168,8 +168,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.37.1-flux.1 h1:WnG2mHxCPZMj/soIq/S/1zvbrGCJN3GJGbNfG06X55M= -github.com/fluxcd/cli-utils v0.37.1-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= +github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= +github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw= @@ -186,40 +186,40 @@ github.com/fluxcd/notification-controller/api v1.8.1 h1:tBg5QrXsVAdMEsV/oq3gqApd github.com/fluxcd/notification-controller/api v1.8.1/go.mod h1:tGlTJS+hSLbgQm1L78hl6N3iWbTerifh1V5Qm8we4Zo= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= -github.com/fluxcd/pkg/apis/event v0.24.0 h1:WVPf0FrJ5JExRDDGoo4W0jZgHZt0n4E48/e8b3TSmkA= -github.com/fluxcd/pkg/apis/event v0.24.0/go.mod h1:Hoi4DejaNKVahGkRXqGBjT9h1aKmhc7RCYcsgoTieqc= -github.com/fluxcd/pkg/apis/kustomize v1.15.0 h1:p8wPIxdmn0vy0a664rsE9JKCfnliZz4HUsDcTy4ZOxA= -github.com/fluxcd/pkg/apis/kustomize v1.15.0/go.mod h1:XWdsx8P15OiMaQIvmUjYWdmD3zAwhl5q9osl5iCqcOk= -github.com/fluxcd/pkg/apis/meta v1.25.0 h1:fmZgMoe7yITGfhFqdOs7w2GOu3Y/2Vvz4+4p/eay3eA= -github.com/fluxcd/pkg/apis/meta v1.25.0/go.mod h1:1D92RqAet0/n/cH5S0khBXweirHWkw9rCO0V4NCY6xc= -github.com/fluxcd/pkg/auth v0.38.2 h1:AjB64oZO1Er3fxJUw1hRbN6J+2Cf0HWgZIOBE5xUtn8= -github.com/fluxcd/pkg/auth v0.38.2/go.mod h1:038UyC92mnW1mzZ/A2fHJQUpuhPkJzw39ppChuOdYfI= +github.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE= +github.com/fluxcd/pkg/apis/event v0.25.0/go.mod h1:TlK8HWYrTwl0raqBRC+ROoNpYW5fdVnwcwOBOx5Kzw8= +github.com/fluxcd/pkg/apis/kustomize v1.16.0 h1:PhWXEhqQqsisIpwp1/wHvTvo+MO+GGzsBPoN0ZnRE3Y= +github.com/fluxcd/pkg/apis/kustomize v1.16.0/go.mod h1:IZOy4CCtR/hxMGb7erK1RfbGnczVv4/dRBoVD37AywI= +github.com/fluxcd/pkg/apis/meta v1.26.0 h1:dxP1FfBpTCYso6odzRcltVnnRuBb2VyhhgV0VX9YbUE= +github.com/fluxcd/pkg/apis/meta v1.26.0/go.mod h1:c7o6mJGLCMvNrfdinGZehkrdZuFT9vZdZNrn66DtVD0= +github.com/fluxcd/pkg/auth v0.40.0 h1:p6Kw6KH+z8oRqngKhmTt8ILKD/rC+8tP87a//kLZhi8= +github.com/fluxcd/pkg/auth v0.40.0/go.mod h1:Oq/hIEKUMTbL2bv5blf+EhC/jXXJLsOjIMtJj/AtG3Y= github.com/fluxcd/pkg/cache v0.13.0 h1:MqtlgOwIVcGKKgV422e39O+KFSVMWuExKeRaMDBjJlk= github.com/fluxcd/pkg/cache v0.13.0/go.mod h1:0xRZ1hitrIFQ6pl68ke2wZLbIqA2VLzY78HpDo9DVxs= -github.com/fluxcd/pkg/chartutil v1.22.0 h1:yxhDsAKK0w5Ol4hr1SVnQZI1c83FR9PghVucNEGq4VM= -github.com/fluxcd/pkg/chartutil v1.22.0/go.mod h1:aw7h410gKTJfk7KchFzv3tZoh6KlwzZFoameLrIEcNg= +github.com/fluxcd/pkg/chartutil v1.23.0 h1:ohstQEVnrBIbN85FGu83hnmAohLl0PdOoPlsM6+cjyI= +github.com/fluxcd/pkg/chartutil v1.23.0/go.mod h1:kFhmD6DwBgRsvC1ilINsomargMi2WbqvSndWQLikkLc= github.com/fluxcd/pkg/envsubst v1.5.0 h1:S07mo+MkGhptdHA4pRze5HPKlc8tHxKswNdcMZi1WDY= github.com/fluxcd/pkg/envsubst v1.5.0/go.mod h1:c3a8DYI855sZUubHFYQbjfjop6Wu4/zg1cLyf7SnCes= -github.com/fluxcd/pkg/git v0.43.0 h1:11LKsTHw+yx3rcGSrSbkURcdc4huUv3FxQZhHIAMofc= -github.com/fluxcd/pkg/git v0.43.0/go.mod h1:cr9eoYLZHKP3NWgJhhJ8pBcllTpl2SbXVoifW37IyIQ= -github.com/fluxcd/pkg/gittestserver v0.25.0 h1:thnS0OOuU2mEA0PjByxrSxrvlvSwVxJSZY1me782Vq4= -github.com/fluxcd/pkg/gittestserver v0.25.0/go.mod h1:cQqa3cOdKdrIDUqV8SCYbIoNw4/a8frJRGofBLv7sWw= -github.com/fluxcd/pkg/kustomize v1.27.0 h1:bWoWVaHV1ZRo3Ei1JXpY58hJK25WWna+az5jj6zseE0= -github.com/fluxcd/pkg/kustomize v1.27.0/go.mod h1:KKb26vz5EApyOrgencDlvixJnuI6cnkWGks95sK9AFs= -github.com/fluxcd/pkg/oci v0.60.0 h1:uyAoYoj0i9rxFYQchThwfe4i/X0eb5l9wJuDbSAbqGs= -github.com/fluxcd/pkg/oci v0.60.0/go.mod h1:5NT4IaYZocOsXLV3IGgj4FRQtSae46DL8Lq3EcDUqME= -github.com/fluxcd/pkg/runtime v0.100.1 h1:UiPmgY8Yv7UF06MT5T8AG9uDGNszm75/DQtK6JEhnrM= -github.com/fluxcd/pkg/runtime v0.100.1/go.mod h1:SctSsHvFwUfiOVP1zirP6mo7I8wQtXeWVl2lNQWal88= +github.com/fluxcd/pkg/git v0.46.0 h1:QMh0+ZzQ2jO6rIGj4ffR5trZ8g/cxvt8cVajReJ8Iyw= +github.com/fluxcd/pkg/git v0.46.0/go.mod h1:iHcIjx9c8zye3PQiajTJYxgOMRiy7WCs+hfLKDswpfI= +github.com/fluxcd/pkg/gittestserver v0.26.0 h1:+RZrCzFRsE+d5WaqAoqaPCEgcgv/jZp6+f7DS0+Ynb8= +github.com/fluxcd/pkg/gittestserver v0.26.0/go.mod h1:7fybYb0yej1fFNiF1ohs0Jr0XzyaZQ/cRh3AFEoCtuc= +github.com/fluxcd/pkg/kustomize v1.28.0 h1:0RuFVczJRabbt8frHZ/ql8aqte6BOOKk274O09l6/hE= +github.com/fluxcd/pkg/kustomize v1.28.0/go.mod h1:cW08mnngSP8MJYb6mDmMvxH8YjNATdiML0udb37dk+M= +github.com/fluxcd/pkg/oci v0.63.0 h1:ZPKTT2C+gWYjhP63xC76iTPdYE9w3ABcsDq77uhAgwo= +github.com/fluxcd/pkg/oci v0.63.0/go.mod h1:qMPz4njvm6hJzdyGSb8ydSqrapXxTQwJonxHIsdeXSQ= +github.com/fluxcd/pkg/runtime v0.103.0 h1:J5y5GPhWdkyqIUBlaI1FP2N02TtZmsjbWhhZubuTSFk= +github.com/fluxcd/pkg/runtime v0.103.0/go.mod h1:mbo2f3azo3yVQgm7XZGxQB6/2zvzQ5Wgtd8TjRRwwAw= github.com/fluxcd/pkg/sourceignore v0.17.0 h1:Z72nruRMhC15zIEpWoDrAcJcJ1El6QDnP/aRDfE4WOA= github.com/fluxcd/pkg/sourceignore v0.17.0/go.mod h1:3e/VmYLId0pI/H5sK7W9Ibif+j0Ahns9RxNjDMtTTfY= -github.com/fluxcd/pkg/ssa v0.67.1 h1:wmwrznP+USRUtppKRjAiBx3ayriygRx0IeMdX7z/HaM= -github.com/fluxcd/pkg/ssa v0.67.1/go.mod h1:PFXVjChubQOiWDxalpwh6PzRsEswGqnKwZB4ScoxDx4= +github.com/fluxcd/pkg/ssa v0.70.0 h1:IBylYPiTK1IEdCC2DvjKXIhwQcbd5VufXA9WS3zO+tE= +github.com/fluxcd/pkg/ssa v0.70.0/go.mod h1:6igtlt7/zF+nNFQpa5ZAkkvtpL6o36NRU39/PqqC+Bg= github.com/fluxcd/pkg/ssh v0.24.0 h1:hrPlxs0hhXf32DRqs68VbsXs0XfQMphyRVIk0rYYJa4= github.com/fluxcd/pkg/ssh v0.24.0/go.mod h1:xWammEqalrpurpcMiixJRXtynRQtBEoqheyU5F/vWrg= github.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY= github.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls= -github.com/fluxcd/pkg/version v0.12.0 h1:MGbdbNf2D5wazMqAkNPn+Lh5j+oY0gxQJFTGyet5Hfc= -github.com/fluxcd/pkg/version v0.12.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= +github.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw= +github.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= github.com/fluxcd/source-controller/api v1.8.0 h1:ndrYmcv6ZMcdQHFSUkOrFVDO7h16SfDBSw/DOqf/LPo= github.com/fluxcd/source-controller/api v1.8.0/go.mod h1:1O7+sMbqc1+3tPvjmtgFz+bASTl794Y9SxpebHDDSGA= github.com/fluxcd/source-watcher/api/v2 v2.1.0 h1:pXKC3VNacjGT6hDyBqP/2kaNlrzNANUn7si5BuW40QE= @@ -325,12 +325,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= -github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -447,8 +445,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc= -github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -484,15 +482,15 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= -github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= +github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -554,52 +552,52 @@ gitlab.com/gitlab-org/api/client-go v1.29.0 h1:3KnF6vENry/9v9eVrnLi2OfBV0m/WSrwh gitlab.com/gitlab-org/api/client-go v1.29.0/go.mod h1:6i3EZtC6gKiTTmDwp+f6r/Yi9OY4AaYubl5B3yXEdHE= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 h1:/Rij/t18Y7rUayNg7Id6rPrEnHgorxYabm2E6wUdPP4= -go.opentelemetry.io/contrib/bridges/prometheus v0.63.0/go.mod h1:AdyDPn6pkbkt2w01n3BubRVk7xAsCRq1Yg1mpfyA/0E= -go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 h1:NLnZybb9KkfMXPwZhd5diBYJoVxiO9Qa06dacEA7ySY= -go.opentelemetry.io/contrib/exporters/autoexport v0.63.0/go.mod h1:OvRg7gm5WRSCtxzGSsrFHbDLToYlStHNZQ+iPNIyD6g= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= -go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= -go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= -go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= -go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= -go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= -go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= -go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= +go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= +go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= +go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= +go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= +go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= +go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= +go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= +go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -729,10 +727,10 @@ google.golang.org/api v0.261.0 h1:3DoJ2GGibaCxNi1lhdScNMx9fTW87ujKHDgyHMMYdoA= google.golang.org/api v0.261.0/go.mod h1:nVH0ZK5C4tO0RdsMscleeTLY7I8m/Nt9IXxcXD2tfts= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= @@ -758,30 +756,30 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v4 v4.1.1 h1:juO/Vack3pNUBCX0emMvHL1RL27CEWwGyCd3HyP3mPA= -helm.sh/helm/v4 v4.1.1/go.mod h1:yH4qpYvTNBTHnkRSenhi1m7oEFKoN6iK3/rYyFJ00IQ= -k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= -k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE= -k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= -k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= -k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= +helm.sh/helm/v4 v4.1.3 h1:Abfmb+oJUtxoaXDyB2Jhw1zRk3hT6aFfHta+AXb8Lno= +helm.sh/helm/v4 v4.1.3/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI= +k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= +k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60= +k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0= +k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU= +k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= +k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/cli-runtime v0.35.2 h1:3DNctzpPNXavqyrm/FFiT60TLk4UjUxuUMYbKOE970E= +k8s.io/cli-runtime v0.35.2/go.mod h1:G2Ieu0JidLm5m1z9b0OkFhnykvJ1w+vjbz1tR5OFKL0= +k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o= +k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g= +k8s.io/component-base v0.35.2 h1:btgR+qNrpWuRSuvWSnQYsZy88yf5gVwemvz0yw79pGc= +k8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc= -k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo= +k8s.io/kubectl v0.35.2 h1:aSmqhSOfsoG9NR5oR8OD5eMKpLN9x8oncxfqLHbJJII= +k8s.io/kubectl v0.35.2/go.mod h1:+OJC779UsDJGxNPbHxCwvb4e4w9Eh62v/DNYU2TlsyM= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= -sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 8ddf4ec3..9dd15723 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -11,10 +11,10 @@ require ( github.com/fluxcd/image-reflector-controller/api v1.0.4 github.com/fluxcd/kustomize-controller/api v1.7.3 github.com/fluxcd/notification-controller/api v1.7.5 - github.com/fluxcd/pkg/apis/event v0.24.0 - github.com/fluxcd/pkg/apis/meta v1.25.0 - github.com/fluxcd/pkg/git v0.43.0 - github.com/fluxcd/pkg/runtime v0.100.1 + github.com/fluxcd/pkg/apis/event v0.25.0 + github.com/fluxcd/pkg/apis/meta v1.26.0 + github.com/fluxcd/pkg/git v0.46.0 + github.com/fluxcd/pkg/runtime v0.103.0 github.com/fluxcd/source-controller/api v1.7.4 github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b github.com/go-git/go-git/v5 v5.16.5 @@ -24,10 +24,10 @@ require ( github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 github.com/onsi/gomega v1.39.0 google.golang.org/grpc v1.77.0 - k8s.io/api v0.35.0 - k8s.io/apimachinery v0.35.0 - k8s.io/client-go v0.35.0 - sigs.k8s.io/controller-runtime v0.23.1 + k8s.io/api v0.35.2 + k8s.io/apimachinery v0.35.2 + k8s.io/client-go v0.35.2 + sigs.k8s.io/controller-runtime v0.23.3 ) require ( @@ -53,7 +53,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -66,9 +66,9 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect - github.com/fluxcd/pkg/apis/kustomize v1.15.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.16.0 // indirect github.com/fluxcd/pkg/ssh v0.24.0 // indirect - github.com/fluxcd/pkg/version v0.12.0 // indirect + github.com/fluxcd/pkg/version v0.14.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.7.0 // indirect @@ -143,7 +143,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.35.0 // indirect + k8s.io/apiextensions-apiserver v0.35.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 7b16a419..c4f3ba6d 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -78,8 +78,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ= github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= @@ -136,22 +136,22 @@ github.com/fluxcd/notification-controller/api v1.7.5 h1:6CO5bKyjodiK9exQFOdBcz0X github.com/fluxcd/notification-controller/api v1.7.5/go.mod h1:IciwSg8Q0pVtdbsyDyEXx/MxBKWeagxAazpm64C8oCE= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= -github.com/fluxcd/pkg/apis/event v0.24.0 h1:WVPf0FrJ5JExRDDGoo4W0jZgHZt0n4E48/e8b3TSmkA= -github.com/fluxcd/pkg/apis/event v0.24.0/go.mod h1:Hoi4DejaNKVahGkRXqGBjT9h1aKmhc7RCYcsgoTieqc= -github.com/fluxcd/pkg/apis/kustomize v1.15.0 h1:p8wPIxdmn0vy0a664rsE9JKCfnliZz4HUsDcTy4ZOxA= -github.com/fluxcd/pkg/apis/kustomize v1.15.0/go.mod h1:XWdsx8P15OiMaQIvmUjYWdmD3zAwhl5q9osl5iCqcOk= -github.com/fluxcd/pkg/apis/meta v1.25.0 h1:fmZgMoe7yITGfhFqdOs7w2GOu3Y/2Vvz4+4p/eay3eA= -github.com/fluxcd/pkg/apis/meta v1.25.0/go.mod h1:1D92RqAet0/n/cH5S0khBXweirHWkw9rCO0V4NCY6xc= -github.com/fluxcd/pkg/git v0.43.0 h1:11LKsTHw+yx3rcGSrSbkURcdc4huUv3FxQZhHIAMofc= -github.com/fluxcd/pkg/git v0.43.0/go.mod h1:cr9eoYLZHKP3NWgJhhJ8pBcllTpl2SbXVoifW37IyIQ= -github.com/fluxcd/pkg/gittestserver v0.25.0 h1:thnS0OOuU2mEA0PjByxrSxrvlvSwVxJSZY1me782Vq4= -github.com/fluxcd/pkg/gittestserver v0.25.0/go.mod h1:cQqa3cOdKdrIDUqV8SCYbIoNw4/a8frJRGofBLv7sWw= -github.com/fluxcd/pkg/runtime v0.100.1 h1:UiPmgY8Yv7UF06MT5T8AG9uDGNszm75/DQtK6JEhnrM= -github.com/fluxcd/pkg/runtime v0.100.1/go.mod h1:SctSsHvFwUfiOVP1zirP6mo7I8wQtXeWVl2lNQWal88= +github.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE= +github.com/fluxcd/pkg/apis/event v0.25.0/go.mod h1:TlK8HWYrTwl0raqBRC+ROoNpYW5fdVnwcwOBOx5Kzw8= +github.com/fluxcd/pkg/apis/kustomize v1.16.0 h1:PhWXEhqQqsisIpwp1/wHvTvo+MO+GGzsBPoN0ZnRE3Y= +github.com/fluxcd/pkg/apis/kustomize v1.16.0/go.mod h1:IZOy4CCtR/hxMGb7erK1RfbGnczVv4/dRBoVD37AywI= +github.com/fluxcd/pkg/apis/meta v1.26.0 h1:dxP1FfBpTCYso6odzRcltVnnRuBb2VyhhgV0VX9YbUE= +github.com/fluxcd/pkg/apis/meta v1.26.0/go.mod h1:c7o6mJGLCMvNrfdinGZehkrdZuFT9vZdZNrn66DtVD0= +github.com/fluxcd/pkg/git v0.46.0 h1:QMh0+ZzQ2jO6rIGj4ffR5trZ8g/cxvt8cVajReJ8Iyw= +github.com/fluxcd/pkg/git v0.46.0/go.mod h1:iHcIjx9c8zye3PQiajTJYxgOMRiy7WCs+hfLKDswpfI= +github.com/fluxcd/pkg/gittestserver v0.26.0 h1:+RZrCzFRsE+d5WaqAoqaPCEgcgv/jZp6+f7DS0+Ynb8= +github.com/fluxcd/pkg/gittestserver v0.26.0/go.mod h1:7fybYb0yej1fFNiF1ohs0Jr0XzyaZQ/cRh3AFEoCtuc= +github.com/fluxcd/pkg/runtime v0.103.0 h1:J5y5GPhWdkyqIUBlaI1FP2N02TtZmsjbWhhZubuTSFk= +github.com/fluxcd/pkg/runtime v0.103.0/go.mod h1:mbo2f3azo3yVQgm7XZGxQB6/2zvzQ5Wgtd8TjRRwwAw= github.com/fluxcd/pkg/ssh v0.24.0 h1:hrPlxs0hhXf32DRqs68VbsXs0XfQMphyRVIk0rYYJa4= github.com/fluxcd/pkg/ssh v0.24.0/go.mod h1:xWammEqalrpurpcMiixJRXtynRQtBEoqheyU5F/vWrg= -github.com/fluxcd/pkg/version v0.12.0 h1:MGbdbNf2D5wazMqAkNPn+Lh5j+oY0gxQJFTGyet5Hfc= -github.com/fluxcd/pkg/version v0.12.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= +github.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw= +github.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= github.com/fluxcd/source-controller/api v1.7.4 h1:+EOVnRA9LmLxOx7J273l7IOEU39m+Slt/nQGBy69ygs= github.com/fluxcd/source-controller/api v1.7.4/go.mod h1:ruf49LEgZRBfcP+eshl2n9SX1MfHayCcViAIGnZcaDY= github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b h1:FSPtvaVgL8azcyweqLmD71elAw4vozuXH/QvsJQ7tg0= @@ -520,22 +520,22 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= -k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= +k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60= +k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0= +k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU= +k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= +k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o= +k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= -sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= From 2288dd90d6c53dbd9e2b8f2a2d016d26ffd040ac Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Thu, 12 Mar 2026 14:01:48 +0000 Subject: [PATCH 08/48] Update toolkit components - helm-controller to v1.5.2 https://github.com/fluxcd/helm-controller/blob/v1.5.2/CHANGELOG.md - kustomize-controller to v1.8.2 https://github.com/fluxcd/kustomize-controller/blob/v1.8.2/CHANGELOG.md - source-controller to v1.8.1 https://github.com/fluxcd/source-controller/blob/v1.8.1/CHANGELOG.md - notification-controller to v1.8.2 https://github.com/fluxcd/notification-controller/blob/v1.8.2/CHANGELOG.md - image-reflector-controller to v1.1.1 https://github.com/fluxcd/image-reflector-controller/blob/v1.1.1/CHANGELOG.md - image-automation-controller to v1.1.1 https://github.com/fluxcd/image-automation-controller/blob/v1.1.1/CHANGELOG.md - source-watcher to v2.1.1 https://github.com/fluxcd/source-watcher/blob/v2.1.1/CHANGELOG.md Signed-off-by: GitHub --- go.mod | 14 +++++----- go.sum | 28 +++++++++---------- .../bases/helm-controller/kustomization.yaml | 4 +-- .../kustomization.yaml | 4 +-- .../kustomization.yaml | 4 +-- .../kustomize-controller/kustomization.yaml | 4 +-- .../kustomization.yaml | 4 +-- .../source-controller/kustomization.yaml | 4 +-- .../bases/source-watcher/kustomization.yaml | 4 +-- manifests/crds/kustomization.yaml | 14 +++++----- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 46cba866..ad55995a 100644 --- a/go.mod +++ b/go.mod @@ -12,11 +12,11 @@ require ( github.com/distribution/distribution/v3 v3.0.0 github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/go-git-providers v0.26.0 - github.com/fluxcd/helm-controller/api v1.5.1 - github.com/fluxcd/image-automation-controller/api v1.1.0 - github.com/fluxcd/image-reflector-controller/api v1.1.0 - github.com/fluxcd/kustomize-controller/api v1.8.1 - github.com/fluxcd/notification-controller/api v1.8.1 + github.com/fluxcd/helm-controller/api v1.5.2 + github.com/fluxcd/image-automation-controller/api v1.1.1 + github.com/fluxcd/image-reflector-controller/api v1.1.1 + github.com/fluxcd/kustomize-controller/api v1.8.2 + github.com/fluxcd/notification-controller/api v1.8.2 github.com/fluxcd/pkg/apis/event v0.25.0 github.com/fluxcd/pkg/apis/meta v1.26.0 github.com/fluxcd/pkg/auth v0.40.0 @@ -31,8 +31,8 @@ require ( github.com/fluxcd/pkg/ssh v0.24.0 github.com/fluxcd/pkg/tar v0.17.0 github.com/fluxcd/pkg/version v0.14.0 - github.com/fluxcd/source-controller/api v1.8.0 - github.com/fluxcd/source-watcher/api/v2 v2.1.0 + github.com/fluxcd/source-controller/api v1.8.1 + github.com/fluxcd/source-watcher/api/v2 v2.1.1 github.com/go-git/go-git/v5 v5.16.5 github.com/go-logr/logr v1.4.3 github.com/gonvenience/bunt v1.4.2 diff --git a/go.sum b/go.sum index ac0034b2..7fd7f027 100644 --- a/go.sum +++ b/go.sum @@ -174,16 +174,16 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw= github.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo= -github.com/fluxcd/helm-controller/api v1.5.1 h1:yraWl0ImzO4yIy/N5d9i54N+OZxKuFZqjed8wrIjy8U= -github.com/fluxcd/helm-controller/api v1.5.1/go.mod h1:Yr0y7GKizbvQQGK5wBX6sGCZrTY86AN9n1PNEsji2XE= -github.com/fluxcd/image-automation-controller/api v1.1.0 h1:CLPNHQskX0falo4s1suG1ztUe9IGaY9q5ntcz5Fxt9A= -github.com/fluxcd/image-automation-controller/api v1.1.0/go.mod h1:dIpTDlWgUfjvdGZCNfe8Ht9sCiHwRbJDoIbwfLQ56wc= -github.com/fluxcd/image-reflector-controller/api v1.1.0 h1:7TtE9DrCnlH1Wn3R3UKXJHNhX/FgS0ejdjFKHzl+XHs= -github.com/fluxcd/image-reflector-controller/api v1.1.0/go.mod h1:hLGsqTv3RydJXaApmN+ZtIOHNxlUdmuOJl323x6dsPE= -github.com/fluxcd/kustomize-controller/api v1.8.1 h1:Pe5+sV1i1EwfK5TA4ogYX6YJ6ADJaETmG58WYieRkG4= -github.com/fluxcd/kustomize-controller/api v1.8.1/go.mod h1:+ZJB/dIGbnSzZ5J/kiJ8n0USmLNAjfeZU6Xfra0oMZA= -github.com/fluxcd/notification-controller/api v1.8.1 h1:tBg5QrXsVAdMEsV/oq3gqApdRDwcO9gyc6plDf/3QGI= -github.com/fluxcd/notification-controller/api v1.8.1/go.mod h1:tGlTJS+hSLbgQm1L78hl6N3iWbTerifh1V5Qm8we4Zo= +github.com/fluxcd/helm-controller/api v1.5.2 h1:3xeFPcsvibingV32y8MRj040ydZIPOMCTToMXOkhtUo= +github.com/fluxcd/helm-controller/api v1.5.2/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI= +github.com/fluxcd/image-automation-controller/api v1.1.1 h1:uiu7kjdVoW8/461HOemX6I7RcPornEzQliWgTg6LnWI= +github.com/fluxcd/image-automation-controller/api v1.1.1/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= +github.com/fluxcd/image-reflector-controller/api v1.1.1 h1:4Bj1abzVnjj8+b/293kNeFMRJc+y2wO8Z12ReZ/gA0w= +github.com/fluxcd/image-reflector-controller/api v1.1.1/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0= +github.com/fluxcd/kustomize-controller/api v1.8.2 h1:LcFUjJccwNrhCo7pQBBneLAlHfZZcb58bWB2LnyFwag= +github.com/fluxcd/kustomize-controller/api v1.8.2/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= +github.com/fluxcd/notification-controller/api v1.8.2 h1:TDrXohUC5Gh3BF+v2ux9/zEG1Ax8u49WDW+3Y6GiIEc= +github.com/fluxcd/notification-controller/api v1.8.2/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE= @@ -220,10 +220,10 @@ github.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY github.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls= github.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw= github.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= -github.com/fluxcd/source-controller/api v1.8.0 h1:ndrYmcv6ZMcdQHFSUkOrFVDO7h16SfDBSw/DOqf/LPo= -github.com/fluxcd/source-controller/api v1.8.0/go.mod h1:1O7+sMbqc1+3tPvjmtgFz+bASTl794Y9SxpebHDDSGA= -github.com/fluxcd/source-watcher/api/v2 v2.1.0 h1:pXKC3VNacjGT6hDyBqP/2kaNlrzNANUn7si5BuW40QE= -github.com/fluxcd/source-watcher/api/v2 v2.1.0/go.mod h1:s5ahWDfD0KmpFAbQf3DHCLnWMRkfqt3l5VoCk08LFts= +github.com/fluxcd/source-controller/api v1.8.1 h1:49HiJF5mNEdZTwueQMRahTVts35B+xhN5CsuOAL9gQ0= +github.com/fluxcd/source-controller/api v1.8.1/go.mod h1:HgZ6NSH1cyOE2jRoNwln1xEwr9ETvrLeiy1o4O04vQM= +github.com/fluxcd/source-watcher/api/v2 v2.1.1 h1:1LfT50ty+78MKKbschAZl28QbVqIyjaNq17KmW5wPJI= +github.com/fluxcd/source-watcher/api/v2 v2.1.1/go.mod h1:6M1BzBGQRoIuSenSQlfJHwMVVobFPiNPxXqfN0IILc4= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= diff --git a/manifests/bases/helm-controller/kustomization.yaml b/manifests/bases/helm-controller/kustomization.yaml index 97df865a..71e59787 100644 --- a/manifests/bases/helm-controller/kustomization.yaml +++ b/manifests/bases/helm-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.1/helm-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.1/helm-controller.deployment.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.2/helm-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.2/helm-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/image-automation-controller/kustomization.yaml b/manifests/bases/image-automation-controller/kustomization.yaml index 43a41cb6..3d8efe05 100644 --- a/manifests/bases/image-automation-controller/kustomization.yaml +++ b/manifests/bases/image-automation-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.0/image-automation-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.0/image-automation-controller.deployment.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/image-reflector-controller/kustomization.yaml b/manifests/bases/image-reflector-controller/kustomization.yaml index 8d38ee5c..d7acc56c 100644 --- a/manifests/bases/image-reflector-controller/kustomization.yaml +++ b/manifests/bases/image-reflector-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.0/image-reflector-controller.crds.yaml -- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.0/image-reflector-controller.deployment.yaml +- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml +- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index 15238b0e..f75f707d 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.1/kustomize-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.1/kustomize-controller.deployment.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/notification-controller/kustomization.yaml b/manifests/bases/notification-controller/kustomization.yaml index 2a763063..71c3de84 100644 --- a/manifests/bases/notification-controller/kustomization.yaml +++ b/manifests/bases/notification-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.1/notification-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.1/notification-controller.deployment.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index 1b2a5f44..eba59908 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.0/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v1.8.0/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/source-watcher/kustomization.yaml b/manifests/bases/source-watcher/kustomization.yaml index 2c50436d..15dd876c 100644 --- a/manifests/bases/source-watcher/kustomization.yaml +++ b/manifests/bases/source-watcher/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-watcher/releases/download/v2.1.0/source-watcher.crds.yaml -- https://github.com/fluxcd/source-watcher/releases/download/v2.1.0/source-watcher.deployment.yaml +- https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml +- https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index a871eb29..de176cc1 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -1,10 +1,10 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.0/source-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.1/kustomize-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.1/helm-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.1/notification-controller.crds.yaml -- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.0/image-reflector-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.0/image-automation-controller.crds.yaml -- https://github.com/fluxcd/source-watcher/releases/download/v2.1.0/source-watcher.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.2/helm-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml +- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml +- https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml From 484346ffcc89082ae09dbfa9386a62c71359812a Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 12 Mar 2026 16:33:04 +0000 Subject: [PATCH 09/48] Add target branch name to update branch Signed-off-by: Matheus Pimenta --- .github/workflows/README.md | 2 +- .github/workflows/conformance.yaml | 2 +- .github/workflows/update.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 79ca735a..95bdc83d 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -23,7 +23,7 @@ amd when it finds a new controller version, the workflow performs the following - Updates the controller API package version in `go.mod`. - Patches the controller CRDs version in the `manifests/crds` overlay. - Patches the controller Deployment version in `manifests/bases` overlay. -- Opens a Pull Request against the `main` branch. +- Opens a Pull Request against the checked out branch. - Triggers the e2e test suite to run for the opened PR. diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 9b66001f..ea981adc 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -3,7 +3,7 @@ name: conformance on: workflow_dispatch: push: - branches: [ 'main', 'update-components', 'release/**', 'conform*' ] + branches: [ 'main', 'update-components-**', 'release/**', 'conform*' ] permissions: contents: read diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml index c92290fc..5ae821fb 100644 --- a/.github/workflows/update.yaml +++ b/.github/workflows/update.yaml @@ -106,7 +106,7 @@ jobs: committer: GitHub author: fluxcdbot signoff: true - branch: update-components + branch: update-components-${{ github.ref_name }} title: Update toolkit components body: | ${{ steps.update.outputs.pr_body }} From 6a5e644798a0d5b31cbc569debe94ec05c177db7 Mon Sep 17 00:00:00 2001 From: Aman-Cool Date: Tue, 10 Feb 2026 14:22:47 +0530 Subject: [PATCH 10/48] fix: return error immediately on failed reconciliation status Co-authored-by: Matheus Pimenta Signed-off-by: Aman-Cool --- cmd/flux/reconcile.go | 9 ++++++++- cmd/flux/resume.go | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cmd/flux/reconcile.go b/cmd/flux/reconcile.go index ffdcce91..9f0787bd 100644 --- a/cmd/flux/reconcile.go +++ b/cmd/flux/reconcile.go @@ -152,7 +152,14 @@ func reconciliationHandled(kubeClient client.Client, namespacedName types.Namesp return false, err } - return result.Status == kstatus.CurrentStatus, nil + switch result.Status { + case kstatus.CurrentStatus: + return true, nil + case kstatus.InProgressStatus: + return false, nil + default: + return false, fmt.Errorf("%s", result.Message) + } } } diff --git a/cmd/flux/resume.go b/cmd/flux/resume.go index fe23a411..e531ecee 100644 --- a/cmd/flux/resume.go +++ b/cmd/flux/resume.go @@ -126,6 +126,17 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error { resume.printMessage(reconcileResps) + // Return an error if any reconciliation failed + var failedCount int + for _, r := range reconcileResps { + if r.resumable != nil && r.err != nil { + failedCount++ + } + } + if failedCount > 0 { + return fmt.Errorf("reconciliation failed for %d %s(s)", failedCount, resume.kind) + } + return nil } From 64e18014c31161bd2e258cab39dbaacf9e377d8d Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 13 Mar 2026 21:45:08 +0200 Subject: [PATCH 11/48] Mark RFC 0010, 0011 and 0012 as implemented Signed-off-by: Stefan Prodan --- .../README.md | 24 ++++++++----------- rfcs/0011-opentelemetry-tracing/README.md | 16 +++---------- rfcs/0012-external-artifact/README.md | 11 +++------ 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/rfcs/0010-multi-tenant-workload-identity/README.md b/rfcs/0010-multi-tenant-workload-identity/README.md index 9ed342d9..920e402e 100644 --- a/rfcs/0010-multi-tenant-workload-identity/README.md +++ b/rfcs/0010-multi-tenant-workload-identity/README.md @@ -1,15 +1,10 @@ # RFC-0010 Multi-Tenant Workload Identity -**Status:** implementable - - +**Status:** implemented **Creation date:** 2025-02-22 -**Last update:** 2025-04-29 +**Last update:** 2026-03-13 ## Summary @@ -1420,10 +1415,11 @@ options to call `gcp.NewTokenSource()` and feed this token source to the `HelmRepository` and `HelmChart`, as well as for SOPS decryption in the `Kustomization` API and Azure Event Hubs in the `Provider` API. - - +* In Flux 2.7 object-level workload identity was introduced for all + the remaining APIs that support cloud providers, i.e. `Bucket`, + `GitRepository` and `ImageUpdateAutomation`, and also all the + remaining types for the `Provider` API, i.e. `azuredevops` and + `googlepubsub`. In addition, support for controller and + object-level workload identity was introduced for the + `Kustomization` and `HelmRelease` APIs for remote cluster + access. diff --git a/rfcs/0011-opentelemetry-tracing/README.md b/rfcs/0011-opentelemetry-tracing/README.md index 768e05a4..3dd5e851 100644 --- a/rfcs/0011-opentelemetry-tracing/README.md +++ b/rfcs/0011-opentelemetry-tracing/README.md @@ -1,15 +1,10 @@ # RFC-0011: OpenTelemetry Tracing -**Status:** provisional - - +**Status:** implemented **Creation date:** 2025-04-24 -**Last update:** 2025-08-13 +**Last update:** 2026-03-13 ## Summary The aim is to be able to collect traces via OpenTelemetry (OTel) across all Flux related objects, such as HelmReleases, Kustomizations and among others. These may be sent towards a tracing provider where may be potentially stored and visualized. Flux does not have any responsibility on storing and visualizing those, it keeps being completely stateless. Thereby, being seamless for the user, the implementation is going to be part of the already existing `Alert` API Type. Therefore, `EventSources` is going to discriminate the events belonging to the specific sources, which are going to be looked up to and send them out towards the `Provider` set. In this way, it could facilitate the observability and monitoring of Flux related objects. @@ -210,9 +205,4 @@ This design ensures trace continuity even in challenging distributed environment ## Implementation History - +* RFC implemented and generally available in Flux [v2.7.0](https://github.com/fluxcd/flux2/releases/tag/v2.7.0) diff --git a/rfcs/0012-external-artifact/README.md b/rfcs/0012-external-artifact/README.md index 602c4918..c8d21bbf 100644 --- a/rfcs/0012-external-artifact/README.md +++ b/rfcs/0012-external-artifact/README.md @@ -1,10 +1,10 @@ # RFC-0012 External Artifact -**Status:** provisional +**Status:** implemented **Creation date:** 2025-04-08 -**Last update:** 2025-09-03 +**Last update:** 2026-03-13 ## Summary @@ -319,9 +319,4 @@ control the adoption of the `ExternalArtifact` feature in their clusters. ## Implementation History - +* RFC implemented and generally available in Flux [v2.7.0](https://github.com/fluxcd/flux2/releases/tag/v2.7.0) From dc5631f12be70ae2cfc1086287d79d8bf0f860fd Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Mon, 16 Mar 2026 13:23:43 +0000 Subject: [PATCH 12/48] Update toolkit components - helm-controller to v1.5.3 https://github.com/fluxcd/helm-controller/blob/v1.5.3/CHANGELOG.md Signed-off-by: GitHub --- go.mod | 2 +- go.sum | 4 ++-- manifests/bases/helm-controller/kustomization.yaml | 4 ++-- manifests/crds/kustomization.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ad55995a..7554accd 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/distribution/distribution/v3 v3.0.0 github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/go-git-providers v0.26.0 - github.com/fluxcd/helm-controller/api v1.5.2 + github.com/fluxcd/helm-controller/api v1.5.3 github.com/fluxcd/image-automation-controller/api v1.1.1 github.com/fluxcd/image-reflector-controller/api v1.1.1 github.com/fluxcd/kustomize-controller/api v1.8.2 diff --git a/go.sum b/go.sum index 7fd7f027..3aed1cda 100644 --- a/go.sum +++ b/go.sum @@ -174,8 +174,8 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw= github.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo= -github.com/fluxcd/helm-controller/api v1.5.2 h1:3xeFPcsvibingV32y8MRj040ydZIPOMCTToMXOkhtUo= -github.com/fluxcd/helm-controller/api v1.5.2/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI= +github.com/fluxcd/helm-controller/api v1.5.3 h1:ruLzuyTHjjE9A5B/U+Id2q7yHXXqSFTswdZ14xCS5So= +github.com/fluxcd/helm-controller/api v1.5.3/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI= github.com/fluxcd/image-automation-controller/api v1.1.1 h1:uiu7kjdVoW8/461HOemX6I7RcPornEzQliWgTg6LnWI= github.com/fluxcd/image-automation-controller/api v1.1.1/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= github.com/fluxcd/image-reflector-controller/api v1.1.1 h1:4Bj1abzVnjj8+b/293kNeFMRJc+y2wO8Z12ReZ/gA0w= diff --git a/manifests/bases/helm-controller/kustomization.yaml b/manifests/bases/helm-controller/kustomization.yaml index 71e59787..1bafd6f3 100644 --- a/manifests/bases/helm-controller/kustomization.yaml +++ b/manifests/bases/helm-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.2/helm-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.2/helm-controller.deployment.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index de176cc1..c29dc4ec 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -3,7 +3,7 @@ kind: Kustomization resources: - https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml - https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.2/helm-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml - https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml - https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml - https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml From 7bf0bda689527ac80f52bda71ad2b461ed58266b Mon Sep 17 00:00:00 2001 From: Rohan Sood <56945243+rohansood10@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:47:27 -0700 Subject: [PATCH 13/48] Add --resolve-symlinks flag to build and push artifact commands This adds a --resolve-symlinks flag to the flux build artifact and flux push artifact commands. When enabled, symlinks in the source directory are resolved (copied as regular files/directories) before building the artifact. This includes: - Recursive symlink resolution with cycle detection - File permission preservation - Proper handling of both single-file and directory symlink targets - Comprehensive test coverage Fixes #5055 Signed-off-by: Rohan Sood <56945243+rohansood10@users.noreply.github.com> --- cmd/flux/build_artifact.go | 153 +++++++++++++++++++++++++++++++- cmd/flux/build_artifact_test.go | 111 +++++++++++++++++++++++ cmd/flux/push_artifact.go | 33 ++++--- 3 files changed, 283 insertions(+), 14 deletions(-) diff --git a/cmd/flux/build_artifact.go b/cmd/flux/build_artifact.go index 9da0ca0e..7dcc7d42 100644 --- a/cmd/flux/build_artifact.go +++ b/cmd/flux/build_artifact.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "os" + "path/filepath" "strings" "github.com/spf13/cobra" @@ -48,9 +49,10 @@ from the given directory or a single manifest file.`, } type buildArtifactFlags struct { - output string - path string - ignorePaths []string + output string + path string + ignorePaths []string + resolveSymlinks bool } var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...) @@ -61,6 +63,7 @@ func init() { buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, "path", "p", "", "Path to the directory where the Kubernetes manifests are located.") buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.") buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format") + buildArtifactCmd.Flags().BoolVar(&buildArtifactArgs.resolveSymlinks, "resolve-symlinks", false, "resolve symlinks by copying their targets into the artifact") buildCmd.AddCommand(buildArtifactCmd) } @@ -85,6 +88,15 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("invalid path '%s', must point to an existing directory or file", path) } + if buildArtifactArgs.resolveSymlinks { + resolved, cleanupDir, err := resolveSymlinks(path) + if err != nil { + return fmt.Errorf("resolving symlinks failed: %w", err) + } + defer os.RemoveAll(cleanupDir) + path = resolved + } + logger.Actionf("building artifact from %s", path) ociClient := oci.NewClient(oci.DefaultOptions()) @@ -96,6 +108,141 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error { return nil } +// resolveSymlinks creates a temporary directory with symlinks resolved to their +// real file contents. This allows building artifacts from symlink trees (e.g., +// those created by Nix) where the actual files live outside the source directory. +// It returns the resolved path and the temporary directory path for cleanup. +func resolveSymlinks(srcPath string) (string, string, error) { + absPath, err := filepath.Abs(srcPath) + if err != nil { + return "", "", err + } + + info, err := os.Stat(absPath) + if err != nil { + return "", "", err + } + + // For a single file, resolve the symlink and return the path to the + // copied file within the temp dir, preserving file semantics for callers. + if !info.IsDir() { + resolved, err := filepath.EvalSymlinks(absPath) + if err != nil { + return "", "", fmt.Errorf("resolving symlink for %s: %w", absPath, err) + } + tmpDir, err := os.MkdirTemp("", "flux-artifact-*") + if err != nil { + return "", "", err + } + dst := filepath.Join(tmpDir, filepath.Base(absPath)) + if err := copyFile(resolved, dst); err != nil { + os.RemoveAll(tmpDir) + return "", "", err + } + return dst, tmpDir, nil + } + + tmpDir, err := os.MkdirTemp("", "flux-artifact-*") + if err != nil { + return "", "", err + } + + visited := make(map[string]bool) + if err := copyDir(absPath, tmpDir, visited); err != nil { + os.RemoveAll(tmpDir) + return "", "", err + } + + return tmpDir, tmpDir, nil +} + +// copyDir recursively copies the contents of srcDir to dstDir, resolving any +// symlinks encountered along the way. The visited map tracks resolved real +// directory paths to detect and break symlink cycles. +func copyDir(srcDir, dstDir string, visited map[string]bool) error { + real, err := filepath.EvalSymlinks(srcDir) + if err != nil { + return fmt.Errorf("resolving symlink %s: %w", srcDir, err) + } + abs, err := filepath.Abs(real) + if err != nil { + return fmt.Errorf("getting absolute path for %s: %w", real, err) + } + if visited[abs] { + return nil // break the cycle + } + visited[abs] = true + + entries, err := os.ReadDir(srcDir) + if err != nil { + return err + } + + for _, entry := range entries { + srcPath := filepath.Join(srcDir, entry.Name()) + dstPath := filepath.Join(dstDir, entry.Name()) + + // Resolve symlinks to get the real path and info. + realPath, err := filepath.EvalSymlinks(srcPath) + if err != nil { + return fmt.Errorf("resolving symlink %s: %w", srcPath, err) + } + realInfo, err := os.Stat(realPath) + if err != nil { + return fmt.Errorf("stat resolved path %s: %w", realPath, err) + } + + if realInfo.IsDir() { + if err := os.MkdirAll(dstPath, realInfo.Mode()); err != nil { + return err + } + // Recursively copy the resolved directory contents. + if err := copyDir(realPath, dstPath, visited); err != nil { + return err + } + continue + } + + if !realInfo.Mode().IsRegular() { + continue + } + + if err := copyFile(realPath, dstPath); err != nil { + return err + } + } + + return nil +} + +func copyFile(src, dst string) error { + srcInfo, err := os.Stat(src) + if err != nil { + return err + } + + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return err + } + + out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) + if err != nil { + return err + } + defer out.Close() + + if _, err := io.Copy(out, in); err != nil { + return err + } + return out.Close() +} + func saveReaderToFile(reader io.Reader) (string, error) { b, err := io.ReadAll(bufio.NewReader(reader)) if err != nil { diff --git a/cmd/flux/build_artifact_test.go b/cmd/flux/build_artifact_test.go index ba84186c..bfdaaaed 100644 --- a/cmd/flux/build_artifact_test.go +++ b/cmd/flux/build_artifact_test.go @@ -18,6 +18,7 @@ package main import ( "os" + "path/filepath" "strings" "testing" @@ -68,3 +69,113 @@ data: } } + +func Test_resolveSymlinks(t *testing.T) { + g := NewWithT(t) + + // Create source directory with a real file + srcDir := t.TempDir() + realFile := filepath.Join(srcDir, "real.yaml") + g.Expect(os.WriteFile(realFile, []byte("apiVersion: v1\nkind: Namespace\nmetadata:\n name: test\n"), 0o644)).To(Succeed()) + + // Create a directory with symlinks pointing to files outside it + symlinkDir := t.TempDir() + symlinkFile := filepath.Join(symlinkDir, "linked.yaml") + g.Expect(os.Symlink(realFile, symlinkFile)).To(Succeed()) + + // Also add a regular file in the symlink dir + regularFile := filepath.Join(symlinkDir, "regular.yaml") + g.Expect(os.WriteFile(regularFile, []byte("apiVersion: v1\nkind: ConfigMap\n"), 0o644)).To(Succeed()) + + // Create a symlinked subdirectory + subDir := filepath.Join(srcDir, "subdir") + g.Expect(os.MkdirAll(subDir, 0o755)).To(Succeed()) + g.Expect(os.WriteFile(filepath.Join(subDir, "nested.yaml"), []byte("nested"), 0o644)).To(Succeed()) + g.Expect(os.Symlink(subDir, filepath.Join(symlinkDir, "linkeddir"))).To(Succeed()) + + // Resolve symlinks + resolved, cleanupDir, err := resolveSymlinks(symlinkDir) + g.Expect(err).To(BeNil()) + t.Cleanup(func() { os.RemoveAll(cleanupDir) }) + + // Verify the regular file was copied + content, err := os.ReadFile(filepath.Join(resolved, "regular.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(string(content)).To(Equal("apiVersion: v1\nkind: ConfigMap\n")) + + // Verify the symlinked file was resolved and copied + content, err = os.ReadFile(filepath.Join(resolved, "linked.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(string(content)).To(ContainSubstring("kind: Namespace")) + + // Verify that the resolved file is a regular file, not a symlink + info, err := os.Lstat(filepath.Join(resolved, "linked.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(info.Mode().IsRegular()).To(BeTrue()) + + // Verify that the symlinked directory was resolved and its contents were copied + content, err = os.ReadFile(filepath.Join(resolved, "linkeddir", "nested.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(string(content)).To(Equal("nested")) + + // Verify that the file inside the symlinked directory is a regular file + info, err = os.Lstat(filepath.Join(resolved, "linkeddir", "nested.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(info.Mode().IsRegular()).To(BeTrue()) +} + +func Test_resolveSymlinks_singleFile(t *testing.T) { + g := NewWithT(t) + + // Create a real file + srcDir := t.TempDir() + realFile := filepath.Join(srcDir, "manifest.yaml") + g.Expect(os.WriteFile(realFile, []byte("kind: ConfigMap"), 0o644)).To(Succeed()) + + // Create a symlink to the real file + linkDir := t.TempDir() + linkFile := filepath.Join(linkDir, "link.yaml") + g.Expect(os.Symlink(realFile, linkFile)).To(Succeed()) + + // Resolve the single symlinked file + resolved, cleanupDir, err := resolveSymlinks(linkFile) + g.Expect(err).To(BeNil()) + t.Cleanup(func() { os.RemoveAll(cleanupDir) }) + + // The returned path should be a file, not a directory + info, err := os.Stat(resolved) + g.Expect(err).To(BeNil()) + g.Expect(info.IsDir()).To(BeFalse()) + + // Verify contents + content, err := os.ReadFile(resolved) + g.Expect(err).To(BeNil()) + g.Expect(string(content)).To(Equal("kind: ConfigMap")) +} + +func Test_resolveSymlinks_cycle(t *testing.T) { + g := NewWithT(t) + + // Create a directory with a symlink cycle: dir/link -> dir + dir := t.TempDir() + g.Expect(os.WriteFile(filepath.Join(dir, "file.yaml"), []byte("data"), 0o644)).To(Succeed()) + g.Expect(os.Symlink(dir, filepath.Join(dir, "cycle"))).To(Succeed()) + + // resolveSymlinks should not infinite-loop + resolved, cleanupDir, err := resolveSymlinks(dir) + g.Expect(err).To(BeNil()) + t.Cleanup(func() { os.RemoveAll(cleanupDir) }) + + // The file should be copied + content, err := os.ReadFile(filepath.Join(resolved, "file.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(string(content)).To(Equal("data")) + + // The cycle directory should exist but not cause infinite nesting + _, err = os.Stat(filepath.Join(resolved, "cycle")) + g.Expect(err).To(BeNil()) + + // There should NOT be deeply nested cycle/cycle/cycle/... paths + _, err = os.Stat(filepath.Join(resolved, "cycle", "cycle", "cycle")) + g.Expect(os.IsNotExist(err)).To(BeTrue()) +} diff --git a/cmd/flux/push_artifact.go b/cmd/flux/push_artifact.go index c37f0ef1..237c2593 100644 --- a/cmd/flux/push_artifact.go +++ b/cmd/flux/push_artifact.go @@ -103,17 +103,18 @@ The command can read the credentials from '~/.docker/config.json' but they can a } type pushArtifactFlags struct { - path string - source string - revision string - creds string - provider flags.SourceOCIProvider - ignorePaths []string - annotations []string - output string - debug bool - reproducible bool - insecure bool + path string + source string + revision string + creds string + provider flags.SourceOCIProvider + ignorePaths []string + annotations []string + output string + debug bool + reproducible bool + insecure bool + resolveSymlinks bool } var pushArtifactArgs = newPushArtifactFlags() @@ -137,6 +138,7 @@ func init() { pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library") pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'") pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pushed without TLS") + pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.resolveSymlinks, "resolve-symlinks", false, "resolve symlinks by copying their targets into the artifact") pushCmd.AddCommand(pushArtifactCmd) } @@ -183,6 +185,15 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("invalid path '%s', must point to an existing directory or file: %w", path, err) } + if pushArtifactArgs.resolveSymlinks { + resolved, cleanupDir, err := resolveSymlinks(path) + if err != nil { + return fmt.Errorf("resolving symlinks failed: %w", err) + } + defer os.RemoveAll(cleanupDir) + path = resolved + } + annotations := map[string]string{} for _, annotation := range pushArtifactArgs.annotations { kv := strings.Split(annotation, "=") From 457abed9f92ab2128d1a9f2872f4416e8c393876 Mon Sep 17 00:00:00 2001 From: Ghassan Malke Date: Mon, 30 Mar 2026 13:11:33 +0200 Subject: [PATCH 14/48] fix: validate --source flag in create kustomization command Signed-off-by: Ghassan Malke --- cmd/flux/create_kustomization.go | 3 ++ cmd/flux/create_kustomization_test.go | 48 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 cmd/flux/create_kustomization_test.go diff --git a/cmd/flux/create_kustomization.go b/cmd/flux/create_kustomization.go index a7ae93ab..45740c34 100644 --- a/cmd/flux/create_kustomization.go +++ b/cmd/flux/create_kustomization.go @@ -136,6 +136,9 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error { if !strings.HasPrefix(kustomizationArgs.path.String(), "./") { return fmt.Errorf("path must begin with ./") } + if kustomizationArgs.source.Name == "" { + return fmt.Errorf("source is required") + } if !createArgs.export { logger.Generatef("generating Kustomization") diff --git a/cmd/flux/create_kustomization_test.go b/cmd/flux/create_kustomization_test.go new file mode 100644 index 00000000..ee743816 --- /dev/null +++ b/cmd/flux/create_kustomization_test.go @@ -0,0 +1,48 @@ +//go:build unit +// +build unit + +/* +Copyright 2026 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 main + +import "testing" + +func TestCreateKustomization(t *testing.T) { + tests := []struct { + name string + args string + assert assertFunc + }{ + { + // A user creating a kustomization without --source gets a confusing + // API-level error about spec.sourceRef.kind instead of a clear message. + name: "missing source", + args: "create kustomization my-app --path=./deploy --export", + assert: assertError("source is required"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args, + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} From 241d703e7febeff62a2a0997972fdcc708f00cca Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Tue, 7 Apr 2026 17:12:55 +0000 Subject: [PATCH 15/48] Update toolkit components - kustomize-controller to v1.8.3 https://github.com/fluxcd/kustomize-controller/blob/v1.8.3/CHANGELOG.md - source-controller to v1.8.2 https://github.com/fluxcd/source-controller/blob/v1.8.2/CHANGELOG.md - notification-controller to v1.8.3 https://github.com/fluxcd/notification-controller/blob/v1.8.3/CHANGELOG.md Signed-off-by: GitHub --- go.mod | 6 +++--- go.sum | 12 ++++++------ .../bases/kustomize-controller/kustomization.yaml | 4 ++-- .../bases/notification-controller/kustomization.yaml | 4 ++-- manifests/bases/source-controller/kustomization.yaml | 4 ++-- manifests/crds/kustomization.yaml | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 7554accd..b6a74595 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/fluxcd/helm-controller/api v1.5.3 github.com/fluxcd/image-automation-controller/api v1.1.1 github.com/fluxcd/image-reflector-controller/api v1.1.1 - github.com/fluxcd/kustomize-controller/api v1.8.2 - github.com/fluxcd/notification-controller/api v1.8.2 + github.com/fluxcd/kustomize-controller/api v1.8.3 + github.com/fluxcd/notification-controller/api v1.8.3 github.com/fluxcd/pkg/apis/event v0.25.0 github.com/fluxcd/pkg/apis/meta v1.26.0 github.com/fluxcd/pkg/auth v0.40.0 @@ -31,7 +31,7 @@ require ( github.com/fluxcd/pkg/ssh v0.24.0 github.com/fluxcd/pkg/tar v0.17.0 github.com/fluxcd/pkg/version v0.14.0 - github.com/fluxcd/source-controller/api v1.8.1 + github.com/fluxcd/source-controller/api v1.8.2 github.com/fluxcd/source-watcher/api/v2 v2.1.1 github.com/go-git/go-git/v5 v5.16.5 github.com/go-logr/logr v1.4.3 diff --git a/go.sum b/go.sum index 3aed1cda..971f9e3b 100644 --- a/go.sum +++ b/go.sum @@ -180,10 +180,10 @@ github.com/fluxcd/image-automation-controller/api v1.1.1 h1:uiu7kjdVoW8/461HOemX github.com/fluxcd/image-automation-controller/api v1.1.1/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= github.com/fluxcd/image-reflector-controller/api v1.1.1 h1:4Bj1abzVnjj8+b/293kNeFMRJc+y2wO8Z12ReZ/gA0w= github.com/fluxcd/image-reflector-controller/api v1.1.1/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0= -github.com/fluxcd/kustomize-controller/api v1.8.2 h1:LcFUjJccwNrhCo7pQBBneLAlHfZZcb58bWB2LnyFwag= -github.com/fluxcd/kustomize-controller/api v1.8.2/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= -github.com/fluxcd/notification-controller/api v1.8.2 h1:TDrXohUC5Gh3BF+v2ux9/zEG1Ax8u49WDW+3Y6GiIEc= -github.com/fluxcd/notification-controller/api v1.8.2/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA= +github.com/fluxcd/kustomize-controller/api v1.8.3 h1:Ux9AAOY0lkP6FgRg5/b/ITvRSy8lz6VBBaZ9bXmTLmI= +github.com/fluxcd/kustomize-controller/api v1.8.3/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= +github.com/fluxcd/notification-controller/api v1.8.3 h1:edYpC/t4pNw/KQur189SRC1XtFNU597ooDTCrW90Xmw= +github.com/fluxcd/notification-controller/api v1.8.3/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE= @@ -220,8 +220,8 @@ github.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY github.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls= github.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw= github.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= -github.com/fluxcd/source-controller/api v1.8.1 h1:49HiJF5mNEdZTwueQMRahTVts35B+xhN5CsuOAL9gQ0= -github.com/fluxcd/source-controller/api v1.8.1/go.mod h1:HgZ6NSH1cyOE2jRoNwln1xEwr9ETvrLeiy1o4O04vQM= +github.com/fluxcd/source-controller/api v1.8.2 h1:i0/6BeNCn+zRfX+gKh4PsFF2NBzBhwXt0wPImVlZObg= +github.com/fluxcd/source-controller/api v1.8.2/go.mod h1:HgZ6NSH1cyOE2jRoNwln1xEwr9ETvrLeiy1o4O04vQM= github.com/fluxcd/source-watcher/api/v2 v2.1.1 h1:1LfT50ty+78MKKbschAZl28QbVqIyjaNq17KmW5wPJI= github.com/fluxcd/source-watcher/api/v2 v2.1.1/go.mod h1:6M1BzBGQRoIuSenSQlfJHwMVVobFPiNPxXqfN0IILc4= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index f75f707d..2ad41b16 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.deployment.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.3/kustomize-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.3/kustomize-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/notification-controller/kustomization.yaml b/manifests/bases/notification-controller/kustomization.yaml index 71c3de84..344c9e29 100644 --- a/manifests/bases/notification-controller/kustomization.yaml +++ b/manifests/bases/notification-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.deployment.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.3/notification-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.3/notification-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index eba59908..b2eef98d 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.2/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.2/source-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index c29dc4ec..3f3f914e 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -1,10 +1,10 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.2/source-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.3/kustomize-controller.crds.yaml - https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.3/notification-controller.crds.yaml - https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml - https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml - https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml From e2af45aee4e3a0fedd9f0c83f06ee50f48b3e35b Mon Sep 17 00:00:00 2001 From: Rafael Peroco Date: Wed, 8 Apr 2026 21:34:57 -0300 Subject: [PATCH 16/48] feat: add --show-source flag to flux get kustomization Fixes #2692 Signed-off-by: Rafael Peroco --- cmd/flux/get_kustomization.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/cmd/flux/get_kustomization.go b/cmd/flux/get_kustomization.go index a95ec655..97a1d93b 100644 --- a/cmd/flux/get_kustomization.go +++ b/cmd/flux/get_kustomization.go @@ -30,13 +30,22 @@ import ( "github.com/fluxcd/flux2/v2/internal/utils" ) +type getKustomizationFlags struct { + showSource bool +} + +var getKsArgs getKustomizationFlags + var getKsCmd = &cobra.Command{ Use: "kustomizations", Aliases: []string{"ks", "kustomization"}, Short: "Get Kustomization statuses", Long: `The get kustomizations command prints the statuses of the resources.`, Example: ` # List all kustomizations and their status - flux get kustomizations`, + flux get kustomizations + + # List all kustomizations with source information + flux get kustomizations --show-source`, ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), RunE: func(cmd *cobra.Command, args []string) error { get := getCommand{ @@ -74,6 +83,7 @@ var getKsCmd = &cobra.Command{ } func init() { + getKsCmd.Flags().BoolVar(&getKsArgs.showSource, "show-source", false, "show the source reference for each kustomization") getCmd.AddCommand(getKsCmd) } @@ -83,12 +93,27 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in status, msg := statusAndMessage(item.Status.Conditions) revision = utils.TruncateHex(revision) msg = utils.TruncateHex(msg) - return append(nameColumns(&item, includeNamespace, includeKind), + row := nameColumns(&item, includeNamespace, includeKind) + if getKsArgs.showSource { + sourceNs := item.Spec.SourceRef.Namespace + if sourceNs == "" { + sourceNs = item.GetNamespace() + } + row = append(row, fmt.Sprintf("%s/%s/%s", + item.Spec.SourceRef.Kind, + sourceNs, + item.Spec.SourceRef.Name)) + } + return append(row, revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a kustomizationListAdapter) headers(includeNamespace bool) []string { - headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"} + headers := []string{"Name"} + if getKsArgs.showSource { + headers = append(headers, "Source") + } + headers = append(headers, "Revision", "Suspended", "Ready", "Message") if includeNamespace { headers = append([]string{"Namespace"}, headers...) } From 8a777bdd0f1e9b0cd26ea849084f27e31ba462fb Mon Sep 17 00:00:00 2001 From: Rafael Peroco Date: Thu, 9 Apr 2026 18:20:43 -0300 Subject: [PATCH 17/48] feat: add --show-source flag to flux get helmrelease Signed-off-by: Rafael Peroco --- cmd/flux/get_helmrelease.go | 45 ++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/cmd/flux/get_helmrelease.go b/cmd/flux/get_helmrelease.go index c5a4b67e..08f88bd9 100644 --- a/cmd/flux/get_helmrelease.go +++ b/cmd/flux/get_helmrelease.go @@ -28,13 +28,22 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" ) +type getHelmReleaseFlags struct { + showSource bool +} + +var getHrArgs getHelmReleaseFlags + var getHelmReleaseCmd = &cobra.Command{ Use: "helmreleases", Aliases: []string{"hr", "helmrelease"}, Short: "Get HelmRelease statuses", Long: "The get helmreleases command prints the statuses of the resources.", Example: ` # List all Helm releases and their status - flux get helmreleases`, + flux get helmreleases + + # List all Helm releases with source information + flux get helmreleases --show-source`, ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), RunE: func(cmd *cobra.Command, args []string) error { get := getCommand{ @@ -69,6 +78,7 @@ var getHelmReleaseCmd = &cobra.Command{ } func init() { + getHelmReleaseCmd.Flags().BoolVar(&getHrArgs.showSource, "show-source", false, "show the source reference for each helmrelease") getCmd.AddCommand(getHelmReleaseCmd) } @@ -79,16 +89,45 @@ func getHelmReleaseRevision(helmRelease helmv2.HelmRelease) string { return helmRelease.Status.LastAttemptedRevision } +func getHelmReleaseSource(item helmv2.HelmRelease) string { + if item.Spec.ChartRef != nil { + ns := item.Spec.ChartRef.Namespace + if ns == "" { + ns = item.GetNamespace() + } + return fmt.Sprintf("%s/%s/%s", + item.Spec.ChartRef.Kind, + ns, + item.Spec.ChartRef.Name) + } + ns := item.Spec.Chart.Spec.SourceRef.Namespace + if ns == "" { + ns = item.GetNamespace() + } + return fmt.Sprintf("%s/%s/%s", + item.Spec.Chart.Spec.SourceRef.Kind, + ns, + item.Spec.Chart.Spec.SourceRef.Name) +} + func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { item := a.Items[i] revision := getHelmReleaseRevision(item) status, msg := statusAndMessage(item.Status.Conditions) - return append(nameColumns(&item, includeNamespace, includeKind), + row := nameColumns(&item, includeNamespace, includeKind) + if getHrArgs.showSource { + row = append(row, getHelmReleaseSource(item)) + } + return append(row, revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a helmReleaseListAdapter) headers(includeNamespace bool) []string { - headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"} + headers := []string{"Name"} + if getHrArgs.showSource { + headers = append(headers, "Source") + } + headers = append(headers, "Revision", "Suspended", "Ready", "Message") if includeNamespace { headers = append([]string{"Namespace"}, headers...) } From 02734f28ba6ab7585cff4da4b2bf617c0cb8829d Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 10 Apr 2026 12:09:42 +0300 Subject: [PATCH 18/48] Add `flux create secret receiver` command Signed-off-by: Stefan Prodan --- cmd/flux/create_receiver.go | 7 +- cmd/flux/create_secret.go | 16 +++ cmd/flux/create_secret_receiver.go | 131 ++++++++++++++++++ cmd/flux/create_secret_receiver_test.go | 69 +++++++++ cmd/flux/main_test.go | 1 + .../receiver/secret-receiver-gcr.yaml | 13 ++ .../receiver/secret-receiver.yaml | 11 ++ internal/flags/receiver_type.go | 68 +++++++++ pkg/manifestgen/sourcesecret/options.go | 12 ++ pkg/manifestgen/sourcesecret/sourcesecret.go | 52 +++++++ 10 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 cmd/flux/create_secret_receiver.go create mode 100644 cmd/flux/create_secret_receiver_test.go create mode 100644 cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr.yaml create mode 100644 cmd/flux/testdata/create_secret/receiver/secret-receiver.yaml create mode 100644 internal/flags/receiver_type.go diff --git a/cmd/flux/create_receiver.go b/cmd/flux/create_receiver.go index ad6436cf..26000b2d 100644 --- a/cmd/flux/create_receiver.go +++ b/cmd/flux/create_receiver.go @@ -30,6 +30,7 @@ import ( notificationv1 "github.com/fluxcd/notification-controller/api/v1" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/utils" ) @@ -49,7 +50,7 @@ var createReceiverCmd = &cobra.Command{ } type receiverFlags struct { - receiverType string + receiverType flags.ReceiverType secretRef string events []string resources []string @@ -58,7 +59,7 @@ type receiverFlags struct { var receiverArgs receiverFlags func init() { - createReceiverCmd.Flags().StringVar(&receiverArgs.receiverType, "type", "", "") + createReceiverCmd.Flags().Var(&receiverArgs.receiverType, "type", receiverArgs.receiverType.Description()) createReceiverCmd.Flags().StringVar(&receiverArgs.secretRef, "secret-ref", "", "") createReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, "event", []string{}, "also accepts comma-separated values") createReceiverCmd.Flags().StringSliceVar(&receiverArgs.resources, "resource", []string{}, "also accepts comma-separated values") @@ -109,7 +110,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error { Labels: sourceLabels, }, Spec: notificationv1.ReceiverSpec{ - Type: receiverArgs.receiverType, + Type: receiverArgs.receiverType.String(), Events: receiverArgs.events, Resources: resources, SecretRef: meta.LocalObjectReference{ diff --git a/cmd/flux/create_secret.go b/cmd/flux/create_secret.go index 376fd24f..1a4cb025 100644 --- a/cmd/flux/create_secret.go +++ b/cmd/flux/create_secret.go @@ -56,6 +56,22 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S } existing.StringData = secret.StringData + if secret.Annotations != nil { + if existing.Annotations == nil { + existing.Annotations = make(map[string]string) + } + for k, v := range secret.Annotations { + existing.Annotations[k] = v + } + } + if secret.Labels != nil { + if existing.Labels == nil { + existing.Labels = make(map[string]string) + } + for k, v := range secret.Labels { + existing.Labels[k] = v + } + } if err := kubeClient.Update(ctx, &existing); err != nil { return err } diff --git a/cmd/flux/create_secret_receiver.go b/cmd/flux/create_secret_receiver.go new file mode 100644 index 00000000..27861231 --- /dev/null +++ b/cmd/flux/create_secret_receiver.go @@ -0,0 +1,131 @@ +/* +Copyright 2026 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 main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" + + notificationv1 "github.com/fluxcd/notification-controller/api/v1" + + "github.com/fluxcd/flux2/v2/internal/flags" + "github.com/fluxcd/flux2/v2/internal/utils" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" +) + +var createSecretReceiverCmd = &cobra.Command{ + Use: "receiver [name]", + Short: "Create or update a Kubernetes secret for a Receiver webhook", + Long: `The create secret receiver command generates a Kubernetes secret with +the token used for webhook payload validation and an annotation with the +computed webhook URL.`, + Example: ` # Create a receiver secret for a GitHub webhook + flux create secret receiver github-receiver \ + --namespace=my-namespace \ + --type=github \ + --hostname=flux.example.com \ + --export + + # Create a receiver secret for GCR with email claim + flux create secret receiver gcr-receiver \ + --namespace=my-namespace \ + --type=gcr \ + --hostname=flux.example.com \ + --email-claim=sa@project.iam.gserviceaccount.com \ + --export`, + RunE: createSecretReceiverCmdRun, +} + +type secretReceiverFlags struct { + receiverType flags.ReceiverType + token string + hostname string + emailClaim string +} + +var secretReceiverArgs secretReceiverFlags + +func init() { + createSecretReceiverCmd.Flags().Var(&secretReceiverArgs.receiverType, "type", secretReceiverArgs.receiverType.Description()) + createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.token, "token", "", "webhook token used for payload validation and URL computation, auto-generated if not specified") + createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.hostname, "hostname", "", "hostname for the webhook URL e.g. flux.example.com") + createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.emailClaim, "email-claim", "", "IAM service account email, required for gcr type") + + createSecretCmd.AddCommand(createSecretReceiverCmd) +} + +func createSecretReceiverCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + + if secretReceiverArgs.receiverType == "" { + return fmt.Errorf("--type is required") + } + + if secretReceiverArgs.hostname == "" { + return fmt.Errorf("--hostname is required") + } + + if secretReceiverArgs.receiverType.String() == notificationv1.GCRReceiver && secretReceiverArgs.emailClaim == "" { + return fmt.Errorf("--email-claim is required for gcr receiver type") + } + + labels, err := parseLabels() + if err != nil { + return err + } + + opts := sourcesecret.Options{ + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: labels, + ReceiverType: secretReceiverArgs.receiverType.String(), + Token: secretReceiverArgs.token, + Hostname: secretReceiverArgs.hostname, + EmailClaim: secretReceiverArgs.emailClaim, + } + + secret, err := sourcesecret.GenerateReceiver(opts) + if err != nil { + return err + } + + if createArgs.export { + rootCmd.Println(secret.Content) + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if err := upsertSecret(ctx, kubeClient, s); err != nil { + return err + } + + logger.Actionf("receiver secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace) + return nil +} diff --git a/cmd/flux/create_secret_receiver_test.go b/cmd/flux/create_secret_receiver_test.go new file mode 100644 index 00000000..8d38cbb2 --- /dev/null +++ b/cmd/flux/create_secret_receiver_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2026 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 main + +import ( + "testing" +) + +func TestCreateReceiverSecret(t *testing.T) { + tests := []struct { + name string + args string + assert assertFunc + }{ + { + name: "missing type", + args: "create secret receiver test-secret --token=t --hostname=h", + assert: assertError("--type is required"), + }, + { + name: "invalid type", + args: "create secret receiver test-secret --type=invalid --token=t --hostname=h", + assert: assertError("invalid argument \"invalid\" for \"--type\" flag: receiver type 'invalid' is not supported, must be one of: generic, generic-hmac, github, gitlab, bitbucket, harbor, dockerhub, quay, gcr, nexus, acr, cdevents"), + }, + { + name: "missing hostname", + args: "create secret receiver test-secret --type=github --token=t", + assert: assertError("--hostname is required"), + }, + { + name: "gcr missing email-claim", + args: "create secret receiver test-secret --type=gcr --token=t --hostname=h", + assert: assertError("--email-claim is required for gcr receiver type"), + }, + { + name: "github receiver secret", + args: "create secret receiver receiver-secret --type=github --token=test-token --hostname=flux.example.com --namespace=my-namespace --export", + assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver.yaml"), + }, + { + name: "gcr receiver secret", + args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --namespace=my-namespace --export", + assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr.yaml"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args, + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index 48c2549b..78159920 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -456,6 +456,7 @@ func resetCmdArgs() { secretGitArgs = NewSecretGitFlags() secretGitHubAppArgs = secretGitHubAppFlags{} secretProxyArgs = secretProxyFlags{} + secretReceiverArgs = secretReceiverFlags{} secretHelmArgs = secretHelmFlags{} secretTLSArgs = secretTLSFlags{} sourceBucketArgs = sourceBucketFlags{} diff --git a/cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr.yaml b/cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr.yaml new file mode 100644 index 00000000..ff4c88b4 --- /dev/null +++ b/cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4 + name: gcr-secret + namespace: my-namespace +stringData: + audience: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4 + email: sa@project.iam.gserviceaccount.com + token: test-token + diff --git a/cmd/flux/testdata/create_secret/receiver/secret-receiver.yaml b/cmd/flux/testdata/create_secret/receiver/secret-receiver.yaml new file mode 100644 index 00000000..f1f63759 --- /dev/null +++ b/cmd/flux/testdata/create_secret/receiver/secret-receiver.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/106120121d366c2f67e93200f6c1dbe938235eb588daa5e8c0516d3a77ac1dee + name: receiver-secret + namespace: my-namespace +stringData: + token: test-token + diff --git a/internal/flags/receiver_type.go b/internal/flags/receiver_type.go new file mode 100644 index 00000000..42948b7a --- /dev/null +++ b/internal/flags/receiver_type.go @@ -0,0 +1,68 @@ +/* +Copyright 2026 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 flags + +import ( + "fmt" + "strings" + + notificationv1 "github.com/fluxcd/notification-controller/api/v1" + + "github.com/fluxcd/flux2/v2/internal/utils" +) + +var supportedReceiverTypes = []string{ + notificationv1.GenericReceiver, + notificationv1.GenericHMACReceiver, + notificationv1.GitHubReceiver, + notificationv1.GitLabReceiver, + notificationv1.BitbucketReceiver, + notificationv1.HarborReceiver, + notificationv1.DockerHubReceiver, + notificationv1.QuayReceiver, + notificationv1.GCRReceiver, + notificationv1.NexusReceiver, + notificationv1.ACRReceiver, + notificationv1.CDEventsReceiver, +} + +type ReceiverType string + +func (r *ReceiverType) String() string { + return string(*r) +} + +func (r *ReceiverType) Set(str string) error { + if strings.TrimSpace(str) == "" { + return fmt.Errorf("no receiver type given, please specify %s", + r.Description()) + } + if !utils.ContainsItemString(supportedReceiverTypes, str) { + return fmt.Errorf("receiver type '%s' is not supported, must be one of: %s", + str, strings.Join(supportedReceiverTypes, ", ")) + } + *r = ReceiverType(str) + return nil +} + +func (r *ReceiverType) Type() string { + return strings.Join(supportedReceiverTypes, "|") +} + +func (r *ReceiverType) Description() string { + return "the receiver type" +} diff --git a/pkg/manifestgen/sourcesecret/options.go b/pkg/manifestgen/sourcesecret/options.go index a226a4db..09f47c11 100644 --- a/pkg/manifestgen/sourcesecret/options.go +++ b/pkg/manifestgen/sourcesecret/options.go @@ -42,6 +42,12 @@ const ( KnownHostsSecretKey = "known_hosts" BearerTokenKey = "bearerToken" TrustPolicyKey = "trustpolicy.json" + TokenSecretKey = "token" + EmailSecretKey = "email" + AudienceSecretKey = "audience" + + // WebhookURLAnnotation is the annotation key for the computed webhook URL. + WebhookURLAnnotation = "notification.toolkit.fluxcd.io/webhook" // Deprecated: Replaced by CACrtSecretKey, but kept for backwards // compatibility with deprecated TLS flags. @@ -82,6 +88,12 @@ type Options struct { GitHubAppInstallationID string GitHubAppPrivateKey string GitHubAppBaseURL string + + // Receiver options + ReceiverType string + Token string + Hostname string + EmailClaim string } type VerificationCrt struct { diff --git a/pkg/manifestgen/sourcesecret/sourcesecret.go b/pkg/manifestgen/sourcesecret/sourcesecret.go index 709f8d01..8b8fc946 100644 --- a/pkg/manifestgen/sourcesecret/sourcesecret.go +++ b/pkg/manifestgen/sourcesecret/sourcesecret.go @@ -18,7 +18,10 @@ package sourcesecret import ( "bytes" + "crypto/rand" + "crypto/sha256" "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "net" @@ -260,6 +263,55 @@ func GenerateGitHubApp(options Options) (*manifestgen.Manifest, error) { return secretToManifest(secret, options) } +func GenerateReceiver(options Options) (*manifestgen.Manifest, error) { + token := options.Token + if token == "" { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return nil, fmt.Errorf("failed to generate random token: %w", err) + } + token = hex.EncodeToString(b) + } + + if options.Hostname == "" { + return nil, fmt.Errorf("hostname is required") + } + + // Compute the webhook path using the same algorithm as notification-controller. + // See: github.com/fluxcd/notification-controller/api/v1.Receiver.GetWebhookPath + digest := sha256.Sum256([]byte(token + options.Name + options.Namespace)) + webhookPath := fmt.Sprintf("/hook/%x", digest) + webhookURL := fmt.Sprintf("https://%s%s", options.Hostname, webhookPath) + + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: options.Name, + Namespace: options.Namespace, + Labels: options.Labels, + Annotations: map[string]string{ + WebhookURLAnnotation: webhookURL, + }, + }, + StringData: map[string]string{ + TokenSecretKey: token, + }, + } + + if options.ReceiverType == "gcr" { + if options.EmailClaim == "" { + return nil, fmt.Errorf("email-claim is required for gcr receiver type") + } + secret.StringData[EmailSecretKey] = options.EmailClaim + secret.StringData[AudienceSecretKey] = webhookURL + } + + return secretToManifest(secret, options) +} + func LoadKeyPairFromPath(path, password string) (*ssh.KeyPair, error) { if path == "" { return nil, nil From c601a212f64dd08994e02f37b916b3672fb97745 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 10 Apr 2026 12:34:26 +0300 Subject: [PATCH 19/48] Add `--audience-claim` for GCR Receivers Signed-off-by: Stefan Prodan --- cmd/flux/create_secret_receiver.go | 25 +++++++++++-------- cmd/flux/create_secret_receiver_test.go | 5 ++++ .../secret-receiver-gcr-audience.yaml | 13 ++++++++++ pkg/manifestgen/sourcesecret/options.go | 9 ++++--- pkg/manifestgen/sourcesecret/sourcesecret.go | 6 ++++- 5 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml diff --git a/cmd/flux/create_secret_receiver.go b/cmd/flux/create_secret_receiver.go index 27861231..50eafbea 100644 --- a/cmd/flux/create_secret_receiver.go +++ b/cmd/flux/create_secret_receiver.go @@ -55,10 +55,11 @@ computed webhook URL.`, } type secretReceiverFlags struct { - receiverType flags.ReceiverType - token string - hostname string - emailClaim string + receiverType flags.ReceiverType + token string + hostname string + emailClaim string + audienceClaim string } var secretReceiverArgs secretReceiverFlags @@ -68,6 +69,7 @@ func init() { createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.token, "token", "", "webhook token used for payload validation and URL computation, auto-generated if not specified") createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.hostname, "hostname", "", "hostname for the webhook URL e.g. flux.example.com") createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.emailClaim, "email-claim", "", "IAM service account email, required for gcr type") + createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.audienceClaim, "audience-claim", "", "custom OIDC token audience for gcr type, defaults to the webhook URL") createSecretCmd.AddCommand(createSecretReceiverCmd) } @@ -93,13 +95,14 @@ func createSecretReceiverCmdRun(cmd *cobra.Command, args []string) error { } opts := sourcesecret.Options{ - Name: name, - Namespace: *kubeconfigArgs.Namespace, - Labels: labels, - ReceiverType: secretReceiverArgs.receiverType.String(), - Token: secretReceiverArgs.token, - Hostname: secretReceiverArgs.hostname, - EmailClaim: secretReceiverArgs.emailClaim, + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: labels, + ReceiverType: secretReceiverArgs.receiverType.String(), + Token: secretReceiverArgs.token, + Hostname: secretReceiverArgs.hostname, + EmailClaim: secretReceiverArgs.emailClaim, + AudienceClaim: secretReceiverArgs.audienceClaim, } secret, err := sourcesecret.GenerateReceiver(opts) diff --git a/cmd/flux/create_secret_receiver_test.go b/cmd/flux/create_secret_receiver_test.go index 8d38cbb2..383f2371 100644 --- a/cmd/flux/create_secret_receiver_test.go +++ b/cmd/flux/create_secret_receiver_test.go @@ -56,6 +56,11 @@ func TestCreateReceiverSecret(t *testing.T) { args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --namespace=my-namespace --export", assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr.yaml"), }, + { + name: "gcr receiver secret with custom audience", + args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --audience-claim=https://custom.audience.example.com --namespace=my-namespace --export", + assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml b/cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml new file mode 100644 index 00000000..9e52a56b --- /dev/null +++ b/cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4 + name: gcr-secret + namespace: my-namespace +stringData: + audience: https://custom.audience.example.com + email: sa@project.iam.gserviceaccount.com + token: test-token + diff --git a/pkg/manifestgen/sourcesecret/options.go b/pkg/manifestgen/sourcesecret/options.go index 09f47c11..9fe7b49d 100644 --- a/pkg/manifestgen/sourcesecret/options.go +++ b/pkg/manifestgen/sourcesecret/options.go @@ -90,10 +90,11 @@ type Options struct { GitHubAppBaseURL string // Receiver options - ReceiverType string - Token string - Hostname string - EmailClaim string + ReceiverType string + Token string + Hostname string + EmailClaim string + AudienceClaim string } type VerificationCrt struct { diff --git a/pkg/manifestgen/sourcesecret/sourcesecret.go b/pkg/manifestgen/sourcesecret/sourcesecret.go index 8b8fc946..54cca4e8 100644 --- a/pkg/manifestgen/sourcesecret/sourcesecret.go +++ b/pkg/manifestgen/sourcesecret/sourcesecret.go @@ -306,7 +306,11 @@ func GenerateReceiver(options Options) (*manifestgen.Manifest, error) { return nil, fmt.Errorf("email-claim is required for gcr receiver type") } secret.StringData[EmailSecretKey] = options.EmailClaim - secret.StringData[AudienceSecretKey] = webhookURL + if options.AudienceClaim != "" { + secret.StringData[AudienceSecretKey] = options.AudienceClaim + } else { + secret.StringData[AudienceSecretKey] = webhookURL + } } return secretToManifest(secret, options) From 69e2c6bc7dcba09f90abc69b9bdc4b7a6521c340 Mon Sep 17 00:00:00 2001 From: iam-karan-suresh Date: Fri, 10 Apr 2026 13:55:48 +0530 Subject: [PATCH 20/48] fix: handle multiple symlinks to same target in build artifact Signed-off-by: iam-karan-suresh --- cmd/flux/build_artifact.go | 2 +- cmd/flux/build_artifact_test.go | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cmd/flux/build_artifact.go b/cmd/flux/build_artifact.go index 7dcc7d42..1dd681f8 100644 --- a/cmd/flux/build_artifact.go +++ b/cmd/flux/build_artifact.go @@ -172,7 +172,7 @@ func copyDir(srcDir, dstDir string, visited map[string]bool) error { return nil // break the cycle } visited[abs] = true - + defer delete(visited, abs) entries, err := os.ReadDir(srcDir) if err != nil { return err diff --git a/cmd/flux/build_artifact_test.go b/cmd/flux/build_artifact_test.go index bfdaaaed..88ac278f 100644 --- a/cmd/flux/build_artifact_test.go +++ b/cmd/flux/build_artifact_test.go @@ -179,3 +179,39 @@ func Test_resolveSymlinks_cycle(t *testing.T) { _, err = os.Stat(filepath.Join(resolved, "cycle", "cycle", "cycle")) g.Expect(os.IsNotExist(err)).To(BeTrue()) } + +func Test_resolveSymlinks_multipleLinksSameTarget(t *testing.T) { + g := NewWithT(t) + + // Create source directory with a real file inside a dir + srcDir := t.TempDir() + targetDir := filepath.Join(srcDir, "target") + g.Expect(os.MkdirAll(targetDir, 0o755)).To(Succeed()) + g.Expect(os.WriteFile(filepath.Join(targetDir, "file.yaml"), []byte("data"), 0o644)).To(Succeed()) + + // Create a directory with multiple symlinks pointing to targetDir + symlinkDir := t.TempDir() + + // Link 1 + link1 := filepath.Join(symlinkDir, "link1") + g.Expect(os.Symlink(targetDir, link1)).To(Succeed()) + + // Link 2 + link2 := filepath.Join(symlinkDir, "link2") + g.Expect(os.Symlink(targetDir, link2)).To(Succeed()) + + // Resolve symlinks + resolved, cleanupDir, err := resolveSymlinks(symlinkDir) + g.Expect(err).To(BeNil()) + t.Cleanup(func() { os.RemoveAll(cleanupDir) }) + + // Verify link1 has the file + content, err := os.ReadFile(filepath.Join(resolved, "link1", "file.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(string(content)).To(Equal("data")) + + // Verify link2 ALSO has the file + content2, err := os.ReadFile(filepath.Join(resolved, "link2", "file.yaml")) + g.Expect(err).To(BeNil()) + g.Expect(string(content2)).To(Equal("data")) +} From 4f2374178cc4b18eb51e86ad4bd2835ad0beb8c9 Mon Sep 17 00:00:00 2001 From: rycli Date: Fri, 10 Apr 2026 20:04:38 +0200 Subject: [PATCH 21/48] chore: bump pkg/kustomize to v1.29.0 Signed-off-by: rycli --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b6a74595..56e40593 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/fluxcd/pkg/chartutil v1.23.0 github.com/fluxcd/pkg/envsubst v1.5.0 github.com/fluxcd/pkg/git v0.46.0 - github.com/fluxcd/pkg/kustomize v1.28.0 + github.com/fluxcd/pkg/kustomize v1.29.0 github.com/fluxcd/pkg/oci v0.63.0 github.com/fluxcd/pkg/runtime v0.103.0 github.com/fluxcd/pkg/sourceignore v0.17.0 diff --git a/go.sum b/go.sum index 971f9e3b..6ca6eaf7 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/fluxcd/pkg/git v0.46.0 h1:QMh0+ZzQ2jO6rIGj4ffR5trZ8g/cxvt8cVajReJ8Iyw github.com/fluxcd/pkg/git v0.46.0/go.mod h1:iHcIjx9c8zye3PQiajTJYxgOMRiy7WCs+hfLKDswpfI= github.com/fluxcd/pkg/gittestserver v0.26.0 h1:+RZrCzFRsE+d5WaqAoqaPCEgcgv/jZp6+f7DS0+Ynb8= github.com/fluxcd/pkg/gittestserver v0.26.0/go.mod h1:7fybYb0yej1fFNiF1ohs0Jr0XzyaZQ/cRh3AFEoCtuc= -github.com/fluxcd/pkg/kustomize v1.28.0 h1:0RuFVczJRabbt8frHZ/ql8aqte6BOOKk274O09l6/hE= -github.com/fluxcd/pkg/kustomize v1.28.0/go.mod h1:cW08mnngSP8MJYb6mDmMvxH8YjNATdiML0udb37dk+M= +github.com/fluxcd/pkg/kustomize v1.29.0 h1:B/5hr9wX6INwaQAZ6BGKVNvZm++A6qjgorUfoaBAwPw= +github.com/fluxcd/pkg/kustomize v1.29.0/go.mod h1:cW08mnngSP8MJYb6mDmMvxH8YjNATdiML0udb37dk+M= github.com/fluxcd/pkg/oci v0.63.0 h1:ZPKTT2C+gWYjhP63xC76iTPdYE9w3ABcsDq77uhAgwo= github.com/fluxcd/pkg/oci v0.63.0/go.mod h1:qMPz4njvm6hJzdyGSb8ydSqrapXxTQwJonxHIsdeXSQ= github.com/fluxcd/pkg/runtime v0.103.0 h1:J5y5GPhWdkyqIUBlaI1FP2N02TtZmsjbWhhZubuTSFk= From e5128ea97ef6561f007a9a844d5e71af588c7dee Mon Sep 17 00:00:00 2001 From: rycli Date: Sun, 29 Mar 2026 16:10:56 +0200 Subject: [PATCH 22/48] feat: add WithInMemoryBuild to use virtual FS for kustomize Signed-off-by: rycli --- cmd/flux/build_kustomization.go | 5 + cmd/flux/build_kustomization_test.go | 42 +++++ cmd/flux/diff_kustomization.go | 5 + internal/build/build.go | 109 +++++++++++-- internal/build/build_test.go | 229 +++++++++++++++++++++++++++ internal/build/diff.go | 1 + 6 files changed, 375 insertions(+), 16 deletions(-) diff --git a/cmd/flux/build_kustomization.go b/cmd/flux/build_kustomization.go index 2000faac..5b337ef5 100644 --- a/cmd/flux/build_kustomization.go +++ b/cmd/flux/build_kustomization.go @@ -72,6 +72,7 @@ type buildKsFlags struct { strictSubst bool recursive bool localSources map[string]string + inMemoryBuild bool } var buildKsArgs buildKsFlags @@ -85,6 +86,8 @@ func init() { "When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.") buildKsCmd.Flags().BoolVarP(&buildKsArgs.recursive, "recursive", "r", false, "Recursively build Kustomizations") buildKsCmd.Flags().StringToStringVar(&buildKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path") + buildKsCmd.Flags().BoolVar(&buildKsArgs.inMemoryBuild, "in-memory-build", true, + "Use in-memory filesystem during build.") buildCmd.AddCommand(buildKsCmd) } @@ -130,6 +133,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { build.WithStrictSubstitute(buildKsArgs.strictSubst), build.WithRecursive(buildKsArgs.recursive), build.WithLocalSources(buildKsArgs.localSources), + build.WithInMemoryBuild(buildKsArgs.inMemoryBuild), ) } else { builder, err = build.NewBuilder(name, buildKsArgs.path, @@ -140,6 +144,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { build.WithStrictSubstitute(buildKsArgs.strictSubst), build.WithRecursive(buildKsArgs.recursive), build.WithLocalSources(buildKsArgs.localSources), + build.WithInMemoryBuild(buildKsArgs.inMemoryBuild), ) } diff --git a/cmd/flux/build_kustomization_test.go b/cmd/flux/build_kustomization_test.go index 7b49506e..723fb40a 100644 --- a/cmd/flux/build_kustomization_test.go +++ b/cmd/flux/build_kustomization_test.go @@ -52,6 +52,12 @@ func TestBuildKustomization(t *testing.T) { resultFile: "./testdata/build-kustomization/podinfo-result.yaml", assertFunc: "assertGoldenTemplateFile", }, + { + name: "build podinfo (on-disk)", + args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo --in-memory-build=false", + resultFile: "./testdata/build-kustomization/podinfo-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, { name: "build podinfo without service", args: "build kustomization podinfo --path ./testdata/build-kustomization/delete-service", @@ -70,12 +76,24 @@ func TestBuildKustomization(t *testing.T) { resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml", assertFunc: "assertGoldenTemplateFile", }, + { + name: "build ignore (on-disk)", + args: "build kustomization podinfo --path ./testdata/build-kustomization/ignore --ignore-paths \"!configmap.yaml,!secret.yaml\" --in-memory-build=false", + resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, { name: "build with recursive", args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization", resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml", assertFunc: "assertGoldenTemplateFile", }, + { + name: "build with recursive (on-disk)", + args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --in-memory-build=false", + resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, } tmpl := map[string]string{ @@ -145,6 +163,12 @@ spec: resultFile: "./testdata/build-kustomization/podinfo-result.yaml", assertFunc: "assertGoldenTemplateFile", }, + { + name: "build podinfo (on-disk)", + args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo --in-memory-build=false", + resultFile: "./testdata/build-kustomization/podinfo-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, { name: "build podinfo without service", args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/delete-service", @@ -175,6 +199,18 @@ spec: resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml", assertFunc: "assertGoldenTemplateFile", }, + { + name: "build with recursive (on-disk)", + args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --in-memory-build=false", + resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, + { + name: "build with recursive in dry-run mode (on-disk)", + args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --in-memory-build=false --dry-run", + resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, } tmpl := map[string]string{ @@ -241,6 +277,12 @@ func TestBuildKustomizationPathNormalization(t *testing.T) { resultFile: "./testdata/build-kustomization/podinfo-result.yaml", assertFunc: "assertGoldenTemplateFile", }, + { + name: "build with absolute path (on-disk)", + args: "build kustomization podinfo --path " + absTestDataPath + " --in-memory-build=false", + resultFile: "./testdata/build-kustomization/podinfo-result.yaml", + assertFunc: "assertGoldenTemplateFile", + }, { name: "build with complex relative path (parent dir)", args: "build kustomization podinfo --path ./testdata/build-kustomization/../build-kustomization/podinfo", diff --git a/cmd/flux/diff_kustomization.go b/cmd/flux/diff_kustomization.go index 0480e293..bc8164f0 100644 --- a/cmd/flux/diff_kustomization.go +++ b/cmd/flux/diff_kustomization.go @@ -62,6 +62,7 @@ type diffKsFlags struct { strictSubst bool recursive bool localSources map[string]string + inMemoryBuild bool } var diffKsArgs diffKsFlags @@ -75,6 +76,8 @@ func init() { "When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.") diffKsCmd.Flags().BoolVarP(&diffKsArgs.recursive, "recursive", "r", false, "Recursively diff Kustomizations") diffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path") + diffKsCmd.Flags().BoolVar(&diffKsArgs.inMemoryBuild, "in-memory-build", true, + "Use in-memory filesystem during build.") diffCmd.AddCommand(diffKsCmd) } @@ -113,6 +116,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithRecursive(diffKsArgs.recursive), build.WithLocalSources(diffKsArgs.localSources), build.WithSingleKustomization(), + build.WithInMemoryBuild(diffKsArgs.inMemoryBuild), ) } else { builder, err = build.NewBuilder(name, diffKsArgs.path, @@ -124,6 +128,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithRecursive(diffKsArgs.recursive), build.WithLocalSources(diffKsArgs.localSources), build.WithSingleKustomization(), + build.WithInMemoryBuild(diffKsArgs.inMemoryBuild), ) } diff --git a/internal/build/build.go b/internal/build/build.go index 7deca47c..6f1b44c5 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -45,6 +45,7 @@ import ( kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/pkg/kustomize" + buildfs "github.com/fluxcd/pkg/kustomize/filesys" runclient "github.com/fluxcd/pkg/runtime/client" ssautil "github.com/fluxcd/pkg/ssa/utils" "sigs.k8s.io/kustomize/kyaml/filesys" @@ -65,6 +66,65 @@ const ( var defaultTimeout = 80 * time.Second +// fsBackend controls how the kustomization manifest is generated +// and which filesystem is used for the kustomize build. +type fsBackend interface { + Generate(gen *kustomize.Generator, dirPath string) (filesys.FileSystem, string, kustomize.Action, error) + Cleanup(dirPath string, action kustomize.Action) error +} + +// onDiskFsBackend writes to the source directory. +type onDiskFsBackend struct{} + +func (onDiskFsBackend) Generate(gen *kustomize.Generator, dirPath string) (filesys.FileSystem, string, kustomize.Action, error) { + action, err := gen.WriteFile(dirPath, kustomize.WithSaveOriginalKustomization()) + if err != nil { + return nil, "", action, err + } + return filesys.MakeFsOnDisk(), dirPath, action, nil +} + +func (onDiskFsBackend) Cleanup(dirPath string, action kustomize.Action) error { + return kustomize.CleanDirectory(dirPath, action) +} + +// inMemoryFsBackend builds in an in-memory filesystem without modifying the source directory. +type inMemoryFsBackend struct{} + +func (inMemoryFsBackend) Generate(gen *kustomize.Generator, dirPath string) (filesys.FileSystem, string, kustomize.Action, error) { + manifest, kfilePath, action, err := gen.GenerateManifest(dirPath) + if err != nil { + return nil, "", action, err + } + + absDirPath, err := filepath.Abs(dirPath) + if err != nil { + return nil, "", action, fmt.Errorf("failed to resolve dirPath: %w", err) + } + absDirPath, err = filepath.EvalSymlinks(absDirPath) + if err != nil { + return nil, "", action, fmt.Errorf("failed to eval symlinks: %w", err) + } + + cwd, err := os.Getwd() + if err != nil { + return nil, "", action, fmt.Errorf("failed to get working directory: %w", err) + } + + diskFS, err := buildfs.MakeFsOnDiskSecure(cwd) + if err != nil { + return nil, "", action, fmt.Errorf("failed to create secure filesystem: %w", err) + } + fs := buildfs.MakeFsInMemory(diskFS) + + if err := fs.WriteFile(filepath.Join(absDirPath, filepath.Base(kfilePath)), manifest); err != nil { + return nil, "", action, err + } + return fs, absDirPath, action, nil +} + +func (inMemoryFsBackend) Cleanup(string, kustomize.Action) error { return nil } + // Builder builds yaml manifests // It retrieves the kustomization object from the k8s cluster // and overlays the manifests with the resources specified in the resourcesPath @@ -88,6 +148,7 @@ type Builder struct { localSources map[string]string // diff needs to handle kustomizations one by one singleKustomization bool + fsBackend fsBackend } // BuilderOptionFunc is a function that configures a Builder @@ -198,6 +259,16 @@ func WithLocalSources(localSources map[string]string) BuilderOptionFunc { } } +// WithInMemoryBuild sets the in-memory build backend +func WithInMemoryBuild(inMemoryBuild bool) BuilderOptionFunc { + return func(b *Builder) error { + if inMemoryBuild { + b.fsBackend = inMemoryFsBackend{} + } + return nil + } +} + // WithSingleKustomization sets the single kustomization field to true func WithSingleKustomization() BuilderOptionFunc { return func(b *Builder) error { @@ -223,6 +294,14 @@ func withSpinnerFrom(in *Builder) BuilderOptionFunc { } } +// withFsBackend sets the build backend +func withFsBackend(s fsBackend) BuilderOptionFunc { + return func(b *Builder) error { + b.fsBackend = s + return nil + } +} + // withKustomization sets the kustomization field func withKustomization(k *kustomizev1.Kustomization) BuilderOptionFunc { return func(b *Builder) error { @@ -258,6 +337,10 @@ func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, er b.timeout = defaultTimeout } + if b.fsBackend == nil { + b.fsBackend = onDiskFsBackend{} + } + if b.dryRun && b.kustomizationFile == "" && b.kustomization == nil { return nil, fmt.Errorf("kustomization file is required for dry-run") } @@ -378,9 +461,9 @@ func (b *Builder) build() (m resmap.ResMap, err error) { b.kustomization = k // generate kustomization.yaml if needed - action, er := b.generate(*k, b.resourcesPath) + buildFS, buildDir, action, er := b.generate(*k, b.resourcesPath) if er != nil { - errf := kustomize.CleanDirectory(b.resourcesPath, action) + errf := b.fsBackend.Cleanup(b.resourcesPath, action) err = fmt.Errorf("failed to generate kustomization.yaml: %w", fmt.Errorf("%v %v", er, errf)) return } @@ -388,14 +471,14 @@ func (b *Builder) build() (m resmap.ResMap, err error) { b.action = action defer func() { - errf := b.Cancel() + errf := b.fsBackend.Cleanup(b.resourcesPath, b.action) if err == nil { err = errf } }() // build the kustomization - m, err = b.do(ctx, *k, b.resourcesPath) + m, err = b.do(ctx, *k, buildFS, buildDir) if err != nil { return } @@ -436,6 +519,7 @@ func (b *Builder) kustomizationBuild(k *kustomizev1.Kustomization) ([]*unstructu WithRecursive(b.recursive), WithLocalSources(b.localSources), WithDryRun(b.dryRun), + withFsBackend(b.fsBackend), ) if err != nil { return nil, err @@ -490,10 +574,10 @@ func (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error) return k, nil } -func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath string) (kustomize.Action, error) { +func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath string) (filesys.FileSystem, string, kustomize.Action, error) { data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization) if err != nil { - return "", err + return nil, "", kustomize.UnchangedAction, err } // a scanner will be used down the line to parse the list @@ -505,12 +589,10 @@ func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath stri b.mu.Lock() defer b.mu.Unlock() - return gen.WriteFile(dirPath, kustomize.WithSaveOriginalKustomization()) + return b.fsBackend.Generate(gen, dirPath) } -func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) { - fs := filesys.MakeFsOnDisk() - +func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, fs filesys.FileSystem, dirPath string) (resmap.ResMap, error) { // acquire the lock b.mu.Lock() defer b.mu.Unlock() @@ -734,12 +816,7 @@ func (b *Builder) Cancel() error { b.mu.Lock() defer b.mu.Unlock() - err := kustomize.CleanDirectory(b.resourcesPath, b.action) - if err != nil { - return err - } - - return nil + return b.fsBackend.Cleanup(b.resourcesPath, b.action) } func (b *Builder) StartSpinner() error { diff --git a/internal/build/build_test.go b/internal/build/build_test.go index bf82513f..fa7ffff3 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -18,12 +18,15 @@ package build import ( "fmt" + "os" + "path/filepath" "strings" "testing" "time" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/kustomize" "github.com/google/go-cmp/cmp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -611,3 +614,229 @@ func Test_kustomizationPath(t *testing.T) { }) } } + +// chdirTemp changes to the given directory and restores the original on cleanup. +func chdirTemp(t *testing.T, dir string) { + t.Helper() + orig, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.Chdir(dir); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { os.Chdir(orig) }) +} + +func Test_inMemoryFsBackend_Generate(t *testing.T) { + srcDir := t.TempDir() + chdirTemp(t, srcDir) + + kusYAML := `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- configmap.yaml +` + cmYAML := `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm +data: + key: value +` + if err := os.WriteFile(filepath.Join(srcDir, "kustomization.yaml"), []byte(kusYAML), 0o644); err != nil { + t.Fatalf("write: %v", err) + } + if err := os.WriteFile(filepath.Join(srcDir, "configmap.yaml"), []byte(cmYAML), 0o644); err != nil { + t.Fatalf("write: %v", err) + } + + // snapshot source dir + beforeFiles := map[string]string{} + filepath.Walk(srcDir, func(p string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + data, _ := os.ReadFile(p) + rel, _ := filepath.Rel(srcDir, p) + beforeFiles[rel] = string(data) + return nil + }) + + ks := unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "kustomize.toolkit.fluxcd.io/v1", + "kind": "Kustomization", + "metadata": map[string]interface{}{"name": "test", "namespace": "default"}, + "spec": map[string]interface{}{ + "targetNamespace": "my-ns", + }, + }} + gen := kustomize.NewGenerator(srcDir, ks) + + backend := inMemoryFsBackend{} + fs, dir, action, err := backend.Generate(gen, srcDir) + if err != nil { + t.Fatalf("Generate: %v", err) + } + + if action != kustomize.UnchangedAction { + t.Errorf("expected UnchangedAction, got %q", action) + } + + // kustomization.yaml should contain the merged targetNamespace + data, err := fs.ReadFile(filepath.Join(dir, "kustomization.yaml")) + if err != nil { + t.Fatalf("ReadFile kustomization.yaml: %v", err) + } + if !strings.Contains(string(data), "my-ns") { + t.Errorf("expected kustomization to contain targetNamespace, got:\n%s", data) + } + + // resource file should be readable from disk through the memory fs + data, err = fs.ReadFile(filepath.Join(dir, "configmap.yaml")) + if err != nil { + t.Fatalf("ReadFile configmap.yaml: %v", err) + } + if diff := cmp.Diff(string(data), cmYAML); diff != "" { + t.Errorf("configmap mismatch: (-got +want)%s", diff) + } + + // source directory must be unmodified + afterFiles := map[string]string{} + filepath.Walk(srcDir, func(p string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + data, _ := os.ReadFile(p) + rel, _ := filepath.Rel(srcDir, p) + afterFiles[rel] = string(data) + return nil + }) + if diff := cmp.Diff(afterFiles, beforeFiles); diff != "" { + t.Errorf("source directory was modified: (-got +want)%s", diff) + } +} + +func Test_inMemoryFsBackend_Generate_parentRef(t *testing.T) { + // tmpDir/ + // configmap.yaml (referenced as ../../configmap.yaml) + // overlay/sub/kustomization.yaml + tmpDir := t.TempDir() + chdirTemp(t, tmpDir) + + cmYAML := `apiVersion: v1 +kind: ConfigMap +metadata: + name: parent-cm +data: + key: value +` + if err := os.WriteFile(filepath.Join(tmpDir, "configmap.yaml"), []byte(cmYAML), 0o644); err != nil { + t.Fatalf("write: %v", err) + } + + overlayDir := filepath.Join(tmpDir, "overlay", "sub") + if err := os.MkdirAll(overlayDir, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + kusYAML := `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../configmap.yaml +` + if err := os.WriteFile(filepath.Join(overlayDir, "kustomization.yaml"), []byte(kusYAML), 0o644); err != nil { + t.Fatalf("write: %v", err) + } + + ks := unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "kustomize.toolkit.fluxcd.io/v1", + "kind": "Kustomization", + "metadata": map[string]interface{}{"name": "test", "namespace": "default"}, + "spec": map[string]interface{}{ + "targetNamespace": "parent-ns", + }, + }} + gen := kustomize.NewGenerator(overlayDir, ks) + + backend := inMemoryFsBackend{} + fs, dir, _, err := backend.Generate(gen, overlayDir) + if err != nil { + t.Fatalf("Generate: %v", err) + } + + // ../../configmap.yaml must resolve through the disk layer + m, err := kustomize.Build(fs, dir) + if err != nil { + t.Fatalf("kustomize.Build failed (parent ref not resolved): %v", err) + } + + resources := m.Resources() + if len(resources) != 1 { + t.Fatalf("expected 1 resource, got %d", len(resources)) + } + if resources[0].GetName() != "parent-cm" { + t.Errorf("expected resource name parent-cm, got %s", resources[0].GetName()) + } + if resources[0].GetNamespace() != "parent-ns" { + t.Errorf("expected namespace parent-ns, got %s", resources[0].GetNamespace()) + } +} + +func Test_inMemoryFsBackend_Generate_outsideCwd(t *testing.T) { + // Two sibling temp dirs: one for the source tree, one as cwd. + // The kustomization references a file that exists on disk but is + // outside cwd, so the secure filesystem must reject it. + // + // parentDir/ + // outside/configmap.yaml (exists but outside cwd) + // cwd/overlay/kustomization.yaml (references ../../outside/configmap.yaml) + parentDir := t.TempDir() + + outsideDir := filepath.Join(parentDir, "outside") + if err := os.MkdirAll(outsideDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(outsideDir, "configmap.yaml"), []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: outside-cm +`), 0o644); err != nil { + t.Fatal(err) + } + + cwdDir := filepath.Join(parentDir, "cwd") + overlayDir := filepath.Join(cwdDir, "overlay") + if err := os.MkdirAll(overlayDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(overlayDir, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../outside/configmap.yaml +`), 0o644); err != nil { + t.Fatal(err) + } + + // Set cwd to cwdDir so the secure root excludes outsideDir. + chdirTemp(t, cwdDir) + + ks := unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "kustomize.toolkit.fluxcd.io/v1", + "kind": "Kustomization", + "metadata": map[string]interface{}{"name": "test", "namespace": "default"}, + }} + gen := kustomize.NewGenerator(overlayDir, ks) + + backend := inMemoryFsBackend{} + fs, dir, _, err := backend.Generate(gen, overlayDir) + if err != nil { + t.Fatalf("Generate: %v", err) + } + + // Build must fail because the resource is outside the secure root. + _, err = kustomize.Build(fs, dir) + if err == nil { + t.Fatal("expected error when referencing resource outside cwd, got nil") + } +} diff --git a/internal/build/diff.go b/internal/build/diff.go index 4485dd0f..8884e57f 100644 --- a/internal/build/diff.go +++ b/internal/build/diff.go @@ -230,6 +230,7 @@ func (b *Builder) kustomizationDiff(kustomization *kustomizev1.Kustomization) (s WithRecursive(b.recursive), WithLocalSources(b.localSources), WithSingleKustomization(), + withFsBackend(b.fsBackend), ) if err != nil { return "", false, err From 2fdbde7fde8c6c451896bcde416a4ed799c2bee1 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 10 Apr 2026 23:20:20 +0300 Subject: [PATCH 23/48] Update otel packages Signed-off-by: Stefan Prodan --- go.mod | 86 ++++++++--------- go.sum | 197 ++++++++++++++++++++------------------- tests/integration/go.mod | 35 +++---- tests/integration/go.sum | 94 +++++++++---------- 4 files changed, 209 insertions(+), 203 deletions(-) diff --git a/go.mod b/go.mod index 56e40593..5563d076 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/ProtonMail/go-crypto v1.3.0 github.com/cyphar/filepath-securejoin v0.6.1 - github.com/distribution/distribution/v3 v3.0.0 + github.com/distribution/distribution/v3 v3.1.0 github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/go-git-providers v0.26.0 github.com/fluxcd/helm-controller/api v1.5.3 @@ -33,7 +33,7 @@ require ( github.com/fluxcd/pkg/version v0.14.0 github.com/fluxcd/source-controller/api v1.8.2 github.com/fluxcd/source-watcher/api/v2 v2.1.1 - github.com/go-git/go-git/v5 v5.16.5 + github.com/go-git/go-git/v5 v5.17.1 github.com/go-logr/logr v1.4.3 github.com/gonvenience/bunt v1.4.2 github.com/gonvenience/ytbx v1.4.7 @@ -50,9 +50,9 @@ require ( github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/spf13/cobra v1.10.2 github.com/theckman/yacspin v0.13.12 - golang.org/x/crypto v0.48.0 - golang.org/x/term v0.40.0 - golang.org/x/text v0.34.0 + golang.org/x/crypto v0.50.0 + golang.org/x/term v0.42.0 + golang.org/x/text v0.36.0 k8s.io/api v0.35.2 k8s.io/apiextensions-apiserver v0.35.2 k8s.io/apimachinery v0.35.2 @@ -102,7 +102,7 @@ require ( github.com/aws/smithy-go v1.24.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect + github.com/bshuster-repo/logrus-logstash-hook v1.1.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -117,8 +117,8 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v29.2.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect - github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect + github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect @@ -136,7 +136,7 @@ require ( github.com/go-errors/errors v1.5.1 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-billy/v5 v5.8.0 // indirect github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect @@ -159,7 +159,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect @@ -171,7 +171,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect - github.com/klauspost/compress v1.18.1 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.9.0 // indirect @@ -200,7 +200,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect - github.com/prometheus/procfs v0.19.2 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect @@ -218,47 +218,47 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect gitlab.com/gitlab-org/api/client-go v1.29.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect - go.opentelemetry.io/otel/log v0.16.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect + go.opentelemetry.io/otel/log v0.19.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/api v0.261.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - helm.sh/helm/v4 v4.1.3 // indirect + helm.sh/helm/v4 v4.1.4 // indirect k8s.io/component-base v0.35.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect diff --git a/go.sum b/go.sum index 6ca6eaf7..843d2366 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -91,8 +93,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= @@ -138,18 +140,18 @@ github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454Wv github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= -github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= +github.com/distribution/distribution/v3 v3.1.0 h1:u1v788HreKTLGdNY6s7px8Exgrs9mZ9UrCDjSrpCM8g= +github.com/distribution/distribution/v3 v3.1.0/go.mod h1:73BuF5/ziMHNVt7nnL1roYpH4Eg/FgUlKZm3WryIx/o= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= +github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/elazarl/goproxy v1.8.0 h1:dt561rX7UAYMeFRLtzFx6uQGl2TpL1dr6uCG23nFQSY= @@ -238,14 +240,14 @@ github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= -github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= -github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-git/go-git/v5 v5.17.1 h1:WnljyxIzSj9BRRUlnmAU35ohDsjRK0EKmL0evDqi5Jk= +github.com/go-git/go-git/v5 v5.17.1/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= @@ -327,8 +329,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -378,8 +380,8 @@ github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PW github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -489,8 +491,8 @@ github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVR github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -508,6 +510,7 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= @@ -548,64 +551,66 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= gitlab.com/gitlab-org/api/client-go v1.29.0 h1:3KnF6vENry/9v9eVrnLi2OfBV0m/WSrwh3RcxgH/hkA= gitlab.com/gitlab-org/api/client-go v1.29.0/go.mod h1:6i3EZtC6gKiTTmDwp+f6r/Yi9OY4AaYubl5B3yXEdHE= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= -go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= -go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= -go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= +go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= +go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= +go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -619,15 +624,15 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -643,10 +648,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -656,8 +661,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -683,8 +688,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -694,8 +699,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -706,8 +711,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -716,23 +721,23 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.261.0 h1:3DoJ2GGibaCxNi1lhdScNMx9fTW87ujKHDgyHMMYdoA= google.golang.org/api v0.261.0/go.mod h1:nVH0ZK5C4tO0RdsMscleeTLY7I8m/Nt9IXxcXD2tfts= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -756,8 +761,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v4 v4.1.3 h1:Abfmb+oJUtxoaXDyB2Jhw1zRk3hT6aFfHta+AXb8Lno= -helm.sh/helm/v4 v4.1.3/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI= +helm.sh/helm/v4 v4.1.4 h1:zwTrNkalG4f7SYigRSdQnYrTj0QEz1qzetzAlYoDVSo= +helm.sh/helm/v4 v4.1.4/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI= k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60= k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0= diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 9dd15723..499a2f35 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -17,13 +17,13 @@ require ( github.com/fluxcd/pkg/runtime v0.103.0 github.com/fluxcd/source-controller/api v1.7.4 github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b - github.com/go-git/go-git/v5 v5.16.5 + github.com/go-git/go-git/v5 v5.17.2 github.com/google/go-containerregistry v0.20.7 github.com/hashicorp/terraform-exec v0.24.0 github.com/hashicorp/terraform-json v0.27.2 github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 github.com/onsi/gomega v1.39.0 - google.golang.org/grpc v1.77.0 + google.golang.org/grpc v1.79.3 k8s.io/api v0.35.2 k8s.io/apimachinery v0.35.2 k8s.io/client-go v0.35.2 @@ -53,12 +53,13 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/devigned/tab v0.1.1 // indirect - github.com/docker/cli v29.0.3+incompatible // indirect + github.com/docker/cli v29.2.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect @@ -71,7 +72,7 @@ require ( github.com/fluxcd/pkg/version v0.14.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-billy/v5 v5.8.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect @@ -120,24 +121,24 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // 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.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.247.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index c4f3ba6d..698e0894 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -81,8 +81,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= @@ -95,8 +95,8 @@ github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= -github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= +github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= @@ -110,12 +110,12 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -164,12 +164,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= -github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= -github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= +github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= +github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -363,16 +363,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= @@ -387,16 +387,16 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -409,18 +409,18 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -436,15 +436,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -452,8 +452,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -464,8 +464,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= @@ -479,17 +479,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 8bc3ba3e1c7ae54229219636e7aa6898e5896207 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 10 Apr 2026 23:30:30 +0300 Subject: [PATCH 24/48] Migrate end-to-end test to latest cloud SDKs Signed-off-by: Stefan Prodan --- tests/integration/azure_test.go | 41 ++++++++--- tests/integration/gcp_test.go | 4 +- tests/integration/go.mod | 25 ++----- tests/integration/go.sum | 118 ++++++++------------------------ 4 files changed, 70 insertions(+), 118 deletions(-) diff --git a/tests/integration/azure_test.go b/tests/integration/azure_test.go index 6c7cab14..c92f47f5 100644 --- a/tests/integration/azure_test.go +++ b/tests/integration/azure_test.go @@ -19,9 +19,10 @@ package integration import ( "context" "fmt" + "log" "os" - eventhub "github.com/Azure/azure-event-hubs-go/v3" + azeventhubs "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2" "github.com/fluxcd/pkg/git" "github.com/fluxcd/test-infra/tftestenv" tfjson "github.com/hashicorp/terraform-json" @@ -148,27 +149,47 @@ func registryLoginACR(ctx context.Context, output map[string]*tfjson.StateOutput } func setupEventHubHandler(ctx context.Context, c chan []byte, eventHubSas string) (func(), error) { - hub, err := eventhub.NewHubFromConnectionString(eventHubSas) + consumerClient, err := azeventhubs.NewConsumerClientFromConnectionString(eventHubSas, "", azeventhubs.DefaultConsumerGroup, nil) if err != nil { return nil, err } - handler := func(ctx context.Context, event *eventhub.Event) error { - c <- event.Data - return nil - } - runtimeInfo, err := hub.GetRuntimeInformation(ctx) + props, err := consumerClient.GetEventHubProperties(ctx, nil) if err != nil { + consumerClient.Close(ctx) return nil, err } - listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset()) + + latest := true + partitionClient, err := consumerClient.NewPartitionClient(props.PartitionIDs[0], &azeventhubs.PartitionClientOptions{ + StartPosition: azeventhubs.StartPosition{Latest: &latest}, + }) if err != nil { + consumerClient.Close(ctx) return nil, err } + receiveCtx, cancel := context.WithCancel(ctx) + go func() { + for { + events, err := partitionClient.ReceiveEvents(receiveCtx, 1, nil) + if err != nil { + if receiveCtx.Err() != nil { + return + } + log.Printf("error receiving event hub events: %s\n", err) + return + } + for _, event := range events { + c <- event.Body + } + } + }() + closefn := func() { - listenerHandler.Close(ctx) - hub.Close(ctx) + cancel() + partitionClient.Close(ctx) + consumerClient.Close(ctx) } return closefn, nil diff --git a/tests/integration/gcp_test.go b/tests/integration/gcp_test.go index 8d901dcb..d33b566b 100644 --- a/tests/integration/gcp_test.go +++ b/tests/integration/gcp_test.go @@ -24,7 +24,7 @@ import ( "os" "strings" - "cloud.google.com/go/pubsub" + "cloud.google.com/go/pubsub/v2" tfjson "github.com/hashicorp/terraform-json" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -159,7 +159,7 @@ func setupPubSubReceiver(ctx context.Context, c chan []byte, projectID string, t return nil, fmt.Errorf("error creating pubsub client: %s", err) } - sub := pubsubClient.Subscription(topicID) + sub := pubsubClient.Subscriber(topicID) go func() { err = sub.Receive(ctx, func(ctx context.Context, message *pubsub.Message) { c <- message.Data diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 499a2f35..0f15b014 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -3,8 +3,8 @@ module github.com/fluxcd/flux2/tests/integration go 1.26.0 require ( - cloud.google.com/go/pubsub v1.50.1 - github.com/Azure/azure-event-hubs-go/v3 v3.6.2 + cloud.google.com/go/pubsub/v2 v2.0.0 + github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2 v2.0.2 github.com/chainguard-dev/git-urls v1.0.2 github.com/fluxcd/helm-controller/api v1.4.5 github.com/fluxcd/image-automation-controller/api v1.0.4 @@ -36,19 +36,10 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.5.2 // indirect - cloud.google.com/go/pubsub/v2 v2.0.0 // indirect dario.cat/mergo v1.0.1 // indirect - github.com/Azure/azure-amqp-common-go/v4 v4.2.0 // indirect - github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/go-amqp v1.4.0 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.30 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.1 // indirect - github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect - github.com/Azure/go-autorest/autorest/validation v0.3.2 // indirect - github.com/Azure/go-autorest/logger v0.2.2 // indirect - github.com/Azure/go-autorest/tracing v0.6.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/go-amqp v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect @@ -58,7 +49,6 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/devigned/tab v0.1.1 // indirect github.com/docker/cli v29.2.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect @@ -78,7 +68,6 @@ require ( 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/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -95,13 +84,11 @@ require ( github.com/hashicorp/hc-install v0.9.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.18.1 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -117,6 +104,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/zclconf/go-cty v1.16.4 // indirect + go.einride.tech/aip v0.73.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect @@ -144,6 +132,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect k8s.io/apiextensions-apiserver v0.35.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 698e0894..8705ffe7 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -9,53 +9,28 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= -cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= -cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= -cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM= -cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk= cloud.google.com/go/pubsub/v2 v2.0.0 h1:0qS6mRJ41gD1lNmM/vdm6bR7DQu6coQcVwD+VPf0Bz0= cloud.google.com/go/pubsub/v2 v2.0.0/go.mod h1:0aztFxNzVQIRSZ8vUr79uH2bS3jwLebwK6q1sgEub+E= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/azure-amqp-common-go/v4 v4.2.0 h1:q/jLx1KJ8xeI8XGfkOWMN9XrXzAfVTkyvCxPvHCjd2I= -github.com/Azure/azure-amqp-common-go/v4 v4.2.0/go.mod h1:GD3m/WPPma+621UaU6KNjKEo5Hl09z86viKwQjTpV0Q= -github.com/Azure/azure-event-hubs-go/v3 v3.6.2 h1:7rNj1/iqS/i3mUKokA2n2eMYO72TB7lO7OmpbKoakKY= -github.com/Azure/azure-event-hubs-go/v3 v3.6.2/go.mod h1:n+ocYr9j2JCLYqUqz9eI+lx/TEAtL/g6rZzyTFSuIpc= -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4= -github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.30 h1:iaZ1RGz/ALZtN5eq4Nr1SOFSlf2E4pDI3Tcsl+dZPVE= -github.com/Azure/go-autorest/autorest v0.11.30/go.mod h1:t1kpPIOpIVX7annvothKvb0stsrXa37i7b+xpmBW8Fs= -github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= -github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= -github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/date v0.3.1 h1:o9Z8Jyt+VJJTCZ/UORishuHOusBwolhjokt9s5k8I4w= -github.com/Azure/go-autorest/autorest/date v0.3.1/go.mod h1:Dz/RDmXlfiFFS/eW+b/xMUSFs1tboPVy6UjgADToWDM= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= -github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= -github.com/Azure/go-autorest/autorest/validation v0.3.2 h1:myD3tcvs+Fk1bkJ1Xx7xidop4z4FWvWADiMGMXeVd2E= -github.com/Azure/go-autorest/autorest/validation v0.3.2/go.mod h1:4z7eU88lSINAB5XL8mhfPumiUdoAQo/c7qXwbsM8Zhc= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.2 h1:hYqBsEBywrrOSW24kkOCXRcKfKhK76OzLTfF+MYDE2o= -github.com/Azure/go-autorest/logger v0.2.2/go.mod h1:I5fg9K52o+iuydlWfa9T5K6WFos9XYr9dYTFzpqgibw= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsful9L/2nyR0= -github.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2 v2.0.2 h1:EBiOwZYJUMsjLGJ9x0oNY6ADf+5915P/jhhVcn42KXc= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2 v2.0.2/go.mod h1:NjuxmUsBJ0Ya9Xxjhjo06bj3/QB4C8z838I5S88UtQQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.3.0 h1:4hGvxD72TluuFIXVr8f4XkKZfqAa7Pj61t0jmQ7+kes= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.3.0/go.mod h1:TSH7DcFItwAufy0Lz+Ft2cyopExCpxbOxI5SkH4dRNo= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 h1:ZJJNFaQ86GVKQ9ehwqyAFE6pIfyicpuJ8IkVaPBc6/4= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3/go.mod h1:URuDvhmATVKqHBH9/0nOiNKk0+YcwfQ3WkK5PqHKxc8= +github.com/Azure/go-amqp v1.5.0 h1:GRiQK1VhrNFbyx5VlmI6BsA1FCp27W5rb9kxOZScnTo= +github.com/Azure/go-amqp v1.5.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= @@ -83,6 +58,8 @@ github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= @@ -91,10 +68,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= -github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= -github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= @@ -187,15 +160,15 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= -github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -253,12 +226,10 @@ github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoK github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -272,6 +243,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -282,8 +255,6 @@ github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 h1:YH424zrwLTlyHS github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5/go.mod h1:PoGiBqKSQK1vIfQ+yVaFcGjDySHvym6FM1cNYnwzbrY= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -302,6 +273,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY= github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -341,7 +314,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= @@ -350,7 +322,6 @@ 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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE= github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= go.einride.tech/aip v0.73.0 h1:bPo4oqBo2ZQeBKo4ZzLb1kxYXTY1ysJhpvQyfuGzvps= @@ -383,18 +354,13 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -402,13 +368,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -417,8 +378,6 @@ golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -430,28 +389,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -461,12 +407,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= @@ -516,8 +458,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= From 082a706f7f1c158d4451e0709633292bb7833368 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sat, 11 Apr 2026 03:25:41 +0300 Subject: [PATCH 25/48] docs: Explain the Pull Request Process Signed-off-by: Stefan Prodan --- CONTRIBUTING.md | 194 ++++++++++++++++-------------------------------- 1 file changed, 64 insertions(+), 130 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abfc1bb9..2082edfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,154 +1,88 @@ # Contributing -Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and -accepts contributions via GitHub pull requests. This document outlines -some of the conventions on to make it easier to get your contribution -accepted. +Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and accepts contributions via GitHub pull requests. +This document outlines the conventions to get your contribution accepted. +We gratefully welcome improvements to documentation as well as code contributions. -We gratefully welcome improvements to issues and documentation as well as to -code. +If you are new to the project, we recommend starting with documentation improvements or +small bug fixes to get familiar with the codebase and the contribution process. + +## Project Structure + +The Flux project consists of a set of Kubernetes controllers and tools that implement the GitOps pattern. +The main repositories in the Flux project are: + +- [fluxcd/flux2](https://github.com/fluxcd/flux2): The Flux distribution and command-line interface (CLI) +- [fluxcd/pkg](https://github.com/fluxcd/pkg): The GitOps Toolkit Go SDK for building Flux controllers and CLI plugins +- [fluxcd/source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets) +- [fluxcd/source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns +- [fluxcd/kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize +- [fluxcd/helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for lifecycle management of Helm releases +- [fluxcd/notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events (alerts and webhook receivers) +- [fluxcd/image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries for new image tags and digests +- [fluxcd/image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patching container image tags and digests in Git repositories +- [fluxcd/website](https://github.com/fluxcd/website): The Flux documentation website accessible at ## Certificate of Origin -By contributing to this project you agree to the Developer Certificate of -Origin (DCO). This document was created by the Linux Kernel community and is a -simple statement that you, as a contributor, have the legal right to make the -contribution. +By contributing to this project you agree to the Developer Certificate of Origin (DCO). +This document was created by the Linux Kernel community and is a simple statement that you, +as a contributor, have the legal right to make the contribution. -We require all commits to be signed. By signing off with your signature, you -certify that you wrote the patch or otherwise have the right to contribute the -material by the rules of the [DCO](DCO): +We require all commits to be signed. By signing off with your signature, you certify that you wrote +the patch or otherwise have the right to contribute the material by the rules of the [DCO](https://raw.githubusercontent.com/fluxcd/flux2/refs/heads/main/DCO): `Signed-off-by: Jane Doe ` -The signature must contain your real name -(sorry, no pseudonyms or anonymous contributions) -If your `user.name` and `user.email` are configured in your Git config, +The signature must contain your real name (sorry, no pseudonyms or anonymous contributions). +If your `user.name` and `user.email` are set in your Git config, you can sign your commit automatically with `git commit -s`. -## Communications - -For realtime communications we use Slack: To join the conversation, simply -join the [CNCF](https://slack.cncf.io/) Slack workspace and use the -[#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel. - -To discuss ideas and specifications we use [Github -Discussions](https://github.com/fluxcd/flux2/discussions). - -For announcements we use a mailing list as well. Simply subscribe to -[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev) -to join the conversation (there you can also add calendar invites -to your Google calendar for our [Flux -meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)). - -## Understanding Flux and the GitOps Toolkit - -If you are entirely new to Flux and the GitOps Toolkit, -you might want to take a look at the [introductory talk and demo](https://www.youtube.com/watch?v=qQBtSkgl7tI). - -This project is composed of: - -- [flux2](https://github.com/fluxcd/flux2): The Flux CLI -- [source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets) -- [source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns -- [kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize -- [helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm -- [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events -- [image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries -- [image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patches container image tags in Git - -### Understanding the code - -To get started with developing controllers, you might want to review -[our guide](https://fluxcd.io/flux/gitops-toolkit/source-watcher/) which -walks you through writing a short and concise controller that watches out -for source changes. - -## How to run the test suite - -Prerequisites: - -* go >= 1.26 -* kubectl >= 1.33 -* kustomize >= 5.0 - -Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with: - -```bash -make install-envtest -``` - -Then you can run the unit tests with: - -```bash -make test -``` - -After [installing Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation) on your machine, -create a cluster for testing with: - -```bash -make setup-kind -``` - -Then you can run the end-to-end tests with: - -```bash -make e2e -``` - -When the output of the Flux CLI changes, to automatically update the golden -files used in the test, pass `-update` flag to the test as: - -```bash -make e2e TEST_ARGS="-update" -``` - -Since not all packages use golden files for testing, `-update` argument must be -passed only for the packages that use golden files. Use the variables -`TEST_PKG_PATH` for unit tests and `E2E_TEST_PKG_PATH` for e2e tests, to set the -path of the target test package: - -```bash -# Unit test -make test TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update" -# e2e test -make e2e E2E_TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update" -``` - -Teardown the e2e environment with: - -```bash -make cleanup-kind -``` - ## Acceptance policy These things will make a PR more likely to be accepted: -- a well-described requirement -- tests for new code -- tests for old code! -- new code and tests follow the conventions in old code and tests -- a good commit message (see below) -- all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) -- names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1) -- code must build on both Linux and Darwin, via plain `go build` -- code should have appropriate test coverage and tests should be written - to work with `go test` +- Addressing an open issue, if one doesn't exist, please open an issue to discuss the problem and the proposed solution before submitting a PR. +- Flux is GA software and we are committed to maintaining backward compatibility. If your contribution introduces a breaking change, expect for your PR to be rejected. +- New code and tests must follow the conventions in the existing code and tests. All new code must have good test coverage and be well documented. +- All top-level Go code and exported names should have doc comments, as should non-trivial unexported type or function declarations. +- Before submitting a PR, make sure that your code is properly formatted by running `make tidy fmt vet` and that all tests are passing by running `make test`. In general, we will merge a PR once one maintainer has endorsed it. For substantial changes, more people may become involved, and you might get asked to resubmit the PR or divide the changes into more than one PR. -### Format of the Commit Message +## Format of the Commit Message -For the GitOps Toolkit controllers we prefer the following rules for good commit messages: +For the Flux project we prefer the following rules: -- Limit the subject to 50 characters and write as the continuation - of the sentence "If applied, this commit will ..." -- Explain what and why in the body, if more than a trivial change; - wrap it at 72 characters. +- Limit the subject to 50 characters, start with a capital letter and do not end with a period. +- Explain what and why in the body, if more than a trivial change; wrap it at 72 characters. +- Use the imperative mood in the subject line (e.g., "Add support for X" instead of "Added support for X" or "Adds support for X"). +- Do not include the issue number in the commit message, use the PR description instead (e.g., "Fixes #123" or "Closes #123"). +- Do not include GitHub mentions (e.g., `@username` or `@team`) within the commit message. -The [following article](https://chris.beams.io/posts/git-commit/#seven-rules) -has some more helpful advice on documenting your work. +## Pull Request Process + +Fork the repository and create a new branch for your changes, do not commit directly to the `main` branch. +Once you have made your changes and committed them, push your branch to your fork and open a pull request +against the `main` branch of the Flux repository. + +During the review process, you may be asked to make changes to your PR. Add commits to address the feedback +without force pushing, as this will make it easier for reviewers to see the changes. +Before committing, make sure to run `make test` to ensure that your code will pass the CI checks. + +When the review process is complete, you will be asked to **squash** the commits and **rebase** your branch. +**Do not merge** the `main` branch into your branch, instead, rebase your branch on top of the latest `main` +branch after **syncing your fork** with the latest changes from the Flux repository. After rebasing, +you can push your branch with the `--force-with-lease` option to update the PR. + +## Communications + +For realtime communications we use Slack. To reach out to the Flux maintainers and contributors, +join the [CNCF](https://slack.cncf.io/) Slack workspace and use the [#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel. +To discuss ideas and specifications we use [GitHub Discussions](https://github.com/fluxcd/flux2/discussions). + +For announcements, we use a mailing list as well. Subscribe to +[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev), there you can also add calendar invites +to your Google calendar for our [Flux dev meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view). From 4eaf59113f45bb7bea7244ff0241b5cc330fd148 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sat, 11 Apr 2026 10:14:26 +0300 Subject: [PATCH 26/48] docs: Add AI Coding Assistants Guidance Signed-off-by: Stefan Prodan --- CONTRIBUTING.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2082edfa..25a5f8b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,47 @@ The main repositories in the Flux project are: - [fluxcd/image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patching container image tags and digests in Git repositories - [fluxcd/website](https://github.com/fluxcd/website): The Flux documentation website accessible at +## AI Coding Assistants Guidance + +Using AI Agents to help write your PR is acceptable, but as the author, you are responsible +for understanding the code and the documentation you submit. Please review all the AI-generated +content and make sure it follows the guidelines in this document before submitting your PR. + +All Flux repositories contain an `AGENTS.md` file. You must point your AI Agent to +`AGENTS.md` and ask it to follow the guidelines and conventions described there. + +Trim down the verbiage in the PR description, commit messages and code comments. +When engaging with Flux maintainers please refrain from using AI Agents to +generate responses, we want to talk to you, not to your AI Agent. + +AI Agents **must not** add `Signed-off-by` or `Co-authored-by` tags to the commit message. +Only humans can legally certify the Developer Certificate of Origin ([DCO](https://developercertificate.org/)). + +You should disclose the use of AI Agents in the description of your PR and +in the commit message using the `Assisted-by: AGENT_NAME/LLM_VERSION` tag. + +Adding the `Assisted-by` tag to the commit message can be done with: + +```sh +git commit -s -m "Your commit message" --trailer "Assisted-by: /" +``` + +**Note** that the `Signed-off-by` tag is set via the `-s` flag using your real name and email +(`user.name` and `user.email` must be set in Git config). + +Example of a commit message disclosing the use of AI assistance: + +```text +Add version info to plugin listing + +Add a version column to the `flux plugin list` table output and populate +it with the semantic version info extracted from the plugin's recipe file. +For plugins installed via symlinks, the version is set to `unknown`. + +Signed-off-by: Jane Doe +Assisted-by: copilot/gpt-5.4 +``` + ## Certificate of Origin By contributing to this project you agree to the Developer Certificate of Origin (DCO). @@ -59,8 +100,8 @@ For the Flux project we prefer the following rules: - Limit the subject to 50 characters, start with a capital letter and do not end with a period. - Explain what and why in the body, if more than a trivial change; wrap it at 72 characters. - Use the imperative mood in the subject line (e.g., "Add support for X" instead of "Added support for X" or "Adds support for X"). -- Do not include the issue number in the commit message, use the PR description instead (e.g., "Fixes #123" or "Closes #123"). -- Do not include GitHub mentions (e.g., `@username` or `@team`) within the commit message. +- Do not include GitHub mentions to issues in the commit message, use the PR description instead (e.g., "Fixes #123" or "Closes #123"). +- Do not include GitHub mentions to accounts (e.g., `@username` or `@team`) within the commit message. ## Pull Request Process From 7ba6dacc5c040b7f12fb637a43431d55ab08b75e Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sun, 12 Apr 2026 21:14:44 +0300 Subject: [PATCH 27/48] Add AI Agents guidance Signed-off-by: Stefan Prodan Assisted-by: claude-code/claude-opus-4-6 --- AGENTS.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..9fe1a1b6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,151 @@ +# AGENTS.md + +Guidance for AI coding assistants working in `fluxcd/flux2`. Read this file before making changes. + +## Contribution workflow for AI agents + +These rules come from [`fluxcd/flux2/CONTRIBUTING.md`](https://github.com/fluxcd/flux2/blob/main/CONTRIBUTING.md) and apply to every Flux repository. + +- **Do not add `Signed-off-by` or `Co-authored-by` trailers with your agent name.** Only a human can legally certify the DCO. +- **Disclose AI assistance** with an `Assisted-by` trailer naming your agent and model: + ```sh + git commit -s -m "Add support for X" --trailer "Assisted-by: /" + ``` + The `-s` flag adds the human's `Signed-off-by` from their git config — do not remove it. +- **Commit message format:** Subject in imperative mood ("Add feature X" instead of "Adding feature X"), capitalized, no trailing period, ≤50 characters. Body wrapped at 72 columns, explaining what and why. No `@mentions` or `#123` issue references in the commit — put those in the PR description. +- **Trim verbiage:** in PR descriptions, commit messages, and code comments. No marketing prose, no restating the diff, no emojis. +- **Rebase, don't merge:** Never merge `main` into the feature branch; rebase onto the latest `main` and push with `--force-with-lease`. Squash before merge when asked. +- **Pre-PR gate:** `make tidy fmt vet && make test` must pass and the working tree must be clean. +- **Flux is GA:** Backward compatibility is mandatory. Breaking changes to CLI flags, output format, or behavior will be rejected. Design additive changes. +- **Copyright:** All new `.go` files must begin with the header from `cmd/flux/main.go` (Apache 2.0). Update the year to the current year when copying. +- **Tests:** New features, improvements and fixes must have test coverage. Add unit tests in `cmd/flux/*_test.go` tagged `//go:build unit`. Follow the existing `cmdTestCase` + golden file patterns. Run tests locally before pushing. + +## Code quality + +Before submitting code, review your changes for the following: + +- **No secrets in logs or output.** Never surface auth tokens, passwords, deploy keys, or credential URLs in error messages, log lines, or CLI output. Bootstrap and source-secret commands handle sensitive material — take extra care. +- **No unchecked I/O.** Close HTTP response bodies, file handles, and tar readers in `defer` statements. Check and propagate errors from I/O operations. +- **No path traversal.** Validate and sanitize file paths extracted from archives or user input. Never `filepath.Join` with untrusted components without validation. +- **No command injection.** Do not shell out via `os/exec` for git, helm, or kustomize operations. Use the Go libraries already in use (`fluxcd/pkg/git`, `fluxcd/pkg/kustomize`, `fluxcd/pkg/ssa`). +- **No hardcoded defaults for security settings.** TLS verification must remain enabled by default. Git auth settings come from user-provided secrets. +- **Error handling.** Wrap errors with `%w` for chain inspection. Do not swallow errors silently. CLI errors must be actionable — tell the user what went wrong and how to fix it without leaking internal state. +- **Resource cleanup.** Ensure temporary files and directories (manifest staging, downloaded tarballs) are cleaned up on all code paths (success and error). Use `defer` and `t.TempDir()` in tests. +- **No panics.** Never use `panic` in runtime code paths. Return errors and let the CLI handle them gracefully. +- **Output discipline.** Machine-readable data (tables, YAML, JSON) goes to stdout via `rootCmd.OutOrStdout()`. Human-readable status messages go to stderr via the `stderrLogger`. +- **Minimal surface.** Keep new exported APIs in `pkg/` to the minimum needed. Every export is a backward-compatibility commitment. + +## Project overview + +flux2 is the Flux CLI (`flux` command) and distribution repository. It is **not** a controller — it consumes CRD APIs from six independent controller repos (source-controller, kustomize-controller, helm-controller, notification-controller, image-reflector-controller, image-automation-controller). It serves two purposes: + +1. **CLI tool** — a Cobra-based binary that installs Flux onto Kubernetes clusters, bootstraps GitOps pipelines, and manages all Flux CRD objects (create, get, export, reconcile, suspend, resume, delete, diff, build, etc.). +2. **Distribution hub** — it bundles the Kustomize manifests for all Flux controllers and releases them as `manifests.tar.gz` on GitHub. Those manifests are also compiled into the binary itself via `//go:embed`. + +## Repository layout + +- `cmd/flux/` — all CLI source. Single `main` package with one file per command or resource type. `main.go` defines the root cobra command with global flags. `manifests.embed.go` embeds the generated controller manifests via `//go:embed`. +- `internal/build/` — `flux build kustomization` logic (kustomize-based diff/build, SOPS secret masking). +- `internal/flags/` — custom `pflag.Value` types providing enum validation (e.g. `LogLevel`, `ECDSACurve`, `RSAKeyBits`, `PublicKeyAlgorithm`, `DecryptionProvider`). +- `internal/tree/` — tree-printing helper for `flux tree kustomization`. +- `internal/utils/` — shared helpers: `KubeClient`, `KubeConfig`, `NewScheme` (registers all controller API groups), `Apply` (SSA-based two-phase apply), `ExecKubectlCommand`, `ValidateComponents`. +- `pkg/bootstrap/` — bootstrap orchestration: `Run()`, `PlainGitBootstrapper`, `ProviderBootstrapper`. `provider/` has the git provider factory (GitHub, GitLab, Gitea, Bitbucket). +- `pkg/log/` — `Logger` interface (`Actionf`, `Generatef`, `Waitingf`, `Successf`, `Warningf`, `Failuref`). +- `pkg/manifestgen/` — manifest generation for install, sync, kustomization, and source secrets. +- `pkg/printers/` — specialized printers `TablePrinter` and `DyffPrinter`. +- `pkg/status/` — `StatusChecker` using `fluxcd/cli-utils` kstatus polling. +- `pkg/uninstall/` — `flux uninstall` logic. +- `manifests/` — Kustomize bases per controller, RBAC, network policies, CRD references, and `scripts/bundle.sh` which runs `kustomize build` to generate `cmd/flux/manifests/`. +- `tests/integration/` — cloud e2e tests (Azure/GCP) with their own `go.mod` and Terraform infrastructure. +- `rfcs/` — Request for Comments documents for major design proposals and changes. + +## CLI architecture + +Commands are Cobra-based, organized as parent + per-resource children: +- Group files (`create.go`, `get.go`, `reconcile.go`, etc.) register the parent subcommand. +- Per-resource files (`create_kustomization.go`, `get_helmrelease.go`, etc.) register children. + +Core interfaces in `cmd/flux/` enable generic command implementations: +- `adapter` / `copyable` / `listAdapter` — wrap controller API types for generic CRUD. +- `reconcilable` — annotate-and-poll pattern for triggering reconciliation. +- `summarisable` — generic table output for `get` commands. + +Each resource type (e.g. `kustomizationAdapter` in `kustomization.go`) wraps the controller API type and implements these interfaces. Follow this pattern when adding new resource support. + +Commands interact with the Kubernetes API via `internal/utils.KubeClient()` → `client.WithWatch`. `internal/utils.NewScheme()` registers all six controller API groups plus core k8s types. `internal/utils.Apply()` implements SSA-based two-phase apply (CRDs/Namespaces first, then remaining objects). + +## Manifest pipeline + +1. `manifests/bases//` contains a Kustomize base per controller referencing the controller's GitHub release for CRDs and deployment manifests. +2. `manifests/install/kustomization.yaml` assembles all bases plus RBAC and policies. +3. `manifests/scripts/bundle.sh` runs `kustomize build` on each base, writing output to `cmd/flux/manifests/` (not checked in — generated). +4. The Makefile `$(EMBEDDED_MANIFESTS_TARGET)` runs `bundle.sh` and creates a sentinel file `cmd/flux/.manifests.done`. +5. `cmd/flux/manifests.embed.go` uses `//go:embed manifests/*.yaml` to compile everything into the binary. + +When modifying `manifests/`, always run `make build` and verify the generated output before committing. Never hand-edit files under `cmd/flux/manifests/`. + +## Build, test, lint + +All targets in the root `Makefile`. Go version tracks `go.mod`. + +- `make tidy` — tidy the root module and `tests/integration/`. +- `make fmt` / `make vet` — run in the root module. +- `make build` — builds `bin/flux` (CGO disabled, version injected via ldflags). Depends on embedded manifests being generated. +- `make build-dev` — builds with `DEV_VERSION`. +- `make install` / `make install-dev` — `go install` or copy to `/usr/local/bin`. +- `make test` — unit tests with envtest: runs `tidy fmt vet install-envtest`, then `go test ./... -coverprofile cover.out --tags=unit $(TEST_ARGS)`. +- `make e2e` — e2e tests against a live cluster: `go test ./cmd/flux/... --tags=e2e -v -failfast`. +- `make test-with-kind` — sets up a kind cluster, runs e2e, tears it down. +- `make install-envtest` — downloads `setup-envtest` and fetches k8s binaries into `testbin/`. + +Run a single test: `make test TEST_ARGS='-run TestCreate -v'`. + +## Codegen and generated files + +Check `go.mod` and the `Makefile` for current dependency and tool versions. The main codegen pipeline is the manifest bundle: + +```sh +./manifests/scripts/bundle.sh +``` + +Generated files (never hand-edit): + +- `cmd/flux/manifests/*.yaml` — generated by `bundle.sh` from `manifests/` sources. +- `cmd/flux/.manifests.done` — sentinel file tracking bundle state. + +Bump `fluxcd/pkg/*` and controller `api` modules as a set. Run `make tidy` after any bump. + +## Conventions + +- Standard `gofmt`. All exported names need doc comments. +- **Command pattern:** follow the existing group-parent + per-resource-child cobra structure. New resources need an adapter type implementing `adapter`, `copyable`, and the relevant command interfaces (`reconcilable`, `summarisable`, etc.). +- **Output:** stderr for human status messages via `stderrLogger` (Unicode symbols: `►` action, `✔` success, `✗` failure, `◎` waiting, `⚠️` warning, `✚` generate). Stdout for machine-readable data (tables, YAML, JSON) via `rootCmd.OutOrStdout()`. +- **Global flags:** kubeconfig flags come from `k8s.io/cli-runtime/pkg/genericclioptions.ConfigFlags`. Client tuning comes from `fluxcd/pkg/runtime/client.Options`. `FLUX_SYSTEM_NAMESPACE` env var overrides the default namespace. +- **SSA apply:** always use `internal/utils.Apply()` (two-phase: CRDs/Namespaces first, then rest). Do not apply manifests directly via the k8s client. +- **Reconcile triggering:** patch `meta.ReconcileRequestAnnotation` with a timestamp, then poll with `kstatus.Compute()` until ready. See `reconcile.go`. +- **Error handling:** return errors from `RunE`. Use `*RequestError` with exit codes for actionable CLI errors. Exit code 1 = warning, anything else = failure. +- **Flags:** use `internal/flags/` custom `pflag.Value` types for enum flags (providers, algorithms, sources). Add new enum types there. + +## Testing + +Three test suites with build tags: + +- **Unit** (`//go:build unit`): lives in `cmd/flux/*_test.go`. Uses `controller-runtime/envtest` for an in-process fake k8s API. CRDs are loaded from `cmd/flux/manifests/` (embedded manifests). Pattern: `cmdTestCase{args: "...", assert: assertGoldenFile("testdata/...")}`. The `executeCommand()` helper captures stdout. +- **E2e** (`//go:build e2e`): lives in `cmd/flux/*_test.go`. Requires a live cluster via `TEST_KUBECONFIG`. `TestMain` runs `flux install` for setup and teardown. +- **Integration** (`//go:build integration`): lives in `tests/integration/` with its own `go.mod`. Uses Terraform-provisioned cloud clusters. + +Golden files live in `cmd/flux/testdata/`. Update them with `go test ./cmd/flux/... --tags=unit -update`. + +Run a single unit test: `make test TEST_ARGS='-run TestInstall -v'`. + +## Gotchas and non-obvious rules + +- The `cmd/flux/manifests/` directory is **generated, not checked in**. It is created by `manifests/scripts/bundle.sh` and embedded into the binary. `make build` and `make test` both trigger the bundle if the sentinel file is stale. +- `kustomize` must be on `PATH` for `bundle.sh` to work. If you see "command not found" errors during build, install kustomize. +- `internal/utils.NewScheme()` registers all six controller API groups. Adding support for a new CRD type means updating the scheme registration there. +- The `VERSION` constant is injected via `-ldflags` at build time. In dev builds it defaults to `0.0.0-dev.0`. The embedded manifest version check (`isEmbeddedVersion`) determines whether `flux install` uses compiled-in manifests or downloads from GitHub. +- `resetCmdArgs()` in tests is critical — Cobra persists flag state between test runs. Every test case must reset to avoid pollution. +- `executeCommand()` captures stdout only. Stderr output (from `stderrLogger`) is not captured in test assertions. If your command's output goes to the wrong stream, tests will silently pass with empty golden files. +- The `adapter` / `listAdapter` interfaces use type assertions internally. If you add a new resource type and forget to implement an interface method, you'll get a runtime panic in the generic command handler, not a compile error. Add interface compliance checks (`var _ reconcilable = ...`). +- Bootstrap commands create real Git commits and push to real repos. E2e tests for bootstrap need careful cleanup. Do not add bootstrap e2e tests without a corresponding teardown. +- `pkg/` packages are importable by external consumers (e.g. Terraform provider, other tools). Treat their exported surface as public API. From 21ca8d4d176bdcce8506645b46741ef1418f186e Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 30 Mar 2026 12:13:21 +0300 Subject: [PATCH 28/48] [RFC] Flux CLI Plugin System Signed-off-by: Stefan Prodan --- rfcs/xxxx-cli-plugin-system/README.md | 312 ++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 rfcs/xxxx-cli-plugin-system/README.md diff --git a/rfcs/xxxx-cli-plugin-system/README.md b/rfcs/xxxx-cli-plugin-system/README.md new file mode 100644 index 00000000..1cb8983f --- /dev/null +++ b/rfcs/xxxx-cli-plugin-system/README.md @@ -0,0 +1,312 @@ +# RFC-XXXX Flux CLI Plugin System + +**Status:** provisional + +**Creation date:** 2026-03-30 + +**Last update:** 2026-03-30 + +## Summary + +This RFC proposes a plugin system for the Flux CLI that allows external CLI tools to be +discoverable and invocable as `flux ` subcommands. Plugins are installed from a +centralized catalog hosted on GitHub, with SHA-256 checksum verification and automatic +version updates. The design follows the established kubectl plugin pattern used across +the Kubernetes ecosystem. + +## Motivation + +The Flux CLI currently has no mechanism for extending its functionality with external tools. +Projects like [flux-operator](https://github.com/controlplaneio-fluxcd/flux-operator) and +[flux-local](https://github.com/allenporter/flux-local) provide complementary CLI tools +that users install and invoke separately. This creates a fragmented user experience where +Flux-related workflows require switching between multiple binaries with different flag +conventions and discovery mechanisms. + +The Kubernetes ecosystem has a proven model for CLI extensibility: kubectl plugins are +executables prefixed with `kubectl-` that can be discovered, installed via +[krew](https://krew.sigs.k8s.io/), and invoked as `kubectl `. This model has +been widely adopted and is well understood by Kubernetes users. + +### Goals + +- Allow external CLI tools to be invoked as `flux ` subcommands without modifying + the external binary. +- Provide a `flux plugin install` command to download plugins from a centralized catalog + with checksum verification. +- Support shell completion for plugin subcommands by delegating to the plugin's own + Cobra `__complete` command. +- Support plugins written as scripts (Python, Bash, etc.) via symlinks into the + plugin directory. +- Ensure built-in commands always take priority over plugins. +- Keep the plugin system lightweight with zero impact on non-plugin Flux commands. + +### Non-Goals + +- Plugin dependency management (plugins are standalone binaries). +- Cosign/SLSA signature verification (SHA-256 only in v1beta1; signatures can be added later). +- Automatic update checks on startup (users run `flux plugin update` explicitly). +- Private catalog authentication (users can use `$FLUXCD_PLUGIN_CATALOG` with TLS). +- Flag sharing between Flux and plugins (`--namespace`, `--context`, etc. are not + forwarded; plugins manage their own flags). + +## Proposal + +### Plugin Discovery + +Plugins are executables prefixed with `flux-` placed in a single plugin directory. +The `flux-` binary maps to the `flux ` command. For example, +`flux-operator` becomes `flux operator`. + +The default plugin directory is `~/.fluxcd/plugins/`. Users can override it with the +`$FLUXCD_PLUGINS` environment variable. Only this single directory is scanned. + +When a plugin is discovered, it appears under a "Plugin Commands:" group in `flux --help`: + +``` +Plugin Commands: + operator Runs the operator plugin + +Additional Commands: + bootstrap Deploy Flux on a cluster the GitOps way. + ... +``` + +### Plugin Execution + +On macOS and Linux, `flux operator export report` replaces the current process with +`flux-operator export report` via `syscall.Exec`, matching kubectl's behavior. +On Windows, the plugin runs as a child process with full I/O passthrough. +All arguments after the plugin name are passed through verbatim with +`DisableFlagParsing: true`. + +### Shell Completion + +Shell completion is delegated to the plugin binary via Cobra's `__complete` protocol. +When the user types `flux operator get `, Flux runs +`flux-operator __complete get ""` and returns the results. This works automatically +for all Cobra-based plugins (like flux-operator). Non-Cobra plugins gracefully degrade +to no completions. + +### Plugin Catalog + +A dedicated GitHub repository ([fluxcd/plugins](https://github.com/fluxcd/plugins)) +serves as the plugin catalog. Each plugin has a YAML manifest: + +```yaml +apiVersion: cli.fluxcd.io/v1beta1 +kind: Plugin +name: operator +description: Flux Operator CLI +homepage: https://fluxoperator.dev/ +source: https://github.com/controlplaneio-fluxcd/flux-operator +bin: flux-operator +versions: + - version: 0.45.0 + platforms: + - os: darwin + arch: arm64 + url: https://github.com/.../flux-operator_0.45.0_darwin_arm64.tar.gz + checksum: sha256:cd85d5d84d264... + - os: linux + arch: amd64 + url: https://github.com/.../flux-operator_0.45.0_linux_amd64.tar.gz + checksum: sha256:96198da969096... +``` + +A generated `catalog.yaml` (`PluginCatalog` kind) contains static metadata for all +plugins, enabling `flux plugin search` with a single HTTP fetch. + +### CLI Commands + +| Command | Description | +|---------|-------------| +| `flux plugin list` (alias: `ls`) | List installed plugins with versions and paths | +| `flux plugin install [@]` | Install a plugin from the catalog | +| `flux plugin uninstall ` | Remove a plugin binary and receipt | +| `flux plugin update [name]` | Update one or all installed plugins | +| `flux plugin search [query]` | Search the plugin catalog | + +### Install Flow + +1. Fetch `plugins/.yaml` from the catalog URL +2. Validate `apiVersion: cli.fluxcd.io/v1beta1` and `kind: Plugin` +3. Resolve version (latest if unspecified, or match `@version`) +4. Find platform entry matching `runtime.GOOS` / `runtime.GOARCH` +5. Download archive to temp file with SHA-256 checksum verification +6. Extract only the declared binary from the archive (tar.gz or zip), streaming + directly to disk without buffering in memory +7. Write binary to plugin directory as `flux-` (mode `0755`) +8. Write install receipt (`flux-.yaml`) recording version, platform, download URL, checksum and timestamp + +Install is idempotent -- reinstalling overwrites the binary and receipt. + +### Install Receipts + +When a plugin is installed via `flux plugin install`, a receipt file is written +next to the binary: + +```yaml +name: operator +version: "0.45.0" +installedAt: "2026-03-30T10:00:00Z" +platform: + os: darwin + arch: arm64 + url: https://github.com/.../flux-operator_0.45.0_darwin_arm64.tar.gz + checksum: sha256:cd85d5d84d264... +``` + +Receipts enable `flux plugin list` to show versions, `flux plugin update` to compare +installed vs. latest, and provenance tracking. Manually installed plugins (no receipt) +show `manual` in listings and are skipped by `flux plugin update`. + +### User Stories + +#### Flux User Installs a Plugin + +As a Flux user, I want to install the Flux Operator CLI as a plugin so that I can +manage Flux instances using `flux operator` instead of a separate `flux-operator` binary. + +```bash +flux plugin install operator +flux operator get instance -n flux-system +``` + +#### Flux User Updates Plugins + +As a Flux user, I want to update all my installed plugins to the latest versions +with a single command. + +```bash +flux plugin update +``` + +#### Flux User Symlinks a Python Plugin + +As a Flux user, I want to use [flux-local](https://github.com/allenporter/flux-local) +(a Python tool) as a Flux CLI plugin by symlinking it into the plugin directory. +Since flux-local is not a Go binary distributed via the catalog, I install it with +pip and register it manually. + +```bash +uv venv +source .venv/bin/activate +uv pip install flux-local +ln -s "$(pwd)/.venv/bin/flux-local" ~/.fluxcd/plugins/flux-local +flux local test +``` + +Manually symlinked plugins show `manual` in `flux plugin list` and are skipped by +`flux plugin update`. + +#### Flux User Discovers Available Plugins + +As a Flux user, I want to search for available plugins so that I can extend my +Flux CLI with community tools. + +```bash +flux plugin search +``` + +#### Plugin Author Publishes a Plugin + +As a plugin author, I want to submit my tool to the Flux plugin catalog so that +Flux users can install it with `flux plugin install `. + +1. Release binary with GoReleaser (produces tarballs/zips + checksums) +2. Submit a PR to `fluxcd/plugins` with `plugins/.yaml` +3. Subsequent releases are picked up by automated polling workflows + +### Alternatives + +#### PATH-based Discovery (kubectl model) + +kubectl discovers plugins by scanning `$PATH` for `kubectl-*` executables. This is +simple but has drawbacks: + +- Scanning the entire PATH is slow on some systems +- No control over what's discoverable (any `flux-*` binary on PATH becomes a plugin) +- No install/update mechanism built in (requires a separate tool like krew) + +The single-directory approach is faster, more predictable, and integrates install/update +directly into the CLI. + +## Design Details + +### Package Structure + +``` +internal/plugin/ + discovery.go # Plugin dir scanning, DI-based Handler + completion.go # Shell completion via Cobra __complete protocol + exec_unix.go # syscall.Exec (//go:build !windows) + exec_windows.go # os/exec fallback (//go:build windows) + catalog.go # Catalog fetching, manifest parsing, version/platform resolution + install.go # Download, verify, extract, receipts + update.go # Compare receipts vs catalog, update check + +cmd/flux/ + plugin.go # Cobra command registration, all plugin subcommands +``` + +The `internal/plugin` package uses dependency injection (injectable `ReadDir`, `Stat`, +`GetEnv`, `HomeDir` on a `Handler` struct) for testability. Tests mock these functions +directly without filesystem fixtures. + +### Plugin Directory + +- **Default**: `~/.fluxcd/plugins/` -- auto-created by install/update commands + (best-effort, no error if filesystem is read-only). +- **Override**: `$FLUXCD_PLUGINS` env var replaces the default directory path. + When set, the CLI does not auto-create the directory. + +### Startup Behavior + +`registerPlugins()` is called in `main()` before `rootCmd.Execute()`. It scans the +plugin directory and registers discovered plugins as Cobra subcommands. The scan is +lightweight (a single `ReadDir` call) and only occurs if the plugin directory exists. +Built-in commands always take priority. + +### Manifest Validation + +Both plugin manifests and the catalog are validated after fetching: + +- `apiVersion` must be `cli.fluxcd.io/v1beta1` +- `kind` must be `Plugin` or `PluginCatalog` respectively +- Checksum format is `:` (currently `sha256:...`), allowing future + algorithm migration without schema changes + +### Security Considerations + +- **Checksum verification**: All downloaded archives are verified against SHA-256 + checksums declared in the catalog manifest before extraction. +- **Path traversal protection**: Archive extraction guards against tar traversal. +- **Response size limits**: HTTP responses from the catalog are capped at 10 MiB to + prevent unbounded memory allocation from malicious servers. +- **No code execution during discovery**: Plugin directory scanning only reads directory + entries and file metadata. No plugin binary is executed during startup. +- **Retryable fetching**: All HTTP/S operations use automatic retries for transient network failures. + +### Catalog Repository CI + +The `fluxcd/plugins` repository includes CI workflows that: + +1. Validate plugin manifests on every PR (schema, name consistency, URL reachability, + checksum verification, binary presence in archives, no builtin collisions) +2. Regenerate `catalog.yaml` when plugins are added or removed +3. Automatically poll upstream repositories for new releases and create update PRs + +### Known Limitations (v1beta1) + +1. **No cosign/SLSA verification** -- SHA-256 only. Signature verification can be added later. +2. **No plugin dependencies** -- plugins are standalone binaries. +3. **No automatic update checks** -- users run `flux plugin update` explicitly. +4. **No private catalog auth** -- `$FLUXCD_PLUGIN_CATALOG` works for private URLs but no token injection. +5. **No version constraints** -- no `>=0.44.0` ranges. Exact version or latest only. +6. **Flag names differ between Flux and plugins** -- e.g., `--context` (flux) vs + `--kube-context` (flux-operator). This is a plugin concern, not a system concern. + +## Implementation History + +- **2026-03-30** PoC plugin catalog repository with example manifests and CI validation workflows available at [fluxcd/plugins](https://github.com/fluxcd/plugins). From 4e52adc7f001183c055a38c675a7f7cbda06a239 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Wed, 1 Apr 2026 14:27:09 +0300 Subject: [PATCH 29/48] Add plugin authors responsibilities Signed-off-by: Stefan Prodan --- rfcs/xxxx-cli-plugin-system/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rfcs/xxxx-cli-plugin-system/README.md b/rfcs/xxxx-cli-plugin-system/README.md index 1cb8983f..c61dff9c 100644 --- a/rfcs/xxxx-cli-plugin-system/README.md +++ b/rfcs/xxxx-cli-plugin-system/README.md @@ -4,7 +4,7 @@ **Creation date:** 2026-03-30 -**Last update:** 2026-03-30 +**Last update:** 2026-04-01 ## Summary @@ -112,6 +112,10 @@ versions: arch: amd64 url: https://github.com/.../flux-operator_0.45.0_linux_amd64.tar.gz checksum: sha256:96198da969096... + - os: windows + arch: amd64 + url: https://github.com/.../flux-operator_0.45.0_windows_amd64.zip + checksum: sha256:9712026094a5... ``` A generated `catalog.yaml` (`PluginCatalog` kind) contains static metadata for all @@ -218,6 +222,9 @@ Flux users can install it with `flux plugin install `. 2. Submit a PR to `fluxcd/plugins` with `plugins/.yaml` 3. Subsequent releases are picked up by automated polling workflows +Plugin authors are responsible for maintaining their plugin definitions in the catalog, +by responding to issues and approving PRs for updates. + ### Alternatives #### PATH-based Discovery (kubectl model) @@ -296,6 +303,7 @@ The `fluxcd/plugins` repository includes CI workflows that: checksum verification, binary presence in archives, no builtin collisions) 2. Regenerate `catalog.yaml` when plugins are added or removed 3. Automatically poll upstream repositories for new releases and create update PRs +4. Plugin authors have to agree to maintain their plugin's definition by responding to issues and approving PRs in the catalog repo. ### Known Limitations (v1beta1) From 36686b945cc8fcb8d7df4c9549e93424f4849938 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Thu, 9 Apr 2026 13:17:48 +0300 Subject: [PATCH 30/48] Add support for direct binary URLs Signed-off-by: Stefan Prodan --- rfcs/xxxx-cli-plugin-system/README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/rfcs/xxxx-cli-plugin-system/README.md b/rfcs/xxxx-cli-plugin-system/README.md index c61dff9c..d756112b 100644 --- a/rfcs/xxxx-cli-plugin-system/README.md +++ b/rfcs/xxxx-cli-plugin-system/README.md @@ -59,7 +59,7 @@ The `flux-` binary maps to the `flux ` command. For example, `flux-operator` becomes `flux operator`. The default plugin directory is `~/.fluxcd/plugins/`. Users can override it with the -`$FLUXCD_PLUGINS` environment variable. Only this single directory is scanned. +`FLUXCD_PLUGINS` environment variable. Only this single directory is scanned. When a plugin is discovered, it appears under a "Plugin Commands:" group in `flux --help`: @@ -118,6 +118,19 @@ versions: checksum: sha256:9712026094a5... ``` +The plugin manifest includes metadata (name, description, homepage, source repo), the binary name +(`bin`), and a list of versions with platform-specific download URLs and checksums. + +The download URLs can point to one of the following formats: + +- An archive containing the binary (`tar`, `tar.gz` or `zip`), with the binary at the root of the archive. The binary name inside the archive must match the `bin` field in the manifest. +- A direct binary URL. The binary is downloaded and saved without extraction. The `bin` field is used for naming the installed plugin, not for discovery in this case. +- Note that when the OS is Windows, the binary name is composed by appending `.exe` to the `bin` field (e.g., `flux-operator.exe`). + +The Flux Operator CLI detects if the URL points to an archive by checking the file extension. +If no extension is present, it checks the content type by probing for archive magic bytes after downloading the file. +If the content is not an archive, it treats it as a direct binary URL. + A generated `catalog.yaml` (`PluginCatalog` kind) contains static metadata for all plugins, enabling `flux plugin search` with a single HTTP fetch. @@ -265,7 +278,7 @@ directly without filesystem fixtures. - **Default**: `~/.fluxcd/plugins/` -- auto-created by install/update commands (best-effort, no error if filesystem is read-only). -- **Override**: `$FLUXCD_PLUGINS` env var replaces the default directory path. +- **Override**: `FLUXCD_PLUGINS` env var replaces the default directory path. When set, the CLI does not auto-create the directory. ### Startup Behavior From 2bfdadd3011fc1e9046c320613d85715c3dad953 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 13 Apr 2026 14:37:05 +0300 Subject: [PATCH 31/48] Add optional field `extractPath` to plugin definition Signed-off-by: Stefan Prodan --- rfcs/xxxx-cli-plugin-system/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/xxxx-cli-plugin-system/README.md b/rfcs/xxxx-cli-plugin-system/README.md index d756112b..9246ac5e 100644 --- a/rfcs/xxxx-cli-plugin-system/README.md +++ b/rfcs/xxxx-cli-plugin-system/README.md @@ -124,6 +124,7 @@ The plugin manifest includes metadata (name, description, homepage, source repo) The download URLs can point to one of the following formats: - An archive containing the binary (`tar`, `tar.gz` or `zip`), with the binary at the root of the archive. The binary name inside the archive must match the `bin` field in the manifest. +- An optional `extractPath` field can be specified in a `platforms` entry to override this default, either because the binary has a different name on this platform, or because it is nested in a subfolder rather than at the root of the archive. It accepts an absolute path to a file within the archive (e.g., `bin/flux-operator`). - A direct binary URL. The binary is downloaded and saved without extraction. The `bin` field is used for naming the installed plugin, not for discovery in this case. - Note that when the OS is Windows, the binary name is composed by appending `.exe` to the `bin` field (e.g., `flux-operator.exe`). From 968bebadf6b7101d946af4d9016e61e9a4efdfce Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 13 Apr 2026 16:09:05 +0300 Subject: [PATCH 32/48] Assign RFC-0013 to the plugin system proposal Signed-off-by: Stefan Prodan --- .../README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename rfcs/{xxxx-cli-plugin-system => 0013-cli-plugin-system}/README.md (99%) diff --git a/rfcs/xxxx-cli-plugin-system/README.md b/rfcs/0013-cli-plugin-system/README.md similarity index 99% rename from rfcs/xxxx-cli-plugin-system/README.md rename to rfcs/0013-cli-plugin-system/README.md index 9246ac5e..4d0cfeef 100644 --- a/rfcs/xxxx-cli-plugin-system/README.md +++ b/rfcs/0013-cli-plugin-system/README.md @@ -1,10 +1,10 @@ -# RFC-XXXX Flux CLI Plugin System +# RFC-0013 Flux CLI Plugin System -**Status:** provisional +**Status:** implementable **Creation date:** 2026-03-30 -**Last update:** 2026-04-01 +**Last update:** 2026-04-13 ## Summary From d349ffe37da90e1fbad0521a98d0bb73de247ce6 Mon Sep 17 00:00:00 2001 From: rycli Date: Sun, 12 Apr 2026 09:19:33 +0200 Subject: [PATCH 33/48] feat: add --ignore-not-found flag to 'flux diff ks' command Signed-off-by: rycli Assisted-by: claude-code/claude-opus-4-6 --- cmd/flux/diff_kustomization.go | 5 + cmd/flux/diff_kustomization_test.go | 116 +++++++++++++++++- .../configmaps/existing.yaml | 7 ++ .../configmaps/kustomization.yaml | 5 + .../build-kustomization/configmaps/new.yaml | 7 ++ ...d.golden => diff-new-kustomization.golden} | 0 .../diff-taking-ownership.golden | 9 ++ .../existing-configmap.yaml | 7 ++ .../flux-kustomization-configmaps.yaml | 14 +++ .../flux-kustomization-local-only.yaml | 14 +++ internal/build/build.go | 21 +++- 11 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 cmd/flux/testdata/build-kustomization/configmaps/existing.yaml create mode 100644 cmd/flux/testdata/build-kustomization/configmaps/kustomization.yaml create mode 100644 cmd/flux/testdata/build-kustomization/configmaps/new.yaml rename cmd/flux/testdata/diff-kustomization/{nothing-is-deployed.golden => diff-new-kustomization.golden} (100%) create mode 100644 cmd/flux/testdata/diff-kustomization/diff-taking-ownership.golden create mode 100644 cmd/flux/testdata/diff-kustomization/existing-configmap.yaml create mode 100644 cmd/flux/testdata/diff-kustomization/flux-kustomization-configmaps.yaml create mode 100644 cmd/flux/testdata/diff-kustomization/flux-kustomization-local-only.yaml diff --git a/cmd/flux/diff_kustomization.go b/cmd/flux/diff_kustomization.go index bc8164f0..9e1ec770 100644 --- a/cmd/flux/diff_kustomization.go +++ b/cmd/flux/diff_kustomization.go @@ -63,6 +63,7 @@ type diffKsFlags struct { recursive bool localSources map[string]string inMemoryBuild bool + ignoreNotFound bool } var diffKsArgs diffKsFlags @@ -78,6 +79,8 @@ func init() { diffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path") diffKsCmd.Flags().BoolVar(&diffKsArgs.inMemoryBuild, "in-memory-build", true, "Use in-memory filesystem during build.") + diffKsCmd.Flags().BoolVar(&diffKsArgs.ignoreNotFound, "ignore-not-found", false, + "Ignore Kustomization not found errors on the cluster when diffing.") diffCmd.AddCommand(diffKsCmd) } @@ -117,6 +120,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithLocalSources(diffKsArgs.localSources), build.WithSingleKustomization(), build.WithInMemoryBuild(diffKsArgs.inMemoryBuild), + build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound), ) } else { builder, err = build.NewBuilder(name, diffKsArgs.path, @@ -129,6 +133,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithLocalSources(diffKsArgs.localSources), build.WithSingleKustomization(), build.WithInMemoryBuild(diffKsArgs.inMemoryBuild), + build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound), ) } diff --git a/cmd/flux/diff_kustomization_test.go b/cmd/flux/diff_kustomization_test.go index 33cea70e..69577dd0 100644 --- a/cmd/flux/diff_kustomization_test.go +++ b/cmd/flux/diff_kustomization_test.go @@ -48,7 +48,7 @@ func TestDiffKustomization(t *testing.T) { name: "diff nothing deployed", args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false", objectFile: "", - assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"), + assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"), }, { name: "diff with a deployment object", @@ -96,7 +96,7 @@ func TestDiffKustomization(t *testing.T) { name: "diff where kustomization file has multiple objects with the same name", args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml", objectFile: "", - assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"), + assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"), }, { name: "diff with recursive", @@ -138,6 +138,118 @@ func TestDiffKustomization(t *testing.T) { } } +// TestDiffKustomizationNotDeployed tests `flux diff ks` when the Kustomization +// CR does not exist in the cluster but is provided via --kustomization-file. +// Reproduces https://github.com/fluxcd/flux2/issues/5439 +func TestDiffKustomizationNotDeployed(t *testing.T) { + // Use a dedicated namespace with NO setup() -- the Kustomization CR + // intentionally does not exist in the cluster. + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setupTestNamespace(tmpl["fluxns"], t) + + tests := []struct { + name string + args string + assert assertFunc + }{ + { + name: "fails without --ignore-not-found", + args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false " + + "--kustomization-file ./testdata/diff-kustomization/flux-kustomization-local-only.yaml", + assert: assertError("failed to get kustomization object: kustomizations.kustomize.toolkit.fluxcd.io \"podinfo\" not found"), + }, + { + name: "succeeds with --ignore-not-found and --kustomization-file", + args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false " + + "--kustomization-file ./testdata/diff-kustomization/flux-kustomization-local-only.yaml " + + "--ignore-not-found", + assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args + " -n " + tmpl["fluxns"], + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} + +// TestDiffKustomizationTakeOwnership tests `flux diff ks` when taking ownership +// of existing resources on the cluster. A "pre-existing" configmap is applied +// to the cluster, and the kustomization contains a matching configmap; the +// diff should show the labels added by flux +func TestDiffKustomizationTakeOwnership(t *testing.T) { + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setupTestNamespace(tmpl["fluxns"], t) + + b, _ := build.NewBuilder("configmaps", "", build.WithClientConfig(kubeconfigArgs, kubeclientOptions)) + resourceManager, err := b.Manager() + if err != nil { + t.Fatal(err) + } + + // Pre-create the "existing" configmap in the cluster without Flux labels + if _, err := resourceManager.ApplyAll(context.Background(), createObjectFromFile("./testdata/diff-kustomization/existing-configmap.yaml", tmpl, t), ssa.DefaultApplyOptions()); err != nil { + t.Fatal(err) + } + + cmd := cmdTestCase{ + args: "diff kustomization configmaps --path ./testdata/build-kustomization/configmaps --progress-bar=false " + + "--kustomization-file ./testdata/diff-kustomization/flux-kustomization-configmaps.yaml " + + "--ignore-not-found" + + " -n " + tmpl["fluxns"], + assert: assertGoldenFile("./testdata/diff-kustomization/diff-taking-ownership.golden"), + } + cmd.runTestCmd(t) +} + +// TestDiffKustomizationNewNamespaceAndConfigmap runs `flux diff ks` when the +// kustomization creates a new namespace and resources inside it. The server-side +// dry-run cannot resolve resources in a namespace that doesn't exist yet, +// consistent with `kubectl diff --server-side` behavior. +func TestDiffKustomizationNewNamespaceAndConfigmap(t *testing.T) { + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setupTestNamespace(tmpl["fluxns"], t) + + cmd := cmdTestCase{ + args: "diff kustomization new-namespace-and-configmap --path ./testdata/build-kustomization/new-namespace-and-configmap --progress-bar=false " + + "--kustomization-file ./testdata/diff-kustomization/flux-kustomization-new-namespace-and-configmap.yaml " + + "--ignore-not-found" + + " -n " + tmpl["fluxns"], + assert: assertError("ConfigMap/new-ns/app-config not found: namespaces \"new-ns\" not found"), + } + cmd.runTestCmd(t) +} + +// TestDiffKustomizationNewNamespaceOnly runs `flux diff ks` when the +// kustomization creates only a new namespace. The diff should show the +// namespace as created. +func TestDiffKustomizationNewNamespaceOnly(t *testing.T) { + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setupTestNamespace(tmpl["fluxns"], t) + + cmd := cmdTestCase{ + args: "diff kustomization new-namespace-only --path ./testdata/build-kustomization/new-namespace-only --progress-bar=false " + + "--kustomization-file ./testdata/diff-kustomization/flux-kustomization-new-namespace-only.yaml " + + "--ignore-not-found" + + " -n " + tmpl["fluxns"], + assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-namespace-only.golden"), + } + cmd.runTestCmd(t) +} + func createObjectFromFile(objectFile string, templateValues map[string]string, t *testing.T) []*unstructured.Unstructured { buf, err := os.ReadFile(objectFile) if err != nil { diff --git a/cmd/flux/testdata/build-kustomization/configmaps/existing.yaml b/cmd/flux/testdata/build-kustomization/configmaps/existing.yaml new file mode 100644 index 00000000..e23da200 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/configmaps/existing.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: existing-config + namespace: default +data: + key: value diff --git a/cmd/flux/testdata/build-kustomization/configmaps/kustomization.yaml b/cmd/flux/testdata/build-kustomization/configmaps/kustomization.yaml new file mode 100644 index 00000000..2d84cfd3 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/configmaps/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ./existing.yaml +- ./new.yaml diff --git a/cmd/flux/testdata/build-kustomization/configmaps/new.yaml b/cmd/flux/testdata/build-kustomization/configmaps/new.yaml new file mode 100644 index 00000000..e33bb513 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/configmaps/new.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: new-config + namespace: default +data: + key: value diff --git a/cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden b/cmd/flux/testdata/diff-kustomization/diff-new-kustomization.golden similarity index 100% rename from cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden rename to cmd/flux/testdata/diff-kustomization/diff-new-kustomization.golden diff --git a/cmd/flux/testdata/diff-kustomization/diff-taking-ownership.golden b/cmd/flux/testdata/diff-kustomization/diff-taking-ownership.golden new file mode 100644 index 00000000..50bb1819 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/diff-taking-ownership.golden @@ -0,0 +1,9 @@ +► ConfigMap/default/existing-config drifted + +metadata ++ one map entry added: + labels: + kustomize.toolkit.fluxcd.io/name: configmaps + kustomize.toolkit.fluxcd.io/namespace: + +► ConfigMap/default/new-config created diff --git a/cmd/flux/testdata/diff-kustomization/existing-configmap.yaml b/cmd/flux/testdata/diff-kustomization/existing-configmap.yaml new file mode 100644 index 00000000..e23da200 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/existing-configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: existing-config + namespace: default +data: + key: value diff --git a/cmd/flux/testdata/diff-kustomization/flux-kustomization-configmaps.yaml b/cmd/flux/testdata/diff-kustomization/flux-kustomization-configmaps.yaml new file mode 100644 index 00000000..e516bf65 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/flux-kustomization-configmaps.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: configmaps +spec: + interval: 5m0s + path: ./kustomize + force: true + prune: true + sourceRef: + kind: GitRepository + name: configmaps + targetNamespace: default diff --git a/cmd/flux/testdata/diff-kustomization/flux-kustomization-local-only.yaml b/cmd/flux/testdata/diff-kustomization/flux-kustomization-local-only.yaml new file mode 100644 index 00000000..1c9a215c --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/flux-kustomization-local-only.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: podinfo +spec: + interval: 5m0s + path: ./kustomize + force: true + prune: true + sourceRef: + kind: GitRepository + name: podinfo + targetNamespace: default diff --git a/internal/build/build.go b/internal/build/build.go index 6f1b44c5..7010c3b6 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -146,8 +146,9 @@ type Builder struct { strictSubst bool recursive bool localSources map[string]string - // diff needs to handle kustomizations one by one + // diff needs to handle kustomizations one by one, and opt-in to ignore kustomizations missing on cluster singleKustomization bool + ignoreNotFound bool fsBackend fsBackend } @@ -235,6 +236,15 @@ func WithStrictSubstitute(strictSubstitute bool) BuilderOptionFunc { } } +// WithIgnoreNotFound ignores NotFound errors from the cluster kustomization +// lookup as long as a local kustomization file is provided +func WithIgnoreNotFound(ignore bool) BuilderOptionFunc { + return func(b *Builder) error { + b.ignoreNotFound = ignore + return nil + } +} + // WithIgnore sets ignore field func WithIgnore(ignore []string) BuilderOptionFunc { return func(b *Builder) error { @@ -345,6 +355,10 @@ func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, er return nil, fmt.Errorf("kustomization file is required for dry-run") } + if b.ignoreNotFound && b.kustomizationFile == "" { + return nil, fmt.Errorf("kustomization file is required when assuming new kustomizations") + } + if !b.dryRun && b.client == nil { return nil, fmt.Errorf("client is required for live run") } @@ -443,10 +457,11 @@ func (b *Builder) build() (m resmap.ResMap, err error) { } else { liveKus, err = b.getKustomization(ctx) if err != nil { - if !apierrors.IsNotFound(err) || b.kustomization == nil { + unknownError := !apierrors.IsNotFound(err) + hasLocalFallback := b.kustomization != nil || b.ignoreNotFound + if unknownError || !hasLocalFallback { return nil, fmt.Errorf("failed to get kustomization object: %w", err) } - // use provided Kustomization liveKus = b.kustomization } } From e9bcccfede649ebc4025c3e1492ae62a98e935e3 Mon Sep 17 00:00:00 2001 From: rycli Date: Mon, 13 Apr 2026 12:54:58 +0200 Subject: [PATCH 34/48] test: add 'flux diff ks' tests for cases that involve new namespaces Signed-off-by: rycli Assisted-by: claude-code/claude-opus-4-6 --- .../new-namespace-and-configmap/configmap.yaml | 7 +++++++ .../new-namespace-and-configmap/kustomization.yaml | 5 +++++ .../new-namespace-and-configmap/namespace.yaml | 4 ++++ .../new-namespace-only/kustomization.yaml | 4 ++++ .../new-namespace-only/namespace.yaml | 4 ++++ .../diff-new-namespace-only.golden | 1 + ...x-kustomization-new-namespace-and-configmap.yaml | 13 +++++++++++++ .../flux-kustomization-new-namespace-only.yaml | 13 +++++++++++++ 8 files changed, 51 insertions(+) create mode 100644 cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/configmap.yaml create mode 100644 cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/kustomization.yaml create mode 100644 cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/namespace.yaml create mode 100644 cmd/flux/testdata/build-kustomization/new-namespace-only/kustomization.yaml create mode 100644 cmd/flux/testdata/build-kustomization/new-namespace-only/namespace.yaml create mode 100644 cmd/flux/testdata/diff-kustomization/diff-new-namespace-only.golden create mode 100644 cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-and-configmap.yaml create mode 100644 cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-only.yaml diff --git a/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/configmap.yaml b/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/configmap.yaml new file mode 100644 index 00000000..078b4c15 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config + namespace: new-ns +data: + key: value diff --git a/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/kustomization.yaml b/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/kustomization.yaml new file mode 100644 index 00000000..491d68b1 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ./namespace.yaml +- ./configmap.yaml diff --git a/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/namespace.yaml b/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/namespace.yaml new file mode 100644 index 00000000..4755a448 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/new-namespace-and-configmap/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: new-ns diff --git a/cmd/flux/testdata/build-kustomization/new-namespace-only/kustomization.yaml b/cmd/flux/testdata/build-kustomization/new-namespace-only/kustomization.yaml new file mode 100644 index 00000000..73029636 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/new-namespace-only/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ./namespace.yaml diff --git a/cmd/flux/testdata/build-kustomization/new-namespace-only/namespace.yaml b/cmd/flux/testdata/build-kustomization/new-namespace-only/namespace.yaml new file mode 100644 index 00000000..4755a448 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/new-namespace-only/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: new-ns diff --git a/cmd/flux/testdata/diff-kustomization/diff-new-namespace-only.golden b/cmd/flux/testdata/diff-kustomization/diff-new-namespace-only.golden new file mode 100644 index 00000000..77493d65 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/diff-new-namespace-only.golden @@ -0,0 +1 @@ +► Namespace/new-ns created diff --git a/cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-and-configmap.yaml b/cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-and-configmap.yaml new file mode 100644 index 00000000..e045b495 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-and-configmap.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: new-namespace-and-configmap +spec: + interval: 5m0s + path: ./kustomize + force: true + prune: true + sourceRef: + kind: GitRepository + name: new-namespace-and-configmap diff --git a/cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-only.yaml b/cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-only.yaml new file mode 100644 index 00000000..56401cf8 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/flux-kustomization-new-namespace-only.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: new-namespace-only +spec: + interval: 5m0s + path: ./kustomize + force: true + prune: true + sourceRef: + kind: GitRepository + name: new-namespace-only From aa608bb769d5e149772f3352eb4cd7ae8c80c6e2 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 30 Mar 2026 11:51:21 +0300 Subject: [PATCH 35/48] Implement plugin catalog and discovery system Signed-off-by: Stefan Prodan --- internal/plugin/catalog.go | 213 +++++++++++++++++++ internal/plugin/catalog_test.go | 239 +++++++++++++++++++++ internal/plugin/completion.go | 75 +++++++ internal/plugin/completion_test.go | 80 +++++++ internal/plugin/discovery.go | 195 +++++++++++++++++ internal/plugin/discovery_test.go | 302 ++++++++++++++++++++++++++ internal/plugin/exec_unix.go | 30 +++ internal/plugin/exec_windows.go | 42 ++++ internal/plugin/install.go | 235 ++++++++++++++++++++ internal/plugin/install_test.go | 331 +++++++++++++++++++++++++++++ internal/plugin/update.go | 85 ++++++++ internal/plugin/update_test.go | 153 +++++++++++++ 12 files changed, 1980 insertions(+) create mode 100644 internal/plugin/catalog.go create mode 100644 internal/plugin/catalog_test.go create mode 100644 internal/plugin/completion.go create mode 100644 internal/plugin/completion_test.go create mode 100644 internal/plugin/discovery.go create mode 100644 internal/plugin/discovery_test.go create mode 100644 internal/plugin/exec_unix.go create mode 100644 internal/plugin/exec_windows.go create mode 100644 internal/plugin/install.go create mode 100644 internal/plugin/install_test.go create mode 100644 internal/plugin/update.go create mode 100644 internal/plugin/update_test.go diff --git a/internal/plugin/catalog.go b/internal/plugin/catalog.go new file mode 100644 index 00000000..84f673d2 --- /dev/null +++ b/internal/plugin/catalog.go @@ -0,0 +1,213 @@ +/* +Copyright 2026 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 plugin + +import ( + "fmt" + "io" + "net/http" + "time" + + "github.com/hashicorp/go-retryablehttp" + "sigs.k8s.io/yaml" +) + +const ( + defaultCatalogBase = "https://raw.githubusercontent.com/fluxcd/plugins/main/" + envCatalogBase = "FLUXCD_PLUGIN_CATALOG" + + pluginAPIVersion = "cli.fluxcd.io/v1beta1" + pluginKind = "Plugin" + catalogKind = "PluginCatalog" +) + +// PluginManifest represents a single plugin's manifest from the catalog. +type PluginManifest struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Name string `json:"name"` + Description string `json:"description"` + Homepage string `json:"homepage,omitempty"` + Source string `json:"source,omitempty"` + Bin string `json:"bin"` + Versions []PluginVersion `json:"versions"` +} + +// PluginVersion represents a version entry in a plugin manifest. +type PluginVersion struct { + Version string `json:"version"` + Platforms []PluginPlatform `json:"platforms"` +} + +// PluginPlatform represents a platform-specific binary entry. +type PluginPlatform struct { + OS string `json:"os"` + Arch string `json:"arch"` + URL string `json:"url"` + Checksum string `json:"checksum"` +} + +// PluginCatalog represents the generated catalog.yaml file. +type PluginCatalog struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Plugins []CatalogEntry `json:"plugins"` +} + +// CatalogEntry is a single entry in the plugin catalog. +type CatalogEntry struct { + Name string `json:"name"` + Description string `json:"description"` + Homepage string `json:"homepage,omitempty"` + Source string `json:"source,omitempty"` + License string `json:"license,omitempty"` +} + +// Receipt records what was installed for a plugin. +type Receipt struct { + Name string `json:"name"` + Version string `json:"version"` + InstalledAt string `json:"installedAt"` + Platform PluginPlatform `json:"platform"` +} + +// CatalogClient fetches plugin manifests and catalogs from a remote URL. +type CatalogClient struct { + BaseURL string + HTTPClient *http.Client + GetEnv func(key string) string +} + +// NewCatalogClient returns a CatalogClient with production defaults. +func NewCatalogClient() *CatalogClient { + return &CatalogClient{ + BaseURL: defaultCatalogBase, + HTTPClient: newHTTPClient(30 * time.Second), + GetEnv: func(key string) string { return "" }, + } +} + +// baseURL returns the effective catalog base URL. +func (c *CatalogClient) baseURL() string { + if env := c.GetEnv(envCatalogBase); env != "" { + return env + } + return c.BaseURL +} + +// FetchManifest fetches a single plugin manifest from the catalog. +func (c *CatalogClient) FetchManifest(name string) (*PluginManifest, error) { + url := c.baseURL() + "plugins/" + name + ".yaml" + body, err := c.fetch(url) + if err != nil { + return nil, fmt.Errorf("plugin %q not found in catalog", name) + } + + var manifest PluginManifest + if err := yaml.Unmarshal(body, &manifest); err != nil { + return nil, fmt.Errorf("failed to parse plugin manifest for %q: %w", name, err) + } + + if manifest.APIVersion != pluginAPIVersion { + return nil, fmt.Errorf("plugin %q has unsupported apiVersion %q (expected %q)", name, manifest.APIVersion, pluginAPIVersion) + } + if manifest.Kind != pluginKind { + return nil, fmt.Errorf("plugin %q has unexpected kind %q (expected %q)", name, manifest.Kind, pluginKind) + } + + return &manifest, nil +} + +// FetchCatalog fetches the generated catalog.yaml. +func (c *CatalogClient) FetchCatalog() (*PluginCatalog, error) { + url := c.baseURL() + "catalog.yaml" + body, err := c.fetch(url) + if err != nil { + return nil, fmt.Errorf("failed to fetch plugin catalog: %w", err) + } + + var catalog PluginCatalog + if err := yaml.Unmarshal(body, &catalog); err != nil { + return nil, fmt.Errorf("failed to parse plugin catalog: %w", err) + } + + if catalog.APIVersion != pluginAPIVersion { + return nil, fmt.Errorf("plugin catalog has unsupported apiVersion %q (expected %q)", catalog.APIVersion, pluginAPIVersion) + } + if catalog.Kind != catalogKind { + return nil, fmt.Errorf("plugin catalog has unexpected kind %q (expected %q)", catalog.Kind, catalogKind) + } + + return &catalog, nil +} + +const maxResponseBytes = 10 << 20 // 10 MiB + +func (c *CatalogClient) fetch(url string) ([]byte, error) { + resp, err := c.HTTPClient.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP %d from %s", resp.StatusCode, url) + } + + return io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes)) +} + +// newHTTPClient returns a retrying HTTP client with the given timeout. +func newHTTPClient(timeout time.Duration) *http.Client { + rc := retryablehttp.NewClient() + rc.RetryMax = 3 + rc.Logger = nil + c := rc.StandardClient() + c.Timeout = timeout + return c +} + +// ResolveVersion finds the requested version in the manifest. +// If version is empty, returns the first (latest) version. +func ResolveVersion(manifest *PluginManifest, version string) (*PluginVersion, error) { + if len(manifest.Versions) == 0 { + return nil, fmt.Errorf("plugin %q has no versions", manifest.Name) + } + + if version == "" { + return &manifest.Versions[0], nil + } + + for i := range manifest.Versions { + if manifest.Versions[i].Version == version { + return &manifest.Versions[i], nil + } + } + + return nil, fmt.Errorf("version %q not found for plugin %q", version, manifest.Name) +} + +// ResolvePlatform finds the platform entry matching the given OS and arch. +func ResolvePlatform(pv *PluginVersion, goos, goarch string) (*PluginPlatform, error) { + for i := range pv.Platforms { + if pv.Platforms[i].OS == goos && pv.Platforms[i].Arch == goarch { + return &pv.Platforms[i], nil + } + } + + return nil, fmt.Errorf("no binary for %s/%s", goos, goarch) +} diff --git a/internal/plugin/catalog_test.go b/internal/plugin/catalog_test.go new file mode 100644 index 00000000..37c4d34c --- /dev/null +++ b/internal/plugin/catalog_test.go @@ -0,0 +1,239 @@ +/* +Copyright 2026 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 plugin + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestFetchManifest(t *testing.T) { + manifest := ` +apiVersion: cli.fluxcd.io/v1beta1 +kind: Plugin +name: operator +description: Flux Operator CLI +bin: flux-operator +versions: + - version: 0.45.0 + platforms: + - os: linux + arch: amd64 + url: https://example.com/flux-operator_0.45.0_linux_amd64.tar.gz + checksum: sha256:abc123 +` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/plugins/operator.yaml" { + w.Write([]byte(manifest)) + return + } + http.NotFound(w, r) + })) + defer server.Close() + + client := &CatalogClient{ + BaseURL: server.URL + "/", + HTTPClient: server.Client(), + GetEnv: func(key string) string { return "" }, + } + + m, err := client.FetchManifest("operator") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if m.Name != "operator" { + t.Errorf("expected name 'operator', got %q", m.Name) + } + if m.Bin != "flux-operator" { + t.Errorf("expected bin 'flux-operator', got %q", m.Bin) + } + if len(m.Versions) != 1 { + t.Fatalf("expected 1 version, got %d", len(m.Versions)) + } + if m.Versions[0].Version != "0.45.0" { + t.Errorf("expected version '0.45.0', got %q", m.Versions[0].Version) + } +} + +func TestFetchManifestNotFound(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.NotFound(w, r) + })) + defer server.Close() + + client := &CatalogClient{ + BaseURL: server.URL + "/", + HTTPClient: server.Client(), + GetEnv: func(key string) string { return "" }, + } + + _, err := client.FetchManifest("nonexistent") + if err == nil { + t.Fatal("expected error, got nil") + } +} + +func TestFetchCatalog(t *testing.T) { + catalog := ` +apiVersion: cli.fluxcd.io/v1beta1 +kind: PluginCatalog +plugins: + - name: operator + description: Flux Operator CLI + homepage: https://fluxoperator.dev/ + source: https://github.com/controlplaneio-fluxcd/flux-operator + license: AGPL-3.0 + - name: schema + description: CRD schemas + homepage: https://example.com/ + source: https://github.com/example/flux-schema + license: Apache-2.0 +` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/catalog.yaml" { + w.Write([]byte(catalog)) + return + } + http.NotFound(w, r) + })) + defer server.Close() + + client := &CatalogClient{ + BaseURL: server.URL + "/", + HTTPClient: server.Client(), + GetEnv: func(key string) string { return "" }, + } + + c, err := client.FetchCatalog() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(c.Plugins) != 2 { + t.Fatalf("expected 2 plugins, got %d", len(c.Plugins)) + } + if c.Plugins[0].Name != "operator" { + t.Errorf("expected name 'operator', got %q", c.Plugins[0].Name) + } + if c.Plugins[1].Name != "schema" { + t.Errorf("expected name 'schema', got %q", c.Plugins[1].Name) + } +} + +func TestCatalogEnvOverride(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/custom/catalog.yaml" { + w.Write([]byte(`apiVersion: cli.fluxcd.io/v1beta1 +kind: PluginCatalog +plugins: [] +`)) + return + } + http.NotFound(w, r) + })) + defer server.Close() + + client := &CatalogClient{ + BaseURL: "https://should-not-be-used/", + HTTPClient: server.Client(), + GetEnv: func(key string) string { + if key == envCatalogBase { + return server.URL + "/custom/" + } + return "" + }, + } + + c, err := client.FetchCatalog() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(c.Plugins) != 0 { + t.Fatalf("expected 0 plugins, got %d", len(c.Plugins)) + } +} + +func TestResolveVersion(t *testing.T) { + manifest := &PluginManifest{ + Name: "operator", + Versions: []PluginVersion{ + {Version: "0.45.0"}, + {Version: "0.44.0"}, + }, + } + + t.Run("latest", func(t *testing.T) { + v, err := ResolveVersion(manifest, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if v.Version != "0.45.0" { + t.Errorf("expected '0.45.0', got %q", v.Version) + } + }) + + t.Run("specific", func(t *testing.T) { + v, err := ResolveVersion(manifest, "0.44.0") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if v.Version != "0.44.0" { + t.Errorf("expected '0.44.0', got %q", v.Version) + } + }) + + t.Run("not found", func(t *testing.T) { + _, err := ResolveVersion(manifest, "0.99.0") + if err == nil { + t.Fatal("expected error, got nil") + } + }) + + t.Run("no versions", func(t *testing.T) { + _, err := ResolveVersion(&PluginManifest{Name: "empty"}, "") + if err == nil { + t.Fatal("expected error, got nil") + } + }) +} + +func TestResolvePlatform(t *testing.T) { + pv := &PluginVersion{ + Version: "0.45.0", + Platforms: []PluginPlatform{ + {OS: "darwin", Arch: "arm64", URL: "https://example.com/darwin_arm64.tar.gz"}, + {OS: "linux", Arch: "amd64", URL: "https://example.com/linux_amd64.tar.gz"}, + }, + } + + t.Run("found", func(t *testing.T) { + p, err := ResolvePlatform(pv, "darwin", "arm64") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if p.OS != "darwin" || p.Arch != "arm64" { + t.Errorf("unexpected platform: %s/%s", p.OS, p.Arch) + } + }) + + t.Run("not found", func(t *testing.T) { + _, err := ResolvePlatform(pv, "windows", "amd64") + if err == nil { + t.Fatal("expected error, got nil") + } + }) +} diff --git a/internal/plugin/completion.go b/internal/plugin/completion.go new file mode 100644 index 00000000..73e31d84 --- /dev/null +++ b/internal/plugin/completion.go @@ -0,0 +1,75 @@ +/* +Copyright 2026 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 plugin + +import ( + "os/exec" + "strconv" + "strings" + + "github.com/spf13/cobra" +) + +// commandFunc is an alias to allow DI in tests. +var commandFunc = exec.Command + +// CompleteFunc returns a ValidArgsFunction that delegates completion +// to the plugin binary via Cobra's __complete protocol. +func CompleteFunc(pluginPath string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + completeArgs := append([]string{"__complete"}, args...) + completeArgs = append(completeArgs, toComplete) + + out, err := commandFunc(pluginPath, completeArgs...).Output() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + return parseCompletionOutput(string(out)) + } +} + +// parseCompletionOutput parses Cobra's __complete output format. +// Each line is a completion, last line is :. +func parseCompletionOutput(out string) ([]string, cobra.ShellCompDirective) { + out = strings.TrimRight(out, "\n") + if out == "" { + return nil, cobra.ShellCompDirectiveError + } + lines := strings.Split(out, "\n") + + // Last line is the directive in format ":N" + lastLine := lines[len(lines)-1] + completions := lines[:len(lines)-1] + + directive := cobra.ShellCompDirectiveDefault + if strings.HasPrefix(lastLine, ":") { + if val, err := strconv.Atoi(lastLine[1:]); err == nil { + directive = cobra.ShellCompDirective(val) + } + } + + var results []string + for _, c := range completions { + if c == "" { + continue + } + results = append(results, c) + } + + return results, directive +} diff --git a/internal/plugin/completion_test.go b/internal/plugin/completion_test.go new file mode 100644 index 00000000..fb96dd56 --- /dev/null +++ b/internal/plugin/completion_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2026 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 plugin + +import ( + "testing" + + "github.com/spf13/cobra" +) + +func TestParseCompletionOutput(t *testing.T) { + tests := []struct { + name string + input string + expectedCompletions []string + expectedDirective cobra.ShellCompDirective + }{ + { + name: "standard output", + input: "instance\nrset\nrsip\nall\n:4\n", + expectedCompletions: []string{"instance", "rset", "rsip", "all"}, + expectedDirective: cobra.ShellCompDirective(4), + }, + { + name: "default directive", + input: "foo\nbar\n:0\n", + expectedCompletions: []string{"foo", "bar"}, + expectedDirective: cobra.ShellCompDirectiveDefault, + }, + { + name: "with descriptions", + input: "get\tGet resources\nbuild\tBuild resources\n:4\n", + expectedCompletions: []string{"get\tGet resources", "build\tBuild resources"}, + expectedDirective: cobra.ShellCompDirective(4), + }, + { + name: "empty completions", + input: ":4\n", + expectedCompletions: nil, + expectedDirective: cobra.ShellCompDirective(4), + }, + { + name: "empty input", + input: "", + expectedCompletions: nil, + expectedDirective: cobra.ShellCompDirectiveError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + completions, directive := parseCompletionOutput(tt.input) + if directive != tt.expectedDirective { + t.Errorf("directive: got %d, want %d", directive, tt.expectedDirective) + } + if len(completions) != len(tt.expectedCompletions) { + t.Fatalf("completions count: got %d, want %d", len(completions), len(tt.expectedCompletions)) + } + for i, c := range completions { + if c != tt.expectedCompletions[i] { + t.Errorf("completion[%d]: got %q, want %q", i, c, tt.expectedCompletions[i]) + } + } + }) + } +} diff --git a/internal/plugin/discovery.go b/internal/plugin/discovery.go new file mode 100644 index 00000000..5d750ac7 --- /dev/null +++ b/internal/plugin/discovery.go @@ -0,0 +1,195 @@ +/* +Copyright 2026 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 plugin + +import ( + "os" + "path/filepath" + "runtime" + "strings" +) + +const ( + pluginPrefix = "flux-" + defaultDirName = "plugins" + defaultBaseDir = ".fluxcd" + envPluginDir = "FLUXCD_PLUGINS" +) + +// reservedNames are command names that cannot be used as plugin names. +var reservedNames = map[string]bool{ + "plugin": true, + "help": true, +} + +// Plugin represents a discovered plugin binary. +type Plugin struct { + Name string // e.g., "operator" (derived from "flux-operator") + Path string // absolute path to binary +} + +// Handler discovers and executes plugins. Uses dependency injection +// for testability. +type Handler struct { + ReadDir func(name string) ([]os.DirEntry, error) + Stat func(name string) (os.FileInfo, error) + GetEnv func(key string) string + HomeDir func() (string, error) +} + +// NewHandler returns a Handler with production defaults. +func NewHandler() *Handler { + return &Handler{ + ReadDir: os.ReadDir, + Stat: os.Stat, + GetEnv: os.Getenv, + HomeDir: os.UserHomeDir, + } +} + +// Discover scans the plugin directory for executables matching flux-*. +// It skips builtins, reserved names, directories, non-executable files, +// and broken symlinks. +func (h *Handler) Discover(builtinNames []string) []Plugin { + dir := h.PluginDir() + if dir == "" { + return nil + } + + entries, err := h.ReadDir(dir) + if err != nil { + return nil + } + + builtins := make(map[string]bool, len(builtinNames)) + for _, name := range builtinNames { + builtins[name] = true + } + + var plugins []Plugin + for _, entry := range entries { + name := entry.Name() + if !strings.HasPrefix(name, pluginPrefix) { + continue + } + if entry.IsDir() { + continue + } + + pluginName := pluginNameFromBinary(name) + if pluginName == "" { + continue + } + if reservedNames[pluginName] || builtins[pluginName] { + continue + } + + fullPath := filepath.Join(dir, name) + + // Use Stat to follow symlinks and check the target. + info, err := h.Stat(fullPath) + if err != nil { + // Broken symlink, permission denied, etc. + continue + } + if !info.Mode().IsRegular() { + continue + } + if !isExecutable(info) { + continue + } + + plugins = append(plugins, Plugin{ + Name: pluginName, + Path: fullPath, + }) + } + + return plugins +} + +// PluginDir returns the plugin directory path. If FLUXCD_PLUGINS is set, +// returns that path. Otherwise returns ~/.fluxcd/plugins/. +// Does not create the directory — callers that write (install, update) +// should call EnsurePluginDir first. +func (h *Handler) PluginDir() string { + if dir := h.GetEnv(envPluginDir); dir != "" { + return dir + } + + home, err := h.HomeDir() + if err != nil { + return "" + } + + return filepath.Join(home, defaultBaseDir, defaultDirName) +} + +// EnsurePluginDir creates the plugin directory if it doesn't exist +// and returns the path. Best-effort — ignores mkdir errors for +// read-only filesystems. User-managed directories (via $FLUXCD_PLUGINS) +// are not auto-created. +func (h *Handler) EnsurePluginDir() string { + if envDir := h.GetEnv(envPluginDir); envDir != "" { + return envDir + } + + home, err := h.HomeDir() + if err != nil { + return "" + } + + dir := filepath.Join(home, defaultBaseDir, defaultDirName) + _ = os.MkdirAll(dir, 0o755) + return dir +} + +// pluginNameFromBinary extracts the plugin name from a binary filename. +// "flux-operator" → "operator", "flux-my-tool" → "my-tool". +// Returns empty string for invalid names. +func pluginNameFromBinary(filename string) string { + if !strings.HasPrefix(filename, pluginPrefix) { + return "" + } + + name := strings.TrimPrefix(filename, pluginPrefix) + + // On Windows, strip known extensions. + if runtime.GOOS == "windows" { + for _, ext := range []string{".exe", ".cmd", ".bat"} { + if strings.HasSuffix(strings.ToLower(name), ext) { + name = name[:len(name)-len(ext)] + break + } + } + } + + if name == "" { + return "" + } + + return name +} + +// isExecutable checks if a file has the executable bit set. +// On Windows, this always returns true (executability is determined by extension). +func isExecutable(info os.FileInfo) bool { + if runtime.GOOS == "windows" { + return true + } + return info.Mode().Perm()&0o111 != 0 +} diff --git a/internal/plugin/discovery_test.go b/internal/plugin/discovery_test.go new file mode 100644 index 00000000..83021c08 --- /dev/null +++ b/internal/plugin/discovery_test.go @@ -0,0 +1,302 @@ +/* +Copyright 2026 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 plugin + +import ( + "fmt" + "io/fs" + "os" + "testing" + "time" +) + +// mockDirEntry implements os.DirEntry for testing. +type mockDirEntry struct { + name string + isDir bool + mode fs.FileMode +} + +func (m *mockDirEntry) Name() string { return m.name } +func (m *mockDirEntry) IsDir() bool { return m.isDir } +func (m *mockDirEntry) Type() fs.FileMode { return m.mode } +func (m *mockDirEntry) Info() (fs.FileInfo, error) { return nil, nil } + +// mockFileInfo implements os.FileInfo for testing. +type mockFileInfo struct { + name string + mode fs.FileMode + isDir bool + regular bool +} + +func (m *mockFileInfo) Name() string { return m.name } +func (m *mockFileInfo) Size() int64 { return 0 } +func (m *mockFileInfo) Mode() fs.FileMode { return m.mode } +func (m *mockFileInfo) ModTime() time.Time { return time.Time{} } +func (m *mockFileInfo) IsDir() bool { return m.isDir } +func (m *mockFileInfo) Sys() any { return nil } + +func newTestHandler(entries []os.DirEntry, statResults map[string]*mockFileInfo, envVars map[string]string) *Handler { + return &Handler{ + ReadDir: func(name string) ([]os.DirEntry, error) { + if entries == nil { + return nil, fmt.Errorf("directory not found") + } + return entries, nil + }, + Stat: func(name string) (os.FileInfo, error) { + if info, ok := statResults[name]; ok { + return info, nil + } + return nil, fmt.Errorf("file not found: %s", name) + }, + GetEnv: func(key string) string { + return envVars[key] + }, + HomeDir: func() (string, error) { + return "/home/testuser", nil + }, + } +} + +func TestDiscover(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-operator", mode: 0}, + &mockDirEntry{name: "flux-local", mode: 0}, + } + stats := map[string]*mockFileInfo{ + "/test/plugins/flux-operator": {name: "flux-operator", mode: 0o755}, + "/test/plugins/flux-local": {name: "flux-local", mode: 0o755}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 2 { + t.Fatalf("expected 2 plugins, got %d", len(plugins)) + } + if plugins[0].Name != "operator" { + t.Errorf("expected name 'operator', got %q", plugins[0].Name) + } + if plugins[1].Name != "local" { + t.Errorf("expected name 'local', got %q", plugins[1].Name) + } +} + +func TestDiscoverSkipsBuiltins(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-version", mode: 0}, + &mockDirEntry{name: "flux-get", mode: 0}, + &mockDirEntry{name: "flux-operator", mode: 0}, + } + stats := map[string]*mockFileInfo{ + "/test/plugins/flux-version": {name: "flux-version", mode: 0o755}, + "/test/plugins/flux-get": {name: "flux-get", mode: 0o755}, + "/test/plugins/flux-operator": {name: "flux-operator", mode: 0o755}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover([]string{"version", "get"}) + if len(plugins) != 1 { + t.Fatalf("expected 1 plugin, got %d", len(plugins)) + } + if plugins[0].Name != "operator" { + t.Errorf("expected name 'operator', got %q", plugins[0].Name) + } +} + +func TestDiscoverSkipsReserved(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-plugin", mode: 0}, + &mockDirEntry{name: "flux-help", mode: 0}, + &mockDirEntry{name: "flux-operator", mode: 0}, + } + stats := map[string]*mockFileInfo{ + "/test/plugins/flux-plugin": {name: "flux-plugin", mode: 0o755}, + "/test/plugins/flux-help": {name: "flux-help", mode: 0o755}, + "/test/plugins/flux-operator": {name: "flux-operator", mode: 0o755}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 1 { + t.Fatalf("expected 1 plugin, got %d", len(plugins)) + } + if plugins[0].Name != "operator" { + t.Errorf("expected name 'operator', got %q", plugins[0].Name) + } +} + +func TestDiscoverSkipsNonExecutable(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-noperm", mode: 0}, + } + stats := map[string]*mockFileInfo{ + "/test/plugins/flux-noperm": {name: "flux-noperm", mode: 0o644}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 0 { + t.Fatalf("expected 0 plugins, got %d", len(plugins)) + } +} + +func TestDiscoverSkipsDirectories(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-somedir", isDir: true, mode: fs.ModeDir}, + } + stats := map[string]*mockFileInfo{} + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 0 { + t.Fatalf("expected 0 plugins, got %d", len(plugins)) + } +} + +func TestDiscoverFollowsSymlinks(t *testing.T) { + entries := []os.DirEntry{ + // Symlink entry — Type() returns symlink, but Stat resolves to regular executable. + &mockDirEntry{name: "flux-linked", mode: fs.ModeSymlink}, + } + stats := map[string]*mockFileInfo{ + "/test/plugins/flux-linked": {name: "flux-linked", mode: 0o755}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 1 { + t.Fatalf("expected 1 plugin, got %d", len(plugins)) + } + if plugins[0].Name != "linked" { + t.Errorf("expected name 'linked', got %q", plugins[0].Name) + } +} + +func TestDiscoverDirNotExist(t *testing.T) { + h := newTestHandler(nil, nil, map[string]string{envPluginDir: "/nonexistent"}) + + plugins := h.Discover(nil) + if len(plugins) != 0 { + t.Fatalf("expected 0 plugins, got %d", len(plugins)) + } +} + +func TestDiscoverCustomDir(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-custom", mode: 0}, + } + stats := map[string]*mockFileInfo{ + "/custom/path/flux-custom": {name: "flux-custom", mode: 0o755}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/custom/path"}) + + plugins := h.Discover(nil) + if len(plugins) != 1 { + t.Fatalf("expected 1 plugin, got %d", len(plugins)) + } + if plugins[0].Path != "/custom/path/flux-custom" { + t.Errorf("expected path '/custom/path/flux-custom', got %q", plugins[0].Path) + } +} + +func TestDiscoverSkipsNonFluxPrefix(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "kubectl-foo", mode: 0}, + &mockDirEntry{name: "random-binary", mode: 0}, + &mockDirEntry{name: "flux-operator", mode: 0}, + } + stats := map[string]*mockFileInfo{ + "/test/plugins/flux-operator": {name: "flux-operator", mode: 0o755}, + } + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 1 { + t.Fatalf("expected 1 plugin, got %d", len(plugins)) + } +} + +func TestDiscoverBrokenSymlink(t *testing.T) { + entries := []os.DirEntry{ + &mockDirEntry{name: "flux-broken", mode: fs.ModeSymlink}, + } + // No stat entry for flux-broken — simulates a broken symlink. + stats := map[string]*mockFileInfo{} + h := newTestHandler(entries, stats, map[string]string{envPluginDir: "/test/plugins"}) + + plugins := h.Discover(nil) + if len(plugins) != 0 { + t.Fatalf("expected 0 plugins, got %d", len(plugins)) + } +} + +func TestPluginNameFromBinary(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"flux-operator", "operator"}, + {"flux-my-tool", "my-tool"}, + {"flux-", ""}, + {"notflux-thing", ""}, + {"flux-a", "a"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got := pluginNameFromBinary(tt.input) + if got != tt.expected { + t.Errorf("pluginNameFromBinary(%q) = %q, want %q", tt.input, got, tt.expected) + } + }) + } +} + +func TestPluginDir(t *testing.T) { + t.Run("uses env var", func(t *testing.T) { + h := &Handler{ + GetEnv: func(key string) string { + if key == envPluginDir { + return "/custom/plugins" + } + return "" + }, + HomeDir: func() (string, error) { + return "/home/user", nil + }, + } + dir := h.PluginDir() + if dir != "/custom/plugins" { + t.Errorf("expected '/custom/plugins', got %q", dir) + } + }) + + t.Run("uses default", func(t *testing.T) { + h := &Handler{ + GetEnv: func(key string) string { return "" }, + HomeDir: func() (string, error) { + return "/home/user", nil + }, + } + dir := h.PluginDir() + if dir != "/home/user/.fluxcd/plugins" { + t.Errorf("expected '/home/user/.fluxcd/plugins', got %q", dir) + } + }) +} diff --git a/internal/plugin/exec_unix.go b/internal/plugin/exec_unix.go new file mode 100644 index 00000000..10b09572 --- /dev/null +++ b/internal/plugin/exec_unix.go @@ -0,0 +1,30 @@ +//go:build !windows + +/* +Copyright 2026 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 plugin + +import ( + "os" + "syscall" +) + +// Exec replaces the current process with the plugin binary. +// This is what kubectl does — no signal forwarding or exit code propagation needed. +func Exec(path string, args []string) error { + return syscall.Exec(path, append([]string{path}, args...), os.Environ()) +} diff --git a/internal/plugin/exec_windows.go b/internal/plugin/exec_windows.go new file mode 100644 index 00000000..51e16186 --- /dev/null +++ b/internal/plugin/exec_windows.go @@ -0,0 +1,42 @@ +//go:build windows + +/* +Copyright 2026 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 plugin + +import ( + "os" + "os/exec" +) + +// Exec runs the plugin as a child process with full I/O passthrough. +// Matches kubectl's Windows fallback pattern. +func Exec(path string, args []string) error { + cmd := exec.Command(path, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + err := cmd.Run() + if err == nil { + os.Exit(0) + } + if exitErr, ok := err.(*exec.ExitError); ok { + os.Exit(exitErr.ExitCode()) + } + return err +} diff --git a/internal/plugin/install.go b/internal/plugin/install.go new file mode 100644 index 00000000..4a29e288 --- /dev/null +++ b/internal/plugin/install.go @@ -0,0 +1,235 @@ +/* +Copyright 2026 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 plugin + +import ( + "archive/tar" + "archive/zip" + "compress/gzip" + "crypto/sha256" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "sigs.k8s.io/yaml" +) + +// Installer handles downloading, verifying, and installing plugins. +type Installer struct { + HTTPClient *http.Client +} + +// NewInstaller returns an Installer with production defaults. +func NewInstaller() *Installer { + return &Installer{ + HTTPClient: newHTTPClient(5 * time.Minute), + } +} + +// Install downloads, verifies, extracts, and installs a plugin binary +// to the given plugin directory. +func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *PluginVersion, plat *PluginPlatform) error { + tmpFile, err := os.CreateTemp("", "flux-plugin-*") + if err != nil { + return fmt.Errorf("failed to create temp file: %w", err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + resp, err := inst.HTTPClient.Get(plat.URL) + if err != nil { + return fmt.Errorf("failed to download plugin: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to download plugin: HTTP %d", resp.StatusCode) + } + + hasher := sha256.New() + writer := io.MultiWriter(tmpFile, hasher) + if _, err := io.Copy(writer, resp.Body); err != nil { + return fmt.Errorf("failed to download plugin: %w", err) + } + tmpFile.Close() + + actualChecksum := fmt.Sprintf("sha256:%x", hasher.Sum(nil)) + if actualChecksum != plat.Checksum { + return fmt.Errorf("checksum verification failed (expected: %s, got: %s)", plat.Checksum, actualChecksum) + } + + binName := manifest.Bin + if runtime.GOOS == "windows" { + binName += ".exe" + } + + destName := pluginPrefix + manifest.Name + if runtime.GOOS == "windows" { + destName += ".exe" + } + destPath := filepath.Join(pluginDir, destName) + + if strings.HasSuffix(plat.URL, ".zip") { + err = extractFromZip(tmpFile.Name(), binName, destPath) + } else { + err = extractFromTarGz(tmpFile.Name(), binName, destPath) + } + if err != nil { + return err + } + + receipt := Receipt{ + Name: manifest.Name, + Version: pv.Version, + InstalledAt: time.Now().UTC().Format(time.RFC3339), + Platform: *plat, + } + return writeReceipt(pluginDir, manifest.Name, &receipt) +} + +// Uninstall removes a plugin binary (or symlink) and its receipt from the +// plugin directory. Returns an error if the plugin is not installed. +func Uninstall(pluginDir, name string) error { + binName := pluginPrefix + name + if runtime.GOOS == "windows" { + binName += ".exe" + } + + binPath := filepath.Join(pluginDir, binName) + + // Use Lstat so we detect symlinks without following them. + if _, err := os.Lstat(binPath); os.IsNotExist(err) { + return fmt.Errorf("plugin %q is not installed", name) + } + + if err := os.Remove(binPath); err != nil { + return fmt.Errorf("failed to remove plugin binary: %w", err) + } + + // Receipt is optional (manually installed plugins don't have one). + if err := os.Remove(receiptPath(pluginDir, name)); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove plugin receipt: %w", err) + } + + return nil +} + +// ReadReceipt reads the install receipt for a plugin. +// Returns nil if no receipt exists. +func ReadReceipt(pluginDir, name string) *Receipt { + data, err := os.ReadFile(receiptPath(pluginDir, name)) + if err != nil { + return nil + } + + var receipt Receipt + if err := yaml.Unmarshal(data, &receipt); err != nil { + return nil + } + + return &receipt +} + +func receiptPath(pluginDir, name string) string { + return filepath.Join(pluginDir, pluginPrefix+name+".yaml") +} + +func writeReceipt(pluginDir, name string, receipt *Receipt) error { + data, err := yaml.Marshal(receipt) + if err != nil { + return fmt.Errorf("failed to marshal receipt: %w", err) + } + + return os.WriteFile(receiptPath(pluginDir, name), data, 0o644) +} + +// extractFromTarGz extracts a named file from a tar.gz archive +// and streams it directly to destPath. +func extractFromTarGz(archivePath, targetName, destPath string) error { + f, err := os.Open(archivePath) + if err != nil { + return err + } + defer f.Close() + + gr, err := gzip.NewReader(f) + if err != nil { + return fmt.Errorf("failed to read gzip: %w", err) + } + defer gr.Close() + + tr := tar.NewReader(gr) + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("failed to read tar: %w", err) + } + + if filepath.IsAbs(header.Name) || strings.Contains(header.Name, "..") { + continue + } + if filepath.Base(header.Name) == targetName && header.Typeflag == tar.TypeReg { + return writeStreamToFile(tr, destPath) + } + } + + return fmt.Errorf("binary %q not found in archive", targetName) +} + +// extractFromZip extracts a named file from a zip archive +// and streams it directly to destPath. +func extractFromZip(archivePath, targetName, destPath string) error { + r, err := zip.OpenReader(archivePath) + if err != nil { + return fmt.Errorf("failed to open zip: %w", err) + } + defer r.Close() + + for _, f := range r.File { + if filepath.Base(f.Name) == targetName && !f.FileInfo().IsDir() { + rc, err := f.Open() + if err != nil { + return fmt.Errorf("failed to open %q in zip: %w", targetName, err) + } + defer rc.Close() + return writeStreamToFile(rc, destPath) + } + } + + return fmt.Errorf("binary %q not found in archive", targetName) +} + +func writeStreamToFile(r io.Reader, destPath string) error { + out, err := os.OpenFile(destPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) + if err != nil { + return fmt.Errorf("failed to create %s: %w", destPath, err) + } + + if _, err := io.Copy(out, r); err != nil { + out.Close() + return fmt.Errorf("failed to write plugin binary: %w", err) + } + return out.Close() +} diff --git a/internal/plugin/install_test.go b/internal/plugin/install_test.go new file mode 100644 index 00000000..7b4d4253 --- /dev/null +++ b/internal/plugin/install_test.go @@ -0,0 +1,331 @@ +/* +Copyright 2026 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 plugin + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/sha256" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +// createTestTarGz creates a tar.gz archive containing a single file. +func createTestTarGz(name string, content []byte) ([]byte, error) { + var buf bytes.Buffer + + gw := gzip.NewWriter(&buf) + tw := tar.NewWriter(gw) + + hdr := &tar.Header{ + Name: name, + Mode: 0o755, + Size: int64(len(content)), + } + if err := tw.WriteHeader(hdr); err != nil { + return nil, err + } + if _, err := tw.Write(content); err != nil { + return nil, err + } + + tw.Close() + gw.Close() + return buf.Bytes(), nil +} + +func TestInstall(t *testing.T) { + binaryContent := []byte("#!/bin/sh\necho hello") + archive, err := createTestTarGz("flux-operator", binaryContent) + if err != nil { + t.Fatalf("failed to create test archive: %v", err) + } + + checksum := fmt.Sprintf("sha256:%x", sha256.Sum256(archive)) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(archive) + })) + defer server.Close() + + pluginDir := t.TempDir() + + manifest := &PluginManifest{ + Name: "operator", + Bin: "flux-operator", + } + pv := &PluginVersion{Version: "0.45.0"} + plat := &PluginPlatform{ + OS: "linux", + Arch: "amd64", + URL: server.URL + "/flux-operator_0.45.0_linux_amd64.tar.gz", + Checksum: checksum, + } + + installer := &Installer{HTTPClient: server.Client()} + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + t.Fatalf("install failed: %v", err) + } + + // Verify binary was written. + binPath := filepath.Join(pluginDir, "flux-operator") + data, err := os.ReadFile(binPath) + if err != nil { + t.Fatalf("binary not found: %v", err) + } + if string(data) != string(binaryContent) { + t.Errorf("binary content mismatch") + } + + // Verify receipt was written. + receipt := ReadReceipt(pluginDir, "operator") + if receipt == nil { + t.Fatal("receipt not found") + } + if receipt.Version != "0.45.0" { + t.Errorf("expected version '0.45.0', got %q", receipt.Version) + } + if receipt.Name != "operator" { + t.Errorf("expected name 'operator', got %q", receipt.Name) + } +} + +func TestInstallChecksumMismatch(t *testing.T) { + binaryContent := []byte("#!/bin/sh\necho hello") + archive, err := createTestTarGz("flux-operator", binaryContent) + if err != nil { + t.Fatalf("failed to create test archive: %v", err) + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(archive) + })) + defer server.Close() + + pluginDir := t.TempDir() + + manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} + pv := &PluginVersion{Version: "0.45.0"} + plat := &PluginPlatform{ + OS: "linux", + Arch: "amd64", + URL: server.URL + "/archive.tar.gz", + Checksum: "sha256:0000000000000000000000000000000000000000000000000000000000000000", + } + + installer := &Installer{HTTPClient: server.Client()} + err = installer.Install(pluginDir, manifest, pv, plat) + if err == nil { + t.Fatal("expected checksum error, got nil") + } + if !bytes.Contains([]byte(err.Error()), []byte("checksum verification failed")) { + t.Errorf("expected checksum error, got: %v", err) + } +} + +func TestInstallBinaryNotInArchive(t *testing.T) { + // Archive contains "wrong-name" instead of "flux-operator". + archive, err := createTestTarGz("wrong-name", []byte("content")) + if err != nil { + t.Fatalf("failed to create test archive: %v", err) + } + + checksum := fmt.Sprintf("sha256:%x", sha256.Sum256(archive)) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(archive) + })) + defer server.Close() + + pluginDir := t.TempDir() + + manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} + pv := &PluginVersion{Version: "0.45.0"} + plat := &PluginPlatform{ + OS: "linux", + Arch: "amd64", + URL: server.URL + "/archive.tar.gz", + Checksum: checksum, + } + + installer := &Installer{HTTPClient: server.Client()} + err = installer.Install(pluginDir, manifest, pv, plat) + if err == nil { + t.Fatal("expected error for missing binary, got nil") + } + if !bytes.Contains([]byte(err.Error()), []byte("not found in archive")) { + t.Errorf("expected 'not found in archive' error, got: %v", err) + } +} + +func TestUninstall(t *testing.T) { + pluginDir := t.TempDir() + + // Create fake binary and receipt. + binPath := filepath.Join(pluginDir, "flux-testplugin") + os.WriteFile(binPath, []byte("binary"), 0o755) + receiptPath := filepath.Join(pluginDir, "flux-testplugin.yaml") + os.WriteFile(receiptPath, []byte("name: testplugin"), 0o644) + + if err := Uninstall(pluginDir, "testplugin"); err != nil { + t.Fatalf("uninstall failed: %v", err) + } + + if _, err := os.Stat(binPath); !os.IsNotExist(err) { + t.Error("binary was not removed") + } + if _, err := os.Stat(receiptPath); !os.IsNotExist(err) { + t.Error("receipt was not removed") + } +} + +func TestUninstallNonExistent(t *testing.T) { + pluginDir := t.TempDir() + + err := Uninstall(pluginDir, "nonexistent") + if err == nil { + t.Fatal("expected error for non-existent plugin, got nil") + } + if !strings.Contains(err.Error(), "is not installed") { + t.Errorf("expected 'is not installed' error, got: %v", err) + } +} + +func TestUninstallSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlinks require elevated privileges on Windows") + } + + pluginDir := t.TempDir() + + // Create a real binary and symlink it into the plugin dir. + realBin := filepath.Join(t.TempDir(), "flux-operator") + os.WriteFile(realBin, []byte("real binary"), 0o755) + + linkPath := filepath.Join(pluginDir, "flux-linked") + os.Symlink(realBin, linkPath) + + if err := Uninstall(pluginDir, "linked"); err != nil { + t.Fatalf("uninstall symlink failed: %v", err) + } + + // Symlink should be removed. + if _, err := os.Lstat(linkPath); !os.IsNotExist(err) { + t.Error("symlink was not removed") + } + // Original binary should still exist. + if _, err := os.Stat(realBin); err != nil { + t.Error("original binary was removed — symlink removal should not affect target") + } +} + +func TestUninstallManualBinary(t *testing.T) { + pluginDir := t.TempDir() + + // Manually copied binary with no receipt. + binPath := filepath.Join(pluginDir, "flux-manual") + os.WriteFile(binPath, []byte("binary"), 0o755) + + if err := Uninstall(pluginDir, "manual"); err != nil { + t.Fatalf("uninstall manual binary failed: %v", err) + } + + if _, err := os.Stat(binPath); !os.IsNotExist(err) { + t.Error("binary was not removed") + } +} + +func TestReadReceipt(t *testing.T) { + pluginDir := t.TempDir() + + t.Run("exists", func(t *testing.T) { + receiptData := `name: operator +version: "0.45.0" +installedAt: "2026-03-28T20:05:00Z" +platform: + os: darwin + arch: arm64 + url: https://example.com/archive.tar.gz + checksum: sha256:abc123 +` + os.WriteFile(filepath.Join(pluginDir, "flux-operator.yaml"), []byte(receiptData), 0o644) + + receipt := ReadReceipt(pluginDir, "operator") + if receipt == nil { + t.Fatal("expected receipt, got nil") + } + if receipt.Version != "0.45.0" { + t.Errorf("expected version '0.45.0', got %q", receipt.Version) + } + if receipt.Platform.OS != "darwin" { + t.Errorf("expected OS 'darwin', got %q", receipt.Platform.OS) + } + }) + + t.Run("not exists", func(t *testing.T) { + receipt := ReadReceipt(pluginDir, "nonexistent") + if receipt != nil { + t.Error("expected nil receipt") + } + }) +} + +func TestExtractFromTarGz(t *testing.T) { + content := []byte("test binary content") + archive, err := createTestTarGz("flux-operator", content) + if err != nil { + t.Fatalf("failed to create archive: %v", err) + } + + tmpFile := filepath.Join(t.TempDir(), "test.tar.gz") + os.WriteFile(tmpFile, archive, 0o644) + + destPath := filepath.Join(t.TempDir(), "flux-operator") + if err := extractFromTarGz(tmpFile, "flux-operator", destPath); err != nil { + t.Fatalf("extract failed: %v", err) + } + data, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("failed to read extracted file: %v", err) + } + if string(data) != string(content) { + t.Errorf("content mismatch: got %q, want %q", string(data), string(content)) + } +} + +func TestExtractFromTarGzNotFound(t *testing.T) { + archive, err := createTestTarGz("other-binary", []byte("content")) + if err != nil { + t.Fatalf("failed to create archive: %v", err) + } + + tmpFile := filepath.Join(t.TempDir(), "test.tar.gz") + os.WriteFile(tmpFile, archive, 0o644) + + destPath := filepath.Join(t.TempDir(), "flux-operator") + err = extractFromTarGz(tmpFile, "flux-operator", destPath) + if err == nil { + t.Fatal("expected error, got nil") + } +} diff --git a/internal/plugin/update.go b/internal/plugin/update.go new file mode 100644 index 00000000..15a02aab --- /dev/null +++ b/internal/plugin/update.go @@ -0,0 +1,85 @@ +/* +Copyright 2026 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 plugin + +const ( + SkipReasonManual = "manually installed" + SkipReasonUpToDate = "already up to date" +) + +// UpdateResult represents the outcome of updating a single plugin. +// When an update is available, Manifest, Version and Platform are +// populated so the caller can install without re-fetching or re-resolving. +type UpdateResult struct { + Name string + FromVersion string + ToVersion string + Skipped bool + SkipReason string + Manifest *PluginManifest + Version *PluginVersion + Platform *PluginPlatform + Err error +} + +// CheckUpdate compares the installed version against the latest in the catalog. +// Returns an UpdateResult describing what should happen. When an update is +// available, Manifest is populated so the caller can install without re-fetching. +func CheckUpdate(pluginDir string, name string, catalog *CatalogClient, goos, goarch string) UpdateResult { + receipt := ReadReceipt(pluginDir, name) + if receipt == nil { + return UpdateResult{ + Name: name, + Skipped: true, + SkipReason: SkipReasonManual, + } + } + + manifest, err := catalog.FetchManifest(name) + if err != nil { + return UpdateResult{Name: name, Err: err} + } + + latest, err := ResolveVersion(manifest, "") + if err != nil { + return UpdateResult{Name: name, Err: err} + } + + if receipt.Version == latest.Version { + return UpdateResult{ + Name: name, + FromVersion: receipt.Version, + ToVersion: latest.Version, + Skipped: true, + SkipReason: SkipReasonUpToDate, + } + } + + plat, err := ResolvePlatform(latest, goos, goarch) + if err != nil { + return UpdateResult{Name: name, Err: err} + } + + return UpdateResult{ + Name: name, + FromVersion: receipt.Version, + ToVersion: latest.Version, + Manifest: manifest, + Version: latest, + Platform: plat, + } +} diff --git a/internal/plugin/update_test.go b/internal/plugin/update_test.go new file mode 100644 index 00000000..53c1fc53 --- /dev/null +++ b/internal/plugin/update_test.go @@ -0,0 +1,153 @@ +/* +Copyright 2026 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 plugin + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" +) + +func TestCheckUpdateUpToDate(t *testing.T) { + manifest := ` +apiVersion: cli.fluxcd.io/v1beta1 +kind: Plugin +name: operator +bin: flux-operator +versions: + - version: 0.45.0 + platforms: + - os: linux + arch: amd64 + url: https://example.com/archive.tar.gz + checksum: sha256:abc123 +` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(manifest)) + })) + defer server.Close() + + pluginDir := t.TempDir() + + // Write receipt with same version. + receiptData := `name: operator +version: "0.45.0" +installedAt: "2026-03-28T20:05:00Z" +platform: + os: linux + arch: amd64 +` + os.WriteFile(filepath.Join(pluginDir, "flux-operator.yaml"), []byte(receiptData), 0o644) + + catalog := &CatalogClient{ + BaseURL: server.URL + "/", + HTTPClient: server.Client(), + GetEnv: func(key string) string { return "" }, + } + + result := CheckUpdate(pluginDir, "operator", catalog, "linux", "amd64") + if result.Err != nil { + t.Fatalf("unexpected error: %v", result.Err) + } + if !result.Skipped { + t.Error("expected skipped=true") + } + if result.SkipReason != SkipReasonUpToDate { + t.Errorf("expected %q, got %q", SkipReasonUpToDate, result.SkipReason) + } +} + +func TestCheckUpdateAvailable(t *testing.T) { + manifest := ` +apiVersion: cli.fluxcd.io/v1beta1 +kind: Plugin +name: operator +bin: flux-operator +versions: + - version: 0.46.0 + platforms: + - os: linux + arch: amd64 + url: https://example.com/archive.tar.gz + checksum: sha256:abc123 + - version: 0.45.0 + platforms: + - os: linux + arch: amd64 + url: https://example.com/archive.tar.gz + checksum: sha256:def456 +` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(manifest)) + })) + defer server.Close() + + pluginDir := t.TempDir() + + receiptData := `name: operator +version: "0.45.0" +installedAt: "2026-03-28T20:05:00Z" +platform: + os: linux + arch: amd64 +` + os.WriteFile(filepath.Join(pluginDir, "flux-operator.yaml"), []byte(receiptData), 0o644) + + catalog := &CatalogClient{ + BaseURL: server.URL + "/", + HTTPClient: server.Client(), + GetEnv: func(key string) string { return "" }, + } + + result := CheckUpdate(pluginDir, "operator", catalog, "linux", "amd64") + if result.Err != nil { + t.Fatalf("unexpected error: %v", result.Err) + } + if result.Skipped { + t.Error("expected skipped=false") + } + if result.FromVersion != "0.45.0" { + t.Errorf("expected from '0.45.0', got %q", result.FromVersion) + } + if result.ToVersion != "0.46.0" { + t.Errorf("expected to '0.46.0', got %q", result.ToVersion) + } +} + +func TestCheckUpdateManualInstall(t *testing.T) { + pluginDir := t.TempDir() + + // No receipt — manually installed. + catalog := &CatalogClient{ + BaseURL: "https://example.com/", + HTTPClient: http.DefaultClient, + GetEnv: func(key string) string { return "" }, + } + + result := CheckUpdate(pluginDir, "operator", catalog, "linux", "amd64") + if result.Err != nil { + t.Fatalf("unexpected error: %v", result.Err) + } + if !result.Skipped { + t.Error("expected skipped=true") + } + if result.SkipReason != SkipReasonManual { + t.Errorf("expected 'manually installed', got %q", result.SkipReason) + } +} From e45e46211b9a2b4159ec84bb920aeb09202cf813 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 30 Mar 2026 11:52:01 +0300 Subject: [PATCH 36/48] Replace yacspin with briandowns/spinner for progress indication Signed-off-by: Stefan Prodan --- go.mod | 4 ++-- go.sum | 4 ++-- internal/build/build.go | 42 ++++++++--------------------------------- internal/build/diff.go | 6 +++--- 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 5563d076..bdc6f6b3 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/ProtonMail/go-crypto v1.3.0 + github.com/briandowns/spinner v1.23.2 github.com/cyphar/filepath-securejoin v0.6.1 github.com/distribution/distribution/v3 v3.1.0 github.com/fluxcd/cli-utils v0.37.2-flux.1 @@ -40,6 +41,7 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.20.7 github.com/hashicorp/go-cleanhttp v0.5.2 + github.com/hashicorp/go-retryablehttp v0.7.8 github.com/homeport/dyff v1.10.2 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/manifoldco/promptui v0.9.0 @@ -49,7 +51,6 @@ require ( github.com/onsi/gomega v1.39.1 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/spf13/cobra v1.10.2 - github.com/theckman/yacspin v0.13.12 golang.org/x/crypto v0.50.0 golang.org/x/term v0.42.0 golang.org/x/text v0.36.0 @@ -162,7 +163,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect diff --git a/go.sum b/go.sum index 843d2366..30e6b07e 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w= +github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= @@ -538,8 +540,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= -github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4= -github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg= github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= diff --git a/internal/build/build.go b/internal/build/build.go index 7010c3b6..17cfb65e 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -30,7 +30,7 @@ import ( "sync" "time" - "github.com/theckman/yacspin" + "github.com/briandowns/spinner" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -141,7 +141,7 @@ type Builder struct { action kustomize.Action kustomization *kustomizev1.Kustomization timeout time.Duration - spinner *yacspin.Spinner + spinner *spinner.Spinner dryRun bool strictSubst bool recursive bool @@ -173,22 +173,9 @@ func WithTimeout(timeout time.Duration) BuilderOptionFunc { func WithProgressBar() BuilderOptionFunc { return func(b *Builder) error { - // Add a spinner - cfg := yacspin.Config{ - Frequency: 100 * time.Millisecond, - CharSet: yacspin.CharSets[59], - Suffix: "Kustomization diffing...", - SuffixAutoColon: true, - Message: spinnerDryRunMessage, - StopCharacter: "✓", - StopColors: []string{"fgGreen"}, - } - spinner, err := yacspin.New(cfg) - if err != nil { - return fmt.Errorf("failed to create spinner: %w", err) - } - b.spinner = spinner - + s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) + s.Suffix = " Kustomization diffing... " + spinnerDryRunMessage + b.spinner = s return nil } } @@ -296,7 +283,7 @@ func withClientConfigFrom(in *Builder) BuilderOptionFunc { } } -// withClientConfigFrom copies spinner field +// withSpinnerFrom copies the spinner field from another Builder. func withSpinnerFrom(in *Builder) BuilderOptionFunc { return func(b *Builder) error { b.spinner = in.spinner @@ -838,12 +825,7 @@ func (b *Builder) StartSpinner() error { if b.spinner == nil { return nil } - - err := b.spinner.Start() - if err != nil { - return fmt.Errorf("failed to start spinner: %w", err) - } - + b.spinner.Start() return nil } @@ -851,14 +833,6 @@ func (b *Builder) StopSpinner() error { if b.spinner == nil { return nil } - - status := b.spinner.Status() - if status == yacspin.SpinnerRunning || status == yacspin.SpinnerPaused { - err := b.spinner.Stop() - if err != nil { - return fmt.Errorf("failed to stop spinner: %w", err) - } - } - + b.spinner.Stop() return nil } diff --git a/internal/build/diff.go b/internal/build/diff.go index 8884e57f..72063d9f 100644 --- a/internal/build/diff.go +++ b/internal/build/diff.go @@ -173,14 +173,14 @@ func (b *Builder) diff() (string, bool, error) { // finished with Kustomization diff if b.spinner != nil { - b.spinner.Message(spinnerDryRunMessage) + b.spinner.Suffix = " " + spinnerDryRunMessage } } } } if b.spinner != nil { - b.spinner.Message("processing inventory") + b.spinner.Suffix = " processing inventory" } if b.kustomization.Spec.Prune && len(diffErrs) == 0 { @@ -204,7 +204,7 @@ func (b *Builder) diff() (string, bool, error) { func (b *Builder) kustomizationDiff(kustomization *kustomizev1.Kustomization) (string, bool, error) { if b.spinner != nil { - b.spinner.Message(fmt.Sprintf("%s in %s", spinnerDryRunMessage, kustomization.Name)) + b.spinner.Suffix = " " + fmt.Sprintf("%s in %s", spinnerDryRunMessage, kustomization.Name) } sourceRef := kustomization.Spec.SourceRef.DeepCopy() From 8be056324a1d7e00eb969bb2f7345934a260bbbe Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 30 Mar 2026 11:52:24 +0300 Subject: [PATCH 37/48] Add plugin management commands Signed-off-by: Stefan Prodan --- cmd/flux/main.go | 2 + cmd/flux/main_test.go | 6 + cmd/flux/plugin.go | 340 +++++++++++++++++++++++++++++++++++++ cmd/flux/plugin_test.go | 264 ++++++++++++++++++++++++++++ internal/plugin/install.go | 4 +- 5 files changed, 615 insertions(+), 1 deletion(-) create mode 100644 cmd/flux/plugin.go create mode 100644 cmd/flux/plugin_test.go diff --git a/cmd/flux/main.go b/cmd/flux/main.go index 815edb17..0e96614d 100644 --- a/cmd/flux/main.go +++ b/cmd/flux/main.go @@ -186,6 +186,8 @@ func main() { // logger, we configure it's logger to do nothing. ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{})) + registerPlugins() + if err := rootCmd.Execute(); err != nil { if err, ok := err.(*RequestError); ok { diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index 78159920..eeb3de80 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -374,6 +374,12 @@ func executeCommand(cmd string) (string, error) { // in subsequent executions which causes tests to fail that rely on the value // of "Changed". resumeCmd.PersistentFlags().Lookup("wait").Changed = false + // Reset the help flag value and Changed state so that a prior + // "--help" invocation does not leak into subsequent test runs. + if hf := rootCmd.Flags().Lookup("help"); hf != nil { + hf.Value.Set("false") + hf.Changed = false + } }() args, err := shellwords.Parse(cmd) if err != nil { diff --git a/cmd/flux/plugin.go b/cmd/flux/plugin.go new file mode 100644 index 00000000..44df6385 --- /dev/null +++ b/cmd/flux/plugin.go @@ -0,0 +1,340 @@ +/* +Copyright 2026 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 main + +import ( + "fmt" + "runtime" + "strings" + "time" + + "github.com/briandowns/spinner" + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" + "github.com/fluxcd/flux2/v2/pkg/printers" +) + +var pluginHandler = plugin.NewHandler() + +var pluginCmd = &cobra.Command{ + Use: "plugin", + Short: "Manage Flux CLI plugins", + Long: `The plugin sub-commands manage Flux CLI plugins.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // No-op: skip root's namespace DNS validation for plugin commands. + return nil + }, +} + +var pluginListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List installed plugins", + Long: `The plugin list command shows all installed plugins with their versions and paths.`, + RunE: pluginListCmdRun, +} + +var pluginInstallCmd = &cobra.Command{ + Use: "install [@]", + Short: "Install a plugin from the catalog", + Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog. + +Examples: + # Install the latest version + flux plugin install operator + + # Install a specific version + flux plugin install operator@0.45.0`, + Args: cobra.ExactArgs(1), + RunE: pluginInstallCmdRun, +} + +var pluginUninstallCmd = &cobra.Command{ + Use: "uninstall ", + Short: "Uninstall a plugin", + Long: `The plugin uninstall command removes a plugin binary and its receipt from the plugin directory.`, + Args: cobra.ExactArgs(1), + RunE: pluginUninstallCmdRun, +} + +var pluginUpdateCmd = &cobra.Command{ + Use: "update [name]", + Short: "Update installed plugins", + Long: `The plugin update command updates installed plugins to their latest versions. + +Examples: + # Update a single plugin + flux plugin update operator + + # Update all installed plugins + flux plugin update`, + Args: cobra.MaximumNArgs(1), + RunE: pluginUpdateCmdRun, +} + +var pluginSearchCmd = &cobra.Command{ + Use: "search [query]", + Short: "Search the plugin catalog", + Long: `The plugin search command lists available plugins from the Flux plugin catalog.`, + Args: cobra.MaximumNArgs(1), + RunE: pluginSearchCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginListCmd) + pluginCmd.AddCommand(pluginInstallCmd) + pluginCmd.AddCommand(pluginUninstallCmd) + pluginCmd.AddCommand(pluginUpdateCmd) + pluginCmd.AddCommand(pluginSearchCmd) + rootCmd.AddCommand(pluginCmd) +} + +// builtinCommandNames returns the names of all non-plugin commands on rootCmd. +func builtinCommandNames() []string { + var names []string + for _, c := range rootCmd.Commands() { + if c.GroupID != "plugin" { + names = append(names, c.Name()) + } + } + return names +} + +// registerPlugins scans the plugin directory and registers discovered +// plugins as Cobra subcommands on rootCmd. +func registerPlugins() { + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + return + } + + if !rootCmd.ContainsGroup("plugin") { + rootCmd.AddGroup(&cobra.Group{ + ID: "plugin", + Title: "Plugin Commands:", + }) + } + + for _, p := range plugins { + cmd := &cobra.Command{ + Use: p.Name, + Short: fmt.Sprintf("Runs the %s plugin", p.Name), + Long: fmt.Sprintf("This command runs the %s plugin.\nUse 'flux %s --help' for full plugin help.", p.Name, p.Name), + DisableFlagParsing: true, + GroupID: "plugin", + ValidArgsFunction: plugin.CompleteFunc(p.Path), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return plugin.Exec(p.Path, args) + }, + } + rootCmd.AddCommand(cmd) + } +} + +func pluginListCmdRun(cmd *cobra.Command, args []string) error { + pluginDir := pluginHandler.PluginDir() + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + cmd.Println("No plugins found") + return nil + } + + header := []string{"NAME", "VERSION", "PATH"} + var rows [][]string + for _, p := range plugins { + version := "manual" + if receipt := plugin.ReadReceipt(pluginDir, p.Name); receipt != nil { + version = receipt.Version + } + rows = append(rows, []string{p.Name, version, p.Path}) + } + + return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) +} + +func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { + nameVersion := args[0] + name, version := parseNameVersion(nameVersion) + + catalogClient := newCatalogClient() + manifest, err := catalogClient.FetchManifest(name) + if err != nil { + return err + } + + pv, err := plugin.ResolveVersion(manifest, version) + if err != nil { + return err + } + + plat, err := plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) + if err != nil { + return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) + } + + pluginDir := pluginHandler.EnsurePluginDir() + + installer := plugin.NewInstaller() + sp := newPluginSpinner(fmt.Sprintf("installing %s v%s", name, pv.Version)) + sp.Start() + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + sp.Stop() + return err + } + sp.Stop() + + logger.Successf("installed %s v%s", name, pv.Version) + return nil +} + +func pluginUninstallCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + pluginDir := pluginHandler.PluginDir() + + if err := plugin.Uninstall(pluginDir, name); err != nil { + return err + } + + logger.Successf("uninstalled %s", name) + return nil +} + +func pluginUpdateCmdRun(cmd *cobra.Command, args []string) error { + catalogClient := newCatalogClient() + + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + cmd.Println("No plugins found") + return nil + } + + // If a specific plugin is requested, filter to just that one. + if len(args) == 1 { + name := args[0] + var found bool + for _, p := range plugins { + if p.Name == name { + plugins = []plugin.Plugin{p} + found = true + break + } + } + if !found { + return fmt.Errorf("plugin %q is not installed", name) + } + } + + pluginDir := pluginHandler.EnsurePluginDir() + installer := plugin.NewInstaller() + for _, p := range plugins { + result := plugin.CheckUpdate(pluginDir, p.Name, catalogClient, runtime.GOOS, runtime.GOARCH) + if result.Err != nil { + logger.Failuref("error checking %s: %v", p.Name, result.Err) + continue + } + if result.Skipped { + if result.SkipReason == plugin.SkipReasonManual { + logger.Warningf("skipping %s (%s)", p.Name, result.SkipReason) + } else { + logger.Successf("%s already up to date (v%s)", p.Name, result.FromVersion) + } + continue + } + + sp := newPluginSpinner(fmt.Sprintf("updating %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion)) + sp.Start() + if err := installer.Install(pluginDir, result.Manifest, result.Version, result.Platform); err != nil { + sp.Stop() + logger.Failuref("error updating %s: %v", p.Name, err) + continue + } + sp.Stop() + logger.Successf("updated %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion) + } + + return nil +} + +func pluginSearchCmdRun(cmd *cobra.Command, args []string) error { + catalogClient := newCatalogClient() + catalog, err := catalogClient.FetchCatalog() + if err != nil { + return err + } + + var query string + if len(args) == 1 { + query = strings.ToLower(args[0]) + } + + pluginDir := pluginHandler.PluginDir() + header := []string{"NAME", "DESCRIPTION", "INSTALLED"} + var rows [][]string + for _, entry := range catalog.Plugins { + if query != "" { + if !strings.Contains(strings.ToLower(entry.Name), query) && + !strings.Contains(strings.ToLower(entry.Description), query) { + continue + } + } + + installed := "" + if receipt := plugin.ReadReceipt(pluginDir, entry.Name); receipt != nil { + installed = receipt.Version + } + + rows = append(rows, []string{entry.Name, entry.Description, installed}) + } + + if len(rows) == 0 { + if query != "" { + cmd.Printf("No plugins matching %q found in catalog\n", query) + } else { + cmd.Println("No plugins found in catalog") + } + return nil + } + + return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) +} + +// parseNameVersion splits "operator@0.45.0" into ("operator", "0.45.0"). +// If no @ is present, version is empty (latest). +func parseNameVersion(s string) (string, string) { + name, version, found := strings.Cut(s, "@") + if found { + return name, version + } + return s, "" +} + +// newCatalogClient creates a CatalogClient that respects FLUXCD_PLUGIN_CATALOG. +func newCatalogClient() *plugin.CatalogClient { + client := plugin.NewCatalogClient() + client.GetEnv = pluginHandler.GetEnv + return client +} + +func newPluginSpinner(message string) *spinner.Spinner { + s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) + s.Suffix = " " + message + return s +} diff --git a/cmd/flux/plugin_test.go b/cmd/flux/plugin_test.go new file mode 100644 index 00000000..90f1b482 --- /dev/null +++ b/cmd/flux/plugin_test.go @@ -0,0 +1,264 @@ +/* +Copyright 2026 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 main + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +func TestPluginAppearsInHelp(t *testing.T) { + origHandler := pluginHandler + defer func() { pluginHandler = origHandler }() + + pluginDir := t.TempDir() + + fakeBin := pluginDir + "/flux-testplugin" + os.WriteFile(fakeBin, []byte("#!/bin/sh\necho test"), 0o755) + + pluginHandler = &plugin.Handler{ + ReadDir: os.ReadDir, + Stat: os.Stat, + GetEnv: func(key string) string { + if key == "FLUXCD_PLUGINS" { + return pluginDir + } + return "" + }, + HomeDir: func() (string, error) { return t.TempDir(), nil }, + } + + registerPlugins() + defer func() { + cmds := rootCmd.Commands() + for _, cmd := range cmds { + if cmd.Name() == "testplugin" { + rootCmd.RemoveCommand(cmd) + break + } + } + }() + + output, err := executeCommand("--help") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !strings.Contains(output, "Plugin Commands:") { + t.Error("expected 'Plugin Commands:' in help output") + } + if !strings.Contains(output, "testplugin") { + t.Error("expected 'testplugin' in help output") + } +} + +func TestPluginListOutput(t *testing.T) { + origHandler := pluginHandler + defer func() { pluginHandler = origHandler }() + + pluginDir := t.TempDir() + + fakeBin := pluginDir + "/flux-myplugin" + os.WriteFile(fakeBin, []byte("#!/bin/sh\necho test"), 0o755) + + pluginHandler = &plugin.Handler{ + ReadDir: os.ReadDir, + Stat: os.Stat, + GetEnv: func(key string) string { + if key == "FLUXCD_PLUGINS" { + return pluginDir + } + return "" + }, + HomeDir: func() (string, error) { return t.TempDir(), nil }, + } + + output, err := executeCommand("plugin list") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !strings.Contains(output, "myplugin") { + t.Errorf("expected 'myplugin' in output, got: %s", output) + } + if !strings.Contains(output, "manual") { + t.Errorf("expected 'manual' in output (no receipt), got: %s", output) + } +} + +func TestPluginListWithReceipt(t *testing.T) { + origHandler := pluginHandler + defer func() { pluginHandler = origHandler }() + + pluginDir := t.TempDir() + + fakeBin := pluginDir + "/flux-myplugin" + os.WriteFile(fakeBin, []byte("#!/bin/sh\necho test"), 0o755) + receipt := pluginDir + "/flux-myplugin.yaml" + os.WriteFile(receipt, []byte("name: myplugin\nversion: \"1.2.3\"\n"), 0o644) + + pluginHandler = &plugin.Handler{ + ReadDir: os.ReadDir, + Stat: os.Stat, + GetEnv: func(key string) string { + if key == "FLUXCD_PLUGINS" { + return pluginDir + } + return "" + }, + HomeDir: func() (string, error) { return t.TempDir(), nil }, + } + + output, err := executeCommand("plugin list") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !strings.Contains(output, "1.2.3") { + t.Errorf("expected version '1.2.3' in output, got: %s", output) + } +} + +func TestPluginListEmpty(t *testing.T) { + origHandler := pluginHandler + defer func() { pluginHandler = origHandler }() + + pluginDir := t.TempDir() + + pluginHandler = &plugin.Handler{ + ReadDir: os.ReadDir, + Stat: os.Stat, + GetEnv: func(key string) string { + if key == "FLUXCD_PLUGINS" { + return pluginDir + } + return "" + }, + HomeDir: func() (string, error) { return t.TempDir(), nil }, + } + + output, err := executeCommand("plugin list") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !strings.Contains(output, "No plugins found") { + t.Errorf("expected 'No plugins found', got: %s", output) + } +} + +func TestNoPluginsNoRegistration(t *testing.T) { + origHandler := pluginHandler + defer func() { pluginHandler = origHandler }() + + pluginHandler = &plugin.Handler{ + ReadDir: func(name string) ([]os.DirEntry, error) { + return nil, fmt.Errorf("no dir") + }, + Stat: os.Stat, + GetEnv: func(key string) string { + if key == "FLUXCD_PLUGINS" { + return "/nonexistent" + } + return "" + }, + HomeDir: func() (string, error) { return t.TempDir(), nil }, + } + + // Verify that registerPlugins with no plugins doesn't add any commands. + before := len(rootCmd.Commands()) + registerPlugins() + after := len(rootCmd.Commands()) + if after != before { + t.Errorf("expected no new commands, got %d new", after-before) + } +} + +func TestPluginSkipsPersistentPreRun(t *testing.T) { + // Plugin commands override root's PersistentPreRunE with a no-op, + // so an invalid namespace should not trigger a validation error. + _, err := executeCommand("plugin list") + if err != nil { + t.Fatalf("plugin list should not trigger root's namespace validation: %v", err) + } +} + +func TestParseNameVersion(t *testing.T) { + tests := []struct { + input string + wantName string + wantVersion string + }{ + {"operator", "operator", ""}, + {"operator@0.45.0", "operator", "0.45.0"}, + {"my-tool@1.0.0", "my-tool", "1.0.0"}, + {"plugin@", "plugin", ""}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + name, version := parseNameVersion(tt.input) + if name != tt.wantName { + t.Errorf("name: got %q, want %q", name, tt.wantName) + } + if version != tt.wantVersion { + t.Errorf("version: got %q, want %q", version, tt.wantVersion) + } + }) + } +} + +func TestPluginDiscoverSkipsBuiltins(t *testing.T) { + origHandler := pluginHandler + defer func() { pluginHandler = origHandler }() + + pluginDir := t.TempDir() + + for _, name := range []string{"flux-get", "flux-create", "flux-version"} { + os.WriteFile(pluginDir+"/"+name, []byte("#!/bin/sh"), 0o755) + } + os.WriteFile(pluginDir+"/flux-myplugin", []byte("#!/bin/sh"), 0o755) + + pluginHandler = &plugin.Handler{ + ReadDir: os.ReadDir, + Stat: os.Stat, + GetEnv: func(key string) string { + if key == "FLUXCD_PLUGINS" { + return pluginDir + } + return "" + }, + HomeDir: func() (string, error) { return t.TempDir(), nil }, + } + + plugins := pluginHandler.Discover(builtinCommandNames()) + + if len(plugins) != 1 { + names := make([]string, len(plugins)) + for i, p := range plugins { + names[i] = p.Name + } + t.Fatalf("expected 1 plugin, got %d: %v", len(plugins), names) + } + if plugins[0].Name != "myplugin" { + t.Errorf("expected 'myplugin', got %q", plugins[0].Name) + } +} diff --git a/internal/plugin/install.go b/internal/plugin/install.go index 4a29e288..b8810772 100644 --- a/internal/plugin/install.go +++ b/internal/plugin/install.go @@ -228,7 +228,9 @@ func writeStreamToFile(r io.Reader, destPath string) error { } if _, err := io.Copy(out, r); err != nil { - out.Close() + if closeErr := out.Close(); closeErr != nil { + return fmt.Errorf("failed to write plugin binary: %w (also failed to close file: %v)", err, closeErr) + } return fmt.Errorf("failed to write plugin binary: %w", err) } return out.Close() From 9a4b93056b4a4ee5b695faad0e41d785e0beceee Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Thu, 9 Apr 2026 00:05:47 +0300 Subject: [PATCH 38/48] Add support for plugin binary artifacts Signed-off-by: Stefan Prodan --- internal/plugin/install.go | 135 ++++++++++-- internal/plugin/install_test.go | 359 ++++++++++++++++++++++++++++++++ 2 files changed, 480 insertions(+), 14 deletions(-) diff --git a/internal/plugin/install.go b/internal/plugin/install.go index b8810772..2bf04bb2 100644 --- a/internal/plugin/install.go +++ b/internal/plugin/install.go @@ -77,21 +77,32 @@ func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *P return fmt.Errorf("checksum verification failed (expected: %s, got: %s)", plat.Checksum, actualChecksum) } + // manifest.Bin is the single source of truth for the installed binary + // name (e.g. "flux-validate"). On Windows we always append ".exe". + // For archives it's also the entry name we look up; for raw binaries + // it's the rename target regardless of the URL's filename. binName := manifest.Bin if runtime.GOOS == "windows" { binName += ".exe" } + destPath := filepath.Join(pluginDir, binName) - destName := pluginPrefix + manifest.Name - if runtime.GOOS == "windows" { - destName += ".exe" + format, err := detectArchiveFormat(tmpFile.Name(), plat.URL) + if err != nil { + return fmt.Errorf("failed to detect plugin format: %w", err) } - destPath := filepath.Join(pluginDir, destName) - if strings.HasSuffix(plat.URL, ".zip") { + switch format { + case formatZip: err = extractFromZip(tmpFile.Name(), binName, destPath) - } else { + case formatTarGz: err = extractFromTarGz(tmpFile.Name(), binName, destPath) + case formatTar: + err = extractFromTar(tmpFile.Name(), binName, destPath) + case formatBinary: + err = copyPluginBinary(tmpFile.Name(), destPath) + default: + return fmt.Errorf("unexpected plugin format: %v", format) } if err != nil { return err @@ -162,6 +173,60 @@ func writeReceipt(pluginDir, name string, receipt *Receipt) error { return os.WriteFile(receiptPath(pluginDir, name), data, 0o644) } +// archiveFormat is the detected format of a downloaded plugin artifact. +type archiveFormat int + +const ( + formatBinary archiveFormat = iota + formatZip + formatTarGz + formatTar +) + +// detectArchiveFormat determines the artifact format by first checking the URL +// extension, then falling back to magic-byte inspection. Returns formatBinary +// if neither indicates a known archive, in which case the downloaded file is +// installed as-is. +func detectArchiveFormat(path, url string) (archiveFormat, error) { + switch lower := strings.ToLower(url); { + case strings.HasSuffix(lower, ".zip"): + return formatZip, nil + case strings.HasSuffix(lower, ".tar.gz"), strings.HasSuffix(lower, ".tgz"): + return formatTarGz, nil + case strings.HasSuffix(lower, ".tar"): + return formatTar, nil + } + + f, err := os.Open(path) + if err != nil { + return formatBinary, err + } + defer f.Close() + + // Read enough bytes to cover the tar "ustar" magic at offset 257. + var hdr [262]byte + n, err := io.ReadFull(f, hdr[:]) + if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF { + return formatBinary, err + } + + // ZIP: PK\x03\x04 (file), PK\x05\x06 (empty), PK\x07\x08 (spanned). + if n >= 4 && hdr[0] == 'P' && hdr[1] == 'K' && + (hdr[2] == 0x03 || hdr[2] == 0x05 || hdr[2] == 0x07) { + return formatZip, nil + } + // gzip: \x1f\x8b + if n >= 2 && hdr[0] == 0x1f && hdr[1] == 0x8b { + return formatTarGz, nil + } + // tar: "ustar" at offset 257 + if n >= 262 && string(hdr[257:262]) == "ustar" { + return formatTar, nil + } + + return formatBinary, nil +} + // extractFromTarGz extracts a named file from a tar.gz archive // and streams it directly to destPath. func extractFromTarGz(archivePath, targetName, destPath string) error { @@ -177,7 +242,26 @@ func extractFromTarGz(archivePath, targetName, destPath string) error { } defer gr.Close() - tr := tar.NewReader(gr) + return extractTarStream(gr, targetName, destPath) +} + +// extractFromTar extracts a named file from an uncompressed tar archive +// and streams it directly to destPath. +func extractFromTar(archivePath, targetName, destPath string) error { + f, err := os.Open(archivePath) + if err != nil { + return err + } + defer f.Close() + + return extractTarStream(f, targetName, destPath) +} + +// extractTarStream walks a tar stream and streams the first matching +// regular file to destPath. Non-regular entries (symlinks, devices, +// directories) and entries with unsafe paths are skipped. +func extractTarStream(r io.Reader, targetName, destPath string) error { + tr := tar.NewReader(r) for { header, err := tr.Next() if err == io.EOF { @@ -187,10 +271,13 @@ func extractFromTarGz(archivePath, targetName, destPath string) error { return fmt.Errorf("failed to read tar: %w", err) } - if filepath.IsAbs(header.Name) || strings.Contains(header.Name, "..") { + if !filepath.IsLocal(header.Name) { continue } - if filepath.Base(header.Name) == targetName && header.Typeflag == tar.TypeReg { + if !header.FileInfo().Mode().IsRegular() { + continue + } + if filepath.Base(header.Name) == targetName { return writeStreamToFile(tr, destPath) } } @@ -198,8 +285,21 @@ func extractFromTarGz(archivePath, targetName, destPath string) error { return fmt.Errorf("binary %q not found in archive", targetName) } -// extractFromZip extracts a named file from a zip archive -// and streams it directly to destPath. +// copyPluginBinary copies a raw downloaded binary to destPath with 0755 mode. +// Used when the downloaded artifact is not an archive. +func copyPluginBinary(srcPath, destPath string) error { + src, err := os.Open(srcPath) + if err != nil { + return fmt.Errorf("failed to open downloaded binary: %w", err) + } + defer src.Close() + + return writeStreamToFile(src, destPath) +} + +// extractFromZip extracts a named file from a zip archive and streams it +// directly to destPath. Non-regular entries (symlinks, devices, directories) +// and entries with unsafe paths are skipped. func extractFromZip(archivePath, targetName, destPath string) error { r, err := zip.OpenReader(archivePath) if err != nil { @@ -208,13 +308,20 @@ func extractFromZip(archivePath, targetName, destPath string) error { defer r.Close() for _, f := range r.File { - if filepath.Base(f.Name) == targetName && !f.FileInfo().IsDir() { + if !filepath.IsLocal(f.Name) { + continue + } + if !f.FileInfo().Mode().IsRegular() { + continue + } + if filepath.Base(f.Name) == targetName { rc, err := f.Open() if err != nil { return fmt.Errorf("failed to open %q in zip: %w", targetName, err) } - defer rc.Close() - return writeStreamToFile(rc, destPath) + err = writeStreamToFile(rc, destPath) + rc.Close() + return err } } diff --git a/internal/plugin/install_test.go b/internal/plugin/install_test.go index 7b4d4253..e783ca97 100644 --- a/internal/plugin/install_test.go +++ b/internal/plugin/install_test.go @@ -18,10 +18,12 @@ package plugin import ( "archive/tar" + "archive/zip" "bytes" "compress/gzip" "crypto/sha256" "fmt" + "io/fs" "net/http" "net/http/httptest" "os" @@ -55,6 +57,97 @@ func createTestTarGz(name string, content []byte) ([]byte, error) { return buf.Bytes(), nil } +// createTestTar creates an uncompressed tar archive containing a single file. +func createTestTar(name string, content []byte) ([]byte, error) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + hdr := &tar.Header{ + Name: name, + Mode: 0o755, + Size: int64(len(content)), + } + if err := tw.WriteHeader(hdr); err != nil { + return nil, err + } + if _, err := tw.Write(content); err != nil { + return nil, err + } + + tw.Close() + return buf.Bytes(), nil +} + +// tarEntry describes a single entry for createTestTarGzMulti. +type tarEntry struct { + header tar.Header + content []byte +} + +// createTestTarGzMulti creates a tar.gz archive with arbitrary entries. +// Used to test rejection of unsafe or non-regular entries. +func createTestTarGzMulti(entries []tarEntry) ([]byte, error) { + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + tw := tar.NewWriter(gw) + + for _, e := range entries { + hdr := e.header + hdr.Size = int64(len(e.content)) + if err := tw.WriteHeader(&hdr); err != nil { + return nil, err + } + if len(e.content) > 0 { + if _, err := tw.Write(e.content); err != nil { + return nil, err + } + } + } + + tw.Close() + gw.Close() + return buf.Bytes(), nil +} + +// zipEntry describes a single entry for createTestZip. +type zipEntry struct { + name string + mode fs.FileMode + content []byte +} + +// createTestZip creates a zip archive with arbitrary entries. Entries may +// carry Unix mode bits (e.g. os.ModeSymlink) to exercise non-regular files. +func createTestZip(entries []zipEntry) ([]byte, error) { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + + for _, e := range entries { + hdr := &zip.FileHeader{ + Name: e.name, + Method: zip.Deflate, + } + mode := e.mode + if mode == 0 { + mode = 0o755 + } + hdr.SetMode(mode) + + w, err := zw.CreateHeader(hdr) + if err != nil { + return nil, err + } + if _, err := w.Write(e.content); err != nil { + return nil, err + } + } + + if err := zw.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + func TestInstall(t *testing.T) { binaryContent := []byte("#!/bin/sh\necho hello") archive, err := createTestTarGz("flux-operator", binaryContent) @@ -291,6 +384,126 @@ platform: }) } +func TestInstallRawBinary(t *testing.T) { + // Bytes that don't match zip/gzip/tar magic — treated as a raw binary. + binaryContent := []byte("#!/bin/sh\necho raw plugin") + checksum := fmt.Sprintf("sha256:%x", sha256.Sum256(binaryContent)) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(binaryContent) + })) + defer server.Close() + + pluginDir := t.TempDir() + + manifest := &PluginManifest{ + Name: "validate", + Bin: "flux-validate", + } + pv := &PluginVersion{Version: "1.2.3"} + plat := &PluginPlatform{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + // URL filename deliberately differs from manifest.Bin — mimics a + // typical GitHub release asset that includes platform/version in + // the name. The installer must rename to manifest.Bin. + URL: server.URL + "/download/flux-validate-" + runtime.GOARCH + "-v1.2.3", + Checksum: checksum, + } + + installer := &Installer{HTTPClient: server.Client()} + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + t.Fatalf("install failed: %v", err) + } + + // The installed file must be named exactly manifest.Bin (+ .exe on Windows), + // regardless of what the URL path looked like. + wantName := "flux-validate" + if runtime.GOOS == "windows" { + wantName += ".exe" + } + binPath := filepath.Join(pluginDir, wantName) + data, err := os.ReadFile(binPath) + if err != nil { + t.Fatalf("binary not found at %s: %v", binPath, err) + } + if !bytes.Equal(data, binaryContent) { + t.Errorf("binary content mismatch: got %q, want %q", data, binaryContent) + } + + // Nothing should have been written under the URL-derived name. + urlDerived := filepath.Join(pluginDir, "flux-validate-"+runtime.GOARCH+"-v1.2.3") + if _, err := os.Stat(urlDerived); !os.IsNotExist(err) { + t.Errorf("unexpected file at URL-derived path %s", urlDerived) + } + + if runtime.GOOS != "windows" { + info, err := os.Stat(binPath) + if err != nil { + t.Fatalf("stat: %v", err) + } + if info.Mode()&0o111 == 0 { + t.Errorf("binary is not executable: mode %v", info.Mode()) + } + } + + // Raw binary install must still produce a receipt. + if receipt := ReadReceipt(pluginDir, "validate"); receipt == nil { + t.Fatal("receipt not found") + } +} + +func TestDetectArchiveFormat(t *testing.T) { + tarGz, err := createTestTarGz("bin", []byte("content")) + if err != nil { + t.Fatalf("createTestTarGz: %v", err) + } + plainTar, err := createTestTar("bin", []byte("content")) + if err != nil { + t.Fatalf("createTestTar: %v", err) + } + + tests := []struct { + name string + url string + content []byte + want archiveFormat + }{ + // Extension-based detection takes precedence over content. + {"zip extension", "https://example.com/plugin.zip", []byte("ignored"), formatZip}, + {"tar.gz extension", "https://example.com/plugin.tar.gz", []byte("ignored"), formatTarGz}, + {"tgz extension", "https://example.com/plugin.tgz", []byte("ignored"), formatTarGz}, + {"tar extension", "https://example.com/plugin.tar", []byte("ignored"), formatTar}, + {"uppercase extension", "https://example.com/PLUGIN.ZIP", []byte("ignored"), formatZip}, + + // Magic-byte detection when extension is absent or unrecognized. + {"zip magic no extension", "https://example.com/download", []byte{'P', 'K', 0x03, 0x04, 0, 0, 0, 0}, formatZip}, + {"gzip magic no extension", "https://example.com/download", tarGz, formatTarGz}, + {"tar magic no extension", "https://example.com/download", plainTar, formatTar}, + + // Fallback to raw binary. + {"unknown content", "https://example.com/download", []byte("#!/bin/sh\necho hi"), formatBinary}, + {"short file", "https://example.com/download", []byte("ab"), formatBinary}, + {"empty file", "https://example.com/download", []byte{}, formatBinary}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tmp := filepath.Join(t.TempDir(), "artifact") + if err := os.WriteFile(tmp, tc.content, 0o644); err != nil { + t.Fatal(err) + } + got, err := detectArchiveFormat(tmp, tc.url) + if err != nil { + t.Fatalf("detectArchiveFormat: %v", err) + } + if got != tc.want { + t.Errorf("got %v, want %v", got, tc.want) + } + }) + } +} + func TestExtractFromTarGz(t *testing.T) { content := []byte("test binary content") archive, err := createTestTarGz("flux-operator", content) @@ -314,6 +527,152 @@ func TestExtractFromTarGz(t *testing.T) { } } +func TestExtractFromTarGzRejectsUnsafeEntries(t *testing.T) { + content := []byte("legit content") + + // Archive contains, in order: + // 1. A symlink whose basename matches the target (must be skipped). + // 2. A regular entry with ".." in the path (must be skipped). + // 3. An absolute-path entry (must be skipped). + // 4. A legitimate regular file that must be extracted. + entries := []tarEntry{ + { + header: tar.Header{ + Name: "flux-operator", + Typeflag: tar.TypeSymlink, + Linkname: "/etc/passwd", + Mode: 0o777, + }, + }, + { + header: tar.Header{ + Name: "../flux-operator", + Typeflag: tar.TypeReg, + Mode: 0o755, + }, + content: []byte("malicious"), + }, + { + header: tar.Header{ + Name: "/absolute/flux-operator", + Typeflag: tar.TypeReg, + Mode: 0o755, + }, + content: []byte("malicious"), + }, + { + header: tar.Header{ + Name: "bin/flux-operator", + Typeflag: tar.TypeReg, + Mode: 0o755, + }, + content: content, + }, + } + + archive, err := createTestTarGzMulti(entries) + if err != nil { + t.Fatalf("createTestTarGzMulti: %v", err) + } + + tmpFile := filepath.Join(t.TempDir(), "test.tar.gz") + os.WriteFile(tmpFile, archive, 0o644) + + destPath := filepath.Join(t.TempDir(), "flux-operator") + if err := extractFromTarGz(tmpFile, "flux-operator", destPath); err != nil { + t.Fatalf("extract failed: %v", err) + } + + data, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("failed to read extracted file: %v", err) + } + if !bytes.Equal(data, content) { + t.Errorf("extracted content mismatch: got %q, want %q", data, content) + } +} + +func TestExtractFromZip(t *testing.T) { + content := []byte("test binary content") + archive, err := createTestZip([]zipEntry{ + {name: "flux-operator", content: content}, + }) + if err != nil { + t.Fatalf("createTestZip: %v", err) + } + + tmpFile := filepath.Join(t.TempDir(), "test.zip") + os.WriteFile(tmpFile, archive, 0o644) + + destPath := filepath.Join(t.TempDir(), "flux-operator") + if err := extractFromZip(tmpFile, "flux-operator", destPath); err != nil { + t.Fatalf("extract failed: %v", err) + } + + data, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("failed to read extracted file: %v", err) + } + if !bytes.Equal(data, content) { + t.Errorf("content mismatch: got %q, want %q", data, content) + } +} + +func TestExtractFromZipRejectsUnsafeEntries(t *testing.T) { + content := []byte("legit content") + + // Archive contains, in order: + // 1. A symlink whose basename matches the target (must be skipped). + // 2. An entry with ".." in the path (must be skipped). + // 3. An absolute-path entry (must be skipped). + // 4. A directory entry whose basename matches (must be skipped). + // 5. A legitimate regular file that must be extracted. + entries := []zipEntry{ + { + name: "flux-operator", + mode: fs.ModeSymlink | 0o777, + content: []byte("/etc/passwd"), + }, + { + name: "../flux-operator", + content: []byte("malicious"), + }, + { + name: "/absolute/flux-operator", + content: []byte("malicious"), + }, + { + name: "flux-operator/", + mode: fs.ModeDir | 0o755, + }, + { + name: "bin/flux-operator", + content: content, + }, + } + + archive, err := createTestZip(entries) + if err != nil { + t.Fatalf("createTestZip: %v", err) + } + + tmpFile := filepath.Join(t.TempDir(), "test.zip") + os.WriteFile(tmpFile, archive, 0o644) + + destPath := filepath.Join(t.TempDir(), "flux-operator") + if err := extractFromZip(tmpFile, "flux-operator", destPath); err != nil { + t.Fatalf("extract failed: %v", err) + } + + data, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("failed to read extracted file: %v", err) + } + if !bytes.Equal(data, content) { + t.Errorf("extracted content mismatch: got %q, want %q", data, content) + } +} + func TestExtractFromTarGzNotFound(t *testing.T) { archive, err := createTestTarGz("other-binary", []byte("content")) if err != nil { From 2cee1d795e9c324859426b6092d7926411a15964 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Thu, 9 Apr 2026 00:42:52 +0300 Subject: [PATCH 39/48] Fetch the plugin catalog from latest immutable release Signed-off-by: Stefan Prodan --- internal/plugin/catalog.go | 5 +++-- internal/plugin/catalog_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/plugin/catalog.go b/internal/plugin/catalog.go index 84f673d2..b1b67bfa 100644 --- a/internal/plugin/catalog.go +++ b/internal/plugin/catalog.go @@ -27,7 +27,8 @@ import ( ) const ( - defaultCatalogBase = "https://raw.githubusercontent.com/fluxcd/plugins/main/" + // defaultCatalogBase points at the latest GitHub release of fluxcd/plugins. + defaultCatalogBase = "https://github.com/fluxcd/plugins/releases/latest/download/" envCatalogBase = "FLUXCD_PLUGIN_CATALOG" pluginAPIVersion = "cli.fluxcd.io/v1beta1" @@ -111,7 +112,7 @@ func (c *CatalogClient) baseURL() string { // FetchManifest fetches a single plugin manifest from the catalog. func (c *CatalogClient) FetchManifest(name string) (*PluginManifest, error) { - url := c.baseURL() + "plugins/" + name + ".yaml" + url := c.baseURL() + name + ".yaml" body, err := c.fetch(url) if err != nil { return nil, fmt.Errorf("plugin %q not found in catalog", name) diff --git a/internal/plugin/catalog_test.go b/internal/plugin/catalog_test.go index 37c4d34c..8134a010 100644 --- a/internal/plugin/catalog_test.go +++ b/internal/plugin/catalog_test.go @@ -38,7 +38,7 @@ versions: checksum: sha256:abc123 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/plugins/operator.yaml" { + if r.URL.Path == "/operator.yaml" { w.Write([]byte(manifest)) return } From c0938d351f4d0988ed2cd24bde3907c377d7800d Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 13 Apr 2026 23:22:47 +0300 Subject: [PATCH 40/48] Add support for `extractPath` Signed-off-by: Stefan Prodan --- internal/plugin/catalog.go | 109 +++++++++++++++++++++------- internal/plugin/discovery.go | 18 +++-- internal/plugin/install.go | 30 ++++++-- internal/plugin/install_test.go | 123 ++++++++++++++++++++++++++++++++ internal/plugin/update.go | 33 ++++++--- 5 files changed, 271 insertions(+), 42 deletions(-) diff --git a/internal/plugin/catalog.go b/internal/plugin/catalog.go index b1b67bfa..1d370a81 100644 --- a/internal/plugin/catalog.go +++ b/internal/plugin/catalog.go @@ -38,59 +38,118 @@ const ( // PluginManifest represents a single plugin's manifest from the catalog. type PluginManifest struct { - APIVersion string `json:"apiVersion"` - Kind string `json:"kind"` - Name string `json:"name"` - Description string `json:"description"` - Homepage string `json:"homepage,omitempty"` - Source string `json:"source,omitempty"` - Bin string `json:"bin"` - Versions []PluginVersion `json:"versions"` + // APIVersion is the manifest schema version (e.g. "cli.fluxcd.io/v1beta1"). + APIVersion string `json:"apiVersion"` + + // Kind is the manifest type, must be "Plugin". + Kind string `json:"kind"` + + // Name is the plugin name used in "flux " invocations. + Name string `json:"name"` + + // Description is a short human-readable summary of the plugin. + Description string `json:"description"` + + // Homepage is the URL to the plugin's documentation site. + Homepage string `json:"homepage,omitempty"` + + // Source is the URL to the plugin's source repository. + Source string `json:"source,omitempty"` + + // Bin is the binary name inside archives and the installed filename + // (e.g. "flux-operator"). On Windows ".exe" is appended automatically. + Bin string `json:"bin"` + + // Versions lists available versions, newest first. + Versions []PluginVersion `json:"versions"` } // PluginVersion represents a version entry in a plugin manifest. type PluginVersion struct { - Version string `json:"version"` + // Version is the semantic version string (e.g. "0.45.0"). + Version string `json:"version"` + + // Platforms lists the platform-specific binaries for this version. Platforms []PluginPlatform `json:"platforms"` } // PluginPlatform represents a platform-specific binary entry. type PluginPlatform struct { - OS string `json:"os"` - Arch string `json:"arch"` - URL string `json:"url"` + // OS is the target operating system (e.g. "darwin", "linux", "windows"). + OS string `json:"os"` + + // Arch is the target architecture (e.g. "amd64", "arm64"). + Arch string `json:"arch"` + + // URL is the download URL for the archive or binary. + URL string `json:"url"` + + // Checksum is the expected digest in ":" format + // (e.g. "sha256:cd85d5d84d264..."). Checksum string `json:"checksum"` + + // ExtractPath overrides the default binary lookup name inside archives. + // When set, it is matched as an exact path within the archive (e.g. + // "bin/flux-operator"). When empty, the archive is searched by the + // base name derived from the manifest's Bin field. + ExtractPath string `json:"extractPath,omitempty"` } // PluginCatalog represents the generated catalog.yaml file. type PluginCatalog struct { - APIVersion string `json:"apiVersion"` - Kind string `json:"kind"` - Plugins []CatalogEntry `json:"plugins"` + // APIVersion is the catalog schema version (e.g. "cli.fluxcd.io/v1beta1"). + APIVersion string `json:"apiVersion"` + + // Kind is the catalog type, must be "PluginCatalog". + Kind string `json:"kind"` + + // Plugins lists all available plugins in the catalog. + Plugins []CatalogEntry `json:"plugins"` } // CatalogEntry is a single entry in the plugin catalog. type CatalogEntry struct { - Name string `json:"name"` + // Name is the plugin name. + Name string `json:"name"` + + // Description is a short human-readable summary of the plugin. Description string `json:"description"` - Homepage string `json:"homepage,omitempty"` - Source string `json:"source,omitempty"` - License string `json:"license,omitempty"` + + // Homepage is the URL to the plugin's documentation site. + Homepage string `json:"homepage,omitempty"` + + // Source is the URL to the plugin's source repository. + Source string `json:"source,omitempty"` + + // License is the SPDX license identifier (e.g. "Apache-2.0"). + License string `json:"license,omitempty"` } // Receipt records what was installed for a plugin. type Receipt struct { - Name string `json:"name"` - Version string `json:"version"` - InstalledAt string `json:"installedAt"` - Platform PluginPlatform `json:"platform"` + // Name is the plugin name (e.g. "operator"). + Name string `json:"name"` + + // Version is the installed semantic version. + Version string `json:"version"` + + // InstalledAt is the RFC 3339 timestamp of the installation. + InstalledAt string `json:"installedAt"` + + // Platform records the platform-specific details used for installation. + Platform PluginPlatform `json:"platform"` } // CatalogClient fetches plugin manifests and catalogs from a remote URL. type CatalogClient struct { - BaseURL string + // BaseURL is the catalog base URL for fetching manifests. + BaseURL string + + // HTTPClient is the HTTP client used for catalog requests. HTTPClient *http.Client - GetEnv func(key string) string + + // GetEnv returns the value of an environment variable. + GetEnv func(key string) string } // NewCatalogClient returns a CatalogClient with production defaults. diff --git a/internal/plugin/discovery.go b/internal/plugin/discovery.go index 5d750ac7..a8ab37a3 100644 --- a/internal/plugin/discovery.go +++ b/internal/plugin/discovery.go @@ -38,16 +38,26 @@ var reservedNames = map[string]bool{ // Plugin represents a discovered plugin binary. type Plugin struct { - Name string // e.g., "operator" (derived from "flux-operator") - Path string // absolute path to binary + // Name is the plugin name, e.g. "operator" (derived from "flux-operator"). + Name string + + // Path is the absolute path to the plugin binary. + Path string } // Handler discovers and executes plugins. Uses dependency injection // for testability. type Handler struct { + // ReadDir lists directory entries. ReadDir func(name string) ([]os.DirEntry, error) - Stat func(name string) (os.FileInfo, error) - GetEnv func(key string) string + + // Stat returns file info, following symlinks. + Stat func(name string) (os.FileInfo, error) + + // GetEnv returns the value of an environment variable. + GetEnv func(key string) string + + // HomeDir returns the current user's home directory. HomeDir func() (string, error) } diff --git a/internal/plugin/install.go b/internal/plugin/install.go index 2bf04bb2..1589e2e2 100644 --- a/internal/plugin/install.go +++ b/internal/plugin/install.go @@ -35,6 +35,7 @@ import ( // Installer handles downloading, verifying, and installing plugins. type Installer struct { + // HTTPClient is the HTTP client used for downloading plugin archives. HTTPClient *http.Client } @@ -87,6 +88,15 @@ func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *P } destPath := filepath.Join(pluginDir, binName) + // extractTarget is the path to match inside the archive. When the + // platform specifies an extractPath, use it verbatim (it may be a + // nested path like "bin/flux-operator"). Otherwise fall back to + // binName which matches by basename. + extractTarget := binName + if plat.ExtractPath != "" { + extractTarget = plat.ExtractPath + } + format, err := detectArchiveFormat(tmpFile.Name(), plat.URL) if err != nil { return fmt.Errorf("failed to detect plugin format: %w", err) @@ -94,11 +104,11 @@ func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *P switch format { case formatZip: - err = extractFromZip(tmpFile.Name(), binName, destPath) + err = extractFromZip(tmpFile.Name(), extractTarget, destPath) case formatTarGz: - err = extractFromTarGz(tmpFile.Name(), binName, destPath) + err = extractFromTarGz(tmpFile.Name(), extractTarget, destPath) case formatTar: - err = extractFromTar(tmpFile.Name(), binName, destPath) + err = extractFromTar(tmpFile.Name(), extractTarget, destPath) case formatBinary: err = copyPluginBinary(tmpFile.Name(), destPath) default: @@ -257,6 +267,16 @@ func extractFromTar(archivePath, targetName, destPath string) error { return extractTarStream(f, targetName, destPath) } +// matchArchiveEntry reports whether the archive entry name matches the +// target. If target contains a path separator it is matched as an exact +// path; otherwise only the base name of the entry is compared. +func matchArchiveEntry(entryName, target string) bool { + if strings.Contains(target, "/") { + return entryName == target + } + return filepath.Base(entryName) == target +} + // extractTarStream walks a tar stream and streams the first matching // regular file to destPath. Non-regular entries (symlinks, devices, // directories) and entries with unsafe paths are skipped. @@ -277,7 +297,7 @@ func extractTarStream(r io.Reader, targetName, destPath string) error { if !header.FileInfo().Mode().IsRegular() { continue } - if filepath.Base(header.Name) == targetName { + if matchArchiveEntry(header.Name, targetName) { return writeStreamToFile(tr, destPath) } } @@ -314,7 +334,7 @@ func extractFromZip(archivePath, targetName, destPath string) error { if !f.FileInfo().Mode().IsRegular() { continue } - if filepath.Base(f.Name) == targetName { + if matchArchiveEntry(f.Name, targetName) { rc, err := f.Open() if err != nil { return fmt.Errorf("failed to open %q in zip: %w", targetName, err) diff --git a/internal/plugin/install_test.go b/internal/plugin/install_test.go index e783ca97..43fb2845 100644 --- a/internal/plugin/install_test.go +++ b/internal/plugin/install_test.go @@ -673,6 +673,129 @@ func TestExtractFromZipRejectsUnsafeEntries(t *testing.T) { } } +func TestInstallExtractPath(t *testing.T) { + binaryContent := []byte("#!/bin/sh\necho nested") + + // Binary is nested at "subdir/flux-operator" inside the archive. + entries := []tarEntry{ + { + header: tar.Header{ + Name: "subdir/flux-operator", + Typeflag: tar.TypeReg, + Mode: 0o755, + }, + content: binaryContent, + }, + } + archive, err := createTestTarGzMulti(entries) + if err != nil { + t.Fatalf("failed to create archive: %v", err) + } + + checksum := fmt.Sprintf("sha256:%x", sha256.Sum256(archive)) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(archive) + })) + defer server.Close() + + pluginDir := t.TempDir() + + manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} + pv := &PluginVersion{Version: "0.45.0"} + plat := &PluginPlatform{ + OS: "linux", + Arch: "amd64", + URL: server.URL + "/archive.tar.gz", + Checksum: checksum, + ExtractPath: "subdir/flux-operator", + } + + installer := &Installer{HTTPClient: server.Client()} + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + t.Fatalf("install failed: %v", err) + } + + // Binary must be installed under manifest.Bin, not the extractPath. + binPath := filepath.Join(pluginDir, "flux-operator") + data, err := os.ReadFile(binPath) + if err != nil { + t.Fatalf("binary not found: %v", err) + } + if !bytes.Equal(data, binaryContent) { + t.Errorf("binary content mismatch") + } +} + +func TestInstallExtractPathZip(t *testing.T) { + binaryContent := []byte("#!/bin/sh\necho nested zip") + + archive, err := createTestZip([]zipEntry{ + {name: "pkg/bin/flux-operator", content: binaryContent}, + }) + if err != nil { + t.Fatalf("createTestZip: %v", err) + } + + checksum := fmt.Sprintf("sha256:%x", sha256.Sum256(archive)) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(archive) + })) + defer server.Close() + + pluginDir := t.TempDir() + + manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} + pv := &PluginVersion{Version: "0.45.0"} + plat := &PluginPlatform{ + OS: "linux", + Arch: "amd64", + URL: server.URL + "/archive.zip", + Checksum: checksum, + ExtractPath: "pkg/bin/flux-operator", + } + + installer := &Installer{HTTPClient: server.Client()} + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + t.Fatalf("install failed: %v", err) + } + + binPath := filepath.Join(pluginDir, "flux-operator") + data, err := os.ReadFile(binPath) + if err != nil { + t.Fatalf("binary not found: %v", err) + } + if !bytes.Equal(data, binaryContent) { + t.Errorf("binary content mismatch") + } +} + +func TestMatchArchiveEntry(t *testing.T) { + tests := []struct { + entry, target string + want bool + }{ + // Basename matching (no slash in target). + {"flux-operator", "flux-operator", true}, + {"bin/flux-operator", "flux-operator", true}, + {"deep/nested/flux-operator", "flux-operator", true}, + {"other-binary", "flux-operator", false}, + + // Exact path matching (slash in target). + {"bin/flux-operator", "bin/flux-operator", true}, + {"flux-operator", "bin/flux-operator", false}, + {"other/flux-operator", "bin/flux-operator", false}, + } + for _, tc := range tests { + t.Run(tc.entry+"_"+tc.target, func(t *testing.T) { + if got := matchArchiveEntry(tc.entry, tc.target); got != tc.want { + t.Errorf("matchArchiveEntry(%q, %q) = %v, want %v", tc.entry, tc.target, got, tc.want) + } + }) + } +} + func TestExtractFromTarGzNotFound(t *testing.T) { archive, err := createTestTarGz("other-binary", []byte("content")) if err != nil { diff --git a/internal/plugin/update.go b/internal/plugin/update.go index 15a02aab..6895fbef 100644 --- a/internal/plugin/update.go +++ b/internal/plugin/update.go @@ -25,15 +25,32 @@ const ( // When an update is available, Manifest, Version and Platform are // populated so the caller can install without re-fetching or re-resolving. type UpdateResult struct { - Name string + // Name is the plugin name. + Name string + + // FromVersion is the currently installed version. FromVersion string - ToVersion string - Skipped bool - SkipReason string - Manifest *PluginManifest - Version *PluginVersion - Platform *PluginPlatform - Err error + + // ToVersion is the latest available version. + ToVersion string + + // Skipped is true when the update was not performed. + Skipped bool + + // SkipReason explains why the update was skipped. + SkipReason string + + // Manifest is the resolved plugin manifest for the update. + Manifest *PluginManifest + + // Version is the resolved target version for the update. + Version *PluginVersion + + // Platform is the resolved platform entry for the update. + Platform *PluginPlatform + + // Err is set when the update check itself failed. + Err error } // CheckUpdate compares the installed version against the latest in the catalog. From 5256361d8cf80af9f723a580b46b70425ea1ca0b Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Tue, 14 Apr 2026 00:02:13 +0300 Subject: [PATCH 41/48] Make plugin types public Signed-off-by: Stefan Prodan --- internal/plugin/catalog.go | 138 ++++---------------------------- internal/plugin/catalog_test.go | 12 +-- internal/plugin/install.go | 12 +-- internal/plugin/install_test.go | 38 ++++----- internal/plugin/update.go | 10 ++- pkg/plugin/types.go | 135 +++++++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 153 deletions(-) create mode 100644 pkg/plugin/types.go diff --git a/internal/plugin/catalog.go b/internal/plugin/catalog.go index 1d370a81..019bfada 100644 --- a/internal/plugin/catalog.go +++ b/internal/plugin/catalog.go @@ -24,122 +24,16 @@ import ( "github.com/hashicorp/go-retryablehttp" "sigs.k8s.io/yaml" + + plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin" ) const ( // defaultCatalogBase points at the latest GitHub release of fluxcd/plugins. defaultCatalogBase = "https://github.com/fluxcd/plugins/releases/latest/download/" envCatalogBase = "FLUXCD_PLUGIN_CATALOG" - - pluginAPIVersion = "cli.fluxcd.io/v1beta1" - pluginKind = "Plugin" - catalogKind = "PluginCatalog" ) -// PluginManifest represents a single plugin's manifest from the catalog. -type PluginManifest struct { - // APIVersion is the manifest schema version (e.g. "cli.fluxcd.io/v1beta1"). - APIVersion string `json:"apiVersion"` - - // Kind is the manifest type, must be "Plugin". - Kind string `json:"kind"` - - // Name is the plugin name used in "flux " invocations. - Name string `json:"name"` - - // Description is a short human-readable summary of the plugin. - Description string `json:"description"` - - // Homepage is the URL to the plugin's documentation site. - Homepage string `json:"homepage,omitempty"` - - // Source is the URL to the plugin's source repository. - Source string `json:"source,omitempty"` - - // Bin is the binary name inside archives and the installed filename - // (e.g. "flux-operator"). On Windows ".exe" is appended automatically. - Bin string `json:"bin"` - - // Versions lists available versions, newest first. - Versions []PluginVersion `json:"versions"` -} - -// PluginVersion represents a version entry in a plugin manifest. -type PluginVersion struct { - // Version is the semantic version string (e.g. "0.45.0"). - Version string `json:"version"` - - // Platforms lists the platform-specific binaries for this version. - Platforms []PluginPlatform `json:"platforms"` -} - -// PluginPlatform represents a platform-specific binary entry. -type PluginPlatform struct { - // OS is the target operating system (e.g. "darwin", "linux", "windows"). - OS string `json:"os"` - - // Arch is the target architecture (e.g. "amd64", "arm64"). - Arch string `json:"arch"` - - // URL is the download URL for the archive or binary. - URL string `json:"url"` - - // Checksum is the expected digest in ":" format - // (e.g. "sha256:cd85d5d84d264..."). - Checksum string `json:"checksum"` - - // ExtractPath overrides the default binary lookup name inside archives. - // When set, it is matched as an exact path within the archive (e.g. - // "bin/flux-operator"). When empty, the archive is searched by the - // base name derived from the manifest's Bin field. - ExtractPath string `json:"extractPath,omitempty"` -} - -// PluginCatalog represents the generated catalog.yaml file. -type PluginCatalog struct { - // APIVersion is the catalog schema version (e.g. "cli.fluxcd.io/v1beta1"). - APIVersion string `json:"apiVersion"` - - // Kind is the catalog type, must be "PluginCatalog". - Kind string `json:"kind"` - - // Plugins lists all available plugins in the catalog. - Plugins []CatalogEntry `json:"plugins"` -} - -// CatalogEntry is a single entry in the plugin catalog. -type CatalogEntry struct { - // Name is the plugin name. - Name string `json:"name"` - - // Description is a short human-readable summary of the plugin. - Description string `json:"description"` - - // Homepage is the URL to the plugin's documentation site. - Homepage string `json:"homepage,omitempty"` - - // Source is the URL to the plugin's source repository. - Source string `json:"source,omitempty"` - - // License is the SPDX license identifier (e.g. "Apache-2.0"). - License string `json:"license,omitempty"` -} - -// Receipt records what was installed for a plugin. -type Receipt struct { - // Name is the plugin name (e.g. "operator"). - Name string `json:"name"` - - // Version is the installed semantic version. - Version string `json:"version"` - - // InstalledAt is the RFC 3339 timestamp of the installation. - InstalledAt string `json:"installedAt"` - - // Platform records the platform-specific details used for installation. - Platform PluginPlatform `json:"platform"` -} - // CatalogClient fetches plugin manifests and catalogs from a remote URL. type CatalogClient struct { // BaseURL is the catalog base URL for fetching manifests. @@ -170,46 +64,46 @@ func (c *CatalogClient) baseURL() string { } // FetchManifest fetches a single plugin manifest from the catalog. -func (c *CatalogClient) FetchManifest(name string) (*PluginManifest, error) { +func (c *CatalogClient) FetchManifest(name string) (*plugintypes.Manifest, error) { url := c.baseURL() + name + ".yaml" body, err := c.fetch(url) if err != nil { return nil, fmt.Errorf("plugin %q not found in catalog", name) } - var manifest PluginManifest + var manifest plugintypes.Manifest if err := yaml.Unmarshal(body, &manifest); err != nil { return nil, fmt.Errorf("failed to parse plugin manifest for %q: %w", name, err) } - if manifest.APIVersion != pluginAPIVersion { - return nil, fmt.Errorf("plugin %q has unsupported apiVersion %q (expected %q)", name, manifest.APIVersion, pluginAPIVersion) + if manifest.APIVersion != plugintypes.APIVersion { + return nil, fmt.Errorf("plugin %q has unsupported apiVersion %q (expected %q)", name, manifest.APIVersion, plugintypes.APIVersion) } - if manifest.Kind != pluginKind { - return nil, fmt.Errorf("plugin %q has unexpected kind %q (expected %q)", name, manifest.Kind, pluginKind) + if manifest.Kind != plugintypes.PluginKind { + return nil, fmt.Errorf("plugin %q has unexpected kind %q (expected %q)", name, manifest.Kind, plugintypes.PluginKind) } return &manifest, nil } // FetchCatalog fetches the generated catalog.yaml. -func (c *CatalogClient) FetchCatalog() (*PluginCatalog, error) { +func (c *CatalogClient) FetchCatalog() (*plugintypes.Catalog, error) { url := c.baseURL() + "catalog.yaml" body, err := c.fetch(url) if err != nil { return nil, fmt.Errorf("failed to fetch plugin catalog: %w", err) } - var catalog PluginCatalog + var catalog plugintypes.Catalog if err := yaml.Unmarshal(body, &catalog); err != nil { return nil, fmt.Errorf("failed to parse plugin catalog: %w", err) } - if catalog.APIVersion != pluginAPIVersion { - return nil, fmt.Errorf("plugin catalog has unsupported apiVersion %q (expected %q)", catalog.APIVersion, pluginAPIVersion) + if catalog.APIVersion != plugintypes.APIVersion { + return nil, fmt.Errorf("plugin catalog has unsupported apiVersion %q (expected %q)", catalog.APIVersion, plugintypes.APIVersion) } - if catalog.Kind != catalogKind { - return nil, fmt.Errorf("plugin catalog has unexpected kind %q (expected %q)", catalog.Kind, catalogKind) + if catalog.Kind != plugintypes.CatalogKind { + return nil, fmt.Errorf("plugin catalog has unexpected kind %q (expected %q)", catalog.Kind, plugintypes.CatalogKind) } return &catalog, nil @@ -243,7 +137,7 @@ func newHTTPClient(timeout time.Duration) *http.Client { // ResolveVersion finds the requested version in the manifest. // If version is empty, returns the first (latest) version. -func ResolveVersion(manifest *PluginManifest, version string) (*PluginVersion, error) { +func ResolveVersion(manifest *plugintypes.Manifest, version string) (*plugintypes.Version, error) { if len(manifest.Versions) == 0 { return nil, fmt.Errorf("plugin %q has no versions", manifest.Name) } @@ -262,7 +156,7 @@ func ResolveVersion(manifest *PluginManifest, version string) (*PluginVersion, e } // ResolvePlatform finds the platform entry matching the given OS and arch. -func ResolvePlatform(pv *PluginVersion, goos, goarch string) (*PluginPlatform, error) { +func ResolvePlatform(pv *plugintypes.Version, goos, goarch string) (*plugintypes.Platform, error) { for i := range pv.Platforms { if pv.Platforms[i].OS == goos && pv.Platforms[i].Arch == goarch { return &pv.Platforms[i], nil diff --git a/internal/plugin/catalog_test.go b/internal/plugin/catalog_test.go index 8134a010..e6134fc6 100644 --- a/internal/plugin/catalog_test.go +++ b/internal/plugin/catalog_test.go @@ -20,6 +20,8 @@ import ( "net/http" "net/http/httptest" "testing" + + plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin" ) func TestFetchManifest(t *testing.T) { @@ -168,9 +170,9 @@ plugins: [] } func TestResolveVersion(t *testing.T) { - manifest := &PluginManifest{ + manifest := &plugintypes.Manifest{ Name: "operator", - Versions: []PluginVersion{ + Versions: []plugintypes.Version{ {Version: "0.45.0"}, {Version: "0.44.0"}, }, @@ -204,7 +206,7 @@ func TestResolveVersion(t *testing.T) { }) t.Run("no versions", func(t *testing.T) { - _, err := ResolveVersion(&PluginManifest{Name: "empty"}, "") + _, err := ResolveVersion(&plugintypes.Manifest{Name: "empty"}, "") if err == nil { t.Fatal("expected error, got nil") } @@ -212,9 +214,9 @@ func TestResolveVersion(t *testing.T) { } func TestResolvePlatform(t *testing.T) { - pv := &PluginVersion{ + pv := &plugintypes.Version{ Version: "0.45.0", - Platforms: []PluginPlatform{ + Platforms: []plugintypes.Platform{ {OS: "darwin", Arch: "arm64", URL: "https://example.com/darwin_arm64.tar.gz"}, {OS: "linux", Arch: "amd64", URL: "https://example.com/linux_amd64.tar.gz"}, }, diff --git a/internal/plugin/install.go b/internal/plugin/install.go index 1589e2e2..7b2129e6 100644 --- a/internal/plugin/install.go +++ b/internal/plugin/install.go @@ -31,6 +31,8 @@ import ( "time" "sigs.k8s.io/yaml" + + plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin" ) // Installer handles downloading, verifying, and installing plugins. @@ -48,7 +50,7 @@ func NewInstaller() *Installer { // Install downloads, verifies, extracts, and installs a plugin binary // to the given plugin directory. -func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *PluginVersion, plat *PluginPlatform) error { +func (inst *Installer) Install(pluginDir string, manifest *plugintypes.Manifest, pv *plugintypes.Version, plat *plugintypes.Platform) error { tmpFile, err := os.CreateTemp("", "flux-plugin-*") if err != nil { return fmt.Errorf("failed to create temp file: %w", err) @@ -118,7 +120,7 @@ func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *P return err } - receipt := Receipt{ + receipt := plugintypes.Receipt{ Name: manifest.Name, Version: pv.Version, InstalledAt: time.Now().UTC().Format(time.RFC3339), @@ -156,13 +158,13 @@ func Uninstall(pluginDir, name string) error { // ReadReceipt reads the install receipt for a plugin. // Returns nil if no receipt exists. -func ReadReceipt(pluginDir, name string) *Receipt { +func ReadReceipt(pluginDir, name string) *plugintypes.Receipt { data, err := os.ReadFile(receiptPath(pluginDir, name)) if err != nil { return nil } - var receipt Receipt + var receipt plugintypes.Receipt if err := yaml.Unmarshal(data, &receipt); err != nil { return nil } @@ -174,7 +176,7 @@ func receiptPath(pluginDir, name string) string { return filepath.Join(pluginDir, pluginPrefix+name+".yaml") } -func writeReceipt(pluginDir, name string, receipt *Receipt) error { +func writeReceipt(pluginDir, name string, receipt *plugintypes.Receipt) error { data, err := yaml.Marshal(receipt) if err != nil { return fmt.Errorf("failed to marshal receipt: %w", err) diff --git a/internal/plugin/install_test.go b/internal/plugin/install_test.go index 43fb2845..dc2dd587 100644 --- a/internal/plugin/install_test.go +++ b/internal/plugin/install_test.go @@ -31,6 +31,8 @@ import ( "runtime" "strings" "testing" + + plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin" ) // createTestTarGz creates a tar.gz archive containing a single file. @@ -164,12 +166,12 @@ func TestInstall(t *testing.T) { pluginDir := t.TempDir() - manifest := &PluginManifest{ + manifest := &plugintypes.Manifest{ Name: "operator", Bin: "flux-operator", } - pv := &PluginVersion{Version: "0.45.0"} - plat := &PluginPlatform{ + pv := &plugintypes.Version{Version: "0.45.0"} + plat := &plugintypes.Platform{ OS: "linux", Arch: "amd64", URL: server.URL + "/flux-operator_0.45.0_linux_amd64.tar.gz", @@ -218,9 +220,9 @@ func TestInstallChecksumMismatch(t *testing.T) { pluginDir := t.TempDir() - manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} - pv := &PluginVersion{Version: "0.45.0"} - plat := &PluginPlatform{ + manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"} + pv := &plugintypes.Version{Version: "0.45.0"} + plat := &plugintypes.Platform{ OS: "linux", Arch: "amd64", URL: server.URL + "/archive.tar.gz", @@ -253,9 +255,9 @@ func TestInstallBinaryNotInArchive(t *testing.T) { pluginDir := t.TempDir() - manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} - pv := &PluginVersion{Version: "0.45.0"} - plat := &PluginPlatform{ + manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"} + pv := &plugintypes.Version{Version: "0.45.0"} + plat := &plugintypes.Platform{ OS: "linux", Arch: "amd64", URL: server.URL + "/archive.tar.gz", @@ -396,12 +398,12 @@ func TestInstallRawBinary(t *testing.T) { pluginDir := t.TempDir() - manifest := &PluginManifest{ + manifest := &plugintypes.Manifest{ Name: "validate", Bin: "flux-validate", } - pv := &PluginVersion{Version: "1.2.3"} - plat := &PluginPlatform{ + pv := &plugintypes.Version{Version: "1.2.3"} + plat := &plugintypes.Platform{ OS: runtime.GOOS, Arch: runtime.GOARCH, // URL filename deliberately differs from manifest.Bin — mimics a @@ -701,9 +703,9 @@ func TestInstallExtractPath(t *testing.T) { pluginDir := t.TempDir() - manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} - pv := &PluginVersion{Version: "0.45.0"} - plat := &PluginPlatform{ + manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"} + pv := &plugintypes.Version{Version: "0.45.0"} + plat := &plugintypes.Platform{ OS: "linux", Arch: "amd64", URL: server.URL + "/archive.tar.gz", @@ -746,9 +748,9 @@ func TestInstallExtractPathZip(t *testing.T) { pluginDir := t.TempDir() - manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"} - pv := &PluginVersion{Version: "0.45.0"} - plat := &PluginPlatform{ + manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"} + pv := &plugintypes.Version{Version: "0.45.0"} + plat := &plugintypes.Platform{ OS: "linux", Arch: "amd64", URL: server.URL + "/archive.zip", diff --git a/internal/plugin/update.go b/internal/plugin/update.go index 6895fbef..6ce87e79 100644 --- a/internal/plugin/update.go +++ b/internal/plugin/update.go @@ -16,6 +16,10 @@ limitations under the License. package plugin +import ( + plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin" +) + const ( SkipReasonManual = "manually installed" SkipReasonUpToDate = "already up to date" @@ -41,13 +45,13 @@ type UpdateResult struct { SkipReason string // Manifest is the resolved plugin manifest for the update. - Manifest *PluginManifest + Manifest *plugintypes.Manifest // Version is the resolved target version for the update. - Version *PluginVersion + Version *plugintypes.Version // Platform is the resolved platform entry for the update. - Platform *PluginPlatform + Platform *plugintypes.Platform // Err is set when the update check itself failed. Err error diff --git a/pkg/plugin/types.go b/pkg/plugin/types.go new file mode 100644 index 00000000..e09d4f30 --- /dev/null +++ b/pkg/plugin/types.go @@ -0,0 +1,135 @@ +/* +Copyright 2026 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 plugin defines the public types for the Flux CLI plugin system. +// These types represent the plugin catalog schema (cli.fluxcd.io/v1beta1) +// and are safe for use by external consumers. +package plugin + +const ( + // APIVersion is the plugin manifest schema version. + APIVersion = "cli.fluxcd.io/v1beta1" + + // PluginKind is the kind for plugin manifests. + PluginKind = "Plugin" + + // CatalogKind is the kind for the plugin catalog. + CatalogKind = "PluginCatalog" +) + +// Manifest represents a single plugin's manifest from the catalog. +type Manifest struct { + // APIVersion is the manifest schema version (e.g. "cli.fluxcd.io/v1beta1"). + APIVersion string `json:"apiVersion"` + + // Kind is the manifest type, must be "Plugin". + Kind string `json:"kind"` + + // Name is the plugin name used in "flux " invocations. + Name string `json:"name"` + + // Description is a short human-readable summary of the plugin. + Description string `json:"description"` + + // Homepage is the URL to the plugin's documentation site. + Homepage string `json:"homepage,omitempty"` + + // Source is the URL to the plugin's source repository. + Source string `json:"source,omitempty"` + + // Bin is the binary name inside archives and the installed filename + // (e.g. "flux-operator"). On Windows ".exe" is appended automatically. + Bin string `json:"bin"` + + // Versions lists available versions, newest first. + Versions []Version `json:"versions"` +} + +// Version represents a version entry in a plugin manifest. +type Version struct { + // Version is the semantic version string (e.g. "0.45.0"). + Version string `json:"version"` + + // Platforms lists the platform-specific binaries for this version. + Platforms []Platform `json:"platforms"` +} + +// Platform represents a platform-specific binary entry. +type Platform struct { + // OS is the target operating system (e.g. "darwin", "linux", "windows"). + OS string `json:"os"` + + // Arch is the target architecture (e.g. "amd64", "arm64"). + Arch string `json:"arch"` + + // URL is the download URL for the archive or binary. + URL string `json:"url"` + + // Checksum is the expected digest in ":" format + // (e.g. "sha256:cd85d5d84d264..."). + Checksum string `json:"checksum"` + + // ExtractPath overrides the default binary lookup name inside archives. + // When set, it is matched as an exact path within the archive (e.g. + // "bin/flux-operator"). When empty, the archive is searched by the + // base name derived from the manifest's Bin field. + ExtractPath string `json:"extractPath,omitempty"` +} + +// Catalog represents the generated catalog.yaml file. +type Catalog struct { + // APIVersion is the catalog schema version (e.g. "cli.fluxcd.io/v1beta1"). + APIVersion string `json:"apiVersion"` + + // Kind is the catalog type, must be "PluginCatalog". + Kind string `json:"kind"` + + // Plugins lists all available plugins in the catalog. + Plugins []CatalogEntry `json:"plugins"` +} + +// CatalogEntry is a single entry in the plugin catalog. +type CatalogEntry struct { + // Name is the plugin name. + Name string `json:"name"` + + // Description is a short human-readable summary of the plugin. + Description string `json:"description"` + + // Homepage is the URL to the plugin's documentation site. + Homepage string `json:"homepage"` + + // Source is the URL to the plugin's source repository. + Source string `json:"source"` + + // License is the SPDX license identifier (e.g. "Apache-2.0"). + License string `json:"license"` +} + +// Receipt records what was installed for a plugin. +type Receipt struct { + // Name is the plugin name (e.g. "operator"). + Name string `json:"name"` + + // Version is the installed semantic version. + Version string `json:"version"` + + // InstalledAt is the RFC 3339 timestamp of the installation. + InstalledAt string `json:"installedAt"` + + // Platform records the platform-specific details used for installation. + Platform Platform `json:"platform"` +} From 474efa09cf2681c6a7fec5a763ecb3f596e0d96c Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 20 Apr 2026 21:42:42 +0300 Subject: [PATCH 42/48] Split plugin commands into individual files Signed-off-by: Stefan Prodan --- cmd/flux/plugin.go | 228 ----------------------------------- cmd/flux/plugin_install.go | 80 ++++++++++++ cmd/flux/plugin_list.go | 57 +++++++++ cmd/flux/plugin_search.go | 81 +++++++++++++ cmd/flux/plugin_uninstall.go | 48 ++++++++ cmd/flux/plugin_update.go | 102 ++++++++++++++++ 6 files changed, 368 insertions(+), 228 deletions(-) create mode 100644 cmd/flux/plugin_install.go create mode 100644 cmd/flux/plugin_list.go create mode 100644 cmd/flux/plugin_search.go create mode 100644 cmd/flux/plugin_uninstall.go create mode 100644 cmd/flux/plugin_update.go diff --git a/cmd/flux/plugin.go b/cmd/flux/plugin.go index 44df6385..efeb3df3 100644 --- a/cmd/flux/plugin.go +++ b/cmd/flux/plugin.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "runtime" "strings" "time" @@ -26,7 +25,6 @@ import ( "github.com/spf13/cobra" "github.com/fluxcd/flux2/v2/internal/plugin" - "github.com/fluxcd/flux2/v2/pkg/printers" ) var pluginHandler = plugin.NewHandler() @@ -41,66 +39,7 @@ var pluginCmd = &cobra.Command{ }, } -var pluginListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List installed plugins", - Long: `The plugin list command shows all installed plugins with their versions and paths.`, - RunE: pluginListCmdRun, -} - -var pluginInstallCmd = &cobra.Command{ - Use: "install [@]", - Short: "Install a plugin from the catalog", - Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog. - -Examples: - # Install the latest version - flux plugin install operator - - # Install a specific version - flux plugin install operator@0.45.0`, - Args: cobra.ExactArgs(1), - RunE: pluginInstallCmdRun, -} - -var pluginUninstallCmd = &cobra.Command{ - Use: "uninstall ", - Short: "Uninstall a plugin", - Long: `The plugin uninstall command removes a plugin binary and its receipt from the plugin directory.`, - Args: cobra.ExactArgs(1), - RunE: pluginUninstallCmdRun, -} - -var pluginUpdateCmd = &cobra.Command{ - Use: "update [name]", - Short: "Update installed plugins", - Long: `The plugin update command updates installed plugins to their latest versions. - -Examples: - # Update a single plugin - flux plugin update operator - - # Update all installed plugins - flux plugin update`, - Args: cobra.MaximumNArgs(1), - RunE: pluginUpdateCmdRun, -} - -var pluginSearchCmd = &cobra.Command{ - Use: "search [query]", - Short: "Search the plugin catalog", - Long: `The plugin search command lists available plugins from the Flux plugin catalog.`, - Args: cobra.MaximumNArgs(1), - RunE: pluginSearchCmdRun, -} - func init() { - pluginCmd.AddCommand(pluginListCmd) - pluginCmd.AddCommand(pluginInstallCmd) - pluginCmd.AddCommand(pluginUninstallCmd) - pluginCmd.AddCommand(pluginUpdateCmd) - pluginCmd.AddCommand(pluginSearchCmd) rootCmd.AddCommand(pluginCmd) } @@ -149,173 +88,6 @@ func registerPlugins() { } } -func pluginListCmdRun(cmd *cobra.Command, args []string) error { - pluginDir := pluginHandler.PluginDir() - plugins := pluginHandler.Discover(builtinCommandNames()) - if len(plugins) == 0 { - cmd.Println("No plugins found") - return nil - } - - header := []string{"NAME", "VERSION", "PATH"} - var rows [][]string - for _, p := range plugins { - version := "manual" - if receipt := plugin.ReadReceipt(pluginDir, p.Name); receipt != nil { - version = receipt.Version - } - rows = append(rows, []string{p.Name, version, p.Path}) - } - - return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) -} - -func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { - nameVersion := args[0] - name, version := parseNameVersion(nameVersion) - - catalogClient := newCatalogClient() - manifest, err := catalogClient.FetchManifest(name) - if err != nil { - return err - } - - pv, err := plugin.ResolveVersion(manifest, version) - if err != nil { - return err - } - - plat, err := plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) - if err != nil { - return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) - } - - pluginDir := pluginHandler.EnsurePluginDir() - - installer := plugin.NewInstaller() - sp := newPluginSpinner(fmt.Sprintf("installing %s v%s", name, pv.Version)) - sp.Start() - if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { - sp.Stop() - return err - } - sp.Stop() - - logger.Successf("installed %s v%s", name, pv.Version) - return nil -} - -func pluginUninstallCmdRun(cmd *cobra.Command, args []string) error { - name := args[0] - pluginDir := pluginHandler.PluginDir() - - if err := plugin.Uninstall(pluginDir, name); err != nil { - return err - } - - logger.Successf("uninstalled %s", name) - return nil -} - -func pluginUpdateCmdRun(cmd *cobra.Command, args []string) error { - catalogClient := newCatalogClient() - - plugins := pluginHandler.Discover(builtinCommandNames()) - if len(plugins) == 0 { - cmd.Println("No plugins found") - return nil - } - - // If a specific plugin is requested, filter to just that one. - if len(args) == 1 { - name := args[0] - var found bool - for _, p := range plugins { - if p.Name == name { - plugins = []plugin.Plugin{p} - found = true - break - } - } - if !found { - return fmt.Errorf("plugin %q is not installed", name) - } - } - - pluginDir := pluginHandler.EnsurePluginDir() - installer := plugin.NewInstaller() - for _, p := range plugins { - result := plugin.CheckUpdate(pluginDir, p.Name, catalogClient, runtime.GOOS, runtime.GOARCH) - if result.Err != nil { - logger.Failuref("error checking %s: %v", p.Name, result.Err) - continue - } - if result.Skipped { - if result.SkipReason == plugin.SkipReasonManual { - logger.Warningf("skipping %s (%s)", p.Name, result.SkipReason) - } else { - logger.Successf("%s already up to date (v%s)", p.Name, result.FromVersion) - } - continue - } - - sp := newPluginSpinner(fmt.Sprintf("updating %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion)) - sp.Start() - if err := installer.Install(pluginDir, result.Manifest, result.Version, result.Platform); err != nil { - sp.Stop() - logger.Failuref("error updating %s: %v", p.Name, err) - continue - } - sp.Stop() - logger.Successf("updated %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion) - } - - return nil -} - -func pluginSearchCmdRun(cmd *cobra.Command, args []string) error { - catalogClient := newCatalogClient() - catalog, err := catalogClient.FetchCatalog() - if err != nil { - return err - } - - var query string - if len(args) == 1 { - query = strings.ToLower(args[0]) - } - - pluginDir := pluginHandler.PluginDir() - header := []string{"NAME", "DESCRIPTION", "INSTALLED"} - var rows [][]string - for _, entry := range catalog.Plugins { - if query != "" { - if !strings.Contains(strings.ToLower(entry.Name), query) && - !strings.Contains(strings.ToLower(entry.Description), query) { - continue - } - } - - installed := "" - if receipt := plugin.ReadReceipt(pluginDir, entry.Name); receipt != nil { - installed = receipt.Version - } - - rows = append(rows, []string{entry.Name, entry.Description, installed}) - } - - if len(rows) == 0 { - if query != "" { - cmd.Printf("No plugins matching %q found in catalog\n", query) - } else { - cmd.Println("No plugins found in catalog") - } - return nil - } - - return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) -} - // parseNameVersion splits "operator@0.45.0" into ("operator", "0.45.0"). // If no @ is present, version is empty (latest). func parseNameVersion(s string) (string, string) { diff --git a/cmd/flux/plugin_install.go b/cmd/flux/plugin_install.go new file mode 100644 index 00000000..71b9b643 --- /dev/null +++ b/cmd/flux/plugin_install.go @@ -0,0 +1,80 @@ +/* +Copyright 2026 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 main + +import ( + "fmt" + "runtime" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +var pluginInstallCmd = &cobra.Command{ + Use: "install [@]", + Short: "Install a plugin from the catalog", + Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog. + +Examples: + # Install the latest version + flux plugin install operator + + # Install a specific version + flux plugin install operator@0.45.0`, + Args: cobra.ExactArgs(1), + RunE: pluginInstallCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginInstallCmd) +} + +func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { + nameVersion := args[0] + name, version := parseNameVersion(nameVersion) + + catalogClient := newCatalogClient() + manifest, err := catalogClient.FetchManifest(name) + if err != nil { + return err + } + + pv, err := plugin.ResolveVersion(manifest, version) + if err != nil { + return err + } + + plat, err := plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) + if err != nil { + return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) + } + + pluginDir := pluginHandler.EnsurePluginDir() + + installer := plugin.NewInstaller() + sp := newPluginSpinner(fmt.Sprintf("installing %s v%s", name, pv.Version)) + sp.Start() + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + sp.Stop() + return err + } + sp.Stop() + + logger.Successf("installed %s v%s", name, pv.Version) + return nil +} diff --git a/cmd/flux/plugin_list.go b/cmd/flux/plugin_list.go new file mode 100644 index 00000000..cdc58ddc --- /dev/null +++ b/cmd/flux/plugin_list.go @@ -0,0 +1,57 @@ +/* +Copyright 2026 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 main + +import ( + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" + "github.com/fluxcd/flux2/v2/pkg/printers" +) + +var pluginListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List installed plugins", + Long: `The plugin list command shows all installed plugins with their versions and paths.`, + RunE: pluginListCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginListCmd) +} + +func pluginListCmdRun(cmd *cobra.Command, args []string) error { + pluginDir := pluginHandler.PluginDir() + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + cmd.Println("No plugins found") + return nil + } + + header := []string{"NAME", "VERSION", "PATH"} + var rows [][]string + for _, p := range plugins { + version := "manual" + if receipt := plugin.ReadReceipt(pluginDir, p.Name); receipt != nil { + version = receipt.Version + } + rows = append(rows, []string{p.Name, version, p.Path}) + } + + return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) +} diff --git a/cmd/flux/plugin_search.go b/cmd/flux/plugin_search.go new file mode 100644 index 00000000..0c9add19 --- /dev/null +++ b/cmd/flux/plugin_search.go @@ -0,0 +1,81 @@ +/* +Copyright 2026 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 main + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" + "github.com/fluxcd/flux2/v2/pkg/printers" +) + +var pluginSearchCmd = &cobra.Command{ + Use: "search [query]", + Short: "Search the plugin catalog", + Long: `The plugin search command lists available plugins from the Flux plugin catalog.`, + Args: cobra.MaximumNArgs(1), + RunE: pluginSearchCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginSearchCmd) +} + +func pluginSearchCmdRun(cmd *cobra.Command, args []string) error { + catalogClient := newCatalogClient() + catalog, err := catalogClient.FetchCatalog() + if err != nil { + return err + } + + var query string + if len(args) == 1 { + query = strings.ToLower(args[0]) + } + + pluginDir := pluginHandler.PluginDir() + header := []string{"NAME", "DESCRIPTION", "INSTALLED"} + var rows [][]string + for _, entry := range catalog.Plugins { + if query != "" { + if !strings.Contains(strings.ToLower(entry.Name), query) && + !strings.Contains(strings.ToLower(entry.Description), query) { + continue + } + } + + installed := "" + if receipt := plugin.ReadReceipt(pluginDir, entry.Name); receipt != nil { + installed = receipt.Version + } + + rows = append(rows, []string{entry.Name, entry.Description, installed}) + } + + if len(rows) == 0 { + if query != "" { + cmd.Printf("No plugins matching %q found in catalog\n", query) + } else { + cmd.Println("No plugins found in catalog") + } + return nil + } + + return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) +} diff --git a/cmd/flux/plugin_uninstall.go b/cmd/flux/plugin_uninstall.go new file mode 100644 index 00000000..844f6d5a --- /dev/null +++ b/cmd/flux/plugin_uninstall.go @@ -0,0 +1,48 @@ +/* +Copyright 2026 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 main + +import ( + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +var pluginUninstallCmd = &cobra.Command{ + Use: "uninstall ", + Aliases: []string{"delete"}, + Short: "Uninstall a plugin", + Long: `The plugin uninstall command removes a plugin binary and its receipt from the plugin directory.`, + Args: cobra.ExactArgs(1), + RunE: pluginUninstallCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginUninstallCmd) +} + +func pluginUninstallCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + pluginDir := pluginHandler.PluginDir() + + if err := plugin.Uninstall(pluginDir, name); err != nil { + return err + } + + logger.Successf("uninstalled %s", name) + return nil +} diff --git a/cmd/flux/plugin_update.go b/cmd/flux/plugin_update.go new file mode 100644 index 00000000..db3a5e0e --- /dev/null +++ b/cmd/flux/plugin_update.go @@ -0,0 +1,102 @@ +/* +Copyright 2026 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 main + +import ( + "fmt" + "runtime" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +var pluginUpdateCmd = &cobra.Command{ + Use: "update [name]", + Aliases: []string{"upgrade"}, + Short: "Update installed plugins", + Long: `The plugin update command updates installed plugins to their latest versions. + +Examples: + # Update a single plugin + flux plugin update operator + + # Update all installed plugins + flux plugin update`, + Args: cobra.MaximumNArgs(1), + RunE: pluginUpdateCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginUpdateCmd) +} + +func pluginUpdateCmdRun(cmd *cobra.Command, args []string) error { + catalogClient := newCatalogClient() + + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + cmd.Println("No plugins found") + return nil + } + + // If a specific plugin is requested, filter to just that one. + if len(args) == 1 { + name := args[0] + var found bool + for _, p := range plugins { + if p.Name == name { + plugins = []plugin.Plugin{p} + found = true + break + } + } + if !found { + return fmt.Errorf("plugin %q is not installed", name) + } + } + + pluginDir := pluginHandler.EnsurePluginDir() + installer := plugin.NewInstaller() + for _, p := range plugins { + result := plugin.CheckUpdate(pluginDir, p.Name, catalogClient, runtime.GOOS, runtime.GOARCH) + if result.Err != nil { + logger.Failuref("error checking %s: %v", p.Name, result.Err) + continue + } + if result.Skipped { + if result.SkipReason == plugin.SkipReasonManual { + logger.Warningf("skipping %s (%s)", p.Name, result.SkipReason) + } else { + logger.Successf("%s already up to date (v%s)", p.Name, result.FromVersion) + } + continue + } + + sp := newPluginSpinner(fmt.Sprintf("updating %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion)) + sp.Start() + if err := installer.Install(pluginDir, result.Manifest, result.Version, result.Platform); err != nil { + sp.Stop() + logger.Failuref("error updating %s: %v", p.Name, err) + continue + } + sp.Stop() + logger.Successf("updated %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion) + } + + return nil +} From 19ab6eeb30fcb092ea047cc6065c6cbae412ca3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:26:09 +0000 Subject: [PATCH 43/48] build(deps): bump github.com/go-git/go-git/v5 from 5.17.1 to 5.18.0 Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.17.1 to 5.18.0. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.17.1...v5.18.0) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-version: 5.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bdc6f6b3..64ffac37 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/fluxcd/pkg/version v0.14.0 github.com/fluxcd/source-controller/api v1.8.2 github.com/fluxcd/source-watcher/api/v2 v2.1.1 - github.com/go-git/go-git/v5 v5.17.1 + github.com/go-git/go-git/v5 v5.18.0 github.com/go-logr/logr v1.4.3 github.com/gonvenience/bunt v1.4.2 github.com/gonvenience/ytbx v1.4.7 diff --git a/go.sum b/go.sum index 30e6b07e..6876267e 100644 --- a/go.sum +++ b/go.sum @@ -246,8 +246,8 @@ github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDz github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.17.1 h1:WnljyxIzSj9BRRUlnmAU35ohDsjRK0EKmL0evDqi5Jk= -github.com/go-git/go-git/v5 v5.17.1/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= +github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= +github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= From c1238ec83484c3432660239ab3cbb6b1cb77666b Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Tue, 21 Apr 2026 10:26:58 +0000 Subject: [PATCH 44/48] Update toolkit components - helm-controller to v1.5.4 https://github.com/fluxcd/helm-controller/blob/v1.5.4/CHANGELOG.md - kustomize-controller to v1.8.4 https://github.com/fluxcd/kustomize-controller/blob/v1.8.4/CHANGELOG.md - source-controller to v1.8.3 https://github.com/fluxcd/source-controller/blob/v1.8.3/CHANGELOG.md - notification-controller to v1.8.4 https://github.com/fluxcd/notification-controller/blob/v1.8.4/CHANGELOG.md - image-automation-controller to v1.1.2 https://github.com/fluxcd/image-automation-controller/blob/v1.1.2/CHANGELOG.md Signed-off-by: GitHub --- go.mod | 10 +++++----- go.sum | 20 +++++++++---------- .../bases/helm-controller/kustomization.yaml | 4 ++-- .../kustomization.yaml | 4 ++-- .../kustomize-controller/kustomization.yaml | 4 ++-- .../kustomization.yaml | 4 ++-- .../source-controller/kustomization.yaml | 4 ++-- manifests/crds/kustomization.yaml | 10 +++++----- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 64ffac37..f70fd894 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( github.com/distribution/distribution/v3 v3.1.0 github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/go-git-providers v0.26.0 - github.com/fluxcd/helm-controller/api v1.5.3 - github.com/fluxcd/image-automation-controller/api v1.1.1 + github.com/fluxcd/helm-controller/api v1.5.4 + github.com/fluxcd/image-automation-controller/api v1.1.2 github.com/fluxcd/image-reflector-controller/api v1.1.1 - github.com/fluxcd/kustomize-controller/api v1.8.3 - github.com/fluxcd/notification-controller/api v1.8.3 + github.com/fluxcd/kustomize-controller/api v1.8.4 + github.com/fluxcd/notification-controller/api v1.8.4 github.com/fluxcd/pkg/apis/event v0.25.0 github.com/fluxcd/pkg/apis/meta v1.26.0 github.com/fluxcd/pkg/auth v0.40.0 @@ -32,7 +32,7 @@ require ( github.com/fluxcd/pkg/ssh v0.24.0 github.com/fluxcd/pkg/tar v0.17.0 github.com/fluxcd/pkg/version v0.14.0 - github.com/fluxcd/source-controller/api v1.8.2 + github.com/fluxcd/source-controller/api v1.8.3 github.com/fluxcd/source-watcher/api/v2 v2.1.1 github.com/go-git/go-git/v5 v5.18.0 github.com/go-logr/logr v1.4.3 diff --git a/go.sum b/go.sum index 6876267e..069554d1 100644 --- a/go.sum +++ b/go.sum @@ -178,16 +178,16 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw= github.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo= -github.com/fluxcd/helm-controller/api v1.5.3 h1:ruLzuyTHjjE9A5B/U+Id2q7yHXXqSFTswdZ14xCS5So= -github.com/fluxcd/helm-controller/api v1.5.3/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI= -github.com/fluxcd/image-automation-controller/api v1.1.1 h1:uiu7kjdVoW8/461HOemX6I7RcPornEzQliWgTg6LnWI= -github.com/fluxcd/image-automation-controller/api v1.1.1/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= +github.com/fluxcd/helm-controller/api v1.5.4 h1:wbAwD+cSGBZEhT3qq1naBKkitdNbqRtWQUFNA3XTXOc= +github.com/fluxcd/helm-controller/api v1.5.4/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI= +github.com/fluxcd/image-automation-controller/api v1.1.2 h1:Maa4qycz+iBCN/yJv9xNvuuj2IYihDAsSVwpHLhxkuk= +github.com/fluxcd/image-automation-controller/api v1.1.2/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= github.com/fluxcd/image-reflector-controller/api v1.1.1 h1:4Bj1abzVnjj8+b/293kNeFMRJc+y2wO8Z12ReZ/gA0w= github.com/fluxcd/image-reflector-controller/api v1.1.1/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0= -github.com/fluxcd/kustomize-controller/api v1.8.3 h1:Ux9AAOY0lkP6FgRg5/b/ITvRSy8lz6VBBaZ9bXmTLmI= -github.com/fluxcd/kustomize-controller/api v1.8.3/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= -github.com/fluxcd/notification-controller/api v1.8.3 h1:edYpC/t4pNw/KQur189SRC1XtFNU597ooDTCrW90Xmw= -github.com/fluxcd/notification-controller/api v1.8.3/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA= +github.com/fluxcd/kustomize-controller/api v1.8.4 h1:13+9CgxGj67Bn7wzFILw8S4r4urEgiLTODwjA4XU0mU= +github.com/fluxcd/kustomize-controller/api v1.8.4/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= +github.com/fluxcd/notification-controller/api v1.8.4 h1:KhHHVhQNtQsY+cVm/Y/8vhhFfrEOxM2AL/8JF8LAjMg= +github.com/fluxcd/notification-controller/api v1.8.4/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE= @@ -224,8 +224,8 @@ github.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY github.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls= github.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw= github.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= -github.com/fluxcd/source-controller/api v1.8.2 h1:i0/6BeNCn+zRfX+gKh4PsFF2NBzBhwXt0wPImVlZObg= -github.com/fluxcd/source-controller/api v1.8.2/go.mod h1:HgZ6NSH1cyOE2jRoNwln1xEwr9ETvrLeiy1o4O04vQM= +github.com/fluxcd/source-controller/api v1.8.3 h1:WNEETjmp/YTZx5IMg9ewz2Wn8YzOVETeJJ0LIPivm40= +github.com/fluxcd/source-controller/api v1.8.3/go.mod h1:sio4t49RDx+S1etHRFAEEw8qfVuw0KKlOg8bRVlEYPM= github.com/fluxcd/source-watcher/api/v2 v2.1.1 h1:1LfT50ty+78MKKbschAZl28QbVqIyjaNq17KmW5wPJI= github.com/fluxcd/source-watcher/api/v2 v2.1.1/go.mod h1:6M1BzBGQRoIuSenSQlfJHwMVVobFPiNPxXqfN0IILc4= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/manifests/bases/helm-controller/kustomization.yaml b/manifests/bases/helm-controller/kustomization.yaml index 1bafd6f3..53c65236 100644 --- a/manifests/bases/helm-controller/kustomization.yaml +++ b/manifests/bases/helm-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.deployment.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.4/helm-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.4/helm-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/image-automation-controller/kustomization.yaml b/manifests/bases/image-automation-controller/kustomization.yaml index 3d8efe05..79744aae 100644 --- a/manifests/bases/image-automation-controller/kustomization.yaml +++ b/manifests/bases/image-automation-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.deployment.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.2/image-automation-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.2/image-automation-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index 2ad41b16..df4d84d8 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.3/kustomize-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.3/kustomize-controller.deployment.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.4/kustomize-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.4/kustomize-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/notification-controller/kustomization.yaml b/manifests/bases/notification-controller/kustomization.yaml index 344c9e29..162db78a 100644 --- a/manifests/bases/notification-controller/kustomization.yaml +++ b/manifests/bases/notification-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.3/notification-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.3/notification-controller.deployment.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.4/notification-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.4/notification-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index b2eef98d..940b7875 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.2/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v1.8.2/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.3/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.3/source-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index 3f3f914e..b4baa49f 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -1,10 +1,10 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.2/source-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.3/kustomize-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.8.3/notification-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.3/source-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.4/kustomize-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.5.4/helm-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.8.4/notification-controller.crds.yaml - https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.2/image-automation-controller.crds.yaml - https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml From df3878d36a0edfaa4c1577f74a10e6f79755219e Mon Sep 17 00:00:00 2001 From: iam-karan-suresh Date: Sun, 26 Apr 2026 12:20:14 +0530 Subject: [PATCH 45/48] feat: adding support digest pinning for flux plugin install Signed-off-by: iam-karan-suresh --- cmd/flux/plugin.go | 6 +++ cmd/flux/plugin_install.go | 36 +++++++++++++----- cmd/flux/plugin_test.go | 22 +++++++++++ internal/plugin/catalog.go | 30 +++++++++++++++ internal/plugin/catalog_test.go | 67 +++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 10 deletions(-) diff --git a/cmd/flux/plugin.go b/cmd/flux/plugin.go index efeb3df3..cf977b9e 100644 --- a/cmd/flux/plugin.go +++ b/cmd/flux/plugin.go @@ -98,6 +98,12 @@ func parseNameVersion(s string) (string, string) { return s, "" } +// isDigestRef reports whether ref is a content-addressable digest +// (e.g. "sha256:06e0a38..."). +func isDigestRef(ref string) bool { + return strings.HasPrefix(ref, "sha256:") +} + // newCatalogClient creates a CatalogClient that respects FLUXCD_PLUGIN_CATALOG. func newCatalogClient() *plugin.CatalogClient { client := plugin.NewCatalogClient() diff --git a/cmd/flux/plugin_install.go b/cmd/flux/plugin_install.go index 71b9b643..58341a80 100644 --- a/cmd/flux/plugin_install.go +++ b/cmd/flux/plugin_install.go @@ -23,10 +23,11 @@ import ( "github.com/spf13/cobra" "github.com/fluxcd/flux2/v2/internal/plugin" + plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin" ) var pluginInstallCmd = &cobra.Command{ - Use: "install [@]", + Use: "install [@|@]", Short: "Install a plugin from the catalog", Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog. @@ -35,7 +36,10 @@ Examples: flux plugin install operator # Install a specific version - flux plugin install operator@0.45.0`, + flux plugin install operator@0.45.0 + + # Install pinned to a specific digest + flux plugin install operator@sha256:06e0a38db4fa6bc9f705a577c7e58dc020bfe2618e45488599e5ef7bb62e3a8a`, Args: cobra.ExactArgs(1), RunE: pluginInstallCmdRun, } @@ -46,7 +50,7 @@ func init() { func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { nameVersion := args[0] - name, version := parseNameVersion(nameVersion) + name, ref := parseNameVersion(nameVersion) catalogClient := newCatalogClient() manifest, err := catalogClient.FetchManifest(name) @@ -54,14 +58,26 @@ func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { return err } - pv, err := plugin.ResolveVersion(manifest, version) - if err != nil { - return err - } + var pv *plugintypes.Version + var plat *plugintypes.Platform - plat, err := plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) - if err != nil { - return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) + if isDigestRef(ref) { + dm, err := plugin.ResolveByDigest(manifest, ref, runtime.GOOS, runtime.GOARCH) + if err != nil { + return err + } + pv = dm.Version + plat = dm.Platform + } else { + pv, err = plugin.ResolveVersion(manifest, ref) + if err != nil { + return err + } + + plat, err = plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) + if err != nil { + return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) + } } pluginDir := pluginHandler.EnsurePluginDir() diff --git a/cmd/flux/plugin_test.go b/cmd/flux/plugin_test.go index 90f1b482..2bcedab5 100644 --- a/cmd/flux/plugin_test.go +++ b/cmd/flux/plugin_test.go @@ -211,6 +211,7 @@ func TestParseNameVersion(t *testing.T) { {"operator@0.45.0", "operator", "0.45.0"}, {"my-tool@1.0.0", "my-tool", "1.0.0"}, {"plugin@", "plugin", ""}, + {"operator@sha256:abc123", "operator", "sha256:abc123"}, } for _, tt := range tests { @@ -226,6 +227,27 @@ func TestParseNameVersion(t *testing.T) { } } +func TestIsDigestRef(t *testing.T) { + tests := []struct { + input string + want bool + }{ + {"sha256:06e0a38db4fa6bc9f705a577c7e58dc020bfe2618e45488599e5ef7bb62e3a8a", true}, + {"0.45.0", false}, + {"", false}, + {"sha256", false}, + {"SHA256:abc", false}, // case-sensitive + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + if got := isDigestRef(tt.input); got != tt.want { + t.Errorf("isDigestRef(%q) = %v, want %v", tt.input, got, tt.want) + } + }) + } +} + func TestPluginDiscoverSkipsBuiltins(t *testing.T) { origHandler := pluginHandler defer func() { pluginHandler = origHandler }() diff --git a/internal/plugin/catalog.go b/internal/plugin/catalog.go index 019bfada..0cb76dbd 100644 --- a/internal/plugin/catalog.go +++ b/internal/plugin/catalog.go @@ -165,3 +165,33 @@ func ResolvePlatform(pv *plugintypes.Version, goos, goarch string) (*plugintypes return nil, fmt.Errorf("no binary for %s/%s", goos, goarch) } + +// DigestMatch holds the version and platform resolved from a digest lookup. +type DigestMatch struct { + Version *plugintypes.Version + Platform *plugintypes.Platform +} + +// ResolveByDigest scans all versions and platforms for a checksum matching +// digest. The digest must be in "algorithm:hex" format (e.g. +// "sha256:06e0a38..."). Only platforms matching goos/goarch are considered. +// Returns the first match (versions are ordered newest-first in the manifest). +func ResolveByDigest(manifest *plugintypes.Manifest, digest, goos, goarch string) (*DigestMatch, error) { + if len(manifest.Versions) == 0 { + return nil, fmt.Errorf("plugin %q has no versions", manifest.Name) + } + + for i := range manifest.Versions { + for j := range manifest.Versions[i].Platforms { + p := &manifest.Versions[i].Platforms[j] + if p.OS == goos && p.Arch == goarch && p.Checksum == digest { + return &DigestMatch{ + Version: &manifest.Versions[i], + Platform: p, + }, nil + } + } + } + + return nil, fmt.Errorf("digest %q not found for plugin %q on %s/%s", digest, manifest.Name, goos, goarch) +} diff --git a/internal/plugin/catalog_test.go b/internal/plugin/catalog_test.go index e6134fc6..6ffdffa3 100644 --- a/internal/plugin/catalog_test.go +++ b/internal/plugin/catalog_test.go @@ -239,3 +239,70 @@ func TestResolvePlatform(t *testing.T) { } }) } + +func TestResolveByDigest(t *testing.T) { + manifest := &plugintypes.Manifest{ + Name: "operator", + Versions: []plugintypes.Version{ + { + Version: "0.46.0", + Platforms: []plugintypes.Platform{ + {OS: "linux", Arch: "amd64", URL: "https://example.com/v46_linux.tar.gz", Checksum: "sha256:aaaa"}, + {OS: "darwin", Arch: "arm64", URL: "https://example.com/v46_darwin.tar.gz", Checksum: "sha256:bbbb"}, + }, + }, + { + Version: "0.45.0", + Platforms: []plugintypes.Platform{ + {OS: "linux", Arch: "amd64", URL: "https://example.com/v45_linux.tar.gz", Checksum: "sha256:cccc"}, + {OS: "darwin", Arch: "arm64", URL: "https://example.com/v45_darwin.tar.gz", Checksum: "sha256:dddd"}, + }, + }, + }, + } + + t.Run("found in latest version", func(t *testing.T) { + dm, err := ResolveByDigest(manifest, "sha256:aaaa", "linux", "amd64") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if dm.Version.Version != "0.46.0" { + t.Errorf("expected version '0.46.0', got %q", dm.Version.Version) + } + if dm.Platform.Checksum != "sha256:aaaa" { + t.Errorf("expected checksum 'sha256:aaaa', got %q", dm.Platform.Checksum) + } + }) + + t.Run("found in older version", func(t *testing.T) { + dm, err := ResolveByDigest(manifest, "sha256:cccc", "linux", "amd64") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if dm.Version.Version != "0.45.0" { + t.Errorf("expected version '0.45.0', got %q", dm.Version.Version) + } + }) + + t.Run("wrong platform", func(t *testing.T) { + // sha256:bbbb exists for darwin/arm64, not linux/amd64. + _, err := ResolveByDigest(manifest, "sha256:bbbb", "linux", "amd64") + if err == nil { + t.Fatal("expected error, got nil") + } + }) + + t.Run("not found", func(t *testing.T) { + _, err := ResolveByDigest(manifest, "sha256:nonexistent", "linux", "amd64") + if err == nil { + t.Fatal("expected error, got nil") + } + }) + + t.Run("no versions", func(t *testing.T) { + _, err := ResolveByDigest(&plugintypes.Manifest{Name: "empty"}, "sha256:abc", "linux", "amd64") + if err == nil { + t.Fatal("expected error, got nil") + } + }) +} From c031d0c2157eb20879ec55eb19cebb24a5bd22a8 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Thu, 9 Apr 2026 22:32:11 +0100 Subject: [PATCH 46/48] Respect kubeconfig context namespace Signed-off-by: Jiri Tyr --- cmd/flux/main.go | 41 +++++- cmd/flux/main_context_ns_test.go | 221 +++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 cmd/flux/main_context_ns_test.go diff --git a/cmd/flux/main.go b/cmd/flux/main.go index 0e96614d..2c4662f6 100644 --- a/cmd/flux/main.go +++ b/cmd/flux/main.go @@ -100,6 +100,16 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`, # Uninstall Flux and delete CRDs flux uninstall`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // If opted in via --ns-follows-kube-context flag or + // FLUX_NS_FOLLOWS_KUBE_CONTEXT env var, and --namespace was not + // explicitly set, respect the namespace from the kubeconfig context. + if !cmd.Flags().Changed("namespace") && + (rootArgs.nsFollowsKubeContext || os.Getenv("FLUX_NS_FOLLOWS_KUBE_CONTEXT") != "") { + if ctxNs := getKubeconfigContextNamespace(kubeconfigArgs); ctxNs != "" { + *kubeconfigArgs.Namespace = ctxNs + } + } + ns, err := cmd.Flags().GetString("namespace") if err != nil { return fmt.Errorf("error getting namespace: %w", err) @@ -116,10 +126,11 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`, var logger = stderrLogger{stderr: os.Stderr} type rootFlags struct { - timeout time.Duration - verbose bool - pollInterval time.Duration - defaults install.Options + timeout time.Duration + verbose bool + pollInterval time.Duration + nsFollowsKubeContext bool + defaults install.Options } // RequestError is a custom error type that wraps an error returned by the flux api. @@ -139,6 +150,8 @@ var kubeclientOptions = new(runclient.Options) func init() { rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation") rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects") + rootCmd.PersistentFlags().BoolVar(&rootArgs.nsFollowsKubeContext, "ns-follows-kube-context", false, + "use the namespace from the kubeconfig context instead of the default flux-system namespace, can also be set via FLUX_NS_FOLLOWS_KUBE_CONTEXT env var") configureDefaultNamespace() kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag @@ -205,6 +218,26 @@ func main() { } } +// getKubeconfigContextNamespace returns the namespace from the current +// kubeconfig context, or an empty string if it cannot be determined. +func getKubeconfigContextNamespace(cf *genericclioptions.ConfigFlags) string { + rawConfig, err := cf.ToRawKubeConfigLoader().RawConfig() + if err != nil { + return "" + } + + currentContext := rawConfig.CurrentContext + if cf.Context != nil && *cf.Context != "" { + currentContext = *cf.Context + } + + if ctx, ok := rawConfig.Contexts[currentContext]; ok { + return ctx.Namespace + } + + return "" +} + func configureDefaultNamespace() { *kubeconfigArgs.Namespace = rootArgs.defaults.Namespace fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE") diff --git a/cmd/flux/main_context_ns_test.go b/cmd/flux/main_context_ns_test.go new file mode 100644 index 00000000..85fe82db --- /dev/null +++ b/cmd/flux/main_context_ns_test.go @@ -0,0 +1,221 @@ +/* +Copyright 2026 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 main + +import ( + "os" + "path/filepath" + "testing" + + . "github.com/onsi/gomega" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +func TestGetKubeconfigContextNamespace(t *testing.T) { + tests := []struct { + name string + kubeconfig string + context string + expectedResult string + }{ + { + name: "returns namespace from current context", + kubeconfig: `apiVersion: v1 +kind: Config +current-context: my-context +contexts: +- name: my-context + context: + cluster: my-cluster + namespace: custom-ns +clusters: +- name: my-cluster + cluster: + server: https://localhost:6443 +`, + expectedResult: "custom-ns", + }, + { + name: "returns empty when context has no namespace", + kubeconfig: `apiVersion: v1 +kind: Config +current-context: my-context +contexts: +- name: my-context + context: + cluster: my-cluster +clusters: +- name: my-cluster + cluster: + server: https://localhost:6443 +`, + expectedResult: "", + }, + { + name: "returns namespace from context specified via --context flag", + kubeconfig: `apiVersion: v1 +kind: Config +current-context: default-context +contexts: +- name: default-context + context: + cluster: my-cluster + namespace: default-ns +- name: other-context + context: + cluster: my-cluster + namespace: other-ns +clusters: +- name: my-cluster + cluster: + server: https://localhost:6443 +`, + context: "other-context", + expectedResult: "other-ns", + }, + { + name: "returns empty when context does not exist", + kubeconfig: `apiVersion: v1 +kind: Config +current-context: non-existent +contexts: [] +clusters: [] +`, + expectedResult: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + // Write temporary kubeconfig. + tmpDir := t.TempDir() + kcPath := filepath.Join(tmpDir, "kubeconfig") + g.Expect(os.WriteFile(kcPath, []byte(tt.kubeconfig), 0o600)).To(Succeed()) + + // Use a local ConfigFlags instance to avoid polluting the + // package-global kubeconfigArgs (which caches a clientConfig + // internally and would leak state across tests). + cf := genericclioptions.NewConfigFlags(false) + cf.KubeConfig = &kcPath + cf.Context = &tt.context + + got := getKubeconfigContextNamespace(cf) + g.Expect(got).To(Equal(tt.expectedResult)) + }) + } +} + +func TestContextNamespaceOptIn(t *testing.T) { + kubeconfig := `apiVersion: v1 +kind: Config +current-context: my-context +contexts: +- name: my-context + context: + cluster: my-cluster + namespace: context-ns +clusters: +- name: my-cluster + cluster: + server: https://localhost:6443 +` + + tests := []struct { + name string + nsFollowsFlag bool + nsFollowsEnv string + envNamespace string + flagNamespace string + expectedNamespace string + }{ + { + name: "ignores context namespace when not opted in", + expectedNamespace: rootArgs.defaults.Namespace, + }, + { + name: "uses context namespace when opted in via flag", + nsFollowsFlag: true, + expectedNamespace: "context-ns", + }, + { + name: "uses context namespace when opted in via env var", + nsFollowsEnv: "1", + expectedNamespace: "context-ns", + }, + { + name: "context namespace takes precedence over FLUX_SYSTEM_NAMESPACE when opted in", + nsFollowsFlag: true, + envNamespace: "env-ns", + expectedNamespace: "context-ns", + }, + { + name: "FLUX_SYSTEM_NAMESPACE used when not opted in", + envNamespace: "env-ns", + expectedNamespace: "env-ns", + }, + { + name: "--namespace flag takes precedence over context namespace", + nsFollowsFlag: true, + flagNamespace: "flag-ns", + expectedNamespace: "flag-ns", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + // Write temporary kubeconfig. + tmpDir := t.TempDir() + kcPath := filepath.Join(tmpDir, "kubeconfig") + g.Expect(os.WriteFile(kcPath, []byte(kubeconfig), 0o600)).To(Succeed()) + + // Use a local ConfigFlags instance to avoid polluting the + // package-global kubeconfigArgs. + cf := genericclioptions.NewConfigFlags(false) + cf.KubeConfig = &kcPath + emptyCtx := "" + cf.Context = &emptyCtx + + // Mirror configureDefaultNamespace behavior on the local instance. + defaultNs := rootArgs.defaults.Namespace + cf.Namespace = &defaultNs + + if tt.envNamespace != "" { + t.Setenv("FLUX_SYSTEM_NAMESPACE", tt.envNamespace) + envNs := tt.envNamespace + cf.Namespace = &envNs + } + if tt.nsFollowsEnv != "" { + t.Setenv("FLUX_NS_FOLLOWS_KUBE_CONTEXT", tt.nsFollowsEnv) + } + + // Simulate PersistentPreRunE behavior. + if tt.flagNamespace != "" { + *cf.Namespace = tt.flagNamespace + } else if tt.nsFollowsFlag || os.Getenv("FLUX_NS_FOLLOWS_KUBE_CONTEXT") != "" { + if ctxNs := getKubeconfigContextNamespace(cf); ctxNs != "" { + *cf.Namespace = ctxNs + } + } + + g.Expect(*cf.Namespace).To(Equal(tt.expectedNamespace)) + }) + } +} From 626bb58a694f3412afcde769dae4c38a4856a8c8 Mon Sep 17 00:00:00 2001 From: Thomas Morin Date: Wed, 6 May 2026 16:56:59 +0200 Subject: [PATCH 47/48] include source-watcher in install manifests Signed-off-by: Thomas Morin --- manifests/install/kustomization.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/manifests/install/kustomization.yaml b/manifests/install/kustomization.yaml index edce8ca8..d860bcf3 100644 --- a/manifests/install/kustomization.yaml +++ b/manifests/install/kustomization.yaml @@ -9,6 +9,7 @@ resources: - ../bases/helm-controller - ../bases/image-reflector-controller - ../bases/image-automation-controller + - ../bases/source-watcher - ../rbac - ../policies transformers: @@ -26,3 +27,6 @@ images: newName: ghcr.io/fluxcd/image-reflector-controller - name: fluxcd/image-automation-controller newName: ghcr.io/fluxcd/image-automation-controller + - name: fluxcd/source-watcher + newName: ghcr.io/fluxcd/source-watcher + From 4b5a433923b8ef8d2ff12cf308f3062bc5b9494e Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Tue, 12 May 2026 10:48:27 +0000 Subject: [PATCH 48/48] Update toolkit components - kustomize-controller to v1.8.5 https://github.com/fluxcd/kustomize-controller/blob/v1.8.5/CHANGELOG.md - source-controller to v1.8.4 https://github.com/fluxcd/source-controller/blob/v1.8.4/CHANGELOG.md - image-automation-controller to v1.1.3 https://github.com/fluxcd/image-automation-controller/blob/v1.1.3/CHANGELOG.md Signed-off-by: GitHub --- go.mod | 6 +++--- go.sum | 12 ++++++------ .../image-automation-controller/kustomization.yaml | 4 ++-- .../bases/kustomize-controller/kustomization.yaml | 4 ++-- manifests/bases/source-controller/kustomization.yaml | 4 ++-- manifests/crds/kustomization.yaml | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index f70fd894..bb9d036e 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,9 @@ require ( github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/go-git-providers v0.26.0 github.com/fluxcd/helm-controller/api v1.5.4 - github.com/fluxcd/image-automation-controller/api v1.1.2 + github.com/fluxcd/image-automation-controller/api v1.1.3 github.com/fluxcd/image-reflector-controller/api v1.1.1 - github.com/fluxcd/kustomize-controller/api v1.8.4 + github.com/fluxcd/kustomize-controller/api v1.8.5 github.com/fluxcd/notification-controller/api v1.8.4 github.com/fluxcd/pkg/apis/event v0.25.0 github.com/fluxcd/pkg/apis/meta v1.26.0 @@ -32,7 +32,7 @@ require ( github.com/fluxcd/pkg/ssh v0.24.0 github.com/fluxcd/pkg/tar v0.17.0 github.com/fluxcd/pkg/version v0.14.0 - github.com/fluxcd/source-controller/api v1.8.3 + github.com/fluxcd/source-controller/api v1.8.4 github.com/fluxcd/source-watcher/api/v2 v2.1.1 github.com/go-git/go-git/v5 v5.18.0 github.com/go-logr/logr v1.4.3 diff --git a/go.sum b/go.sum index 069554d1..2e37bfaa 100644 --- a/go.sum +++ b/go.sum @@ -180,12 +180,12 @@ github.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gm github.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo= github.com/fluxcd/helm-controller/api v1.5.4 h1:wbAwD+cSGBZEhT3qq1naBKkitdNbqRtWQUFNA3XTXOc= github.com/fluxcd/helm-controller/api v1.5.4/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI= -github.com/fluxcd/image-automation-controller/api v1.1.2 h1:Maa4qycz+iBCN/yJv9xNvuuj2IYihDAsSVwpHLhxkuk= -github.com/fluxcd/image-automation-controller/api v1.1.2/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= +github.com/fluxcd/image-automation-controller/api v1.1.3 h1:IlLFkg7suJ70pZmNYFchm4Et3thIDtgtj6jgDUejbr0= +github.com/fluxcd/image-automation-controller/api v1.1.3/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660= github.com/fluxcd/image-reflector-controller/api v1.1.1 h1:4Bj1abzVnjj8+b/293kNeFMRJc+y2wO8Z12ReZ/gA0w= github.com/fluxcd/image-reflector-controller/api v1.1.1/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0= -github.com/fluxcd/kustomize-controller/api v1.8.4 h1:13+9CgxGj67Bn7wzFILw8S4r4urEgiLTODwjA4XU0mU= -github.com/fluxcd/kustomize-controller/api v1.8.4/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= +github.com/fluxcd/kustomize-controller/api v1.8.5 h1:4fGPh6foGVKUUbt5OjVzbC5iTyX+Q+NS50atPboDC4w= +github.com/fluxcd/kustomize-controller/api v1.8.5/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc= github.com/fluxcd/notification-controller/api v1.8.4 h1:KhHHVhQNtQsY+cVm/Y/8vhhFfrEOxM2AL/8JF8LAjMg= github.com/fluxcd/notification-controller/api v1.8.4/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= @@ -224,8 +224,8 @@ github.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY github.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls= github.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw= github.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= -github.com/fluxcd/source-controller/api v1.8.3 h1:WNEETjmp/YTZx5IMg9ewz2Wn8YzOVETeJJ0LIPivm40= -github.com/fluxcd/source-controller/api v1.8.3/go.mod h1:sio4t49RDx+S1etHRFAEEw8qfVuw0KKlOg8bRVlEYPM= +github.com/fluxcd/source-controller/api v1.8.4 h1:ZJIh7OFhjxZgsD81ahxxbu3ggA7qySIjKWbK5+PKmOI= +github.com/fluxcd/source-controller/api v1.8.4/go.mod h1:sio4t49RDx+S1etHRFAEEw8qfVuw0KKlOg8bRVlEYPM= github.com/fluxcd/source-watcher/api/v2 v2.1.1 h1:1LfT50ty+78MKKbschAZl28QbVqIyjaNq17KmW5wPJI= github.com/fluxcd/source-watcher/api/v2 v2.1.1/go.mod h1:6M1BzBGQRoIuSenSQlfJHwMVVobFPiNPxXqfN0IILc4= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/manifests/bases/image-automation-controller/kustomization.yaml b/manifests/bases/image-automation-controller/kustomization.yaml index 79744aae..d9943543 100644 --- a/manifests/bases/image-automation-controller/kustomization.yaml +++ b/manifests/bases/image-automation-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.2/image-automation-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.2/image-automation-controller.deployment.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.3/image-automation-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.3/image-automation-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index df4d84d8..3472ab8d 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.4/kustomize-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.4/kustomize-controller.deployment.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.5/kustomize-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.5/kustomize-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index 940b7875..a0476190 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.3/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v1.8.3/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.4/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.4/source-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index b4baa49f..28710b0f 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -1,10 +1,10 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.8.3/source-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.4/kustomize-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.8.4/source-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.5/kustomize-controller.crds.yaml - https://github.com/fluxcd/helm-controller/releases/download/v1.5.4/helm-controller.crds.yaml - https://github.com/fluxcd/notification-controller/releases/download/v1.8.4/notification-controller.crds.yaml - https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.2/image-automation-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.3/image-automation-controller.crds.yaml - https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml