1
0
mirror of synced 2026-05-25 18:03:33 +00:00

Compare commits

..

185 Commits

Author SHA1 Message Date
dependabot[bot]
a99083843c build(deps): bump github.com/Azure/go-ntlmssp
Bumps [github.com/Azure/go-ntlmssp](https://github.com/Azure/go-ntlmssp) from 0.0.0-20221128193559-754e69321358 to 0.1.1.
- [Release notes](https://github.com/Azure/go-ntlmssp/releases)
- [Commits](https://github.com/Azure/go-ntlmssp/commits/v0.1.1)

---
updated-dependencies:
- dependency-name: github.com/Azure/go-ntlmssp
  dependency-version: 0.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 18:56:42 +00:00
Matheus Pimenta
fa7cd5f847 Merge pull request #5907 from fluxcd/update-pkg-deps/main
Update fluxcd/pkg dependencies
2026-05-21 19:54:25 +01:00
matheuscscp
6d95d5b1a3 Update fluxcd/pkg dependencies
Signed-off-by: GitHub <noreply@github.com>
2026-05-21 18:40:33 +00:00
Matheus Pimenta
f75d52d5c6 Merge pull request #5903 from fluxcd/update-components-main
Update toolkit components
2026-05-20 12:01:00 +01:00
fluxcdbot
272410d3e9 Update toolkit components
- helm-controller to v1.5.5
  https://github.com/fluxcd/helm-controller/blob/v1.5.5/CHANGELOG.md
- source-controller to v1.8.5
  https://github.com/fluxcd/source-controller/blob/v1.8.5/CHANGELOG.md
- image-reflector-controller to v1.1.2
  https://github.com/fluxcd/image-reflector-controller/blob/v1.1.2/CHANGELOG.md
- image-automation-controller to v1.1.4
  https://github.com/fluxcd/image-automation-controller/blob/v1.1.4/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2026-05-20 10:44:10 +00:00
Matheus Pimenta
63281daf2f Merge pull request #5890 from fluxcd/update-components-main
Update toolkit components
2026-05-12 12:05:41 +01:00
fluxcdbot
4b5a433923 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 <noreply@github.com>
2026-05-12 10:48:27 +00:00
Matheus Pimenta
abb86f161b Merge pull request #5881 from tmmorin/include-source-watcher-in-install-manifests
include source-watcher in install.yaml manifests
2026-05-06 16:48:35 +01:00
Thomas Morin
626bb58a69 include source-watcher in install manifests
Signed-off-by: Thomas Morin <thomas.morin@orange.com>
2026-05-06 17:42:52 +02:00
Matheus Pimenta
c8b4c4c620 Merge pull request #5831 from jtyr/jtyr-context-ns
Add `--ns-follows-kube-context` global flag for using the kubeconfig context namespace
2026-04-30 09:58:47 +01:00
Jiri Tyr
c031d0c215 Respect kubeconfig context namespace
Signed-off-by: Jiri Tyr <jiri.tyr@gmail.com>
2026-04-30 08:19:41 +01:00
Stefan Prodan
4f5b2fcab9 Merge pull request #5872 from Iam-Karan-Suresh/digest-pinning
Add digest pinning support to `flux plugin install`
2026-04-29 17:00:37 +03:00
iam-karan-suresh
df3878d36a feat: adding support digest pinning for flux plugin install
Signed-off-by: iam-karan-suresh <karansuresh.info@gmail.com>
2026-04-29 18:01:18 +05:30
Matheus Pimenta
4e78a9d7e0 Merge pull request #5856 from fluxcd/update-components-main
Update toolkit components
2026-04-21 11:40:53 +01:00
fluxcdbot
c1238ec834 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 <noreply@github.com>
2026-04-21 10:26:58 +00:00
Stefan Prodan
99a7d2d735 Merge pull request #5853 from fluxcd/dependabot/go_modules/github.com/go-git/go-git/v5-5.18.0
build(deps): bump github.com/go-git/go-git/v5 from 5.17.1 to 5.18.0
2026-04-21 10:56:39 +03:00
dependabot[bot]
19ab6eeb30 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] <support@github.com>
2026-04-21 07:26:09 +00:00
Stefan Prodan
00d918ecaa Merge pull request #5849 from fluxcd/plugin-system
[RFC-0013] Implement plugin system
2026-04-21 10:24:55 +03:00
Stefan Prodan
474efa09cf Split plugin commands into individual files
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-20 21:42:42 +03:00
Stefan Prodan
5256361d8c Make plugin types public
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-14 00:46:29 +03:00
Stefan Prodan
c0938d351f Add support for extractPath
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-14 00:46:23 +03:00
Stefan Prodan
2cee1d795e Fetch the plugin catalog from latest immutable release
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-14 00:46:23 +03:00
Stefan Prodan
9a4b93056b Add support for plugin binary artifacts
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-14 00:46:23 +03:00
Stefan Prodan
8be056324a Add plugin management commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-14 00:46:22 +03:00
Stefan Prodan
e45e46211b Replace yacspin with briandowns/spinner for progress indication
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 22:22:53 +03:00
Stefan Prodan
aa608bb769 Implement plugin catalog and discovery system
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 22:21:33 +03:00
Stefan Prodan
7d27a26665 Merge pull request #5845 from rycli/enable-diff-new-ks
Add `--ignore-not-found` to `flux diff ks`
2026-04-13 19:59:54 +03:00
rycli
e9bcccfede test: add 'flux diff ks' tests for cases that involve new namespaces
Signed-off-by: rycli <cyril@ryc.li>
Assisted-by: claude-code/claude-opus-4-6
2026-04-13 18:36:21 +02:00
rycli
d349ffe37d feat: add --ignore-not-found flag to 'flux diff ks' command
Signed-off-by: rycli <cyril@ryc.li>
Assisted-by: claude-code/claude-opus-4-6
2026-04-13 18:35:52 +02:00
Stefan Prodan
ac7f72b62b Merge pull request #5795 from fluxcd/rfc-cli-plugin-system
[RFC-0013] Flux CLI Plugin System
2026-04-13 17:00:05 +03:00
Stefan Prodan
968bebadf6 Assign RFC-0013 to the plugin system proposal
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 16:09:20 +03:00
Stefan Prodan
2bfdadd301 Add optional field extractPath to plugin definition
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 16:09:16 +03:00
Stefan Prodan
36686b945c Add support for direct binary URLs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 16:09:16 +03:00
Stefan Prodan
4e52adc7f0 Add plugin authors responsibilities
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 16:09:16 +03:00
Stefan Prodan
21ca8d4d17 [RFC] Flux CLI Plugin System
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-13 16:09:16 +03:00
Stefan Prodan
3e198177da Merge pull request #5847 from fluxcd/ai-guidance
Add AI Agents guidance
2026-04-13 09:56:28 +03:00
Stefan Prodan
7ba6dacc5c Add AI Agents guidance
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
Assisted-by: claude-code/claude-opus-4-6
2026-04-12 21:14:44 +03:00
Stefan Prodan
c97bdd412f Merge pull request #5841 from fluxcd/ai-contrib-guidelines
docs: Add AI Coding Assistants Guidance
2026-04-12 19:58:50 +03:00
Stefan Prodan
4eaf59113f docs: Add AI Coding Assistants Guidance
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-12 16:07:18 +03:00
Stefan Prodan
082a706f7f docs: Explain the Pull Request Process
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-11 11:40:24 +03:00
Stefan Prodan
8668902dd1 Merge pull request #5840 from fluxcd/update-components-e2e
Migrate end-to-end test to latest cloud SDKs
2026-04-11 01:18:55 +03:00
Stefan Prodan
8bc3ba3e1c Migrate end-to-end test to latest cloud SDKs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-10 23:56:03 +03:00
Stefan Prodan
2fdbde7fde Update otel packages
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-10 23:20:20 +03:00
Stefan Prodan
7d7f20da25 Merge pull request #5794 from rycli/fix/in-memory-kustomize-build
Add `--in-memory-build` to `flux build ks` and `flux diff ks`
2026-04-10 23:06:22 +03:00
rycli
e5128ea97e feat: add WithInMemoryBuild to use virtual FS for kustomize
Signed-off-by: rycli <cyril@ryc.li>
2026-04-10 21:36:45 +02:00
rycli
4f2374178c chore: bump pkg/kustomize to v1.29.0
Signed-off-by: rycli <cyril@ryc.li>
2026-04-10 20:20:14 +02:00
Stefan Prodan
125464ed72 Merge pull request #5833 from Iam-Karan-Suresh/fix/resolve-symlinks
fix: handle multiple symlinks to same target in build artifact
2026-04-10 14:03:51 +03:00
iam-karan-suresh
69e2c6bc7d fix: handle multiple symlinks to same target in build artifact
Signed-off-by: iam-karan-suresh <karansuresh.info@gmail.com>
2026-04-10 16:15:11 +05:30
Stefan Prodan
7c9810ea3b Merge pull request #5835 from fluxcd/create-secret-receiver
Add `flux create secret receiver` command
2026-04-10 13:12:31 +03:00
Stefan Prodan
c601a212f6 Add --audience-claim for GCR Receivers
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-10 12:34:26 +03:00
Stefan Prodan
02734f28ba Add flux create secret receiver command
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-04-10 12:09:42 +03:00
Stefan Prodan
3d4eec61fe Merge pull request #5828 from rafaelperoco/feat/show-source-get-kustomization
Add `--show-source` to `flux get ks` and `flux get hr`
2026-04-10 10:48:03 +03:00
Rafael Peroco
8a777bdd0f feat: add --show-source flag to flux get helmrelease
Signed-off-by: Rafael Peroco <rafaelperoco@gmail.com>
2026-04-09 18:20:43 -03:00
Rafael Peroco
e2af45aee4 feat: add --show-source flag to flux get kustomization
Fixes #2692

Signed-off-by: Rafael Peroco <rafaelperoco@gmail.com>
2026-04-08 21:49:15 -03:00
Stefan Prodan
befe53a722 Merge pull request #5821 from fluxcd/update-components-main
Update toolkit components
2026-04-07 20:27:35 +03:00
fluxcdbot
241d703e7f 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 <noreply@github.com>
2026-04-07 17:12:55 +00:00
Matheus Pimenta
c432d380dd Merge pull request #5798 from gma1k/fix/create-kustomization-source-validation
fix: validate --source flag in create kustomization command
2026-03-30 12:41:13 +01:00
Ghassan Malke
457abed9f9 fix: validate --source flag in create kustomization command
Signed-off-by: Ghassan Malke <gmalke@shiftbase.com>
2026-03-30 13:20:31 +02:00
Stefan Prodan
5fc8afcaaf Merge pull request #5724 from rohansood10/feat/resolve-symlinks-5055
Add --resolve-symlinks flag to build and push artifact commands
2026-03-28 10:46:53 +02:00
Rohan Sood
7bf0bda689 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>
2026-03-20 11:47:27 -07:00
Matheus Pimenta
d9f51d047d Merge pull request #5780 from fluxcd/update-components-main
Update toolkit components
2026-03-16 13:39:39 +00:00
fluxcdbot
dc5631f12b 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 <noreply@github.com>
2026-03-16 13:23:43 +00:00
Matheus Pimenta
3f9d5bdc3d Merge pull request #5776 from fluxcd/rfcs-implemented
Mark RFC 0010, 0011 and 0012 as implemented
2026-03-13 20:38:50 +00:00
Stefan Prodan
64e18014c3 Mark RFC 0010, 0011 and 0012 as implemented
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-13 22:08:38 +02:00
Matheus Pimenta
e9226713e8 Merge pull request #5701 from Aman-Cool/fix/resume-exit-code
Fix/resume exit code
2026-03-13 10:37:23 +00:00
Aman-Cool
6a5e644798 fix: return error immediately on failed reconciliation status
Co-authored-by: Matheus Pimenta <matheuscscp@gmail.com>
Signed-off-by: Aman-Cool <aman017102007@gmail.com>
2026-03-13 15:34:12 +05:30
Matheus Pimenta
0b0be7c1b6 Merge pull request #5773 from fluxcd/update-branch-name
Add target branch name to update branch
2026-03-12 16:37:04 +00:00
Matheus Pimenta
484346ffcc Add target branch name to update branch
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-03-12 16:34:49 +00:00
Matheus Pimenta
5b3acbfcb5 Merge pull request #5769 from fluxcd/update-components
Update toolkit components
2026-03-12 14:15:28 +00:00
fluxcdbot
2288dd90d6 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 <noreply@github.com>
2026-03-12 14:01:48 +00:00
Matheus Pimenta
af05357a62 Merge pull request #5766 from fluxcd/update-pkg-deps/main
Update fluxcd/pkg dependencies
2026-03-12 10:46:05 +00:00
matheuscscp
64808a0eac Update fluxcd/pkg dependencies
Signed-off-by: GitHub <noreply@github.com>
2026-03-12 10:23:07 +00:00
Stefan Prodan
2ead4fb31c Merge pull request #5764 from fluxcd/dependabot/github_actions/ci-c90743e802
build(deps): bump the ci group across 1 directory with 11 updates
2026-03-11 19:02:08 +02:00
dependabot[bot]
b60dfbe970 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](7a3fe6cf4c...4b73464bb3)

Updates `replicatedhq/replicated-actions` from 1.19.0 to 1.20.0
- [Release notes](https://github.com/replicatedhq/replicated-actions/releases)
- [Commits](49b440dabd...1abb33f527)

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](b9cd54a3c3...5e8dbf3c6d)

Updates `docker/setup-qemu-action` from 3.7.0 to 4.0.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](c7c5346462...ce360397dd)

Updates `docker/setup-buildx-action` from 3.12.0 to 4.0.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](8d2750c68a...4d04d5d948)

Updates `docker/login-action` from 3.7.0 to 4.0.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](c94ce9fb46...b45d80f862)

Updates `actions/upload-artifact` from 6.0.0 to 7.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](b7c566a772...bbbca2ddaa)

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](89a39a4e59...0d579ffd05)

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](28d71544de...57aae52805)

Updates `sigstore/cosign-installer` from 4.0.0 to 4.1.0
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](faadad0cce...ba7bc0a3fe)

Updates `goreleaser/goreleaser-action` from 6.4.0 to 7.0.0
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](e435ccd777...ec59f474b9)

---
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] <support@github.com>
2026-03-11 16:46:48 +00:00
Stefan Prodan
ee8bb8d8a0 Merge pull request #5763 from gaganhr94/fix/token-permissions
ci: add top-level permissions to upgrade-fluxcd-pkg workflow
2026-03-11 18:42:27 +02:00
Gagan H R
5f3098477e 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 <hrgagan4@gmail.com>
2026-03-11 21:40:14 +05:30
Matheus Pimenta
4c79a76e94 Merge pull request #5743 from fluxcd/rn-template
Add missing things to release notes template
2026-02-27 12:56:46 +00:00
Matheus Pimenta
1516761fc8 Add missing things to release notes template
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-27 12:38:51 +00:00
Matheus Pimenta
52b1c1152b Merge pull request #5740 from fluxcd/update-components
Update toolkit components
2026-02-27 09:28:07 +00:00
fluxcdbot
ab4bbffa5b 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 <noreply@github.com>
2026-02-27 09:08:54 +00:00
Matheus Pimenta
e7314e8926 Merge pull request #5733 from fluxcd/remove-workaround
Remove no longer needed workaround for Flux 2.8
2026-02-25 11:00:16 +00:00
Matheus Pimenta
2666eaf8fc Remove no longer needed workaround for Flux 2.8
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-25 10:48:35 +00:00
Matheus Pimenta
8262f8099e Merge pull request #5732 from fluxcd/label-2.8
Add backport label for Flux 2.8
2026-02-24 13:30:57 +00:00
Matheus Pimenta
cbc5c736f4 Add backport label for Flux 2.8
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-24 13:24:55 +00:00
Stefan Prodan
ac71dd88a3 Merge pull request #5731 from fluxcd/conform-min-1.33
Update conformance tests to min Kubernetes 1.33
2026-02-24 15:15:56 +02:00
Stefan Prodan
c5e5dfb8ae Update conformance tests to min Kubernetes 1.33
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-02-24 14:45:55 +02:00
Matheus Pimenta
6ae880501e Merge pull request #5730 from fluxcd/bump-min-k8s-version
Set Kubernetes 1.33 as min supported version
2026-02-24 12:16:55 +00:00
Matheus Pimenta
fd547dfe42 Bump minimum Kubernetes version to 1.33
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-24 12:06:45 +00:00
Matheus Pimenta
436dc7920a Merge pull request #5729 from fluxcd/debug-e2e
Dump debug info on e2e tests
2026-02-24 11:38:39 +00:00
Matheus Pimenta
7a8cf63623 Dump debug info on e2e tests
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-24 11:22:17 +00:00
Stefan Prodan
a6aefab55b Merge pull request #5726 from veeceey/fix/issue-1153-windows-cross-drive-bootstrap
Fix bootstrap failure on Windows cross-drive paths
2026-02-24 13:21:58 +02:00
Varun Chawla
5e5ee73046 Fix bootstrap failure on Windows when working directory is on different drive than TEMP
When running flux bootstrap from a drive (e.g. D:\) different from
where %TEMP% lives (typically C:\), filepath.Rel fails because Go
can't compute relative paths across different drive letters.

The original code converted absolute paths to relative as a workaround
for a kustomize bug (kubernetes-sigs/kustomize#2789) that was caused
by go-getter. Since kustomize dropped go-getter in 2021, absolute
paths work fine now. Instead of hard-failing when filepath.Rel errors,
keep the absolute path as a fallback.

Signed-off-by: Varun Chawla <varun_6april@hotmail.com>
2026-02-22 20:27:12 -08:00
Stefan Prodan
8362c88791 Merge pull request #5720 from fluxcd/dependabot/github_actions/ci-47823c94aa
build(deps): bump the ci group across 1 directory with 12 updates
2026-02-20 23:07:39 +02:00
dependabot[bot]
340a048e8b build(deps): bump the ci group across 1 directory with 12 updates
Bumps the ci group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `5.0.0` | `6.0.2` |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.0.0` | `6.2.0` |
| [helm/kind-action](https://github.com/helm/kind-action) | `1.12.0` | `1.13.0` |
| [fluxcd/pkg](https://github.com/fluxcd/pkg) | `1.22.0` | `1.27.0` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `3.6.0` | `3.7.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3.11.1` | `3.12.0` |
| [docker/login-action](https://github.com/docker/login-action) | `3.6.0` | `3.7.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.2` | `6.0.0` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.30.5` | `4.32.3` |
| [anchore/sbom-action](https://github.com/anchore/sbom-action) | `0.20.6` | `0.22.2` |
| [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) | `3.10.0` | `4.0.0` |
| [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) | `7.0.8` | `8.1.0` |



Updates `actions/checkout` from 5.0.0 to 6.0.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...de0fac2e45)

Updates `actions/setup-go` from 6.0.0 to 6.2.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](4469467582...7a3fe6cf4c)

Updates `helm/kind-action` from 1.12.0 to 1.13.0
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](a1b0e39133...92086f6be0)

Updates `fluxcd/pkg` from 1.22.0 to 1.27.0
- [Commits](bf02f0a2d6...9a8c0edd5d)

Updates `docker/setup-qemu-action` from 3.6.0 to 3.7.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](29109295f8...c7c5346462)

Updates `docker/setup-buildx-action` from 3.11.1 to 3.12.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](e468171a9d...8d2750c68a)

Updates `docker/login-action` from 3.6.0 to 3.7.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](5e57cd1181...c94ce9fb46)

Updates `actions/upload-artifact` from 4.6.2 to 6.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](ea165f8d65...b7c566a772)

Updates `github/codeql-action` from 3.30.5 to 4.32.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3599b3baa1...9e907b5e64)

Updates `anchore/sbom-action` from 0.20.6 to 0.22.2
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](f8bdd1d8ac...28d71544de)

Updates `sigstore/cosign-installer` from 3.10.0 to 4.0.0
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](d7543c93d8...faadad0cce)

Updates `peter-evans/create-pull-request` from 7.0.8 to 8.1.0
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](271a8d0340...c0f553fe54)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: actions/setup-go
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: helm/kind-action
  dependency-version: 1.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/pkg
  dependency-version: 1.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: docker/setup-qemu-action
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: docker/login-action
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: github/codeql-action
  dependency-version: 4.32.3
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: anchore/sbom-action
  dependency-version: 0.22.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: peter-evans/create-pull-request
  dependency-version: 8.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-20 20:38:58 +00:00
Matheus Pimenta
4b2fc84402 Merge pull request #5725 from fluxcd/update-components
Update helm-controller to v1.5.0
2026-02-20 14:03:23 +00:00
fluxcdbot
2673348c2f Update toolkit components
- helm-controller to v1.5.0
  https://github.com/fluxcd/helm-controller/blob/v1.5.0/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2026-02-20 13:45:59 +00:00
Matheus Pimenta
7132eb3435 Merge pull request #5722 from fluxcd/update-components
Update toolkit components
2026-02-19 12:20:21 +00:00
fluxcdbot
473b02ce5c Update toolkit components
- kustomize-controller to v1.8.0
  https://github.com/fluxcd/kustomize-controller/blob/v1.8.0/CHANGELOG.md
- source-controller to v1.8.0
  https://github.com/fluxcd/source-controller/blob/v1.8.0/CHANGELOG.md
- notification-controller to v1.8.0
  https://github.com/fluxcd/notification-controller/blob/v1.8.0/CHANGELOG.md
- image-reflector-controller to v1.1.0
  https://github.com/fluxcd/image-reflector-controller/blob/v1.1.0/CHANGELOG.md
- image-automation-controller to v1.1.0
  https://github.com/fluxcd/image-automation-controller/blob/v1.1.0/CHANGELOG.md
- source-watcher to v2.1.0
  https://github.com/fluxcd/source-watcher/blob/v2.1.0/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2026-02-19 10:25:55 +00:00
Stefan Prodan
862d9ddb6d Merge pull request #5723 from fluxcd/go-1.26
Build with Go 1.26
2026-02-19 12:25:09 +02:00
Stefan Prodan
33b9345883 Build with Go 1.26
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-02-19 11:11:47 +02:00
Matheus Pimenta
e169a97577 Merge pull request #5721 from fluxcd/fix-5683
Fix event listing ignoring pagination token
2026-02-18 16:58:28 +00:00
Matheus Pimenta
4eddf80724 Fix event listing ignoring pagination token
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-18 15:53:10 +00:00
Matheus Pimenta
99f182be06 Merge pull request #5719 from fluxcd/update-pkg-deps/main
Update fluxcd/pkg dependencies
2026-02-16 13:43:36 +00:00
matheuscscp
cf785cebcc Update fluxcd/pkg dependencies
Signed-off-by: GitHub <noreply@github.com>
2026-02-16 13:18:51 +00:00
Matheus Pimenta
7ff4c32d16 Merge pull request #5717 from fluxcd/cmd-bump
Introduce workflow for bumping fluxcd/pkg deps
2026-02-16 13:17:38 +00:00
Matheus Pimenta
75bf2d608f Introduce workflow for bumping fluxcd/pkg deps
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-16 13:02:26 +00:00
Matheus Pimenta
f950198f9d Merge pull request #5713 from fluxcd/migrate-v2.8
Add 2.8 to supported versions for `flux migrate -f`
2026-02-12 11:13:27 +00:00
Matheus Pimenta
2a2201fe56 Add 2.8 to supported versions for flux migrate -f
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-02-12 11:02:58 +00:00
Stefan Prodan
a4903a95be Merge pull request #5690 from Aman-Cool/fix/resume-success-after-failure
Fix resume command logging success after reconciliation failure
2026-01-25 10:29:02 +02:00
Aman-Cool
8c041095ab Fix resume command logging success after reconciliation failure
Signed-off-by: Aman-Cool <aman017102007@gmail.com>
2026-01-25 04:53:17 +05:30
Stefan Prodan
63dfdd133c Merge pull request #5688 from fluxcd/k8s-1.35.0
Update dependencies to Kubernetes v1.35.0
2026-01-22 18:12:41 +02:00
Stefan Prodan
b2be6f96c9 Update dependencies to Kubernetes v1.35.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-01-22 17:01:34 +02:00
Matheus Pimenta
1dfc906802 Merge pull request #5682 from fluxcd/gh-app-owner
Introduce support for looking up GH app installation ID
2026-01-19 12:20:27 +00:00
Matheus Pimenta
d57313ae2c Introduce support for looking up GH app installation ID
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-01-19 12:03:08 +00:00
Matheus Pimenta
c125bcb1ca Merge pull request #5674 from sibasispadhi/fix-windows-path-issue-5673
fix: normalize path for Windows compatibility
2026-01-13 16:41:23 +00:00
Sibasis Padhi
7dd9fde7ce fix: normalize paths to prevent concatenation on Windows
Fixes #5673

On Windows, when using absolute paths like C:\path\to\dir,
the path could be incorrectly concatenated, resulting in:
C:\working\dir\C:\path\to\dir\file

This fix applies filepath.Abs() and filepath.Clean() to normalize
the path before using it, ensuring absolute paths are handled
correctly on all platforms.

Changes:
- Apply filepath.Abs() to convert relative paths to absolute
- Apply filepath.Clean() to remove redundant separators and resolve ..
- Add tests for absolute paths, complex paths with .., and paths
  with redundant separators to verify normalization works correctly

The tests use actual 'flux build kustomization' commands with:
1. Absolute paths (prevents concatenation bugs)
2. Paths with parent directory (..) references
3. Paths with redundant separators (//)

All tests verify the command produces correct output, ensuring
the path normalization fix works as expected.

Signed-off-by: Sibasis Padhi <sibasis.padhi@gmail.com>
2026-01-13 10:25:59 -06:00
Stefan Prodan
f2f7d59577 Merge pull request #5663 from fluxcd/conform-k8s-1.35
Run conformance tests for Kubernetes 1.35.0
2025-12-18 11:47:06 +02:00
Stefan Prodan
7459e457bf Run conformance tests for Kubernetes 1.35.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-12-18 11:15:02 +02:00
Stefan Prodan
145f98b53a ci: Move release to large runners
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-12-18 11:13:58 +02:00
Stefan Prodan
6c58ea576e Merge pull request #5659 from ivan-munteanu/retry-pulling-the-binary
Added retry logic with delays to the Flux CLI download
2025-12-15 20:17:23 +02:00
ivan-munteanu
580ef30c8f Added retry logic to install.sh
What Changed:
- Downloads now retry up to 5 times with 5-second delays between attempts
- Applied to both the Flux binary and checksums file downloads
- Clear feedback on retry attempts and failures

Why This Matters
- Improves Reliability: Network hiccups, rate limiting, or temporary outages no longer cause immediate failures. The action automatically recovers from transient issues.

Signed-off-by: ivan-munteanu <148127170+ivan-munteanu@users.noreply.github.com>
2025-12-15 09:14:27 -05:00
Matheus Pimenta
94e9af6b2a Merge pull request #5656 from jaxels10/main
fix: #5654 by checking if both --chart and --chart-ref are set
2025-12-10 21:32:28 +00:00
Jesper Axelsen
3fb05a604f fix: #5654 by check if both --chart and --chart-ref are set
Signed-off-by: Jesper Axelsen <jesperbaxelsen@gmail.com>
2025-12-10 22:08:31 +01:00
Matheus Pimenta
9b76ba19a8 Merge pull request #5648 from fluxcd/update-components
Update toolkit components
2025-11-27 10:08:05 +00:00
fluxcdbot
1e7dd5dfd8 Update toolkit components
- helm-controller to v1.4.5
  https://github.com/fluxcd/helm-controller/blob/v1.4.5/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2025-11-27 09:53:28 +00:00
Stefan Prodan
d6dec730d8 Merge pull request #5597 from anshuishere/skip-tenant-ns-create
Allow option to skip tenant namespace creation
2025-11-21 17:16:29 +02:00
Anshuman Singh
0ba28f3f91 Allow option to skip tenant namespace creation
Add --skip-namespace flag to the 'create tenant' command to skip automatic
namespace creation when the namespace already exists.

Signed-off-by: Anshuman Singh <anshumanchauhan9@gmail.com>
2025-11-21 17:46:12 +05:30
Matheus Pimenta
55936e9366 Merge pull request #5639 from fluxcd/update-components
Update toolkit components
2025-11-20 07:17:19 +00:00
fluxcdbot
6ecad4783f Update toolkit components
- helm-controller to v1.4.4
  https://github.com/fluxcd/helm-controller/blob/v1.4.4/CHANGELOG.md
- kustomize-controller to v1.7.3
  https://github.com/fluxcd/kustomize-controller/blob/v1.7.3/CHANGELOG.md
- source-controller to v1.7.4
  https://github.com/fluxcd/source-controller/blob/v1.7.4/CHANGELOG.md
- notification-controller to v1.7.5
  https://github.com/fluxcd/notification-controller/blob/v1.7.5/CHANGELOG.md
- image-reflector-controller to v1.0.4
  https://github.com/fluxcd/image-reflector-controller/blob/v1.0.4/CHANGELOG.md
- image-automation-controller to v1.0.4
  https://github.com/fluxcd/image-automation-controller/blob/v1.0.4/CHANGELOG.md
- source-watcher to v2.0.3
  https://github.com/fluxcd/source-watcher/blob/v2.0.3/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2025-11-20 07:04:02 +00:00
Matheus Pimenta
5759d08473 Merge pull request #5625 from hown3d/report-diff-skip
diff: report if object is skipped
2025-11-19 08:31:43 +00:00
Lukas Hoehl
5048de80f0 diff: report if object is skipped
Signed-off-by: Lukas Hoehl <lukas.hoehl@stackit.cloud>
2025-11-19 07:24:11 +01:00
Matheus Pimenta
97a437d059 Merge pull request #5633 from fluxcd/upgrade-deps
Upgrade k8s to 1.34.2, c-r to 0.22.4 and helm to 3.19.2
2025-11-18 15:02:24 +00:00
Matheus Pimenta
cfb28ffdc0 Upgrade k8s to 1.34.2, c-r to 0.22.4 and helm to 3.19.2
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-11-18 14:34:27 +00:00
Matheus Pimenta
ae9ef62f39 Merge pull request #5630 from fluxcd/fix-5629
Fix panic on reconcile with source of ExternalArtifact kind
2025-11-17 14:32:37 +00:00
Matheus Pimenta
69feb7214a Fix panic on reconcile with source of ExternalArtifact kind
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-11-17 13:31:13 +00:00
Matheus Pimenta
e95da82f5a Merge pull request #5627 from fluxcd/fix-5626
Add source.extensions.fluxcd.io group to aggregated RBAC roles
2025-11-13 12:56:50 +01:00
Matheus Pimenta
7c5f9befb4 Add source.extensions.fluxcd.io group to aggregated RBAC roles
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-11-13 11:52:09 +00:00
Stefan Prodan
26a8d0c1c7 Merge pull request #5614 from fluxcd/conform-source-watcher
ci: Include source-watcher in the e2e test suite
2025-11-01 11:46:35 +02:00
Stefan Prodan
833815c71d ci: Include source-watcher in the e2e test suite
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-11-01 10:34:07 +02:00
Matheus Pimenta
31287b9b27 Merge pull request #5602 from jaxels10/main
fix: return accepted values for flags when calling Values.Type()
2025-10-28 14:09:23 +00:00
Jesper Axelsen
28f5b553a2 fix: return supported values for flags when calling Values.Type()
Signed-off-by: Jesper Axelsen <jesperbaxelsen@gmail.com>
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-28 13:35:22 +00:00
Matheus Pimenta
b33f173670 Merge pull request #5603 from fluxcd/update-components
Update toolkit components
2025-10-28 13:17:06 +00:00
Matheus Pimenta
d8c6ee167c Fix bootstrap e2e test for image policy
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-28 12:59:53 +00:00
fluxcdbot
e288cb2771 Update toolkit components
- helm-controller to v1.4.3
  https://github.com/fluxcd/helm-controller/blob/v1.4.3/CHANGELOG.md
- kustomize-controller to v1.7.2
  https://github.com/fluxcd/kustomize-controller/blob/v1.7.2/CHANGELOG.md
- source-controller to v1.7.3
  https://github.com/fluxcd/source-controller/blob/v1.7.3/CHANGELOG.md
- notification-controller to v1.7.4
  https://github.com/fluxcd/notification-controller/blob/v1.7.4/CHANGELOG.md
- image-reflector-controller to v1.0.3
  https://github.com/fluxcd/image-reflector-controller/blob/v1.0.3/CHANGELOG.md
- image-automation-controller to v1.0.3
  https://github.com/fluxcd/image-automation-controller/blob/v1.0.3/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2025-10-28 12:39:44 +00:00
Stefan Prodan
5f2a6ebc2b Merge pull request #5583 from dgunzy/add-export-source-external
[RFC-0012] Add command `flux export source external`
2025-10-28 09:33:06 +02:00
Daniel Guns
cdc37c304a Adding export source external
Signed-off-by: Daniel Guns <danbguns@gmail.com>
2025-10-24 14:52:36 -03:00
Matheus Pimenta
60e4d99b57 Merge pull request #5594 from fluxcd/pin-cosign-v2.6.1
Pin cosign to v2.6.1
2025-10-20 15:54:43 +01:00
Matheus Pimenta
8229ffb674 Pin cosign to v2.6.1
xref: https://github.com/fluxcd/source-controller/issues/1923
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-20 15:34:46 +01:00
Stefan Prodan
9b944da896 Merge pull request #5589 from akshatsinha0/fix/check-prompt-write-error
fix: handle error when writing password prompt to stdout
2025-10-13 22:30:18 +03:00
Akshat Sinha
5b37a6b04b fix(cli): handle error when writing password prompt and correct spelling (i) Add error handling for fmt.Fprint when writing password prompt to stdout (ii) Fixed : initalization to initialization in the commented region
Signed-off-by: Akshat Sinha <akshatsinhasramhardy@gmail.com>
2025-10-13 20:21:49 +05:30
Stefan Prodan
9f18062d43 Merge pull request #5555 from dgunzy/add-get-source-external-artifact
[RFC-0012] Add command `flux get source external`
2025-10-10 15:56:18 +03:00
Daniel Guns
1055f28524 Adding get source external-artifact
Signed-off-by: Daniel Guns <danbguns@gmail.com>
2025-10-09 08:45:01 -03:00
Matheus Pimenta
7b0021c1a8 Merge pull request #5581 from fluxcd/restore-github-pat-for-backports
Restore GitHub PAT for backports
2025-10-09 11:54:20 +01:00
Matheus Pimenta
ba997449aa Restore GitHub PAT for backports
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-09 11:48:57 +01:00
Matheus Pimenta
ca2f0205c4 Merge pull request #5578 from fluxcd/update-components
Update toolkit components
2025-10-08 18:53:00 +01:00
fluxcdbot
058525fe37 Update toolkit components
- helm-controller to v1.4.2
  https://github.com/fluxcd/helm-controller/blob/v1.4.2/CHANGELOG.md
- kustomize-controller to v1.7.1
  https://github.com/fluxcd/kustomize-controller/blob/v1.7.1/CHANGELOG.md
- source-controller to v1.7.2
  https://github.com/fluxcd/source-controller/blob/v1.7.2/CHANGELOG.md
- notification-controller to v1.7.3
  https://github.com/fluxcd/notification-controller/blob/v1.7.3/CHANGELOG.md
- image-reflector-controller to v1.0.2
  https://github.com/fluxcd/image-reflector-controller/blob/v1.0.2/CHANGELOG.md
- image-automation-controller to v1.0.2
  https://github.com/fluxcd/image-automation-controller/blob/v1.0.2/CHANGELOG.md
- source-watcher to v2.0.2
  https://github.com/fluxcd/source-watcher/blob/v2.0.2/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2025-10-08 17:36:49 +00:00
Stefan Prodan
686ee31f8a Merge pull request #5576 from fluxcd/k8s-1.34.1
Update dependencies to Kubernetes v1.34.1 and Go 1.25.2
2025-10-08 19:26:14 +03:00
Stefan Prodan
767f235f94 Update dependencies to Kubernetes v1.34.1 and Go 1.25.2
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-08 18:42:48 +03:00
Stefan Prodan
d5a2c66746 Merge pull request #5574 from fluxcd/fix-manifestgen
Fix manifest generation for `--storage-adv-addr` and `--events-addr` flags
2025-10-08 10:59:43 +03:00
Stefan Prodan
f2ff083b8e Use RUNTIME_NAMESPACE when setting --events-addr
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-07 20:47:37 +03:00
Stefan Prodan
8c45f25f33 Fix --storage-adv-addr for source-watcher
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-07 20:46:31 +03:00
Stefan Prodan
f85cbfa9c8 Merge pull request #5570 from fluxcd/remove-aur-pkgs
Disable AUR publishing
2025-10-06 18:46:03 +03:00
Stefan Prodan
71a3dad213 Disable AUR publishing
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-06 18:43:27 +03:00
Matheus Pimenta
72e0535958 Merge pull request #5568 from fluxcd/update-components
Update toolkit components
2025-10-06 12:55:14 +01:00
fluxcdbot
4f2d1c3a2a Update toolkit components
- helm-controller to v1.4.1
  https://github.com/fluxcd/helm-controller/blob/v1.4.1/CHANGELOG.md
- source-controller to v1.7.1
  https://github.com/fluxcd/source-controller/blob/v1.7.1/CHANGELOG.md
- notification-controller to v1.7.2
  https://github.com/fluxcd/notification-controller/blob/v1.7.2/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2025-10-06 11:38:19 +00:00
Stefan Prodan
8e99cf7c93 Merge pull request #5566 from ramasai1/refactor-variable-sub
refactor: convert `Kustomization` resource into unstructured map only once during variable substitution
2025-10-06 12:25:16 +03:00
Ramasai Venkatsitarambhaskar Tadepalli
2bb7f38603 refactor: convert Kustomization resource into unstructured map only once during variable substitution
Signed-off-by: Ramasai Venkatsitarambhaskar Tadepalli <ramasai.tadepalli@mongodb.com>
2025-10-04 21:56:20 -04:00
Matheus Pimenta
0fe4449870 Merge pull request #5563 from fluxcd/fix-migrate-f
Fix `flux migrate -f` not considering kind comments
2025-10-04 16:33:35 +01:00
Matheus Pimenta
7c5fb2297c Fix flux migrate -f not considering kind comments
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-04 14:26:54 +01:00
Stefan Prodan
f4a811fbd3 Merge pull request #5562 from fluxcd/source-watcher-links
Add source-watcher to docs
2025-10-04 09:50:09 +03:00
Stefan Prodan
bb3726bb87 Add source-watcher to docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-03 19:45:13 +03:00
Matheus Pimenta
333c8fe704 Merge pull request #5560 from fluxcd/fix-migrate-files
Fix `flux migrate -f` command to work with comments
2025-10-03 15:45:13 +01:00
Matheus Pimenta
83213ce83f Fix migrate -f command to work with comments
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-03 14:54:41 +01:00
Stefan Prodan
69718599ac Merge pull request #5558 from fluxcd/improve-flux-migrate
Improve `flux migrate` for live cluster migrations
2025-10-03 16:52:44 +03:00
Stefan Prodan
0255957dd7 Improve flux migrate for live cluster migrations
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-10-03 15:24:44 +03:00
Stefan Prodan
69b4b85cd9 Merge pull request #5554 from fluxcd/migrate-dir
Extend `flux migrate` to work with local files
2025-10-03 11:49:51 +03:00
Matheus Pimenta
a9b5be7ff4 Extend flux migrate to work with local files
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-02 15:35:02 +01:00
Matheus Pimenta
1b46056e7d Merge pull request #5551 from fluxcd/fix-5549
Fix `flux push artifact` not working with `--provider`
2025-10-01 10:39:42 +01:00
Matheus Pimenta
039d79b3c2 Fix flux push artifact not working with --provider
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-01 09:37:35 +01:00
Stefan Prodan
66b8aca399 Merge pull request #5548 from fluxcd/dependabot/github_actions/ci-b5b9679c22
build(deps): bump the ci group across 1 directory with 3 updates
2025-10-01 11:03:14 +03:00
dependabot[bot]
41c413e178 build(deps): bump the ci group across 1 directory with 3 updates
Bumps the ci group with 3 updates in the / directory: [docker/login-action](https://github.com/docker/login-action), [ossf/scorecard-action](https://github.com/ossf/scorecard-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `docker/login-action` from 3.5.0 to 3.6.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](184bdaa072...5e57cd1181)

Updates `ossf/scorecard-action` from 2.4.2 to 2.4.3
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](05b42c6244...4eaacf0543)

Updates `github/codeql-action` from 3.30.3 to 3.30.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](192325c861...3599b3baa1)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: github/codeql-action
  dependency-version: 3.30.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 07:52:39 +00:00
Matheus Pimenta
d5f8720c4d Merge pull request #5550 from fluxcd/update-labels
Add backport label for Flux 2.7
2025-10-01 08:08:08 +01:00
Matheus Pimenta
e6eb9d79e3 Add backport label for Flux 2.7
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-10-01 08:07:01 +01:00
Stefan Prodan
b90d1738a9 Merge pull request #5547 from fluxcd/ci-fix-release-flux-manifests
ci: Set `GITHUB_TOKEN` in the `release-flux-manifests` workflow
2025-10-01 00:11:33 +03:00
Stefan Prodan
f9e66dee9e ci: Set GITHUB_TOKEN in the release-flux-manifests workflow
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-09-30 23:44:54 +03:00
131 changed files with 7636 additions and 1327 deletions

12
.github/labels.yaml vendored
View File

@@ -44,12 +44,12 @@
description: Feature request proposals in the RFC format
color: '#D621C3'
aliases: ['area/RFC']
- name: backport:release/v2.4.x
description: To be backported to release/v2.4.x
color: '#ffd700'
- 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'

View File

@@ -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.

View File

@@ -24,6 +24,6 @@ jobs:
name: action on ${{ matrix.version }}
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup flux
uses: ./action

View File

@@ -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.4.0
uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.9.0
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@@ -3,13 +3,13 @@ name: conformance
on:
workflow_dispatch:
push:
branches: [ 'main', 'update-components', 'release/**', 'conform*' ]
branches: [ 'main', 'update-components-**', 'release/**', 'conform*' ]
permissions:
contents: read
env:
GO_VERSION: 1.25.x
GO_VERSION: 1.26.x
jobs:
conform-kubernetes:
@@ -19,13 +19,13 @@ jobs:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml
KUBERNETES_VERSION: [1.32.1, 1.33.0, 1.34.1]
KUBERNETES_VERSION: [1.33.0, 1.34.1, 1.35.0]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@@ -40,7 +40,7 @@ jobs:
run: |
make build
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.30.0
cluster_name: ${{ steps.prep.outputs.CLUSTER }}
@@ -76,13 +76,13 @@ jobs:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Available versions can be found with "replicated cluster versions"
K3S_VERSION: [ 1.32.9, 1.33.5, 1.34.1 ]
K3S_VERSION: [ 1.33.7, 1.34.3, 1.35.0 ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@@ -97,7 +97,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Build
run: make build-dev
- name: Create repository
@@ -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 }}
@@ -168,13 +168,13 @@ jobs:
strategy:
matrix:
# Keep this list up-to-date with https://endoflife.date/red-hat-openshift
OPENSHIFT_VERSION: [ 4.19.0-okd, 4.20.0-okd ]
OPENSHIFT_VERSION: [ 4.20.0-okd ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@@ -189,7 +189,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Build
run: make build-dev
- name: Create repository
@@ -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 }}

View File

@@ -29,14 +29,14 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: CheckoutD
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
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: ./

View File

@@ -17,27 +17,27 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
go-version: 1.26.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.30.0
cluster_name: kind
# The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.32.1-amd64
kubectl_version: v1.32.0
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.33.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Setup yq
uses: fluxcd/pkg/actions/yq@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/yq@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Build
run: make build-dev
- name: Set outputs

View File

@@ -29,14 +29,14 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
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@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Log into us-central1-docker.pkg.dev
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: us-central1-docker.pkg.dev
username: oauth2accesstoken

View File

@@ -23,16 +23,16 @@ jobs:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
go-version: 1.26.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.30.0
cluster_name: kind
@@ -40,13 +40,13 @@ jobs:
config: .github/kind/config.yaml # disable KIND-net
# The versions below should target the oldest supported Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.32.1-amd64
kubectl_version: v1.32.0
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.33.0
- name: Setup Calico for network policy
run: |
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Run tests
run: make test
- name: Run e2e tests

View File

@@ -19,7 +19,7 @@ jobs:
actions: read
contents: read
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
@@ -28,12 +28,12 @@ jobs:
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
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@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
sarif_file: results.sarif

View File

@@ -13,42 +13,44 @@ jobs:
hashes: ${{ steps.slsa.outputs.hashes }}
image_url: ${{ steps.slsa.outputs.image_url }}
image_digest: ${{ steps.slsa.outputs.image_digest }}
runs-on: ubuntu-latest
runs-on:
group: "Default Larger Runners"
labels: ubuntu-latest-16-cores
permissions:
contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Unshallow
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
go-version: 1.26.x
cache: false
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Setup Syft
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # v0.20.6
uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1
- name: Setup Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.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@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.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@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -61,7 +63,7 @@ jobs:
run: |
kustomize build manifests/crds > all-crds.yaml
- name: Generate OpenAPI JSON schemas from CRDs
uses: fluxcd/pkg/actions/crdjsonschema@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/crdjsonschema@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
with:
crd: all-crds.yaml
output: schemas
@@ -70,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
@@ -101,9 +103,9 @@ jobs:
id-token: write
packages: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Setup Flux CLI
uses: ./action/
with:
@@ -114,13 +116,13 @@ jobs:
VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.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@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -148,7 +150,7 @@ jobs:
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.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

View File

@@ -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.4.0
uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.9.0
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}
fossa-token: ${{ secrets.FOSSA_TOKEN }}

View File

@@ -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.4.0
uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.9.0
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,11 +16,11 @@ jobs:
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
go-version: 1.26.x
cache-dependency-path: |
**/go.sum
**/go.mod
@@ -96,7 +96,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: |
@@ -106,7 +106,7 @@ jobs:
committer: GitHub <noreply@github.com>
author: fluxcdbot <fluxcdbot@users.noreply.github.com>
signoff: true
branch: update-components
branch: update-components-${{ github.ref_name }}
title: Update toolkit components
body: |
${{ steps.update.outputs.pr_body }}

View File

@@ -0,0 +1,13 @@
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
secrets:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}

151
AGENTS.md Normal file
View File

@@ -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: <agent-name>/<model-id>"
```
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/<controller>/` 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.

View File

@@ -1,154 +1,129 @@
# 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 <https://fluxcd.io/>
## 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: <agent>/<model>"
```
**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 <jane.doe@example.com>
Assisted-by: copilot/gpt-5.4
```
## 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 <jane.doe@example.com>`
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-manager](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git and Helm repositories, S3-compatible Buckets)
- [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.24
* kubectl >= 1.30
* kustomize >= 5.0
* coreutils (on Mac OS)
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 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.
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).

View File

@@ -1,16 +1,16 @@
FROM alpine:3.22 AS builder
FROM alpine:3.23 AS builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.34.1
ARG KUBECTL_VER=1.35.0
RUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
RUN kubectl version --client=true
FROM alpine:3.22 AS flux-cli
FROM alpine:3.23 AS flux-cli
RUN apk add --no-cache ca-certificates

View File

@@ -17,8 +17,8 @@ rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2
all: test build
tidy:
go mod tidy -compat=1.25
cd tests/integration && go mod tidy -compat=1.25
go mod tidy -compat=1.26
cd tests/integration && go mod tidy -compat=1.26
fmt:
go fmt ./...

View File

@@ -52,12 +52,14 @@ guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
### Components
- [Source Controller](https://fluxcd.io/flux/components/source/)
- [Source Controllers](https://fluxcd.io/flux/components/source/)
- [GitRepository CRD](https://fluxcd.io/flux/components/source/gitrepositories/)
- [OCIRepository CRD](https://fluxcd.io/flux/components/source/ocirepositories/)
- [HelmRepository CRD](https://fluxcd.io/flux/components/source/helmrepositories/)
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
- [ExternalArtifact CRD](https://fluxcd.io/flux/components/source/externalartifacts/)
- [ArtifactGenerator CRD](https://fluxcd.io/flux/components/source/artifactgenerators/)
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)
- [Helm Controller](https://fluxcd.io/flux/components/helm/)

View File

@@ -77,9 +77,37 @@ runs:
FLUX_DOWNLOAD_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/"
curl -fsSL -o "$DL_DIR/$FLUX_TARGET_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_TARGET_FILE"
curl -fsSL -o "$DL_DIR/$FLUX_CHECKSUMS_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_CHECKSUMS_FILE"
MAX_RETRIES=5
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Downloading flux binary (attempt $i/$MAX_RETRIES)"
if curl -fsSL -o "$DL_DIR/$FLUX_TARGET_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_TARGET_FILE"; then
break
fi
if [ $i -lt $MAX_RETRIES ]; then
echo "Download failed, retrying in ${RETRY_DELAY} seconds..."
sleep $RETRY_DELAY
else
echo "Failed to download flux binary after $MAX_RETRIES attempts"
exit 1
fi
done
for i in $(seq 1 $MAX_RETRIES); do
echo "Downloading checksums file (attempt $i/$MAX_RETRIES)"
if curl -fsSL -o "$DL_DIR/$FLUX_CHECKSUMS_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_CHECKSUMS_FILE"; then
break
fi
if [ $i -lt $MAX_RETRIES ]; then
echo "Download failed, retrying in ${RETRY_DELAY} seconds..."
sleep $RETRY_DELAY
else
echo "Failed to download checksums file after $MAX_RETRIES attempts"
exit 1
fi
done
echo "Verifying checksum"
sum=""
if command -v openssl > /dev/null; then

View File

@@ -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
defer delete(visited, abs)
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 {

View File

@@ -18,6 +18,7 @@ package main
import (
"os"
"path/filepath"
"strings"
"testing"
@@ -68,3 +69,149 @@ 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())
}
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"))
}

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"os"
"os/signal"
"path/filepath"
"github.com/spf13/cobra"
@@ -71,6 +72,7 @@ type buildKsFlags struct {
strictSubst bool
recursive bool
localSources map[string]string
inMemoryBuild bool
}
var buildKsArgs buildKsFlags
@@ -84,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)
}
@@ -97,6 +101,13 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
}
// Normalize the path to handle Windows absolute and relative paths correctly
buildKsArgs.path, err = filepath.Abs(buildKsArgs.path)
if err != nil {
return fmt.Errorf("failed to resolve absolute path: %w", err)
}
buildKsArgs.path = filepath.Clean(buildKsArgs.path)
if fs, err := os.Stat(buildKsArgs.path); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
}
@@ -122,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,
@@ -132,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),
)
}

View File

@@ -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{
@@ -218,3 +254,71 @@ spec:
})
}
}
// TestBuildKustomizationPathNormalization verifies that absolute and complex
// paths are normalized to prevent path concatenation bugs (issue #5673).
// Without normalization, paths could be duplicated like: /path/test/path/test/file
func TestBuildKustomizationPathNormalization(t *testing.T) {
// Get absolute path to testdata to test absolute path handling
absTestDataPath, err := filepath.Abs("testdata/build-kustomization/podinfo")
if err != nil {
t.Fatalf("failed to get absolute path: %v", err)
}
tests := []struct {
name string
args string
resultFile string
assertFunc string
}{
{
name: "build with absolute path",
args: "build kustomization podinfo --path " + absTestDataPath,
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",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with path containing redundant separators",
args: "build kustomization podinfo --path ./testdata//build-kustomization//podinfo",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setup(t, tmpl)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var assert assertFunc
switch tt.assertFunc {
case "assertGoldenTemplateFile":
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
case "assertError":
assert = assertError(tt.resultFile)
}
cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"],
assert: assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -60,7 +60,7 @@ type checkFlags struct {
}
var kubernetesConstraints = []string{
">=1.32.0-0",
">=1.33.0-0",
}
var checkArgs checkFlags

View File

@@ -182,6 +182,10 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("chart or chart-ref is required")
}
if helmReleaseArgs.chart != "" && helmReleaseArgs.chartRef != "" {
return fmt.Errorf("cannot use --chart in combination with --chart-ref")
}
sourceLabels, err := parseLabels()
if err != nil {
return err

View File

@@ -42,6 +42,11 @@ func TestCreateHelmRelease(t *testing.T) {
args: "create helmrelease podinfo --export",
assert: assertError("chart or chart-ref is required"),
},
{
name: "chart and chartRef used in combination",
args: "create helmrelease podinfo --chart podinfo --chart-ref foobar/podinfo --export",
assert: assertError("cannot use --chart in combination with --chart-ref"),
},
{
name: "unknown source kind",
args: "create helmrelease podinfo --source foobar/podinfo --chart podinfo --export",

View File

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

View File

@@ -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)
})
}
}

View File

@@ -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{

View File

@@ -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
}

View File

@@ -46,16 +46,18 @@ var createSecretGitHubAppCmd = &cobra.Command{
}
type secretGitHubAppFlags struct {
appID string
appInstallationID string
privateKeyFile string
baseURL string
appID string
appInstallationOwner string
appInstallationID string
privateKeyFile string
baseURL string
}
var secretGitHubAppArgs = secretGitHubAppFlags{}
func init() {
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appID, "app-id", "", "github app ID")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appInstallationOwner, "app-installation-owner", "", "github app installation owner (user or organization)")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appInstallationID, "app-installation-id", "", "github app installation ID")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.privateKeyFile, "app-private-key", "", "github app private key file path")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.baseURL, "app-base-url", "", "github app base URL")
@@ -70,33 +72,19 @@ func createSecretGitHubAppCmdRun(cmd *cobra.Command, args []string) error {
secretName := args[0]
if secretGitHubAppArgs.appID == "" {
return fmt.Errorf("--app-id is required")
}
if secretGitHubAppArgs.appInstallationID == "" {
return fmt.Errorf("--app-installation-id is required")
}
if secretGitHubAppArgs.privateKeyFile == "" {
return fmt.Errorf("--app-private-key is required")
}
privateKey, err := os.ReadFile(secretGitHubAppArgs.privateKeyFile)
if err != nil {
return fmt.Errorf("unable to read private key file: %w", err)
}
opts := sourcesecret.Options{
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
GitHubAppID: secretGitHubAppArgs.appID,
GitHubAppInstallationID: secretGitHubAppArgs.appInstallationID,
GitHubAppPrivateKey: string(privateKey),
}
if secretGitHubAppArgs.baseURL != "" {
opts.GitHubAppBaseURL = secretGitHubAppArgs.baseURL
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
GitHubAppID: secretGitHubAppArgs.appID,
GitHubAppInstallationOwner: secretGitHubAppArgs.appInstallationOwner,
GitHubAppInstallationID: secretGitHubAppArgs.appInstallationID,
GitHubAppPrivateKey: string(privateKey),
GitHubAppBaseURL: secretGitHubAppArgs.baseURL,
}
secret, err := sourcesecret.GenerateGitHubApp(opts)

View File

@@ -31,21 +31,6 @@ func TestCreateSecretGitHubApp(t *testing.T) {
args: "create secret githubapp",
assert: assertError("name is required"),
},
{
name: "create githubapp secret with missing app-id",
args: "create secret githubapp appinfo",
assert: assertError("--app-id is required"),
},
{
name: "create githubapp secret with missing appInstallationID",
args: "create secret githubapp appinfo --app-id 1",
assert: assertError("--app-installation-id is required"),
},
{
name: "create githubapp secret with missing private key file",
args: "create secret githubapp appinfo --app-id 1 --app-installation-id 2",
assert: assertError("--app-private-key is required"),
},
{
name: "create githubapp secret with private key file that does not exist",
args: "create secret githubapp appinfo --app-id 1 --app-installation-id 2 --app-private-key pk.pem",
@@ -53,7 +38,7 @@ func TestCreateSecretGitHubApp(t *testing.T) {
},
{
name: "create githubapp secret with app info",
args: "create secret githubapp appinfo --namespace my-namespace --app-id 1 --app-installation-id 2 --app-private-key ./testdata/create_secret/githubapp/test-private-key.pem --export",
args: "create secret githubapp appinfo --namespace my-namespace --app-id 1 --app-installation-owner my-org --app-private-key ./testdata/create_secret/githubapp/test-private-key.pem --export",
assert: assertGoldenFile("testdata/create_secret/githubapp/secret.yaml"),
},
{

View File

@@ -0,0 +1,134 @@
/*
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
audienceClaim 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")
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.audienceClaim, "audience-claim", "", "custom OIDC token audience for gcr type, defaults to the webhook URL")
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,
AudienceClaim: secretReceiverArgs.audienceClaim,
}
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
}

View File

@@ -0,0 +1,74 @@
/*
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"),
},
{
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) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -62,6 +62,8 @@ type diffKsFlags struct {
strictSubst bool
recursive bool
localSources map[string]string
inMemoryBuild bool
ignoreNotFound bool
}
var diffKsArgs diffKsFlags
@@ -75,6 +77,10 @@ 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.")
diffKsCmd.Flags().BoolVar(&diffKsArgs.ignoreNotFound, "ignore-not-found", false,
"Ignore Kustomization not found errors on the cluster when diffing.")
diffCmd.AddCommand(diffKsCmd)
}
@@ -113,6 +119,8 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
build.WithInMemoryBuild(diffKsArgs.inMemoryBuild),
build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound),
)
} else {
builder, err = build.NewBuilder(name, diffKsArgs.path,
@@ -124,6 +132,8 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
build.WithInMemoryBuild(diffKsArgs.inMemoryBuild),
build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound),
)
}

View File

@@ -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 {

View File

@@ -196,11 +196,14 @@ func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []cli
func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
listOpts := &metav1.ListOptions{}
clientListOpts = append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
err := runtimeresource.FollowContinue(listOpts,
func(options metav1.ListOptions) (runtime.Object, error) {
newEvents := &corev1.EventList{}
if err := kubeclient.List(ctx, newEvents, clientListOpts...); err != nil {
opts := append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
if options.Continue != "" {
opts = append(opts, client.Continue(options.Continue))
}
if err := kubeclient.List(ctx, newEvents, opts...); err != nil {
return nil, fmt.Errorf("error getting events: %w", err)
}
el.Items = append(el.Items, newEvents.Items...)

View File

@@ -20,11 +20,13 @@ package main
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -419,6 +421,108 @@ func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event
}
}
// paginatedClient wraps a client.Client and simulates real Kubernetes API
// pagination by splitting List results into pages of pageSize items,
// using the ListMeta.Continue token.
type paginatedClient struct {
client.Client
pageSize int
}
func (c *paginatedClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
listOpts := &client.ListOptions{}
listOpts.ApplyOptions(opts)
// Fetch all results from the underlying client (without Limit/Continue).
stripped := make([]client.ListOption, 0, len(opts))
for _, o := range opts {
if _, ok := o.(client.Limit); ok {
continue
}
if _, ok := o.(client.Continue); ok {
continue
}
stripped = append(stripped, o)
}
if err := c.Client.List(ctx, list, stripped...); err != nil {
return err
}
items, err := meta.ExtractList(list)
if err != nil {
return err
}
// Determine the page window based on the Continue token.
start := 0
if listOpts.Continue != "" {
n, err := strconv.Atoi(listOpts.Continue)
if err != nil {
return fmt.Errorf("invalid continue token: %w", err)
}
start = n
}
if start > len(items) {
start = len(items)
}
end := start + c.pageSize
if end > len(items) {
end = len(items)
}
page := items[start:end]
if err := meta.SetList(list, page); err != nil {
return err
}
// Set the Continue token when there are more pages.
listAccessor, err := meta.ListAccessor(list)
if err != nil {
return err
}
if end < len(items) {
listAccessor.SetContinue(strconv.Itoa(end))
} else {
listAccessor.SetContinue("")
}
return nil
}
func Test_addEventsToList_pagination(t *testing.T) {
g := NewWithT(t)
objs, err := ssautil.ReadObjects(strings.NewReader(objects))
g.Expect(err).To(Not(HaveOccurred()))
builder := fake.NewClientBuilder().WithScheme(utils.NewScheme())
for _, obj := range objs {
builder = builder.WithObjects(obj)
}
eventList := &corev1.EventList{}
for _, obj := range objs {
infoEvent := createEvent(obj, eventv1.EventSeverityInfo, "Info Message", "Info Reason")
warningEvent := createEvent(obj, eventv1.EventSeverityError, "Error Message", "Error Reason")
eventList.Items = append(eventList.Items, infoEvent, warningEvent)
}
builder = builder.WithLists(eventList)
c := builder.Build()
totalEvents := len(eventList.Items)
g.Expect(totalEvents).To(BeNumerically(">", 2), "need more than 2 events to test pagination")
// Wrap the client to paginate at 2 items per page, forcing multiple
// round-trips through FollowContinue.
pc := &paginatedClient{Client: c, pageSize: 2}
el := &corev1.EventList{}
err = addEventsToList(context.Background(), pc, el, nil)
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(el.Items).To(HaveLen(totalEvents),
"addEventsToList should collect all events across paginated responses")
}
func kindNameIndexer(obj client.Object) []string {
e, ok := obj.(*corev1.Event)
if !ok {

View File

@@ -0,0 +1,84 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
)
var exportSourceExternalCmd = &cobra.Command{
Use: "external [name]",
Short: "Export ExternalArtifact sources in YAML format",
Long: "The export source external command exports one or all ExternalArtifact sources in YAML format.",
Example: ` # Export all ExternalArtifact sources
flux export source external --all > sources.yaml
# Export a specific ExternalArtifact
flux export source external my-artifact > source.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),
RunE: exportWithSecretCommand{
list: externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},
object: externalArtifactAdapter{&sourcev1.ExternalArtifact{}},
}.run,
}
func init() {
exportSourceCmd.AddCommand(exportSourceExternalCmd)
}
func exportExternalArtifact(source *sourcev1.ExternalArtifact) any {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)
export := sourcev1.ExternalArtifact{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
Labels: source.Labels,
Annotations: source.Annotations,
},
Spec: source.Spec,
}
return export
}
func getExternalArtifactSecret(source *sourcev1.ExternalArtifact) *types.NamespacedName {
// ExternalArtifact does not have a secretRef in its spec, this satisfies the interface
return nil
}
func (ex externalArtifactAdapter) secret() *types.NamespacedName {
return getExternalArtifactSecret(ex.ExternalArtifact)
}
func (ex externalArtifactListAdapter) secretItem(i int) *types.NamespacedName {
return getExternalArtifactSecret(&ex.ExternalArtifactList.Items[i])
}
func (ex externalArtifactAdapter) export() any {
return exportExternalArtifact(ex.ExternalArtifact)
}
func (ex externalArtifactListAdapter) exportItem(i int) any {
return exportExternalArtifact(&ex.ExternalArtifactList.Items[i])
}

View File

@@ -110,6 +110,12 @@ func TestExport(t *testing.T) {
"testdata/export/bucket.yaml",
tmpl,
},
{
"source external",
"export source external flux-system",
"testdata/export/external-artifact.yaml",
tmpl,
},
}
for _, tt := range cases {

View File

@@ -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...)
}

View File

@@ -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...)
}

View File

@@ -59,6 +59,10 @@ var getSourceAllCmd = &cobra.Command{
apiType: helmChartType,
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
},
{
apiType: externalArtifactType,
list: &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},
},
}
for _, c := range allSourceCmd {

View File

@@ -0,0 +1,108 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/fluxcd/flux2/v2/internal/utils"
)
var getSourceExternalCmd = &cobra.Command{
Use: "external",
Short: "Get ExternalArtifact source statuses",
Long: `The get sources external command prints the status of the ExternalArtifact sources.`,
Example: ` # List all ExternalArtifacts and their status
flux get sources external
# List ExternalArtifacts from all namespaces
flux get sources external --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
apiType: externalArtifactType,
list: &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.ExternalArtifact)
if !ok {
return nil, fmt.Errorf("impossible to cast type %#v to ExternalArtifact", obj)
}
sink := &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{
Items: []sourcev1.ExternalArtifact{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
}
func init() {
getSourceCmd.AddCommand(getSourceExternalCmd)
}
func (a *externalArtifactListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := a.Items[i]
var revision string
if item.Status.Artifact != nil {
revision = item.Status.Artifact.Revision
}
status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
var source string
if item.Spec.SourceRef != nil {
source = fmt.Sprintf("%s/%s/%s",
item.Spec.SourceRef.Kind,
item.Spec.SourceRef.Namespace,
item.Spec.SourceRef.Name)
}
return append(nameColumns(&item, includeNamespace, includeKind),
revision, source, status, msg)
}
func (a externalArtifactListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Source", "Ready", "Message"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}
return headers
}
func (a externalArtifactListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -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
@@ -180,12 +193,14 @@ func main() {
// This is required because controller-runtime expects its consumers to
// set a logger through log.SetLogger within 30 seconds of the program's
// initalization. If not set, the entire debug stack is printed as an
// initialization. If not set, the entire debug stack is printed as an
// error, see: https://github.com/kubernetes-sigs/controller-runtime/blob/ed8be90/pkg/log/log.go#L59
// Since we have our own logging and don't care about controller-runtime's
// 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 {
@@ -203,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")
@@ -225,7 +260,9 @@ func configureDefaultNamespace() {
func readPasswordFromStdin(prompt string) (string, error) {
var out string
var err error
fmt.Fprint(os.Stdout, prompt)
if _, err := fmt.Fprint(os.Stdout, prompt); err != nil {
return "", fmt.Errorf("failed to write prompt: %w", err)
}
stdinFD := int(os.Stdin.Fd())
if term.IsTerminal(stdinFD) {
var inBytes []byte

View File

@@ -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))
})
}
}

View File

@@ -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 {
@@ -454,7 +460,9 @@ func resetCmdArgs() {
rhrArgs = reconcileHelmReleaseFlags{}
rksArgs = reconcileKsFlags{}
secretGitArgs = NewSecretGitFlags()
secretGitHubAppArgs = secretGitHubAppFlags{}
secretProxyArgs = secretProxyFlags{}
secretReceiverArgs = secretReceiverFlags{}
secretHelmArgs = secretHelmFlags{}
secretTLSArgs = secretTLSFlags{}
sourceBucketArgs = sourceBucketFlags{}

View File

@@ -59,71 +59,82 @@ type APIVersions struct {
// TODO: Update this mapping when new Flux minor versions are released!
// latestAPIVersions contains the latest API versions for each GroupKind
// for each supported Flux version. We maintain the latest two minor versions.
// for each supported Flux version. The number of latest minor versions
// we maintain here must match what's documented here:
//
// https://fluxcd.io/flux/releases/#supported-releases
var latestAPIVersions = []APIVersions{
{
FluxVersion: "2.7",
LatestVersions: map[schema.GroupKind]string{
// source-controller
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
// kustomize-controller
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
// helm-controller
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
// notification-controller
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
// image-reflector-controller
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImageRepositoryKind}: imagev1.GroupVersion.Version,
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImagePolicyKind}: imagev1.GroupVersion.Version,
// image-automation-controller
{Group: imageautov1.GroupVersion.Group, Kind: imageautov1.ImageUpdateAutomationKind}: imageautov1.GroupVersion.Version,
// source-watcher
{Group: swv1b1.GroupVersion.Group, Kind: swv1b1.ArtifactGeneratorKind}: swv1b1.GroupVersion.Version,
},
FluxVersion: "2.8",
LatestVersions: flux27LatestAPIVersions,
},
{
FluxVersion: "2.6",
LatestVersions: map[schema.GroupKind]string{
// source-controller
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
// kustomize-controller
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
// helm-controller
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
// notification-controller
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
// image-reflector-controller
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImageRepositoryKind}: imagev1b2.GroupVersion.Version,
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImagePolicyKind}: imagev1b2.GroupVersion.Version,
// image-automation-controller
{Group: imageautov1b2.GroupVersion.Group, Kind: imageautov1b2.ImageUpdateAutomationKind}: imageautov1b2.GroupVersion.Version,
},
FluxVersion: "2.7",
LatestVersions: flux27LatestAPIVersions,
},
{
FluxVersion: "2.6",
LatestVersions: flux26LatestAPIVersions,
},
}
var flux27LatestAPIVersions = map[schema.GroupKind]string{
// source-controller
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
// kustomize-controller
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
// helm-controller
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
// notification-controller
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
// image-reflector-controller
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImageRepositoryKind}: imagev1.GroupVersion.Version,
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImagePolicyKind}: imagev1.GroupVersion.Version,
// image-automation-controller
{Group: imageautov1.GroupVersion.Group, Kind: imageautov1.ImageUpdateAutomationKind}: imageautov1.GroupVersion.Version,
// source-watcher
{Group: swv1b1.GroupVersion.Group, Kind: swv1b1.ArtifactGeneratorKind}: swv1b1.GroupVersion.Version,
}
var flux26LatestAPIVersions = map[schema.GroupKind]string{
// source-controller
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
// kustomize-controller
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
// helm-controller
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
// notification-controller
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
// image-reflector-controller
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImageRepositoryKind}: imagev1b2.GroupVersion.Version,
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImagePolicyKind}: imagev1b2.GroupVersion.Version,
// image-automation-controller
{Group: imageautov1b2.GroupVersion.Group, Kind: imageautov1b2.ImageUpdateAutomationKind}: imageautov1b2.GroupVersion.Version,
}
var migrateCmd = &cobra.Command{

118
cmd/flux/plugin.go Normal file
View File

@@ -0,0 +1,118 @@
/*
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"
"strings"
"time"
"github.com/briandowns/spinner"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
)
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
},
}
func init() {
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)
}
}
// 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, ""
}
// 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()
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
}

View File

@@ -0,0 +1,96 @@
/*
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"
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
)
var pluginInstallCmd = &cobra.Command{
Use: "install <name>[@<version>|@<digest>]",
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
# Install pinned to a specific digest
flux plugin install operator@sha256:06e0a38db4fa6bc9f705a577c7e58dc020bfe2618e45488599e5ef7bb62e3a8a`,
Args: cobra.ExactArgs(1),
RunE: pluginInstallCmdRun,
}
func init() {
pluginCmd.AddCommand(pluginInstallCmd)
}
func pluginInstallCmdRun(cmd *cobra.Command, args []string) error {
nameVersion := args[0]
name, ref := parseNameVersion(nameVersion)
catalogClient := newCatalogClient()
manifest, err := catalogClient.FetchManifest(name)
if err != nil {
return err
}
var pv *plugintypes.Version
var plat *plugintypes.Platform
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()
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
}

57
cmd/flux/plugin_list.go Normal file
View File

@@ -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)
}

81
cmd/flux/plugin_search.go Normal file
View File

@@ -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)
}

286
cmd/flux/plugin_test.go Normal file
View File

@@ -0,0 +1,286 @@
/*
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", ""},
{"operator@sha256:abc123", "operator", "sha256:abc123"},
}
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 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 }()
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)
}
}

View File

@@ -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 <name>",
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
}

102
cmd/flux/plugin_update.go Normal file
View File

@@ -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
}

View File

@@ -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, "=")

View File

@@ -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)
}
}
}

View File

@@ -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
}
@@ -251,7 +262,8 @@ func (resume resumeCommand) printMessage(responses []reconcileResponse) {
continue
}
if r.err != nil {
logger.Failuref("%s", r.err.Error())
logger.Failuref("%s %s reconciliation failed: %s", resume.kind, r.asClientObject().GetName(), r.err.Error())
continue
}
logger.Successf("%s %s reconciliation completed", resume.kind, r.asClientObject().GetName())
logger.Successf("%s", r.successMessage())

View File

@@ -195,3 +195,37 @@ func (a helmRepositoryListAdapter) asClientList() client.ObjectList {
func (a helmRepositoryListAdapter) len() int {
return len(a.HelmRepositoryList.Items)
}
// sourcev1.ExternalArtifact
var externalArtifactType = apiType{
kind: sourcev1.ExternalArtifactKind,
humanKind: "source external-artifact",
groupVersion: sourcev1.GroupVersion,
}
type externalArtifactAdapter struct {
*sourcev1.ExternalArtifact
}
func (a externalArtifactAdapter) asClientObject() client.Object {
return a.ExternalArtifact
}
func (a externalArtifactAdapter) deepCopyClientObject() client.Object {
return a.ExternalArtifact.DeepCopy()
}
// sourcev1.ExternalArtifactList
type externalArtifactListAdapter struct {
*sourcev1.ExternalArtifactList
}
func (a externalArtifactListAdapter) asClientList() client.ObjectList {
return a.ExternalArtifactList
}
func (a externalArtifactListAdapter) len() int {
return len(a.ExternalArtifactList.Items)
}

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: existing-config
namespace: default
data:
key: value

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./existing.yaml
- ./new.yaml

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: new-config
namespace: default
data:
key: value

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: new-ns
data:
key: value

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespace.yaml
- ./configmap.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: new-ns

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespace.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: new-ns

View File

@@ -1,3 +1,3 @@
► checking prerequisites
✔ Kubernetes {{ .serverVersion }} >=1.32.0-0
✔ Kubernetes {{ .serverVersion }} >=1.33.0-0
✔ prerequisites checks passed

View File

@@ -6,7 +6,7 @@ metadata:
namespace: my-namespace
stringData:
githubAppID: "1"
githubAppInstallationID: "2"
githubAppInstallationOwner: my-org
githubAppPrivateKey: |-
-----BEGIN RSA PRIVATE KEY-----
YcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
► Namespace/new-ns created

View File

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

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: existing-config
namespace: default
data:
key: value

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: ExternalArtifact
metadata:
name: flux-system
namespace: {{ .fluxns }}
spec:
sourceRef:
apiVersion: source.example.com/v1alpha1
kind: GitHubRelease
name: flux-system
namespace: {{ .fluxns }}

View File

@@ -165,3 +165,15 @@ spec:
endpoint: s3.amazonaws.com
region: us-east-1
timeout: 30s
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: ExternalArtifact
metadata:
name: flux-system
namespace: {{ .fluxns }}
spec:
sourceRef:
apiVersion: source.example.com/v1alpha1
kind: GitHubRelease
name: flux-system
namespace: {{ .fluxns }}

View File

@@ -26,6 +26,8 @@ The following template can be used for the GitHub release page:
<!-- Text describing the most important changes in this release -->
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
<!-- List of fixes and improvements to the controllers and CLI -->
@@ -36,7 +38,7 @@ The following template can be used for the GitHub release page:
## Components changelog
- <name>-controller [v<version>](https://github.com/fluxcd/<name>-controller/blob/<version>/CHANGELOG.md
- <name>-controller [v<version>](https://github.com/fluxcd/<name>-controller/blob/<version>/CHANGELOG.md)
## CLI changelog

289
go.mod
View File

@@ -1,148 +1,149 @@
module github.com/fluxcd/flux2/v2
go 1.25.0
go 1.26.0
// Fix CVE-2022-28948.
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/cyphar/filepath-securejoin v0.6.0
github.com/distribution/distribution/v3 v3.0.0
github.com/fluxcd/cli-utils v0.36.0-flux.15
github.com/fluxcd/go-git-providers v0.25.0
github.com/fluxcd/helm-controller/api v1.4.5
github.com/fluxcd/image-automation-controller/api v1.0.4
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.21.0
github.com/fluxcd/pkg/apis/meta v1.23.0
github.com/fluxcd/pkg/auth v0.33.0
github.com/fluxcd/pkg/chartutil v1.17.0
github.com/fluxcd/pkg/envsubst v1.5.0
github.com/fluxcd/pkg/git v0.38.0
github.com/fluxcd/pkg/git/gogit v0.42.0
github.com/fluxcd/pkg/kustomize v1.24.0
github.com/fluxcd/pkg/oci v0.58.0
github.com/fluxcd/pkg/runtime v0.90.0
github.com/fluxcd/pkg/sourceignore v0.15.0
github.com/fluxcd/pkg/ssa v0.61.0
github.com/fluxcd/pkg/ssh v0.23.0
github.com/fluxcd/pkg/tar v0.16.0
github.com/fluxcd/pkg/version v0.11.0
github.com/fluxcd/source-controller/api v1.7.4
github.com/fluxcd/source-watcher/api/v2 v2.0.3
github.com/go-git/go-git/v5 v5.16.3
github.com/ProtonMail/go-crypto v1.4.1
github.com/briandowns/spinner v1.23.2
github.com/cyphar/filepath-securejoin v0.6.1
github.com/distribution/distribution/v3 v3.1.1
github.com/fluxcd/cli-utils v1.2.0
github.com/fluxcd/go-git-providers v0.26.0
github.com/fluxcd/helm-controller/api v1.5.5
github.com/fluxcd/image-automation-controller/api v1.1.4
github.com/fluxcd/image-reflector-controller/api v1.1.2
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.26.0
github.com/fluxcd/pkg/apis/meta v1.27.0
github.com/fluxcd/pkg/auth v0.45.0
github.com/fluxcd/pkg/chartutil v1.24.0
github.com/fluxcd/pkg/envsubst v1.7.0
github.com/fluxcd/pkg/git v0.49.0
github.com/fluxcd/pkg/kustomize v1.32.0
github.com/fluxcd/pkg/oci v0.66.0
github.com/fluxcd/pkg/runtime v0.106.0
github.com/fluxcd/pkg/sourceignore v0.18.0
github.com/fluxcd/pkg/ssa v0.74.0
github.com/fluxcd/pkg/ssh v0.25.0
github.com/fluxcd/pkg/tar v1.2.0
github.com/fluxcd/pkg/version v0.15.0
github.com/fluxcd/source-controller/api v1.8.5
github.com/fluxcd/source-watcher/api/v2 v2.1.1
github.com/go-git/go-git/v5 v5.19.1
github.com/go-logr/logr v1.4.3
github.com/gonvenience/bunt v1.4.2
github.com/gonvenience/ytbx v1.4.7
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.6
github.com/google/go-containerregistry v0.21.5
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
github.com/mattn/go-shellwords v1.0.12
github.com/notaryproject/notation-go v1.3.2
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.38.2
github.com/onsi/gomega v1.40.0
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/spf13/cobra v1.10.1
github.com/theckman/yacspin v0.13.12
golang.org/x/crypto v0.44.0
golang.org/x/term v0.37.0
golang.org/x/text v0.31.0
k8s.io/api v0.34.2
k8s.io/apiextensions-apiserver v0.34.2
k8s.io/apimachinery v0.34.2
k8s.io/cli-runtime v0.34.2
k8s.io/client-go v0.34.2
k8s.io/kubectl v0.34.2
sigs.k8s.io/controller-runtime v0.22.4
sigs.k8s.io/kustomize/api v0.21.0
sigs.k8s.io/kustomize/kyaml v0.21.0
github.com/spf13/cobra v1.10.2
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.36.0
k8s.io/apiextensions-apiserver v0.36.0
k8s.io/apimachinery v0.36.0
k8s.io/cli-runtime v0.36.0
k8s.io/client-go v0.36.0
k8s.io/kubectl v0.36.0
sigs.k8s.io/controller-runtime v0.24.0
sigs.k8s.io/kustomize/api v0.21.1
sigs.k8s.io/kustomize/kyaml v0.21.1
sigs.k8s.io/yaml v1.6.0
)
require (
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
code.gitea.io/sdk/gitea v0.22.0 // indirect
code.gitea.io/sdk/gitea v0.23.2 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/42wim/httpsig v1.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.20 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.52.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.4 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.74.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 // indirect
github.com/aws/smithy-go v1.23.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.57.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.83.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
github.com/aws/smithy-go v1.25.1 // indirect
github.com/aws/smithy-go/aws-http-auth v1.1.3 // 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
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
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 v28.4.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/cli v29.4.0+incompatible // 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/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/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.14.0 // indirect
github.com/fluxcd/pkg/cache v0.12.0 // indirect
github.com/fluxcd/pkg/apis/acl v0.10.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v1.18.0 // indirect
github.com/fluxcd/pkg/cache v0.14.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
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.6.2 // indirect
github.com/go-git/go-billy/v5 v5.9.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
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/gonvenience/idem v0.0.2 // indirect
github.com/gonvenience/neat v1.3.16 // indirect
@@ -150,21 +151,18 @@ require (
github.com/gonvenience/text v1.0.9 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-github/v75 v75.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-github/v82 v82.0.0 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // 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.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
@@ -173,7 +171,8 @@ 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.0 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // 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
@@ -181,96 +180,96 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/term v0.5.2 // 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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/notaryproject/notation-core-go v1.3.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.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.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
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect
github.com/wI2L/jsondiff v0.6.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
gitlab.com/gitlab-org/api/client-go v0.142.5 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // 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.yaml.in/yaml/v2 v2.4.3 // 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.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.47.0 // indirect
golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/time v0.14.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.15.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.256.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
google.golang.org/api v0.278.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // 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/v3 v3.19.2 // indirect
k8s.io/component-base v0.34.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
helm.sh/helm/v4 v4.1.4 // indirect
k8s.io/component-base v0.36.0 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
)

651
go.sum
View File

@@ -1,39 +1,40 @@
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
code.gitea.io/sdk/gitea v0.22.0 h1:HCKq7bX/HQ85Nw7c/HAhWgRye+vBp5nQOE8Md1+9Ef0=
code.gitea.io/sdk/gitea v0.22.0/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg=
code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
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/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
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-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/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
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/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k=
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.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/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 h1:figxyQZXzZQIcP3njhC68bYUiTw45J8/SsHaLW8Ax0M=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0/go.mod h1:TmlMW4W5OvXOmOyKNnor8nlMMiO1ctIyzmHme/VHsrA=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
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 v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
@@ -41,56 +42,64 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
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=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk=
github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
github.com/aws/aws-sdk-go-v2/config v1.31.20 h1:/jWF4Wu90EhKCgjTdy1DGxcbcbNrjfBHvksEL79tfQc=
github.com/aws/aws-sdk-go-v2/config v1.31.20/go.mod h1:95Hh1Tc5VYKL9NJ7tAkDcqeKt+MCXQB1hQZaRdJIZE0=
github.com/aws/aws-sdk-go-v2/credentials v1.18.24 h1:iJ2FmPT35EaIB0+kMa6TnQ+PwG5A1prEdAw+PsMzfHg=
github.com/aws/aws-sdk-go-v2/credentials v1.18.24/go.mod h1:U91+DrfjAiXPDEGYhh/x29o4p0qHX5HDqG7y5VViv64=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/ecr v1.52.0 h1:gEBb0lnIUkc/dey1rhT6iMDLRkLODMWomFLOYGHBwGQ=
github.com/aws/aws-sdk-go-v2/service/ecr v1.52.0/go.mod h1:1NVD1KuMjH2GqnPwMotPndQaT/MreKkWpjkF12d6oKU=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.4 h1:0rqbFeBlrTHNEIdrcH9g1yW0QjBOaCrGcTQ6sLcsH9w=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.4/go.mod h1:x7gU4CAyAz4BsM9hlRkhHiYw2GIr1QCmN45uwQw9l/E=
github.com/aws/aws-sdk-go-v2/service/eks v1.74.9 h1:ugqH9Vu52QlUhpTbW75rsv0WA9k704DEwOCoxWsLy+4=
github.com/aws/aws-sdk-go-v2/service/eks v1.74.9/go.mod h1:xHVz3A2oEVl3UzjCOSEz/fBeBoFrS6FJ3cc/jo0WLyM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 h1:NjShtS1t8r5LUfFVtFeI8xLAHQNTa7UI0VawXlrBMFQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.3/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 h1:gTsnx0xXNQ6SBbymoDvcoRHL+q4l/dAFsQuKfDWSaGc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo=
github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3gKGwVAf7xnx0QT0=
github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk=
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU=
github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
github.com/aws/aws-sdk-go-v2/service/ecr v1.57.2 h1:rHEW02JFJUV2/ttjzyPIvbD0YraqpyU2w6m6DfQUmdg=
github.com/aws/aws-sdk-go-v2/service/ecr v1.57.2/go.mod h1:gNS8pNht4VMzPd4UtQUL3NTUQbjEPLLmb9MqmqrqsCM=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15 h1:nW/zPIjkAgHV1xv8NHdLQtGMoHVj2toMj8/H6SMqjVw=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15/go.mod h1:FXDXpYy2PKdkQQr4ERMoRzVKcga0O/hmtRbMaQSpe8U=
github.com/aws/aws-sdk-go-v2/service/eks v1.83.0 h1:mS5rkyFt+NYryy0p4n8o80tJjBmXiQrRCQjP8jZcSLY=
github.com/aws/aws-sdk-go-v2/service/eks v1.83.0/go.mod h1:JQcyECIV9iZHm+GMrWn1pTPTJYRavOVsqPvlCbjt+Fg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/smithy-go/aws-http-auth v1.1.3 h1:8/T7/2n8x+x9sIAmi5h5mDKS8v7/u2GEpF6T6RrGMrc=
github.com/aws/smithy-go/aws-http-auth v1.1.3/go.mod h1:KL46VTjVK9De3jurMqDLBkXCP9vrAvD03zQrmyzyrQ0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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/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=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
@@ -113,21 +122,21 @@ 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/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -136,26 +145,22 @@ 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.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0=
github.com/distribution/distribution/v3 v3.1.1/go.mod h1:d7lXwZpph0bVcOj4Aqn0nMrWHIwRQGdiV5TLeI+/w6Y=
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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.4.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/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
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.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/elazarl/goproxy v1.8.0 h1:dt561rX7UAYMeFRLtzFx6uQGl2TpL1dr6uCG23nFQSY=
github.com/elazarl/goproxy v1.8.0/go.mod h1:b5xm6W48AUHNpRTCvlnd0YVh+JafCCtsLsJZvvNTz+E=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
@@ -164,68 +169,66 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/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.36.0-flux.15 h1:Et5QLnIpRjj+oZtM9gEybkAaoNsjysHq0y1253Ai94Y=
github.com/fluxcd/cli-utils v0.36.0-flux.15/go.mod h1:AqRUmWIfNE7cdL6NWSGF0bAlypGs+9x5UQ2qOtlEzv4=
github.com/fluxcd/cli-utils v1.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI=
github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M=
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.25.0 h1:zkVgujjo2VjKXbucrlTyNhHd9x+27oqyghJX9uLwQv4=
github.com/fluxcd/go-git-providers v0.25.0/go.mod h1:8Mx5WRYb61FIjOA26DAi4Ls2rZUHSxP8Nl9qkQHDch8=
github.com/fluxcd/helm-controller/api v1.4.5 h1:hMEBtgXUbJjp+ah0jPI3OOQNVngoToOQvTgFgVpAjNg=
github.com/fluxcd/helm-controller/api v1.4.5/go.mod h1:rCgx3qhjjtoIH+1EbzFC2vN71/pp0PgMDrZnGCZX5XY=
github.com/fluxcd/image-automation-controller/api v1.0.4 h1:Fgdy97hXkyh/JFjxLIyq4ZDHsKsa49aumtrvIyjVd08=
github.com/fluxcd/image-automation-controller/api v1.0.4/go.mod h1:LLBf4XQJAgnpIMlZUwfpVIkCdUtBOi31B6fDbPwBCq4=
github.com/fluxcd/image-reflector-controller/api v1.0.4 h1:/JGpTZf4eMcKG2FpWfP5H7SneSrD5P8EvwGnHiH/WLY=
github.com/fluxcd/image-reflector-controller/api v1.0.4/go.mod h1:5GS4ojHaz+W6hK80WakGIOYk8sn93AyV5X+YOne1XMw=
github.com/fluxcd/kustomize-controller/api v1.7.3 h1:g+C9Il+H33DQi/ZiQ8KpTvL9KXebXnS4oM/0uJ/C8Gw=
github.com/fluxcd/kustomize-controller/api v1.7.3/go.mod h1:Yj80JyfQpBUgLhsUZ/c86qcvPGO2+P1VCKsb8fL+L/k=
github.com/fluxcd/notification-controller/api v1.7.5 h1:6CO5bKyjodiK9exQFOdBcz0XLeo17rrrWQBTJL9NNa8=
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.21.0 h1:VVl0WmgDXJwDS3Pivkk+31h3fWHbq+BpbNLUF5d61ec=
github.com/fluxcd/pkg/apis/event v0.21.0/go.mod h1:jacQdE6DdxoBsUOLMzEZNtpd4TqtYaiH1DWoyHMSUSo=
github.com/fluxcd/pkg/apis/kustomize v1.14.0 h1:PmWqMpRX0v7/aCAUNWfohe4o1qa9G3Cg/vVr5PCedI4=
github.com/fluxcd/pkg/apis/kustomize v1.14.0/go.mod h1:CGRpU9Od4ht5+MHL6QlMfWaW87U9UTfGVM5CM4PZ28I=
github.com/fluxcd/pkg/apis/meta v1.23.0 h1:fLis5YcHnOsyKYptzBtituBm5EWNx13I0bXQsy0FG4s=
github.com/fluxcd/pkg/apis/meta v1.23.0/go.mod h1:UWsIbBPCxYvoVklr2mV2uLFBf/n17dNAmKFjRfApdDo=
github.com/fluxcd/pkg/auth v0.33.0 h1:3ccwqpBr8uWEQgl15b7S0PwJ9EgtcKObg4J1jnaof2w=
github.com/fluxcd/pkg/auth v0.33.0/go.mod h1:ZAFC8pNZxhe+7RV2cQO1K9X62HM8BbRBnCE118oY/0A=
github.com/fluxcd/pkg/cache v0.12.0 h1:mabABT3jIfuo84VbIW+qvfqMZ7PbM5tXQgQvA2uo2rc=
github.com/fluxcd/pkg/cache v0.12.0/go.mod h1:HL/9cgBmwCdKIr3JH57rxrGdb7rOgX5Z1eJlHsaV1vE=
github.com/fluxcd/pkg/chartutil v1.17.0 h1:UiSBRujE2/Qo8qrv8F3XGEMI5YANS0PpbG/r+CxKUW0=
github.com/fluxcd/pkg/chartutil v1.17.0/go.mod h1:Zt8EolwLyYj0689Ivk9cL4mYZlR3BBi/XVGyeGmPVlE=
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.38.0 h1:fFH2PkL+VCtQ1aJec/6l3Wq5fQG1w02HHKfVY+gz1S4=
github.com/fluxcd/pkg/git v0.38.0/go.mod h1:PHilCGIM2t10CJ++yK4SFHIcBAXqMk14XcwZ/Rqw23I=
github.com/fluxcd/pkg/git/gogit v0.42.0 h1:AaaMNbuzO0lARhI2SoqLKkQhEN6QYE0fT5VG9oyMUTc=
github.com/fluxcd/pkg/git/gogit v0.42.0/go.mod h1:DxH7DalONwiZ29odi7TjmLlhO9xsO7svy9GPGgHsHsc=
github.com/fluxcd/pkg/gittestserver v0.22.0 h1:LkOmXAoYB/OoVDMhneeyqUIGvSCb9fJtcFIAFkNGpzc=
github.com/fluxcd/pkg/gittestserver v0.22.0/go.mod h1:kFBmc9akpmdY5EU5d0MuSj2eHgq6ebkmEKf6MEUyTIg=
github.com/fluxcd/pkg/kustomize v1.24.0 h1:ckFB7hh9FpJA1Oy3bYl88p9On/zsZZTbwlLBgP6eUkA=
github.com/fluxcd/pkg/kustomize v1.24.0/go.mod h1:cydG0vKpDuUaoP5STpKfxY3zqgzaARv5HsWDOFyt5nA=
github.com/fluxcd/pkg/oci v0.58.0 h1:T5rBq+4uUHyMF9EUAlb3Wffa/yrYrVm97NO21p607Sg=
github.com/fluxcd/pkg/oci v0.58.0/go.mod h1:hKX3KlqMtMHeGgR/LmReNwIgTHIidR0Qh7eOoffZjvc=
github.com/fluxcd/pkg/runtime v0.90.0 h1:IONDsN9npJdWqbSAfsI8j10sXpgaLd6ywycKwp35Wwo=
github.com/fluxcd/pkg/runtime v0.90.0/go.mod h1:D/gUsaSpyw6Od2QEL7MELi5m+oUmwokuxUVZ+vKQxdo=
github.com/fluxcd/pkg/sourceignore v0.15.0 h1:tB30fuk4jlB3UGlR7ppJguZ3zaJh1iwuTCEufs91jSM=
github.com/fluxcd/pkg/sourceignore v0.15.0/go.mod h1:mZ9X6gNtNkq9ZsD35LebEYjePc7DRvB2JdowMNoj6IU=
github.com/fluxcd/pkg/ssa v0.61.0 h1:GeueQfZVrjPLEzmEkq6gpFTBr1MDcqUihCQDf6AaIo8=
github.com/fluxcd/pkg/ssa v0.61.0/go.mod h1:PNRlgihYbmlQU5gzsB14nrsNMbtACNanBnKhLCWmeX8=
github.com/fluxcd/pkg/ssh v0.23.0 h1:PqmBpQB7Rxspdb3LZZo2yflC7m990EU/cYtjK3sO3Tg=
github.com/fluxcd/pkg/ssh v0.23.0/go.mod h1:cwKVFIi64ELlBsruJqbRMYcvrEQm65GSd4A3U3Cabpw=
github.com/fluxcd/pkg/tar v0.16.0 h1:P7hR2FjLBuI9AIndRqrZaO7VYFbbBzbYMBsLe2hh7fI=
github.com/fluxcd/pkg/tar v0.16.0/go.mod h1:Bz1DmQ5vTY3/HLWw9LM0kHRL1vtgF4eVs5QmeRAD8UM=
github.com/fluxcd/pkg/version v0.11.0 h1:gcAXw/HZ4XX9v+2xhO+NWf/hAArYKgSmzqT9Yrx4VjY=
github.com/fluxcd/pkg/version v0.11.0/go.mod h1:XsgsKJVmVFWnG3DE19YBM0EeWVuG4BPAHpAmOe6GFmo=
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/source-watcher/api/v2 v2.0.3 h1:SsVGAaMBxzvcgrOz/Kl6c2ybMHVqoiEFwtI+bDuSeSs=
github.com/fluxcd/source-watcher/api/v2 v2.0.3/go.mod h1:Nx3QZweVyuhaOtSNrw+oxifG+qrakPvjgNAN9qlUTb0=
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.5 h1:xQA/9gbifMvZPGhSNKHsrkq829dI/yTBASVdYp9/s4Y=
github.com/fluxcd/helm-controller/api v1.5.5/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI=
github.com/fluxcd/image-automation-controller/api v1.1.4 h1:i78AwbcICXSX+a1MQwjNA1Uxxs1e3kfi3EJ21fWzb7w=
github.com/fluxcd/image-automation-controller/api v1.1.4/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660=
github.com/fluxcd/image-reflector-controller/api v1.1.2 h1:VPwUgA8WyPVVs16uSkwvjOAY6pvTYgAb0fL90t0RKLE=
github.com/fluxcd/image-reflector-controller/api v1.1.2/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0=
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.10.0 h1:KPfAmELNvtvaz8wixnm/MYXqa+MJf7ntVVMUU93Aenk=
github.com/fluxcd/pkg/apis/acl v0.10.0/go.mod h1:a87i2A7AlFO5N2J8CxtzaUCCDmuLLWOHwkKu3eJF5fY=
github.com/fluxcd/pkg/apis/event v0.26.0 h1:QzBRz9Qy91jzJmLlOhd4ecp6OWDpMVFvm311AwxCXzY=
github.com/fluxcd/pkg/apis/event v0.26.0/go.mod h1:0yy7FMJABzq8PP5/VEi1Gro6ssPaPlH9xuPIoF+Rm6M=
github.com/fluxcd/pkg/apis/kustomize v1.18.0 h1:FCNjViCLyKYj6lddpnjXybKBTC2eK6eXK9YOaNwLVTM=
github.com/fluxcd/pkg/apis/kustomize v1.18.0/go.mod h1:mvtMtM4NNLipdCna6DYPC6Bd42xeaF15N+tNO+F6kxY=
github.com/fluxcd/pkg/apis/meta v1.27.0 h1:EspByEk5j8w3rs1cGbEh9AjSmpDwQIz7DFG/zzqf6uI=
github.com/fluxcd/pkg/apis/meta v1.27.0/go.mod h1:2t6JyrRfvIBhx6EBnXfFh/6sCCJ1db9WGaqko0JmNOE=
github.com/fluxcd/pkg/auth v0.45.0 h1:3p/CMdFJ1c8LevdLd2cikackaTW1Tw8JB2xg4YqpP8A=
github.com/fluxcd/pkg/auth v0.45.0/go.mod h1:/ijjR9G/l6URmEo/zWzpJ3XIMIXWP1Ad7AXTCqmWioY=
github.com/fluxcd/pkg/cache v0.14.0 h1:wEwJA8NhYj+nH9P6ifcsglDZARWlcbxbmwngGOzfU4c=
github.com/fluxcd/pkg/cache v0.14.0/go.mod h1:KwzU2gyVQ83YOHJsbBeveJ0HsXmLrH0I668zX19d/+s=
github.com/fluxcd/pkg/chartutil v1.24.0 h1:Rh9o50eJUnAioL5q2JVPxO4DjFjwHwPE5K9LcBy7lqA=
github.com/fluxcd/pkg/chartutil v1.24.0/go.mod h1:oycULCP00m46dxiskme1Yawe74UFLZzX0jqHb6xzdmQ=
github.com/fluxcd/pkg/envsubst v1.7.0 h1:PL9Nj/V2fgaMR9KYZR7mEEw+vlYgP80nFZjOQQKAfJs=
github.com/fluxcd/pkg/envsubst v1.7.0/go.mod h1:aoWeSIOamhqBZ3bHVj1GDwpdA10DXrI8yYbyjPiFly0=
github.com/fluxcd/pkg/git v0.49.0 h1:OtI0TjMVC/GV/yPos4waA5fAs5u3/2YEYBFuZuJA3Rc=
github.com/fluxcd/pkg/git v0.49.0/go.mod h1:Xnrqtz60/0jqfcy5UsY9sxlvPmlvzelPsej2v3L7wW8=
github.com/fluxcd/pkg/gittestserver v0.29.0 h1:2j03zKVL6iVn6oiUuecG/O/3Q1pULWM9JrF/HSjkpnc=
github.com/fluxcd/pkg/gittestserver v0.29.0/go.mod h1:O8151jV0ppBZTb9IUXMjxh6hZpkiuLq8JQHDBPOkZFw=
github.com/fluxcd/pkg/kustomize v1.32.0 h1:5lLT2dgR+JrcoJHB7/K50o0AcJikKvXcRd3r7jIYZC8=
github.com/fluxcd/pkg/kustomize v1.32.0/go.mod h1:Xz1QIUIKexXuSolRQY63843wSycPVuIsVhE9C+aJWl8=
github.com/fluxcd/pkg/oci v0.66.0 h1:nlBA3/MvJShjA+VFPKDRoXLUpH+e8THVvCMST+qUTlI=
github.com/fluxcd/pkg/oci v0.66.0/go.mod h1:9Z4Juu/BhLLKHKOkOalfwIkZYsjTViD0YDHzW9w5R9c=
github.com/fluxcd/pkg/runtime v0.106.0 h1:SGzUaO/G6bUBLkZIxLGnCVEEURSPuldSG2exYjlrsQk=
github.com/fluxcd/pkg/runtime v0.106.0/go.mod h1:7lCogUwTGKoG3bfBvCn6eQgPYF7cvVp5R+gL3bB1HX8=
github.com/fluxcd/pkg/sourceignore v0.18.0 h1:WU2tPKasG9AM7/H/LlqdjULyaSknnZBTrpHsDDtOuns=
github.com/fluxcd/pkg/sourceignore v0.18.0/go.mod h1:mnH7rFFlEbMTclhz7JZP7tiHssKdXRNpCqnly2JGvaI=
github.com/fluxcd/pkg/ssa v0.74.0 h1:a5VqQXBQ5TQlzucwZ9l37bshzR3IPK5awzfMEBF3pdM=
github.com/fluxcd/pkg/ssa v0.74.0/go.mod h1:6mdhG+O1db8oXsP7Ex3buJTexM5r9F7+LzSybBgT1f0=
github.com/fluxcd/pkg/ssh v0.25.0 h1:4Y9WmuNqyKvH759UznU5DGHRcOuoJ/dQM6sbsaDZYYM=
github.com/fluxcd/pkg/ssh v0.25.0/go.mod h1:Fli2Ogu4uaIVGbCy+r0vvZlMO0RfuInyNY1q2FVIx0o=
github.com/fluxcd/pkg/tar v1.2.0 h1:T6WFB5M0YRHktlrgdKNskqpdp76TVDdWTOeuWz33CFs=
github.com/fluxcd/pkg/tar v1.2.0/go.mod h1:Wlalp5vIVe+BbckkKkqExKcoHAeeWJPAzwK7ONeFcS0=
github.com/fluxcd/pkg/version v0.15.0 h1:E2Ju4i0vj8ZXLHKz/F4a8JTmDh7Jcg8okB0hK5rEoTM=
github.com/fluxcd/pkg/version v0.15.0/go.mod h1:LEHnvLMgbTk4kelF+JHHzaG77kY9uTWodMtadPRMEW8=
github.com/fluxcd/source-controller/api v1.8.5 h1:mLKc9YVMk46JCt1BQbkG6irkrpBZp95kiXh2+GYB6KQ=
github.com/fluxcd/source-controller/api v1.8.5/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=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -240,14 +243,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.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
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.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
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.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
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=
@@ -269,14 +272,11 @@ github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDq
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -301,43 +301,38 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic=
github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=
github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=
github.com/google/go-github/v82 v82.0.0 h1:OH09ESON2QwKCUVMYmMcVu1IFKFoaZHwqYaUtr/MVfk=
github.com/google/go-github/v82 v82.0.0/go.mod h1:hQ6Xo0VKfL8RZ7z1hSfB4fvISg0QqHOqe9BP0qo+WvM=
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/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.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=
@@ -387,10 +382,10 @@ 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
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=
@@ -423,8 +418,6 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
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/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
@@ -433,10 +426,6 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -452,8 +441,6 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/notaryproject/notation-core-go v1.3.0 h1:mWJaw1QBpBxpjLSiKOjzbZvB+xh2Abzk14FHWQ+9Kfs=
github.com/notaryproject/notation-core-go v1.3.0/go.mod h1:hzvEOit5lXfNATGNBT8UQRx2J6Fiw/dq/78TQL8aE64=
github.com/notaryproject/notation-go v1.3.2 h1:4223iLXOHhEV7ZPzIUJEwwMkhlgzoYFCsMJvSH1Chb8=
@@ -464,10 +451,10 @@ 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.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw=
github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
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.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -480,8 +467,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
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.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -501,15 +488,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.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=
@@ -523,18 +510,17 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -556,86 +542,93 @@ 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.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
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=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
github.com/wI2L/jsondiff v0.6.1 h1:ISZb9oNWbP64LHnu4AUhsMF5W0FIj5Ok3Krip9Shqpw=
github.com/wI2L/jsondiff v0.6.1/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM=
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/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/gitlab-org/api/client-go v0.142.5 h1:zvengEU958Fjwasi1V+9QNRw0viqNKkqUwvFD15XDZI=
gitlab.com/gitlab-org/api/client-go v0.142.5/go.mod h1:Ru5IRauphXt9qwmTzJD7ou1dH7Gc6pnsdFWEiMMpmB0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/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.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
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.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.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/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.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.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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -645,22 +638,20 @@ 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.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
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=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-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=
@@ -671,23 +662,21 @@ 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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.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=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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.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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.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=
@@ -695,7 +684,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
@@ -714,8 +702,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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.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=
@@ -725,8 +713,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.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
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=
@@ -737,47 +725,42 @@ 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
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-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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.278.0 h1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E=
google.golang.org/api v0.278.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
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-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/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.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -790,41 +773,41 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/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/v3 v3.19.2 h1:psQjaM8aIWrSVEly6PgYtLu/y6MRSmok4ERiGhZmtUY=
helm.sh/helm/v3 v3.19.2/go.mod h1:gX10tB5ErM+8fr7bglUUS/UfTOO8UUTYWIBH1IYNnpE=
k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo=
k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE=
k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/cli-runtime v0.34.2 h1:cct1GEuWc3IyVT8MSCoIWzRGw9HJ/C5rgP32H60H6aE=
k8s.io/cli-runtime v0.34.2/go.mod h1:X13tsrYexYUCIq8MarCBy8lrm0k0weFPTpcaNo7lms4=
k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ=
k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM=
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-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/kubectl v0.34.2 h1:+fWGrVlDONMUmmQLDaGkQ9i91oszjjRAa94cr37hzqA=
k8s.io/kubectl v0.34.2/go.mod h1:X2KTOdtZZNrTWmUD4oHApJ836pevSl+zvC5sI6oO2YQ=
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.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
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.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg=
k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA=
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo=
k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms=
k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4=
sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
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.0 h1:I7nry5p8iDJbuRdYS7ez8MUvw7XVNPcIP5GkzzuXIIQ=
sigs.k8s.io/kustomize/api v0.21.0/go.mod h1:XGVQuR5n2pXKWbzXHweZU683pALGw/AMVO4zU4iS8SE=
sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ=
sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI=
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -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"
@@ -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
@@ -81,13 +141,15 @@ type Builder struct {
action kustomize.Action
kustomization *kustomizev1.Kustomization
timeout time.Duration
spinner *yacspin.Spinner
spinner *spinner.Spinner
dryRun bool
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
}
// BuilderOptionFunc is a function that configures a Builder
@@ -111,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
}
}
@@ -174,6 +223,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 {
@@ -198,6 +256,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 {
@@ -215,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
@@ -223,6 +291,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,10 +334,18 @@ 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")
}
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")
}
@@ -360,10 +444,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
}
}
@@ -378,9 +463,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 +473,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 +521,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 +576,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 +591,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()
@@ -520,30 +604,32 @@ func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomizatio
return nil, fmt.Errorf("kustomize build failed: %w", err)
}
if kustomization.Spec.PostBuild == nil {
return m, nil
}
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)
if err != nil {
return nil, err
}
for _, res := range m.Resources() {
// run variable substitutions
if kustomization.Spec.PostBuild != nil {
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)
outRes, err := kustomize.SubstituteVariables(ctx,
b.client,
unstructured.Unstructured{Object: data},
res,
kustomize.SubstituteWithDryRun(b.dryRun),
kustomize.SubstituteWithStrict(b.strictSubst),
)
if err != nil {
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
}
if outRes != nil {
_, err = m.Replace(res)
if err != nil {
return nil, err
}
outRes, err := kustomize.SubstituteVariables(ctx,
b.client,
unstructured.Unstructured{Object: data},
res,
kustomize.SubstituteWithDryRun(b.dryRun),
kustomize.SubstituteWithStrict(b.strictSubst),
)
if err != nil {
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
}
if outRes != nil {
_, err = m.Replace(res)
if err != nil {
return nil, err
}
}
}
}
@@ -732,24 +818,14 @@ 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 {
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
}
@@ -757,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
}

View File

@@ -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")
}
}

View File

@@ -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()
@@ -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

View File

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

197
internal/plugin/catalog.go Normal file
View File

@@ -0,0 +1,197 @@
/*
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"
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"
)
// CatalogClient fetches plugin manifests and catalogs from a remote URL.
type CatalogClient struct {
// BaseURL is the catalog base URL for fetching manifests.
BaseURL string
// HTTPClient is the HTTP client used for catalog requests.
HTTPClient *http.Client
// GetEnv returns the value of an environment variable.
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) (*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 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 != plugintypes.APIVersion {
return nil, fmt.Errorf("plugin %q has unsupported apiVersion %q (expected %q)", name, manifest.APIVersion, plugintypes.APIVersion)
}
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() (*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 plugintypes.Catalog
if err := yaml.Unmarshal(body, &catalog); err != nil {
return nil, fmt.Errorf("failed to parse plugin catalog: %w", err)
}
if catalog.APIVersion != plugintypes.APIVersion {
return nil, fmt.Errorf("plugin catalog has unsupported apiVersion %q (expected %q)", catalog.APIVersion, plugintypes.APIVersion)
}
if catalog.Kind != plugintypes.CatalogKind {
return nil, fmt.Errorf("plugin catalog has unexpected kind %q (expected %q)", catalog.Kind, plugintypes.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 *plugintypes.Manifest, version string) (*plugintypes.Version, 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 *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
}
}
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)
}

View File

@@ -0,0 +1,308 @@
/*
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"
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
)
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 == "/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 := &plugintypes.Manifest{
Name: "operator",
Versions: []plugintypes.Version{
{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(&plugintypes.Manifest{Name: "empty"}, "")
if err == nil {
t.Fatal("expected error, got nil")
}
})
}
func TestResolvePlatform(t *testing.T) {
pv := &plugintypes.Version{
Version: "0.45.0",
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"},
},
}
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")
}
})
}
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")
}
})
}

View File

@@ -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 :<directive_int>.
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
}

View File

@@ -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])
}
}
})
}
}

View File

@@ -0,0 +1,205 @@
/*
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 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 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)
}
// 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
}

View File

@@ -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)
}
})
}

View File

@@ -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())
}

View File

@@ -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
}

366
internal/plugin/install.go Normal file
View File

@@ -0,0 +1,366 @@
/*
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"
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
)
// Installer handles downloading, verifying, and installing plugins.
type Installer struct {
// HTTPClient is the HTTP client used for downloading plugin archives.
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 *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)
}
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)
}
// 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)
// 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)
}
switch format {
case formatZip:
err = extractFromZip(tmpFile.Name(), extractTarget, destPath)
case formatTarGz:
err = extractFromTarGz(tmpFile.Name(), extractTarget, destPath)
case formatTar:
err = extractFromTar(tmpFile.Name(), extractTarget, destPath)
case formatBinary:
err = copyPluginBinary(tmpFile.Name(), destPath)
default:
return fmt.Errorf("unexpected plugin format: %v", format)
}
if err != nil {
return err
}
receipt := plugintypes.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) *plugintypes.Receipt {
data, err := os.ReadFile(receiptPath(pluginDir, name))
if err != nil {
return nil
}
var receipt plugintypes.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 *plugintypes.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)
}
// 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 {
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()
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)
}
// 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.
func extractTarStream(r io.Reader, targetName, destPath string) error {
tr := tar.NewReader(r)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to read tar: %w", err)
}
if !filepath.IsLocal(header.Name) {
continue
}
if !header.FileInfo().Mode().IsRegular() {
continue
}
if matchArchiveEntry(header.Name, targetName) {
return writeStreamToFile(tr, destPath)
}
}
return fmt.Errorf("binary %q not found in archive", targetName)
}
// 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 {
return fmt.Errorf("failed to open zip: %w", err)
}
defer r.Close()
for _, f := range r.File {
if !filepath.IsLocal(f.Name) {
continue
}
if !f.FileInfo().Mode().IsRegular() {
continue
}
if matchArchiveEntry(f.Name, targetName) {
rc, err := f.Open()
if err != nil {
return fmt.Errorf("failed to open %q in zip: %w", targetName, err)
}
err = writeStreamToFile(rc, destPath)
rc.Close()
return err
}
}
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 {
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()
}

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