1
0
mirror of synced 2026-05-30 03:40:47 +00:00

Compare commits

..

120 Commits

Author SHA1 Message Date
Hidde Beydals 02ee573bc2 Cover signingKey round-trip in export tests
Extends the existing TestExport 'image update' case with a signingKey
block on the seeded ImageUpdateAutomation, asserting the new field
survives the kubeClient.Get + serialize path. Parallels how the
existing fixture exercises every other field on the resource.

Also patches the embedded CRD source under manifests/bases/image-
automation-controller/ to inject the signingKey.type schema property
into both v1 and v1beta2. The patch is transitional and should be
removed once the image-automation-controller release bundle includes
the new type field natively.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:34 +02:00
Hidde Beydals 944f7cc3f0 Cover create image update signing flags
Adds golden-file tests for the new --signing-key-secret and
--signing-key-type flags: no-signing (baseline), default-gpg (asserts
type: gpg is rendered explicitly when only the secret is set), ssh,
and the two validation-error cases. Establishes
cmd/flux/testdata/create_image_update/ for future expansion of this
command's coverage.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:33 +02:00
Hidde Beydals ea730551d4 Add signing-key flags to create image update
Closes a pre-existing gap where the ImageUpdateAutomation SigningKey
field was reachable only by hand-editing the rendered YAML. The two
new flags --signing-key-secret and --signing-key-type populate the
spec.git.commit.signingKey block directly.

When --signing-key-secret is set without --signing-key-type, the run
function fills in 'gpg' explicitly so the rendered YAML matches what
the apiserver would default it to. Validation rejects --signing-key-
type without --signing-key-secret and rejects values outside
{gpg, ssh}, using the typed SigningKeyType constants exported from
the image-automation-controller API so the validator and populator
share a single source of truth.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:33 +02:00
Hidde Beydals 8b20c2efc1 Test bootstrap signing flag validation
Covers the validation matrix of the new --gpg-* / --ssh-signing-*
surface: mutual exclusion (across GPG/SSH groups and within the SSH
group between --ssh-signing-key-file and --ssh-signing-reuse-private-
key), alias resolution between --ssh-signing-password and
--ssh-signing-passphrase, the dependency checks (--ssh-signing-
password requires --ssh-signing-key-file; --ssh-signing-reuse-
private-key requires --private-key-file), and pre-flight key-parse
failures (malformed PEM, encrypted SSH key without passphrase, GPG
ring with wrong passphrase). Test keys are checked in so the test
does not depend on local ssh-keygen or gpg invocations at run time.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:32 +02:00
Hidde Beydals d446b60b7e Wire SSH signing into provider bootstrap commands
Adds the same explicit-path SSH-signing wiring to flux bootstrap
github / gitlab / gitea / bitbucket-server, consulting the new
effectiveSshSigningPassword helper for the resolved passphrase.

The reuse-path wiring applies only to gitlab and bitbucket-server
(which consume --private-key-file as the SSH transport key). github
and gitea generate the transport key in-process, so they reject
--ssh-signing-reuse-private-key explicitly with a message explaining
why. The reject check fires immediately after each subcommand's
bootstrapOpts slice literal closes, before any conditional appends,
so the failure semantics match the reading order of the code.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:31 +02:00
Hidde Beydals d8ae939d79 Wire SSH signing into bootstrap git
Reads --ssh-signing-key-file when set, decodes the file contents,
resolves the effective signing passphrase, and appends
bootstrap.WithSSHCommitSigning to the bootstrap options. When
--ssh-signing-reuse-private-key is set, reads the transport
--private-key-file, pre-flights it against the subcommand-local
gitArgs.password, and reuses the same bytes + passphrase for signing.

The reuse-path pre-flight lives in this subcommand's RunE because
bootstrapValidate does not have access to the transport password.
Mutual exclusion with --gpg-* and explicit-path key-parse validation
are enforced upstream in bootstrapValidate.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:31 +02:00
Hidde Beydals e1970390a1 Add SSH signing flags to bootstrap
Introduces four new persistent flags on flux bootstrap:
--ssh-signing-key-file, --ssh-signing-password, the hidden alias
--ssh-signing-passphrase, and the reuse boolean
--ssh-signing-reuse-private-key. They sit next to the existing
--gpg-key-ring / --gpg-passphrase / --gpg-key-id surface.

bootstrapValidate pre-flights the configured signing key for the
explicit GPG and SSH paths so malformed PEM, wrong passphrases, and
unsupported SSH algorithms surface before any clone runs. The GPG
pre-flight calls the now-exported SelectOpenPGPSigningEntity from
pkg/bootstrap directly, so the pre-flight cannot drift from the
bootstrap commit path. The reuse path's pre-flight runs inside each
subcommand's RunE (where the subcommand-local SSH transport password
is in scope) and lands with the wiring commits that follow.

A small effectiveSshSigningPassword helper resolves the
--ssh-signing-passphrase alias purely (returning the resolved value
or a mutual-exclusion error) instead of mutating the
package-scoped bootstrapArgs singleton inside bootstrapValidate.

Mutual exclusion is enforced between the GPG and SSH groups, and
between --ssh-signing-key-file and --ssh-signing-reuse-private-key.
--ssh-signing-reuse-private-key requires --private-key-file;
--ssh-signing-password requires --ssh-signing-key-file. The
--ssh-signing-passphrase alias is hidden in --help.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:30 +02:00
Hidde Beydals 1be91ee7dd Cover pkg/bootstrap SSH signing roundtrip
Adds two layers of coverage for the SSH commit-signing path that the
previous commit wires through PlainGitBootstrapper.

TestPlainGitBootstrapper_resolveSigner exercises every branch of the
new dispatcher: nil configuration, GPG-only, SSH-only, encrypted-SSH-
without-passphrase failure, and the documented GPG-wins-when-both-
set precedence.

TestPlainGitBootstrapper_sshSignerProducesVerifiableCommit drives an
end-to-end roundtrip: resolveSigner returns an SSH signer, the signer
plugs into repository.WithSigner, gogit.Client.Commit produces a
commit object, and signature.VerifySSHSignature cryptographically
verifies the gpgsig header against the matching authorized_key.
Catches regressions in the SSH-signing wiring that the dispatcher
unit tests would miss.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:30 +02:00
Hidde Beydals 88c5a7f68d Migrate bootstrap signing to generic Signer
Bumps fluxcd/pkg/git to a pseudo-version exposing the generic
signature.Signer interface and the NewOpenPGPSigner / NewSSHSigner
constructors, and migrates pkg/bootstrap's two WithSigner call sites
accordingly.

Adds a parallel WithSSHCommitSigning option alongside the existing
WithGitCommitSigning so callers can sign commits with an SSH private
key. PlainGitBootstrapper now dispatches through a new resolveSigner
helper that returns either an OpenPGP or SSH signer; the
repository.WithSigner option is appended conditionally to avoid the
typed-nil interface hazard the new generic field introduces.

The bootstrap path's OpenPGP entity selector is renamed and exported
as SelectOpenPGPSigningEntity so the flux CLI's pre-flight (introduced
later in this branch) can call it directly instead of carrying a
duplicate.

Also bumps image-automation-controller/api to a pseudo-version that
exposes SigningKey.Type and the SigningKeyTypeGPG/SigningKeyTypeSSH
constants; the bump is bundled here so the rest of the branch builds
incrementally. Refs fluxcd/pkg#398[1].

[1]: https://github.com/fluxcd/pkg/issues/398

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-05-29 22:18:17 +02:00
Matheus Pimenta 8c41d5b56d Merge pull request #5908 from fluxcd/trigger-receiver
Introduce `flux trigger receiver`
2026-05-23 12:46:55 +01:00
Matheus Pimenta 4bfdb6d459 Introduce flux trigger receiver
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-05-23 01:44:07 +01:00
Stefan Prodan 9d9e56208c Merge pull request #5909 from immanuwell/validate-helm-source-url
Validate Helm source URL schemes
2026-05-22 12:26:13 +03:00
Immanuel Tikhonov 5425087730 Validate Helm source URL schemes
Reject HelmRepository source URLs with schemes unsupported by the
source-controller API before generating or applying the object.

Signed-off-by: Immanuel Tikhonov <pchpr.00@list.ru>
Assisted-by: codex/gpt-5
2026-05-22 08:56:53 +04: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](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417)

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

Updates `hashicorp/setup-terraform` from 3.1.2 to 4.0.0
- [Release notes](https://github.com/hashicorp/setup-terraform/releases)
- [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/setup-terraform/compare/b9cd54a3c349d3f38e8881555d616ced269862dd...5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85)

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

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

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

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

Updates `github/codeql-action` from 4.32.4 to 4.32.6
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...0d579ffd059c29b07949a3cce3983f0780820c98)

Updates `anchore/sbom-action` from 0.22.2 to 0.23.1
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/28d71544de8eaf1b958d335707167c5f783590ad...57aae528053a48a3f6235f2d9461b05fbcb7366d)

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

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

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: replicatedhq/replicated-actions
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: hashicorp/setup-terraform
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: docker/login-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: github/codeql-action
  dependency-version: 4.32.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: anchore/sbom-action
  dependency-version: 0.23.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <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](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

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

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

Updates `fluxcd/pkg` from 1.22.0 to 1.27.0
- [Commits](https://github.com/fluxcd/pkg/compare/bf02f0a2d612cc07e0892166369fa8f63246aabb...9a8c0edd5da84dc51a585738c67e3a3950d7fbf0)

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

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

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

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

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](https://github.com/github/codeql-action/compare/3599b3baa15b485a2e49ef411a7a4bb2452e7f93...9e907b5e64f6b83e7804b09294d44122997950d6)

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](https://github.com/anchore/sbom-action/compare/f8bdd1d8ac5e901a77a92f111440fdb1b593736b...28d71544de8eaf1b958d335707167c5f783590ad)

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

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](https://github.com/peter-evans/create-pull-request/compare/271a8d0340265f705b14b6d32b9829c1cb33d45e...c0f553fe549906ede9cf27b5156039d195d2ece0)

---
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
146 changed files with 8679 additions and 1790 deletions
+3 -3
View File
@@ -44,12 +44,12 @@
description: Feature request proposals in the RFC format description: Feature request proposals in the RFC format
color: '#D621C3' color: '#D621C3'
aliases: ['area/RFC'] aliases: ['area/RFC']
- name: backport:release/v2.5.x
description: To be backported to release/v2.5.x
color: '#ffd700'
- name: backport:release/v2.6.x - name: backport:release/v2.6.x
description: To be backported to release/v2.6.x description: To be backported to release/v2.6.x
color: '#ffd700' color: '#ffd700'
- name: backport:release/v2.7.x - name: backport:release/v2.7.x
description: To be backported to release/v2.7.x description: To be backported to release/v2.7.x
color: '#ffd700' color: '#ffd700'
- name: backport:release/v2.8.x
description: To be backported to release/v2.8.x
color: '#ffd700'
+1 -1
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`. - Updates the controller API package version in `go.mod`.
- Patches the controller CRDs version in the `manifests/crds` overlay. - Patches the controller CRDs version in the `manifests/crds` overlay.
- Patches the controller Deployment version in `manifests/bases` 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. - Triggers the e2e test suite to run for the opened PR.
+1 -1
View File
@@ -24,6 +24,6 @@ jobs:
name: action on ${{ matrix.version }} name: action on ${{ matrix.version }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup flux - name: Setup flux
uses: ./action uses: ./action
+1 -1
View File
@@ -8,6 +8,6 @@ jobs:
permissions: permissions:
contents: write # for reading and creating branches. contents: write # for reading and creating branches.
pull-requests: write # for creating pull requests against release 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: secrets:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }} github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
+17 -17
View File
@@ -3,13 +3,13 @@ name: conformance
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ 'main', 'update-components', 'release/**', 'conform*' ] branches: [ 'main', 'update-components-**', 'release/**', 'conform*' ]
permissions: permissions:
contents: read contents: read
env: env:
GO_VERSION: 1.25.x GO_VERSION: 1.26.x
jobs: jobs:
conform-kubernetes: conform-kubernetes:
@@ -23,9 +23,9 @@ jobs:
fail-fast: false fail-fast: false
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache-dependency-path: | cache-dependency-path: |
@@ -40,7 +40,7 @@ jobs:
run: | run: |
make build make build
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with: with:
version: v0.30.0 version: v0.30.0
cluster_name: ${{ steps.prep.outputs.CLUSTER }} cluster_name: ${{ steps.prep.outputs.CLUSTER }}
@@ -76,13 +76,13 @@ jobs:
matrix: matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes # Keep this list up-to-date with https://endoflife.date/kubernetes
# Available versions can be found with "replicated cluster versions" # 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 fail-fast: false
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache-dependency-path: | cache-dependency-path: |
@@ -97,7 +97,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml" KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Create repository - name: Create repository
@@ -107,7 +107,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Create cluster - name: Create cluster
id: 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: with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }} api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: "k3s" kubernetes-distribution: "k3s"
@@ -150,7 +150,7 @@ jobs:
kubectl delete ns flux-system --wait kubectl delete ns flux-system --wait
- name: Delete cluster - name: Delete cluster
if: ${{ always() }} 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 continue-on-error: true
with: with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }} api-token: ${{ secrets.REPLICATED_API_TOKEN }}
@@ -168,13 +168,13 @@ jobs:
strategy: strategy:
matrix: matrix:
# Keep this list up-to-date with https://endoflife.date/red-hat-openshift # 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 fail-fast: false
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache-dependency-path: | cache-dependency-path: |
@@ -189,7 +189,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml" KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Create repository - name: Create repository
@@ -199,7 +199,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Create cluster - name: Create cluster
id: 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: with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }} api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: "openshift" kubernetes-distribution: "openshift"
@@ -240,7 +240,7 @@ jobs:
kubectl delete ns flux-system --wait kubectl delete ns flux-system --wait
- name: Delete cluster - name: Delete cluster
if: ${{ always() }} 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 continue-on-error: true
with: with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }} api-token: ${{ secrets.REPLICATED_API_TOKEN }}
+4 -4
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]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: CheckoutD - name: CheckoutD
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: 1.25.x go-version: 1.26.x
cache-dependency-path: tests/integration/go.sum cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform - name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0
- name: Setup Flux CLI - name: Setup Flux CLI
run: make build run: make build
working-directory: ./ working-directory: ./
+8 -8
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]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: 1.25.x go-version: 1.26.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with: with:
version: v0.30.0 version: v0.30.0
cluster_name: kind cluster_name: kind
# The versions below should target the newest Kubernetes version # The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes # Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.32.1-amd64 node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.32.0 kubectl_version: v1.33.0
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Setup yq - name: Setup yq
uses: fluxcd/pkg/actions/yq@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/yq@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Set outputs - name: Set outputs
+7 -7
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]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: 1.25.x go-version: 1.26.x
cache-dependency-path: tests/integration/go.sum cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform - name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0
- name: Setup Flux CLI - name: Setup Flux CLI
run: make build run: make build
working-directory: ./ working-directory: ./
@@ -56,11 +56,11 @@ jobs:
- name: Setup gcloud - name: Setup gcloud
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
- name: Setup QEMU - 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 - 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 - 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: with:
registry: us-central1-docker.pkg.dev registry: us-central1-docker.pkg.dev
username: oauth2accesstoken username: oauth2accesstoken
+7 -7
View File
@@ -23,16 +23,16 @@ jobs:
- 5000:5000 - 5000:5000
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: 1.25.x go-version: 1.26.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with: with:
version: v0.30.0 version: v0.30.0
cluster_name: kind cluster_name: kind
@@ -40,13 +40,13 @@ jobs:
config: .github/kind/config.yaml # disable KIND-net config: .github/kind/config.yaml # disable KIND-net
# The versions below should target the oldest supported Kubernetes version # The versions below should target the oldest supported Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes # Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.32.1-amd64 node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.32.0 kubectl_version: v1.33.0
- name: Setup Calico for network policy - name: Setup Calico for network policy
run: | run: |
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Run tests - name: Run tests
run: make test run: make test
- name: Run e2e tests - name: Run e2e tests
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
actions: read actions: read
contents: read contents: read
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run analysis - name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with: with:
@@ -28,12 +28,12 @@ jobs:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true publish_results: true
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
retention-days: 5 retention-days: 5
- name: Upload SARIF results - 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: with:
sarif_file: results.sarif sarif_file: results.sarif
+17 -17
View File
@@ -22,35 +22,35 @@ jobs:
packages: write # needed for ghcr access packages: write # needed for ghcr access
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Unshallow - name: Unshallow
run: git fetch --prune --unshallow run: git fetch --prune --unshallow
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: 1.25.x go-version: 1.26.x
cache: false cache: false
- name: Setup QEMU - 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 - name: Setup Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Setup Syft - 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 - name: Setup Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
with: with:
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3 cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with: with:
registry: ghcr.io registry: ghcr.io
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with: with:
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -63,7 +63,7 @@ jobs:
run: | run: |
kustomize build manifests/crds > all-crds.yaml kustomize build manifests/crds > all-crds.yaml
- name: Generate OpenAPI JSON schemas from CRDs - name: Generate OpenAPI JSON schemas from CRDs
uses: fluxcd/pkg/actions/crdjsonschema@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/crdjsonschema@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
with: with:
crd: all-crds.yaml crd: all-crds.yaml
output: schemas output: schemas
@@ -72,7 +72,7 @@ jobs:
tar -czvf ./output/crd-schemas.tar.gz -C schemas . tar -czvf ./output/crd-schemas.tar.gz -C schemas .
- name: Run GoReleaser - name: Run GoReleaser
id: run-goreleaser id: run-goreleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with: with:
version: latest version: latest
args: release --skip=validate args: release --skip=validate
@@ -103,9 +103,9 @@ jobs:
id-token: write id-token: write
packages: write packages: write
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
- name: Setup Flux CLI - name: Setup Flux CLI
uses: ./action/ uses: ./action/
with: with:
@@ -116,13 +116,13 @@ jobs:
VERSION=$(flux version --client | awk '{ print $NF }') VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with: with:
registry: ghcr.io registry: ghcr.io
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with: with:
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -150,7 +150,7 @@ jobs:
--path="./flux-system" \ --path="./flux-system" \
--source=${{ github.repositoryUrl }} \ --source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}" --revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 - uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
with: with:
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3 cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
- name: Sign manifests - name: Sign manifests
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
permissions: permissions:
contents: read # for reading the repository code. contents: read # for reading the repository code.
security-events: write # for uploading the CodeQL analysis results. 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: secrets:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
fossa-token: ${{ secrets.FOSSA_TOKEN }} fossa-token: ${{ secrets.FOSSA_TOKEN }}
+1 -1
View File
@@ -12,6 +12,6 @@ jobs:
permissions: permissions:
contents: read # for reading the labels file. contents: read # for reading the labels file.
issues: write # for creating and updating labels. 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: secrets:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -16,11 +16,11 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: 1.25.x go-version: 1.26.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
@@ -96,7 +96,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with: with:
token: ${{ secrets.BOT_GITHUB_TOKEN }} token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: | commit-message: |
@@ -106,7 +106,7 @@ jobs:
committer: GitHub <noreply@github.com> committer: GitHub <noreply@github.com>
author: fluxcdbot <fluxcdbot@users.noreply.github.com> author: fluxcdbot <fluxcdbot@users.noreply.github.com>
signoff: true signoff: true
branch: update-components branch: update-components-${{ github.ref_name }}
title: Update toolkit components title: Update toolkit components
body: | body: |
${{ steps.update.outputs.pr_body }} ${{ steps.update.outputs.pr_body }}
+13
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
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.
+105 -130
View File
@@ -1,154 +1,129 @@
# Contributing # Contributing
Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and accepts contributions via GitHub pull requests.
accepts contributions via GitHub pull requests. This document outlines This document outlines the conventions to get your contribution accepted.
some of the conventions on to make it easier to get your contribution We gratefully welcome improvements to documentation as well as code contributions.
accepted.
We gratefully welcome improvements to issues and documentation as well as to If you are new to the project, we recommend starting with documentation improvements or
code. 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 ## Certificate of Origin
By contributing to this project you agree to the Developer Certificate of By contributing to this project you agree to the Developer Certificate of Origin (DCO).
Origin (DCO). This document was created by the Linux Kernel community and is a This document was created by the Linux Kernel community and is a simple statement that you,
simple statement that you, as a contributor, have the legal right to make the as a contributor, have the legal right to make the contribution.
contribution.
We require all commits to be signed. By signing off with your signature, you We require all commits to be signed. By signing off with your signature, you certify that you wrote
certify that you wrote the patch or otherwise have the right to contribute the 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):
material by the rules of the [DCO](DCO):
`Signed-off-by: Jane Doe <jane.doe@example.com>` `Signed-off-by: Jane Doe <jane.doe@example.com>`
The signature must contain your real name The signature must contain your real name (sorry, no pseudonyms or anonymous contributions).
(sorry, no pseudonyms or anonymous contributions) If your `user.name` and `user.email` are set in your Git config,
If your `user.name` and `user.email` are configured in your Git config,
you can sign your commit automatically with `git commit -s`. you can sign your commit automatically with `git commit -s`.
## Communications
For realtime communications we use Slack: To join the conversation, simply
join the [CNCF](https://slack.cncf.io/) Slack workspace and use the
[#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel.
To discuss ideas and specifications we use [Github
Discussions](https://github.com/fluxcd/flux2/discussions).
For announcements we use a mailing list as well. Simply subscribe to
[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev)
to join the conversation (there you can also add calendar invites
to your Google calendar for our [Flux
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)).
## Understanding Flux and the GitOps Toolkit
If you are entirely new to Flux and the GitOps Toolkit,
you might want to take a look at the [introductory talk and demo](https://www.youtube.com/watch?v=qQBtSkgl7tI).
This project is composed of:
- [flux2](https://github.com/fluxcd/flux2): The Flux CLI
- [source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets)
- [source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns
- [kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize
- [helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm
- [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events
- [image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries
- [image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patches container image tags in Git
### Understanding the code
To get started with developing controllers, you might want to review
[our guide](https://fluxcd.io/flux/gitops-toolkit/source-watcher/) which
walks you through writing a short and concise controller that watches out
for source changes.
## How to run the test suite
Prerequisites:
* go >= 1.25
* kubectl >= 1.30
* kustomize >= 5.0
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
```bash
make install-envtest
```
Then you can run the unit tests with:
```bash
make test
```
After [installing Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation) on your machine,
create a cluster for testing with:
```bash
make setup-kind
```
Then you can run the end-to-end tests with:
```bash
make e2e
```
When the output of the Flux CLI changes, to automatically update the golden
files used in the test, pass `-update` flag to the test as:
```bash
make e2e TEST_ARGS="-update"
```
Since not all packages use golden files for testing, `-update` argument must be
passed only for the packages that use golden files. Use the variables
`TEST_PKG_PATH` for unit tests and `E2E_TEST_PKG_PATH` for e2e tests, to set the
path of the target test package:
```bash
# Unit test
make test TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
# e2e test
make e2e E2E_TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
```
Teardown the e2e environment with:
```bash
make cleanup-kind
```
## Acceptance policy ## Acceptance policy
These things will make a PR more likely to be accepted: These things will make a PR more likely to be accepted:
- a well-described requirement - 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.
- tests for new code - 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.
- tests for old code! - 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.
- new code and tests follow the conventions in old code and tests - All top-level Go code and exported names should have doc comments, as should non-trivial unexported type or function declarations.
- a good commit message (see below) - 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`.
- 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`
In general, we will merge a PR once one maintainer has endorsed it. In general, we will merge a PR once one maintainer has endorsed it.
For substantial changes, more people may become involved, and you might 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. 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 - Limit the subject to 50 characters, start with a capital letter and do not end with a period.
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.
- Explain what and why in the body, if more than a trivial change; - 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").
wrap it at 72 characters. - 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) ## Pull Request Process
has some more helpful advice on documenting your work.
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).
+2 -2
View File
@@ -1,4 +1,4 @@
FROM alpine:3.22 AS builder FROM alpine:3.23 AS builder
RUN apk add --no-cache ca-certificates curl RUN apk add --no-cache ca-certificates curl
@@ -10,7 +10,7 @@ RUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
RUN kubectl version --client=true 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 RUN apk add --no-cache ca-certificates
+2 -2
View File
@@ -17,8 +17,8 @@ rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2
all: test build all: test build
tidy: tidy:
go mod tidy -compat=1.25 go mod tidy -compat=1.26
cd tests/integration && go mod tidy -compat=1.25 cd tests/integration && go mod tidy -compat=1.26
fmt: fmt:
go fmt ./... go fmt ./...
+90
View File
@@ -20,9 +20,11 @@ import (
"context" "context"
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/signature"
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@@ -30,6 +32,7 @@ import (
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/bootstrap"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/v2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
) )
@@ -79,6 +82,11 @@ type bootstrapFlags struct {
gpgPassphrase string gpgPassphrase string
gpgKeyID string gpgKeyID string
sshSigningKeyFile string
sshSigningPassword string
sshSigningPassphrase string
sshSigningReusePrivateKey bool
force bool force bool
commitMessageAppendix string commitMessageAppendix string
@@ -139,6 +147,12 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, "gpg-passphrase", "", "passphrase for decrypting GPG private key") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, "gpg-passphrase", "", "passphrase for decrypting GPG private key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, "gpg-key-id", "", "key id for selecting a particular key") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, "gpg-key-id", "", "key id for selecting a particular key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshSigningKeyFile, "ssh-signing-key-file", "", "path to an SSH private key file used for signing commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshSigningPassword, "ssh-signing-password", "", "passphrase for decrypting SSH signing key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshSigningPassphrase, "ssh-signing-passphrase", "", "alias for --ssh-signing-password")
bootstrapCmd.PersistentFlags().MarkHidden("ssh-signing-passphrase")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.sshSigningReusePrivateKey, "ssh-signing-reuse-private-key", false, "use the SSH transport key (--private-key-file) to sign commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a different tool such as Helm") bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a different tool such as Helm")
@@ -195,6 +209,31 @@ func bootstrapValidate() error {
return fmt.Errorf("invalid --registry-creds format, expected 'user:password'") return fmt.Errorf("invalid --registry-creds format, expected 'user:password'")
} }
sshSigningSet := bootstrapArgs.sshSigningKeyFile != "" || bootstrapArgs.sshSigningReusePrivateKey
if bootstrapArgs.gpgKeyRingPath != "" && sshSigningSet {
return fmt.Errorf("--gpg-* and --ssh-signing-* are mutually exclusive; pick one signing format")
}
if bootstrapArgs.sshSigningKeyFile != "" && bootstrapArgs.sshSigningReusePrivateKey {
return fmt.Errorf("--ssh-signing-key-file and --ssh-signing-reuse-private-key are mutually exclusive")
}
if bootstrapArgs.sshSigningReusePrivateKey && bootstrapArgs.privateKeyFile == "" {
return fmt.Errorf("--ssh-signing-reuse-private-key requires --private-key-file")
}
sshSigningPwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
if sshSigningPwd != "" && bootstrapArgs.sshSigningKeyFile == "" {
return fmt.Errorf("--ssh-signing-password requires --ssh-signing-key-file")
}
if err := preflightSigningKey(); err != nil {
return err
}
if len(bootstrapArgs.sshHostKeyAlgorithms) > 0 { if len(bootstrapArgs.sshHostKeyAlgorithms) > 0 {
git.HostKeyAlgos = bootstrapArgs.sshHostKeyAlgorithms git.HostKeyAlgos = bootstrapArgs.sshHostKeyAlgorithms
} }
@@ -214,6 +253,57 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
return m return m
} }
// preflightSigningKey reads and parses the configured signing key so
// malformed PEM, wrong passphrases, and unsupported SSH algorithms
// surface before any clone runs.
func preflightSigningKey() error {
switch {
case bootstrapArgs.gpgKeyRingPath != "":
ring, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
if err != nil {
return fmt.Errorf("invalid GPG signing key: %w", err)
}
if _, err := bootstrap.SelectOpenPGPSigningEntity(ring, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID); err != nil {
return fmt.Errorf("invalid GPG signing key: %w", err)
}
case bootstrapArgs.sshSigningKeyFile != "":
pemBytes, err := os.ReadFile(bootstrapArgs.sshSigningKeyFile)
if err != nil {
return fmt.Errorf("failed to read SSH signing key file: %w", err)
}
pwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
if _, err := signature.NewSSHSigner(pemBytes, []byte(pwd)); err != nil {
return fmt.Errorf("invalid SSH signing key: %w", err)
}
}
return nil
}
// effectiveSshSigningPassword resolves the SSH signing-key passphrase
// from --ssh-signing-password and its hidden alias
// --ssh-signing-passphrase. When both are set with the same value, the
// value is returned. When both are set with different non-empty values,
// an error is returned. When neither is set, an empty string is
// returned with no error.
func effectiveSshSigningPassword() (string, error) {
pw := bootstrapArgs.sshSigningPassword
alias := bootstrapArgs.sshSigningPassphrase
switch {
case pw != "" && alias != "":
if pw != alias {
return "", fmt.Errorf("--ssh-signing-password and --ssh-signing-passphrase are aliases; do not pass both")
}
return pw, nil
case pw == "" && alias != "":
return alias, nil
default:
return pw, nil
}
}
// confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation. // confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation.
// It returns a nil error if Flux is not installed or the user confirms overriding an existing installation // It returns a nil error if Flux is not installed or the user confirms overriding an existing installation
func confirmBootstrap(ctx context.Context, kubeClient client.Client) error { func confirmBootstrap(ctx context.Context, kubeClient client.Client) error {
+26
View File
@@ -24,6 +24,7 @@ import (
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/gogit"
"github.com/fluxcd/pkg/git/signature"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
@@ -287,6 +288,31 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile()) bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
} }
if bootstrapArgs.sshSigningKeyFile != "" {
pemBytes, err := os.ReadFile(bootstrapArgs.sshSigningKeyFile)
if err != nil {
return fmt.Errorf("failed to read SSH signing key file: %w", err)
}
pwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(pwd)))
}
if bootstrapArgs.sshSigningReusePrivateKey {
pemBytes, err := os.ReadFile(bootstrapArgs.privateKeyFile)
if err != nil {
return fmt.Errorf("failed to read transport private key for signing: %w", err)
}
if _, err := signature.NewSSHSigner(pemBytes, []byte(gitArgs.password)); err != nil {
return fmt.Errorf("invalid signing key (reused from --private-key-file): %w", err)
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(gitArgs.password)))
}
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...) b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
+28
View File
@@ -30,6 +30,7 @@ import (
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/gogit"
"github.com/fluxcd/pkg/git/signature"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
@@ -315,6 +316,33 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
if bootstrapArgs.sshSigningKeyFile != "" {
pemBytes, err := os.ReadFile(bootstrapArgs.sshSigningKeyFile)
if err != nil {
return fmt.Errorf("failed to read SSH signing key file: %w", err)
}
pwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(pwd)))
}
if bootstrapArgs.sshSigningReusePrivateKey {
pemBytes, err := os.ReadFile(bootstrapArgs.privateKeyFile)
if err != nil {
return fmt.Errorf("failed to read transport private key for signing: %w", err)
}
// Reuse-path pre-flight: bootstrapValidate cannot run this check
// because the SSH transport password is subcommand-local.
if _, err := signature.NewSSHSigner(pemBytes, []byte(gitArgs.password)); err != nil {
return fmt.Errorf("invalid signing key (reused from --private-key-file): %w", err)
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(gitArgs.password)))
}
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs
b, err := bootstrap.NewPlainGitProvider(gitClient, kubeClient, bootstrapOpts...) b, err := bootstrap.NewPlainGitProvider(gitClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
+19
View File
@@ -252,6 +252,12 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
if bootstrapArgs.sshSigningReusePrivateKey {
return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap gitea'; " +
"that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse")
}
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
} }
@@ -265,6 +271,19 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile()) bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
} }
if bootstrapArgs.sshSigningKeyFile != "" {
pemBytes, err := os.ReadFile(bootstrapArgs.sshSigningKeyFile)
if err != nil {
return fmt.Errorf("failed to read SSH signing key file: %w", err)
}
pwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(pwd)))
}
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...) b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
+19
View File
@@ -259,6 +259,12 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
if bootstrapArgs.sshSigningReusePrivateKey {
return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap github'; " +
"that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse")
}
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
} }
@@ -272,6 +278,19 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile()) bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
} }
if bootstrapArgs.sshSigningKeyFile != "" {
pemBytes, err := os.ReadFile(bootstrapArgs.sshSigningKeyFile)
if err != nil {
return fmt.Errorf("failed to read SSH signing key file: %w", err)
}
pwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(pwd)))
}
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...) b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
+26
View File
@@ -27,6 +27,7 @@ import (
"github.com/fluxcd/go-git-providers/gitprovider" "github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/gogit"
"github.com/fluxcd/pkg/git/signature"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
@@ -321,6 +322,31 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile()) bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
} }
if bootstrapArgs.sshSigningKeyFile != "" {
pemBytes, err := os.ReadFile(bootstrapArgs.sshSigningKeyFile)
if err != nil {
return fmt.Errorf("failed to read SSH signing key file: %w", err)
}
pwd, err := effectiveSshSigningPassword()
if err != nil {
return err
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(pwd)))
}
if bootstrapArgs.sshSigningReusePrivateKey {
pemBytes, err := os.ReadFile(bootstrapArgs.privateKeyFile)
if err != nil {
return fmt.Errorf("failed to read transport private key for signing: %w", err)
}
if _, err := signature.NewSSHSigner(pemBytes, []byte(gitArgs.password)); err != nil {
return fmt.Errorf("invalid signing key (reused from --private-key-file): %w", err)
}
bootstrapOpts = append(bootstrapOpts,
bootstrap.WithSSHCommitSigning(pemBytes, []byte(gitArgs.password)))
}
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...) b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
+155
View File
@@ -0,0 +1,155 @@
/*
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"
"testing"
)
func TestBootstrapValidate_signingFlags(t *testing.T) {
tests := []struct {
name string
gpgRing string
gpgPass string
sshKey string
sshPass string
sshPassp string
privateKey string
reuse bool
wantErr string
}{
{name: "no signing flags is valid"},
{name: "GPG only is valid", gpgRing: "./testdata/bootstrap/gpg.pgp"},
{name: "SSH only is valid", sshKey: "./testdata/bootstrap/ed25519.private"},
{
name: "Reuse-private-key with private-key-file is valid",
privateKey: "./testdata/bootstrap/ed25519.private",
reuse: true,
},
{
name: "GPG + SSH errors",
gpgRing: "./testdata/bootstrap/gpg.pgp",
sshKey: "./testdata/bootstrap/ed25519.private",
wantErr: "--gpg-* and --ssh-signing-* are mutually exclusive",
},
{
name: "GPG + reuse errors",
gpgRing: "./testdata/bootstrap/gpg.pgp",
privateKey: "./testdata/bootstrap/ed25519.private",
reuse: true,
wantErr: "--gpg-* and --ssh-signing-* are mutually exclusive",
},
{
name: "SSH key-file + reuse errors",
sshKey: "./testdata/bootstrap/ed25519.private",
privateKey: "./testdata/bootstrap/ed25519.private",
reuse: true,
wantErr: "--ssh-signing-key-file and --ssh-signing-reuse-private-key are mutually exclusive",
},
{
name: "Reuse without private-key-file errors",
reuse: true,
wantErr: "--ssh-signing-reuse-private-key requires --private-key-file",
},
{
name: "SSH password without key errors",
sshPass: "secret",
wantErr: "--ssh-signing-password requires --ssh-signing-key-file",
},
{
name: "SSH passphrase alias alone applies",
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
sshPassp: "abcde12345",
},
{
name: "SSH password and passphrase with same value passes",
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
sshPass: "abcde12345",
sshPassp: "abcde12345",
},
{
name: "SSH password and passphrase with different values errors",
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
sshPass: "right",
sshPassp: "wrong",
wantErr: "are aliases; do not pass both",
},
{
name: "SSH malformed key fails pre-flight",
sshKey: "./testdata/bootstrap/malformed.private",
wantErr: "invalid SSH signing key",
},
{
name: "SSH encrypted key without password fails pre-flight",
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
wantErr: "passphrase required",
},
// The GPG fixture used here is encrypted (passphrase: "right") so that
// passing the wrong passphrase exercises the Decrypt error path.
// An unencrypted key would make Decrypt a no-op regardless of the
// passphrase supplied.
{
name: "GPG with wrong passphrase fails pre-flight",
gpgRing: "./testdata/bootstrap/gpg-encrypted.pgp",
gpgPass: "wrong",
wantErr: "invalid GPG signing key",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
savedGpgRing := bootstrapArgs.gpgKeyRingPath
savedGpgPass := bootstrapArgs.gpgPassphrase
savedSshKey := bootstrapArgs.sshSigningKeyFile
savedSshPass := bootstrapArgs.sshSigningPassword
savedSshPassp := bootstrapArgs.sshSigningPassphrase
savedPrivKey := bootstrapArgs.privateKeyFile
savedReuse := bootstrapArgs.sshSigningReusePrivateKey
defer func() {
bootstrapArgs.gpgKeyRingPath = savedGpgRing
bootstrapArgs.gpgPassphrase = savedGpgPass
bootstrapArgs.sshSigningKeyFile = savedSshKey
bootstrapArgs.sshSigningPassword = savedSshPass
bootstrapArgs.sshSigningPassphrase = savedSshPassp
bootstrapArgs.privateKeyFile = savedPrivKey
bootstrapArgs.sshSigningReusePrivateKey = savedReuse
}()
bootstrapArgs.gpgKeyRingPath = tt.gpgRing
bootstrapArgs.gpgPassphrase = tt.gpgPass
bootstrapArgs.sshSigningKeyFile = tt.sshKey
bootstrapArgs.sshSigningPassword = tt.sshPass
bootstrapArgs.sshSigningPassphrase = tt.sshPassp
bootstrapArgs.privateKeyFile = tt.privateKey
bootstrapArgs.sshSigningReusePrivateKey = tt.reuse
err := bootstrapValidate()
if tt.wantErr == "" {
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
return
}
if err == nil {
t.Fatalf("expected error containing %q, got nil", tt.wantErr)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("expected error containing %q, got: %v", tt.wantErr, err)
}
})
}
}
+150 -3
View File
@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -48,9 +49,10 @@ from the given directory or a single manifest file.`,
} }
type buildArtifactFlags struct { type buildArtifactFlags struct {
output string output string
path string path string
ignorePaths []string ignorePaths []string
resolveSymlinks bool
} }
var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...) 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.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().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().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) 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) 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) logger.Actionf("building artifact from %s", path)
ociClient := oci.NewClient(oci.DefaultOptions()) ociClient := oci.NewClient(oci.DefaultOptions())
@@ -96,6 +108,141 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
return nil 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) { func saveReaderToFile(reader io.Reader) (string, error) {
b, err := io.ReadAll(bufio.NewReader(reader)) b, err := io.ReadAll(bufio.NewReader(reader))
if err != nil { if err != nil {
+147
View File
@@ -18,6 +18,7 @@ package main
import ( import (
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "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"))
}
+5
View File
@@ -72,6 +72,7 @@ type buildKsFlags struct {
strictSubst bool strictSubst bool
recursive bool recursive bool
localSources map[string]string localSources map[string]string
inMemoryBuild bool
} }
var buildKsArgs buildKsFlags var buildKsArgs buildKsFlags
@@ -85,6 +86,8 @@ func init() {
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.") "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().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().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) buildCmd.AddCommand(buildKsCmd)
} }
@@ -130,6 +133,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithStrictSubstitute(buildKsArgs.strictSubst), build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive), build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources), build.WithLocalSources(buildKsArgs.localSources),
build.WithInMemoryBuild(buildKsArgs.inMemoryBuild),
) )
} else { } else {
builder, err = build.NewBuilder(name, buildKsArgs.path, builder, err = build.NewBuilder(name, buildKsArgs.path,
@@ -140,6 +144,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithStrictSubstitute(buildKsArgs.strictSubst), build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive), build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources), build.WithLocalSources(buildKsArgs.localSources),
build.WithInMemoryBuild(buildKsArgs.inMemoryBuild),
) )
} }
+42
View File
@@ -52,6 +52,12 @@ func TestBuildKustomization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-result.yaml", resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile", 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", name: "build podinfo without service",
args: "build kustomization podinfo --path ./testdata/build-kustomization/delete-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", resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml",
assertFunc: "assertGoldenTemplateFile", 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", 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", 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", resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile", 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{ tmpl := map[string]string{
@@ -145,6 +163,12 @@ spec:
resultFile: "./testdata/build-kustomization/podinfo-result.yaml", resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile", 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", name: "build podinfo without service",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/delete-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", resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile", 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{ tmpl := map[string]string{
@@ -241,6 +277,12 @@ func TestBuildKustomizationPathNormalization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-result.yaml", resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile", 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)", name: "build with complex relative path (parent dir)",
args: "build kustomization podinfo --path ./testdata/build-kustomization/../build-kustomization/podinfo", args: "build kustomization podinfo --path ./testdata/build-kustomization/../build-kustomization/podinfo",
+1 -1
View File
@@ -60,7 +60,7 @@ type checkFlags struct {
} }
var kubernetesConstraints = []string{ var kubernetesConstraints = []string{
">=1.32.0-0", ">=1.33.0-0",
} }
var checkArgs checkFlags var checkArgs checkFlags
+25
View File
@@ -23,6 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
) )
@@ -75,6 +76,8 @@ type imageUpdateFlags struct {
commitTemplate string commitTemplate string
authorName string authorName string
authorEmail string authorEmail string
signingKeySecret string
signingKeyType string
} }
var imageUpdateArgs = imageUpdateFlags{} var imageUpdateArgs = imageUpdateFlags{}
@@ -89,6 +92,8 @@ func init() {
flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages") flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages")
flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author") flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author")
flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author") flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author")
flags.StringVar(&imageUpdateArgs.signingKeySecret, "signing-key-secret", "", "name of the Secret containing the signing key referenced in spec.git.commit.signingKey")
flags.StringVar(&imageUpdateArgs.signingKeyType, "signing-key-type", "", "signing-key format: gpg or ssh (defaults to gpg when --signing-key-secret is set)")
createImageCmd.AddCommand(createImageUpdateCmd) createImageCmd.AddCommand(createImageUpdateCmd)
} }
@@ -112,6 +117,15 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("the author email is required (--author-email)") return fmt.Errorf("the author email is required (--author-email)")
} }
if imageUpdateArgs.signingKeyType != "" && imageUpdateArgs.signingKeySecret == "" {
return fmt.Errorf("--signing-key-type requires --signing-key-secret")
}
if imageUpdateArgs.signingKeyType != "" &&
imageUpdateArgs.signingKeyType != string(autov1.SigningKeyTypeGPG) &&
imageUpdateArgs.signingKeyType != string(autov1.SigningKeyTypeSSH) {
return fmt.Errorf("--signing-key-type must be one of: gpg, ssh")
}
labels, err := parseLabels() labels, err := parseLabels()
if err != nil { if err != nil {
return err return err
@@ -163,6 +177,17 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
} }
} }
if imageUpdateArgs.signingKeySecret != "" {
keyType := imageUpdateArgs.signingKeyType
if keyType == "" {
keyType = string(autov1.SigningKeyTypeGPG)
}
update.Spec.GitSpec.Commit.SigningKey = &autov1.SigningKey{
SecretRef: meta.LocalObjectReference{Name: imageUpdateArgs.signingKeySecret},
Type: autov1.SigningKeyType(keyType),
}
}
if createArgs.export { if createArgs.export {
return printExport(exportImageUpdate(&update)) return printExport(exportImageUpdate(&update))
} }
+62
View File
@@ -0,0 +1,62 @@
/*
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 TestCreateImageUpdate(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "no signing key",
args: "create image update flux-system --git-repo-ref=flux-system --checkout-branch=main --author-name=flux --author-email=flux@example.com --interval=1m0s --namespace=flux-system --export",
assert: assertGoldenFile("./testdata/create_image_update/no-signing.yaml"),
},
{
name: "signing secret without explicit type defaults to gpg",
args: "create image update flux-system --git-repo-ref=flux-system --checkout-branch=main --author-name=flux --author-email=flux@example.com --signing-key-secret=my-key --interval=1m0s --namespace=flux-system --export",
assert: assertGoldenFile("./testdata/create_image_update/signing-default-gpg.yaml"),
},
{
name: "ssh signing key",
args: "create image update flux-system --git-repo-ref=flux-system --checkout-branch=main --author-name=flux --author-email=flux@example.com --signing-key-secret=my-deploy-key --signing-key-type=ssh --interval=1m0s --namespace=flux-system --export",
assert: assertGoldenFile("./testdata/create_image_update/signing-ssh.yaml"),
},
{
name: "signing-key-type without secret errors",
args: "create image update flux-system --git-repo-ref=flux-system --checkout-branch=main --author-name=flux --author-email=flux@example.com --signing-key-type=ssh --namespace=flux-system --export",
assert: assertError("--signing-key-type requires --signing-key-secret"),
},
{
name: "invalid signing-key-type errors",
args: "create image update flux-system --git-repo-ref=flux-system --checkout-branch=main --author-name=flux --author-email=flux@example.com --signing-key-secret=k --signing-key-type=pgp --namespace=flux-system --export",
assert: assertError("--signing-key-type must be one of: gpg, ssh"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}
+3
View File
@@ -136,6 +136,9 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
if !strings.HasPrefix(kustomizationArgs.path.String(), "./") { if !strings.HasPrefix(kustomizationArgs.path.String(), "./") {
return fmt.Errorf("path must begin with ./") return fmt.Errorf("path must begin with ./")
} }
if kustomizationArgs.source.Name == "" {
return fmt.Errorf("source is required")
}
if !createArgs.export { if !createArgs.export {
logger.Generatef("generating Kustomization") logger.Generatef("generating Kustomization")
+48
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)
})
}
}
+4 -3
View File
@@ -30,6 +30,7 @@ import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
) )
@@ -49,7 +50,7 @@ var createReceiverCmd = &cobra.Command{
} }
type receiverFlags struct { type receiverFlags struct {
receiverType string receiverType flags.ReceiverType
secretRef string secretRef string
events []string events []string
resources []string resources []string
@@ -58,7 +59,7 @@ type receiverFlags struct {
var receiverArgs receiverFlags var receiverArgs receiverFlags
func init() { 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().StringVar(&receiverArgs.secretRef, "secret-ref", "", "")
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, "event", []string{}, "also accepts comma-separated values") createReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, "event", []string{}, "also accepts comma-separated values")
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.resources, "resource", []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, Labels: sourceLabels,
}, },
Spec: notificationv1.ReceiverSpec{ Spec: notificationv1.ReceiverSpec{
Type: receiverArgs.receiverType, Type: receiverArgs.receiverType.String(),
Events: receiverArgs.events, Events: receiverArgs.events,
Resources: resources, Resources: resources,
SecretRef: meta.LocalObjectReference{ SecretRef: meta.LocalObjectReference{
+16
View File
@@ -56,6 +56,22 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
} }
existing.StringData = secret.StringData 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 { if err := kubeClient.Update(ctx, &existing); err != nil {
return err return err
} }
+134
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
}
+74
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)
})
}
}
+9 -6
View File
@@ -114,9 +114,16 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if _, err := url.Parse(sourceHelmArgs.url); err != nil { helmURL, err := url.Parse(sourceHelmArgs.url)
if err != nil {
return fmt.Errorf("url parse failed: %w", err) return fmt.Errorf("url parse failed: %w", err)
} }
if helmURL.Scheme != "http" && helmURL.Scheme != "https" && helmURL.Scheme != sourcev1.HelmRepositoryTypeOCI {
return fmt.Errorf("url scheme '%s' not supported, can be: http, https and oci", helmURL.Scheme)
}
if helmURL.Host == "" {
return fmt.Errorf("url host is required")
}
helmRepository := &sourcev1.HelmRepository{ helmRepository := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -132,11 +139,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
}, },
} }
url, err := url.Parse(sourceHelmArgs.url) if helmURL.Scheme == sourcev1.HelmRepositoryTypeOCI {
if err != nil {
return fmt.Errorf("failed to parse URL: %w", err)
}
if url.Scheme == sourcev1.HelmRepositoryTypeOCI {
helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI
helmRepository.Spec.Provider = sourceHelmArgs.ociProvider helmRepository.Spec.Provider = sourceHelmArgs.ociProvider
} }
+6
View File
@@ -36,6 +36,12 @@ func TestCreateSourceHelm(t *testing.T) {
resultFile: "name is required", resultFile: "name is required",
assertFunc: "assertError", assertFunc: "assertError",
}, },
{
name: "unsupported URL scheme",
args: "create source helm podinfo --url=git://example.com/charts --export",
resultFile: "url scheme 'git' not supported, can be: http, https and oci",
assertFunc: "assertError",
},
{ {
name: "OCI repo", name: "OCI repo",
args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export", args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export",
+10
View File
@@ -62,6 +62,8 @@ type diffKsFlags struct {
strictSubst bool strictSubst bool
recursive bool recursive bool
localSources map[string]string localSources map[string]string
inMemoryBuild bool
ignoreNotFound bool
} }
var diffKsArgs diffKsFlags 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.") "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().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().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) diffCmd.AddCommand(diffKsCmd)
} }
@@ -113,6 +119,8 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithRecursive(diffKsArgs.recursive), build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources), build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(), build.WithSingleKustomization(),
build.WithInMemoryBuild(diffKsArgs.inMemoryBuild),
build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound),
) )
} else { } else {
builder, err = build.NewBuilder(name, diffKsArgs.path, builder, err = build.NewBuilder(name, diffKsArgs.path,
@@ -124,6 +132,8 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithRecursive(diffKsArgs.recursive), build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources), build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(), build.WithSingleKustomization(),
build.WithInMemoryBuild(diffKsArgs.inMemoryBuild),
build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound),
) )
} }
+114 -2
View File
@@ -48,7 +48,7 @@ func TestDiffKustomization(t *testing.T) {
name: "diff nothing deployed", name: "diff nothing deployed",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false", args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
objectFile: "", 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", 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", 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", args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml",
objectFile: "", objectFile: "",
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"), assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"),
}, },
{ {
name: "diff with recursive", 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 { func createObjectFromFile(objectFile string, templateValues map[string]string, t *testing.T) []*unstructured.Unstructured {
buf, err := os.ReadFile(objectFile) buf, err := os.ReadFile(objectFile)
if err != nil { if err != nil {
+5 -2
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 { func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
listOpts := &metav1.ListOptions{} listOpts := &metav1.ListOptions{}
clientListOpts = append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
err := runtimeresource.FollowContinue(listOpts, err := runtimeresource.FollowContinue(listOpts,
func(options metav1.ListOptions) (runtime.Object, error) { func(options metav1.ListOptions) (runtime.Object, error) {
newEvents := &corev1.EventList{} 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) return nil, fmt.Errorf("error getting events: %w", err)
} }
el.Items = append(el.Items, newEvents.Items...) el.Items = append(el.Items, newEvents.Items...)
+104
View File
@@ -20,11 +20,13 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"strings" "strings"
"testing" "testing"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"sigs.k8s.io/controller-runtime/pkg/client" "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 { func kindNameIndexer(obj client.Object) []string {
e, ok := obj.(*corev1.Event) e, ok := obj.(*corev1.Event)
if !ok { if !ok {
+42 -3
View File
@@ -28,13 +28,22 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2" helmv2 "github.com/fluxcd/helm-controller/api/v2"
) )
type getHelmReleaseFlags struct {
showSource bool
}
var getHrArgs getHelmReleaseFlags
var getHelmReleaseCmd = &cobra.Command{ var getHelmReleaseCmd = &cobra.Command{
Use: "helmreleases", Use: "helmreleases",
Aliases: []string{"hr", "helmrelease"}, Aliases: []string{"hr", "helmrelease"},
Short: "Get HelmRelease statuses", Short: "Get HelmRelease statuses",
Long: "The get helmreleases command prints the statuses of the resources.", Long: "The get helmreleases command prints the statuses of the resources.",
Example: ` # List all Helm releases and their status 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)), ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{ get := getCommand{
@@ -69,6 +78,7 @@ var getHelmReleaseCmd = &cobra.Command{
} }
func init() { func init() {
getHelmReleaseCmd.Flags().BoolVar(&getHrArgs.showSource, "show-source", false, "show the source reference for each helmrelease")
getCmd.AddCommand(getHelmReleaseCmd) getCmd.AddCommand(getHelmReleaseCmd)
} }
@@ -79,16 +89,45 @@ func getHelmReleaseRevision(helmRelease helmv2.HelmRelease) string {
return helmRelease.Status.LastAttemptedRevision 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 { func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := a.Items[i] item := a.Items[i]
revision := getHelmReleaseRevision(item) revision := getHelmReleaseRevision(item)
status, msg := statusAndMessage(item.Status.Conditions) 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) revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string { 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 { if includeNamespace {
headers = append([]string{"Namespace"}, headers...) headers = append([]string{"Namespace"}, headers...)
} }
+28 -3
View File
@@ -30,13 +30,22 @@ import (
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
) )
type getKustomizationFlags struct {
showSource bool
}
var getKsArgs getKustomizationFlags
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{
Use: "kustomizations", Use: "kustomizations",
Aliases: []string{"ks", "kustomization"}, Aliases: []string{"ks", "kustomization"},
Short: "Get Kustomization statuses", Short: "Get Kustomization statuses",
Long: `The get kustomizations command prints the statuses of the resources.`, Long: `The get kustomizations command prints the statuses of the resources.`,
Example: ` # List all kustomizations and their status 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)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{ get := getCommand{
@@ -74,6 +83,7 @@ var getKsCmd = &cobra.Command{
} }
func init() { func init() {
getKsCmd.Flags().BoolVar(&getKsArgs.showSource, "show-source", false, "show the source reference for each kustomization")
getCmd.AddCommand(getKsCmd) getCmd.AddCommand(getKsCmd)
} }
@@ -83,12 +93,27 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision) revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg) 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) revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }
func (a kustomizationListAdapter) headers(includeNamespace bool) []string { 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 { if includeNamespace {
headers = append([]string{"Namespace"}, headers...) headers = append([]string{"Namespace"}, headers...)
} }
+39 -4
View File
@@ -100,6 +100,16 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
# Uninstall Flux and delete CRDs # Uninstall Flux and delete CRDs
flux uninstall`, flux uninstall`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 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") ns, err := cmd.Flags().GetString("namespace")
if err != nil { if err != nil {
return fmt.Errorf("error getting namespace: %w", err) 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} var logger = stderrLogger{stderr: os.Stderr}
type rootFlags struct { type rootFlags struct {
timeout time.Duration timeout time.Duration
verbose bool verbose bool
pollInterval time.Duration pollInterval time.Duration
defaults install.Options nsFollowsKubeContext bool
defaults install.Options
} }
// RequestError is a custom error type that wraps an error returned by the flux api. // 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() { func init() {
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation") 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.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() configureDefaultNamespace()
kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag
@@ -186,6 +199,8 @@ func main() {
// logger, we configure it's logger to do nothing. // logger, we configure it's logger to do nothing.
ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{})) ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{}))
registerPlugins()
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
if err, ok := err.(*RequestError); ok { 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() { func configureDefaultNamespace() {
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace *kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE") fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE")
+221
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))
})
}
}
+7
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 // in subsequent executions which causes tests to fail that rely on the value
// of "Changed". // of "Changed".
resumeCmd.PersistentFlags().Lookup("wait").Changed = false 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) args, err := shellwords.Parse(cmd)
if err != nil { if err != nil {
@@ -456,6 +462,7 @@ func resetCmdArgs() {
secretGitArgs = NewSecretGitFlags() secretGitArgs = NewSecretGitFlags()
secretGitHubAppArgs = secretGitHubAppFlags{} secretGitHubAppArgs = secretGitHubAppFlags{}
secretProxyArgs = secretProxyFlags{} secretProxyArgs = secretProxyFlags{}
secretReceiverArgs = secretReceiverFlags{}
secretHelmArgs = secretHelmFlags{} secretHelmArgs = secretHelmFlags{}
secretTLSArgs = secretTLSFlags{} secretTLSArgs = secretTLSFlags{}
sourceBucketArgs = sourceBucketFlags{} sourceBucketArgs = sourceBucketFlags{}
+71 -60
View File
@@ -59,71 +59,82 @@ type APIVersions struct {
// TODO: Update this mapping when new Flux minor versions are released! // TODO: Update this mapping when new Flux minor versions are released!
// latestAPIVersions contains the latest API versions for each GroupKind // 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{ var latestAPIVersions = []APIVersions{
{ {
FluxVersion: "2.7", FluxVersion: "2.8",
LatestVersions: map[schema.GroupKind]string{ LatestVersions: flux27LatestAPIVersions,
// 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.6", FluxVersion: "2.7",
LatestVersions: map[schema.GroupKind]string{ LatestVersions: flux27LatestAPIVersions,
// 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.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{ var migrateCmd = &cobra.Command{
+118
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
}
+96
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
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
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
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)
}
}
+48
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
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
}
+22 -11
View File
@@ -103,17 +103,18 @@ The command can read the credentials from '~/.docker/config.json' but they can a
} }
type pushArtifactFlags struct { type pushArtifactFlags struct {
path string path string
source string source string
revision string revision string
creds string creds string
provider flags.SourceOCIProvider provider flags.SourceOCIProvider
ignorePaths []string ignorePaths []string
annotations []string annotations []string
output string output string
debug bool debug bool
reproducible bool reproducible bool
insecure bool insecure bool
resolveSymlinks bool
} }
var pushArtifactArgs = newPushArtifactFlags() var pushArtifactArgs = newPushArtifactFlags()
@@ -137,6 +138,7 @@ func init() {
pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library") 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.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.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) 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) 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{} annotations := map[string]string{}
for _, annotation := range pushArtifactArgs.annotations { for _, annotation := range pushArtifactArgs.annotations {
kv := strings.Split(annotation, "=") kv := strings.Split(annotation, "=")
+8 -1
View File
@@ -152,7 +152,14 @@ func reconciliationHandled(kubeClient client.Client, namespacedName types.Namesp
return false, err 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)
}
} }
} }
+11
View File
@@ -126,6 +126,17 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
resume.printMessage(reconcileResps) 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 return nil
} }
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDuUiEMA0
eUvKlmOsur2w9FAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIDF/w86ZQb5qmZtv
m1GvyLojiJdhmPtI9hJ9XPcP7HBoAAAAkG2cIOuSVdWInSC0P81ExiorUpiAGOjxxpgvKW
VYERfU1zU72Z/c9n1+z/IH5cJOhZ1vlqBO0rubl4s0KQFvY/LKcsc4N0x0uzpqrvcJP4tO
9VW8LrMnrPp7b6KVJPsbeSW1SBcUM24aCMzF4/wV03mN/Uqz30s+YgS9SU4Lz8AOkX58xX
yAV0gkmndIzZl+Og==
-----END OPENSSH PRIVATE KEY-----
+7
View File
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAWDldtCFdSMXIV1vLwXvRwk4eEmSoDCpxNkcbNph3dCAAAAIjjSDmx40g5
sQAAAAtzc2gtZWQyNTUxOQAAACAWDldtCFdSMXIV1vLwXvRwk4eEmSoDCpxNkcbNph3dCA
AAAEAGpzSFuLkCNDD49+tysxSFFwdOsRnDj67vDT9bfwoSDhYOV20IV1IxchXW8vBe9HCT
h4SZKgMKnE2Rxs2mHd0IAAAABHRlc3QB
-----END OPENSSH PRIVATE KEY-----
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
not a real ssh key
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: existing-config
namespace: default
data:
key: value
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./existing.yaml
- ./new.yaml
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: new-config
namespace: default
data:
key: value
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: new-ns
data:
key: value
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespace.yaml
- ./configmap.yaml
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: new-ns
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespace.yaml
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: new-ns
+1 -1
View File
@@ -1,3 +1,3 @@
► checking prerequisites ► checking prerequisites
✔ Kubernetes {{ .serverVersion }} >=1.32.0-0 ✔ Kubernetes {{ .serverVersion }} >=1.33.0-0
✔ prerequisites checks passed ✔ prerequisites checks passed
+19
View File
@@ -0,0 +1,19 @@
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: flux-system
namespace: flux-system
spec:
git:
checkout:
ref:
branch: main
commit:
author:
email: flux@example.com
name: flux
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
@@ -0,0 +1,23 @@
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: flux-system
namespace: flux-system
spec:
git:
checkout:
ref:
branch: main
commit:
author:
email: flux@example.com
name: flux
signingKey:
secretRef:
name: my-key
type: gpg
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
+23
View File
@@ -0,0 +1,23 @@
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: flux-system
namespace: flux-system
spec:
git:
checkout:
ref:
branch: main
commit:
author:
email: flux@example.com
name: flux
signingKey:
secretRef:
name: my-deploy-key
type: ssh
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
► Namespace/new-ns created
@@ -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
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: existing-config
namespace: default
data:
key: value
@@ -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
@@ -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
@@ -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
@@ -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
+4
View File
@@ -10,6 +10,10 @@ spec:
author: author:
email: fluxcdbot@users.noreply.github.com email: fluxcdbot@users.noreply.github.com
name: fluxcdbot name: fluxcdbot
signingKey:
secretRef:
name: my-signing-key
type: ssh
interval: 1m0s interval: 1m0s
sourceRef: sourceRef:
kind: GitRepository kind: GitRepository
+4
View File
@@ -67,6 +67,10 @@ spec:
email: fluxcdbot@users.noreply.github.com email: fluxcdbot@users.noreply.github.com
name: fluxcdbot name: fluxcdbot
messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}' messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
signingKey:
secretRef:
name: my-signing-key
type: ssh
update: update:
path: ./clusters/my-cluster path: ./clusters/my-cluster
strategy: Setters strategy: Setters
+31
View File
@@ -0,0 +1,31 @@
/*
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"
)
var triggerCmd = &cobra.Command{
Use: "trigger",
Short: "Trigger Flux resources from outside the cluster",
Long: `The trigger sub-commands invoke Flux resources from outside the cluster, such as a Receiver's incoming webhook.`,
}
func init() {
rootCmd.AddCommand(triggerCmd)
}
+368
View File
@@ -0,0 +1,368 @@
/*
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 (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/auth/actionsoidc"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
)
const (
// genericOIDCReceiver mirrors notificationv1.GenericOIDCReceiver from the
// upcoming notification-controller release.
// TODO: Replace it with the constant from the api module once the dependency
// is bumped.
genericOIDCReceiver = "generic-oidc"
// defaultOIDCAudience mirrors notificationv1.DefaultOIDCAudience.
// TODO: Replace it with the constant from the api module once the dependency
// is bumped.
defaultOIDCAudience = "notification-controller"
// defaultOIDCTokenEnvVar is the environment variable the OIDC token is read
// from when neither --oidc-provider nor --oidc-token is set.
defaultOIDCTokenEnvVar = "FLUX_TRIGGER_RECEIVER_OIDC_TOKEN"
)
const (
oidcProviderGitHub = "github"
oidcProviderForgejo = "forgejo"
)
var triggerReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Trigger the webhook of a Receiver",
Long: `The trigger receiver command sends a request to the incoming webhook of a Receiver.
The command computes the webhook path from the Receiver name, namespace and token,
appends it to the base URL and sends an HTTP POST request with the given payload.
It does not require access to the Kubernetes cluster.`,
Example: ` # Trigger a generic Receiver
flux trigger receiver my-receiver \
--token=my-token \
--url=https://flux-webhook.example.com
# Trigger a generic Receiver with a custom JSON payload
flux trigger receiver my-receiver \
--token=my-token \
--url=https://flux-webhook.example.com \
--payload='{"image":"ghcr.io/org/app:v1.0.0"}'
# Trigger a generic-hmac Receiver
flux trigger receiver my-receiver \
--type=generic-hmac \
--token=my-token \
--url=https://flux-webhook.example.com \
--payload='{"image":"ghcr.io/org/app:v1.0.0"}'
# Trigger a generic-oidc Receiver from a GitHub Actions workflow.
# The job needs 'permissions: id-token: write'. The OIDC token is fetched
# automatically and the receiver token is not used by this type.
flux trigger receiver my-receiver \
--type=generic-oidc \
--oidc-provider=github \
--url=https://flux-webhook.example.com
# Trigger a generic-oidc Receiver from a GitHub Actions workflow with a custom OIDC audience
flux trigger receiver my-receiver \
--type=generic-oidc \
--oidc-provider=github \
--oidc-audience=my-flux-instance \
--url=https://flux-webhook.example.com
# Trigger a generic-oidc Receiver from a Forgejo Actions workflow
flux trigger receiver my-receiver \
--type=generic-oidc \
--oidc-provider=forgejo \
--url=https://flux-webhook.example.com
# Trigger a generic-oidc Receiver from a GitLab CI/CD job, reading the OIDC
# token from an id_token environment variable defined in the job spec.
flux trigger receiver my-receiver \
--type=generic-oidc \
--oidc-token="${MY_ID_TOKEN}" \
--url=https://flux-webhook.example.com
# Trigger a generic-oidc Receiver from a GitLab CI/CD job, reading the OIDC
# token from the default FLUX_TRIGGER_RECEIVER_OIDC_TOKEN environment variable,
# e.g. defined as:
# job:
# id_tokens:
# FLUX_TRIGGER_RECEIVER_OIDC_TOKEN:
# aud: notification-controller
flux trigger receiver my-receiver \
--type=generic-oidc \
--url=https://flux-webhook.example.com
# Trigger a Receiver in a specific namespace
flux trigger receiver my-receiver -n apps \
--token=my-token \
--url=https://flux-webhook.example.com
# Trigger a Receiver in the namespace of the current kubeconfig context
flux trigger receiver my-receiver \
--ns-follows-kube-context \
--token=my-token \
--url=https://flux-webhook.example.com`,
Args: cobra.ExactArgs(1),
RunE: triggerReceiverCmdRun,
}
type triggerReceiverFlags struct {
token string
url string
receiverType string
oidcProvider string
oidcToken string
oidcAudience string
payload string
retries int
retryDelay time.Duration
}
var triggerReceiverArgs triggerReceiverFlags
func init() {
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.token, "token", "",
"the Receiver token, required for all types except generic-oidc where it must not be set")
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.url, "url", "",
"the base URL of the notification-controller webhook receiver, may contain a base path")
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.receiverType, "type", notificationv1.GenericReceiver,
fmt.Sprintf("the Receiver type, one of: %s, %s, %s",
notificationv1.GenericReceiver, notificationv1.GenericHMACReceiver, genericOIDCReceiver))
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.oidcProvider, "oidc-provider", "",
fmt.Sprintf("the OIDC provider to fetch the token from, one of: %s, %s (generic-oidc only, mutually exclusive with --oidc-token)",
oidcProviderGitHub, oidcProviderForgejo))
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.oidcToken, "oidc-token", "",
fmt.Sprintf("the OIDC token to authenticate the request (generic-oidc only, mutually exclusive with --oidc-provider); defaults to the %s environment variable", defaultOIDCTokenEnvVar))
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.oidcAudience, "oidc-audience", "",
fmt.Sprintf("the audience of the OIDC token to fetch (requires --oidc-provider); defaults to %q", defaultOIDCAudience))
triggerReceiverCmd.Flags().StringVar(&triggerReceiverArgs.payload, "payload", "{}",
"the JSON payload to send in the request body")
triggerReceiverCmd.Flags().IntVar(&triggerReceiverArgs.retries, "retries", 10,
"the number of times to retry on connection errors or retryable HTTP status codes (404, 408, 429, 5xx); set to 0 to disable")
triggerReceiverCmd.Flags().DurationVar(&triggerReceiverArgs.retryDelay, "retry-delay", 10*time.Second,
"the delay between retries")
triggerCmd.AddCommand(triggerReceiverCmd)
}
func triggerReceiverCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if triggerReceiverArgs.url == "" {
return fmt.Errorf("--url is required")
}
if err := validateTriggerReceiverArgs(); err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
// For generic-oidc the Receiver has no secretRef, so the webhook path is
// salted with an empty token. For all other types the token is required.
pathToken := triggerReceiverArgs.token
if triggerReceiverArgs.receiverType == genericOIDCReceiver {
pathToken = ""
}
receiver := &notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
},
}
webhookURL := strings.TrimRight(triggerReceiverArgs.url, "/") + receiver.GetWebhookPath(pathToken)
payload := []byte(triggerReceiverArgs.payload)
// Compute the request headers once; the auth material does not change between
// attempts, so they are applied to a fresh request on each retry.
headers := map[string]string{
"Content-Type": "application/json",
"User-Agent": fmt.Sprintf("flux/v%s", VERSION),
}
switch triggerReceiverArgs.receiverType {
case notificationv1.GenericReceiver:
// No authentication, the payload is sent as-is.
case notificationv1.GenericHMACReceiver:
mac := hmac.New(sha256.New, []byte(triggerReceiverArgs.token))
mac.Write(payload)
headers["X-Signature"] = "sha256=" + hex.EncodeToString(mac.Sum(nil))
case genericOIDCReceiver:
oidcToken, err := resolveOIDCToken(ctx)
if err != nil {
return err
}
headers["Authorization"] = "Bearer " + oidcToken
}
// send performs a single attempt. It reports retryable=true for transient
// failures (connection errors and retryable HTTP status codes) so the caller
// can retry; permanent failures (e.g. authentication or validation errors)
// report retryable=false and fail immediately.
send := func() (retryable bool, err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, webhookURL, bytes.NewReader(payload))
if err != nil {
return false, fmt.Errorf("unable to create request: %w", err)
}
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return true, fmt.Errorf("request to %s failed: %w", webhookURL, err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
statusErr := fmt.Errorf("request to %s failed with status %s", webhookURL, resp.Status)
if msg := strings.TrimSpace(string(body)); msg != "" {
statusErr = fmt.Errorf("request to %s failed with status %s: %s", webhookURL, resp.Status, msg)
}
return isRetryableStatus(resp.StatusCode), statusErr
}
return false, nil
}
logger.Actionf("triggering Receiver %s/%s", *kubeconfigArgs.Namespace, name)
for attempt := 0; ; attempt++ {
retryable, err := send()
if err == nil {
logger.Successf("Receiver %s/%s triggered", *kubeconfigArgs.Namespace, name)
return nil
}
if !retryable || attempt >= triggerReceiverArgs.retries {
return err
}
logger.Waitingf("%s; retrying in %s (%d/%d)",
err, triggerReceiverArgs.retryDelay, attempt+1, triggerReceiverArgs.retries)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(triggerReceiverArgs.retryDelay):
}
}
}
// isRetryableStatus reports whether an HTTP status returned by the webhook
// receiver is worth retrying. 404 is included because the Receiver's webhook
// path may not be registered yet right after the notification-controller starts
// or while the Receiver reconciles.
func isRetryableStatus(code int) bool {
switch code {
case http.StatusNotFound, // 404
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout: // 504
return true
default:
return false
}
}
// validateTriggerReceiverArgs validates the receiver type and the combination of
// token and OIDC flags.
func validateTriggerReceiverArgs() error {
isOIDC := triggerReceiverArgs.receiverType == genericOIDCReceiver
switch triggerReceiverArgs.receiverType {
case notificationv1.GenericReceiver, notificationv1.GenericHMACReceiver, genericOIDCReceiver:
default:
return fmt.Errorf("invalid --type %q, must be one of: %s, %s, %s",
triggerReceiverArgs.receiverType,
notificationv1.GenericReceiver, notificationv1.GenericHMACReceiver, genericOIDCReceiver)
}
if !isOIDC {
if triggerReceiverArgs.token == "" {
return fmt.Errorf("--token is required for --type=%s", triggerReceiverArgs.receiverType)
}
if triggerReceiverArgs.oidcProvider != "" || triggerReceiverArgs.oidcToken != "" || triggerReceiverArgs.oidcAudience != "" {
return fmt.Errorf("--oidc-provider, --oidc-token and --oidc-audience can only be set for --type=%s", genericOIDCReceiver)
}
return nil
}
// generic-oidc.
if triggerReceiverArgs.token != "" {
return fmt.Errorf("--token must not be set for --type=%s, the Receiver of this type has no secret", genericOIDCReceiver)
}
if triggerReceiverArgs.oidcProvider != "" && triggerReceiverArgs.oidcToken != "" {
return fmt.Errorf("--oidc-provider and --oidc-token are mutually exclusive")
}
if triggerReceiverArgs.oidcProvider != "" {
switch triggerReceiverArgs.oidcProvider {
case oidcProviderGitHub, oidcProviderForgejo:
default:
return fmt.Errorf("invalid --oidc-provider %q, must be one of: %s, %s",
triggerReceiverArgs.oidcProvider, oidcProviderGitHub, oidcProviderForgejo)
}
}
if triggerReceiverArgs.oidcAudience != "" && triggerReceiverArgs.oidcProvider == "" {
return fmt.Errorf("--oidc-audience can only be set together with --oidc-provider")
}
return nil
}
// resolveOIDCToken returns the OIDC token used to authenticate the request,
// either by fetching it from the configured provider or by reading it from the
// --oidc-token flag or the default environment variable.
func resolveOIDCToken(ctx context.Context) (string, error) {
switch {
case triggerReceiverArgs.oidcProvider != "":
audience := triggerReceiverArgs.oidcAudience
if audience == "" {
audience = defaultOIDCAudience
}
// GitHub and Forgejo Actions expose the same token request endpoint.
token, _, err := actionsoidc.FetchToken(ctx, audience)
return token, err
case triggerReceiverArgs.oidcToken != "":
return triggerReceiverArgs.oidcToken, nil
default:
token := os.Getenv(defaultOIDCTokenEnvVar)
if token == "" {
return "", fmt.Errorf("no OIDC token provided: set --oidc-provider, --oidc-token or the %s environment variable", defaultOIDCTokenEnvVar)
}
return token, nil
}
}
+353
View File
@@ -0,0 +1,353 @@
//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 (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
)
// resetTriggerReceiverArgs restores the package-global flags to their defaults
// so tests do not leak state into each other.
func resetTriggerReceiverArgs(t *testing.T) {
t.Helper()
prev := triggerReceiverArgs
prevNS := kubeconfigArgs.Namespace
prevTimeout := rootArgs.timeout
triggerReceiverArgs = triggerReceiverFlags{
receiverType: notificationv1.GenericReceiver,
payload: "{}",
}
ns := "default"
kubeconfigArgs.Namespace = &ns
rootArgs.timeout = time.Minute
t.Cleanup(func() {
triggerReceiverArgs = prev
kubeconfigArgs.Namespace = prevNS
rootArgs.timeout = prevTimeout
})
}
func TestValidateTriggerReceiverArgs(t *testing.T) {
tests := []struct {
name string
args triggerReceiverFlags
wantErr string
}{
{
name: "generic requires token",
args: triggerReceiverFlags{receiverType: notificationv1.GenericReceiver},
wantErr: "--token is required",
},
{
name: "generic with token is valid",
args: triggerReceiverFlags{receiverType: notificationv1.GenericReceiver, token: "t"},
},
{
name: "generic rejects oidc flags",
args: triggerReceiverFlags{receiverType: notificationv1.GenericReceiver, token: "t", oidcProvider: "github"},
wantErr: "can only be set for --type=generic-oidc",
},
{
name: "hmac with token is valid",
args: triggerReceiverFlags{receiverType: notificationv1.GenericHMACReceiver, token: "t"},
},
{
name: "unknown type",
args: triggerReceiverFlags{receiverType: "bogus", token: "t"},
wantErr: "invalid --type",
},
{
name: "oidc rejects token",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver, token: "t"},
wantErr: "--token must not be set",
},
{
name: "oidc provider and token mutually exclusive",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver, oidcProvider: "github", oidcToken: "x"},
wantErr: "mutually exclusive",
},
{
name: "oidc invalid provider",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver, oidcProvider: "gitlab"},
wantErr: "invalid --oidc-provider",
},
{
name: "oidc audience requires provider",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver, oidcToken: "x", oidcAudience: "aud"},
wantErr: "--oidc-audience can only be set together with --oidc-provider",
},
{
name: "oidc with provider is valid",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver, oidcProvider: "forgejo", oidcAudience: "aud"},
},
{
name: "oidc with token is valid",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver, oidcToken: "x"},
},
{
name: "oidc without provider or token is valid (env fallback)",
args: triggerReceiverFlags{receiverType: genericOIDCReceiver},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resetTriggerReceiverArgs(t)
triggerReceiverArgs = tt.args
err := validateTriggerReceiverArgs()
if tt.wantErr == "" {
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
return
}
if err == nil {
t.Fatalf("expected error containing %q, got nil", tt.wantErr)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("expected error containing %q, got: %v", tt.wantErr, err)
}
})
}
}
func TestTriggerReceiverRun(t *testing.T) {
const name = "my-receiver"
const ns = "default"
const token = "my-token"
expectedPath := (&notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
}).GetWebhookPath(token)
expectedOIDCPath := (&notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
}).GetWebhookPath("")
t.Run("generic sends payload with default headers", func(t *testing.T) {
resetTriggerReceiverArgs(t)
var got *http.Request
var gotBody string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
got = r
b, _ := io.ReadAll(r.Body)
gotBody = string(b)
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.token = token
triggerReceiverArgs.payload = `{"hello":"world"}`
if err := triggerReceiverCmdRun(nil, []string{name}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.URL.Path != expectedPath {
t.Errorf("path = %q, want %q", got.URL.Path, expectedPath)
}
if got.Method != http.MethodPost {
t.Errorf("method = %q, want POST", got.Method)
}
if ct := got.Header.Get("Content-Type"); ct != "application/json" {
t.Errorf("Content-Type = %q, want application/json", ct)
}
if ua := got.Header.Get("User-Agent"); !strings.HasPrefix(ua, "flux/v") {
t.Errorf("User-Agent = %q, want prefix flux/v", ua)
}
if gotBody != `{"hello":"world"}` {
t.Errorf("body = %q", gotBody)
}
})
t.Run("generic-hmac sets X-Signature", func(t *testing.T) {
resetTriggerReceiverArgs(t)
var sig string
payload := `{"a":1}`
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sig = r.Header.Get("X-Signature")
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.token = token
triggerReceiverArgs.receiverType = notificationv1.GenericHMACReceiver
triggerReceiverArgs.payload = payload
if err := triggerReceiverCmdRun(nil, []string{name}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
mac := hmac.New(sha256.New, []byte(token))
mac.Write([]byte(payload))
want := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if sig != want {
t.Errorf("X-Signature = %q, want %q", sig, want)
}
})
t.Run("generic-oidc with --oidc-token sets bearer and empty-token path", func(t *testing.T) {
resetTriggerReceiverArgs(t)
var auth, path string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth = r.Header.Get("Authorization")
path = r.URL.Path
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.receiverType = genericOIDCReceiver
triggerReceiverArgs.oidcToken = "the-oidc-token"
if err := triggerReceiverCmdRun(nil, []string{name}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if auth != "Bearer the-oidc-token" {
t.Errorf("Authorization = %q, want Bearer the-oidc-token", auth)
}
if path != expectedOIDCPath {
t.Errorf("path = %q, want %q (empty token salt)", path, expectedOIDCPath)
}
})
t.Run("generic-oidc reads default env var", func(t *testing.T) {
resetTriggerReceiverArgs(t)
t.Setenv(defaultOIDCTokenEnvVar, "env-oidc-token")
var auth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth = r.Header.Get("Authorization")
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.receiverType = genericOIDCReceiver
if err := triggerReceiverCmdRun(nil, []string{name}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if auth != "Bearer env-oidc-token" {
t.Errorf("Authorization = %q, want Bearer env-oidc-token", auth)
}
})
t.Run("non-2xx response is an error", func(t *testing.T) {
resetTriggerReceiverArgs(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("nope"))
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.token = token
err := triggerReceiverCmdRun(nil, []string{name})
if err == nil || !strings.Contains(err.Error(), "nope") {
t.Fatalf("expected error containing response body, got: %v", err)
}
})
t.Run("retries on retryable status then succeeds", func(t *testing.T) {
resetTriggerReceiverArgs(t)
var attempts int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.AddInt32(&attempts, 1) < 3 {
w.WriteHeader(http.StatusNotFound) // transient: path not registered yet
return
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.token = token
triggerReceiverArgs.retries = 5
triggerReceiverArgs.retryDelay = time.Millisecond
if err := triggerReceiverCmdRun(nil, []string{name}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := atomic.LoadInt32(&attempts); got != 3 {
t.Errorf("attempts = %d, want 3", got)
}
})
t.Run("does not retry non-retryable status", func(t *testing.T) {
resetTriggerReceiverArgs(t)
var attempts int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&attempts, 1)
w.WriteHeader(http.StatusForbidden)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.token = token
triggerReceiverArgs.retries = 5
triggerReceiverArgs.retryDelay = time.Millisecond
if err := triggerReceiverCmdRun(nil, []string{name}); err == nil {
t.Fatal("expected error")
}
if got := atomic.LoadInt32(&attempts); got != 1 {
t.Errorf("attempts = %d, want 1 (no retry on 403)", got)
}
})
t.Run("returns error after exhausting retries", func(t *testing.T) {
resetTriggerReceiverArgs(t)
var attempts int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&attempts, 1)
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer srv.Close()
triggerReceiverArgs.url = srv.URL
triggerReceiverArgs.token = token
triggerReceiverArgs.retries = 2
triggerReceiverArgs.retryDelay = time.Millisecond
if err := triggerReceiverCmdRun(nil, []string{name}); err == nil {
t.Fatal("expected error after exhausting retries")
}
if got := atomic.LoadInt32(&attempts); got != 3 {
t.Errorf("attempts = %d, want 3 (1 initial + 2 retries)", got)
}
})
}
+3 -1
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 --> <!-- 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 ### Fixes and improvements
<!-- List of fixes and improvements to the controllers and CLI --> <!-- 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 ## 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 ## CLI changelog
+135 -129
View File
@@ -1,81 +1,82 @@
module github.com/fluxcd/flux2/v2 module github.com/fluxcd/flux2/v2
go 1.25.0 go 1.26.0
// Fix CVE-2022-28948. // Fix CVE-2022-28948.
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require ( require (
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.4.0
github.com/ProtonMail/go-crypto v1.3.0 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/cyphar/filepath-securejoin v0.6.1
github.com/distribution/distribution/v3 v3.0.0 github.com/distribution/distribution/v3 v3.1.1
github.com/fluxcd/cli-utils v0.37.1-flux.1 github.com/fluxcd/cli-utils v1.2.0
github.com/fluxcd/go-git-providers v0.25.0 github.com/fluxcd/go-git-providers v0.26.0
github.com/fluxcd/helm-controller/api v1.4.5 github.com/fluxcd/helm-controller/api v1.5.5
github.com/fluxcd/image-automation-controller/api v1.0.4 github.com/fluxcd/image-automation-controller/api v1.0.1-0.20260529125431-20ebc65ab20f
github.com/fluxcd/image-reflector-controller/api v1.0.4 github.com/fluxcd/image-reflector-controller/api v1.1.2
github.com/fluxcd/kustomize-controller/api v1.7.3 github.com/fluxcd/kustomize-controller/api v1.8.5
github.com/fluxcd/notification-controller/api v1.7.5 github.com/fluxcd/notification-controller/api v1.8.4
github.com/fluxcd/pkg/apis/event v0.22.0 github.com/fluxcd/pkg/apis/event v0.26.0
github.com/fluxcd/pkg/apis/meta v1.25.0 github.com/fluxcd/pkg/apis/meta v1.27.0
github.com/fluxcd/pkg/auth v0.36.0 github.com/fluxcd/pkg/auth v0.45.0
github.com/fluxcd/pkg/chartutil v1.21.0 github.com/fluxcd/pkg/chartutil v1.24.0
github.com/fluxcd/pkg/envsubst v1.5.0 github.com/fluxcd/pkg/envsubst v1.7.0
github.com/fluxcd/pkg/git v0.41.0 github.com/fluxcd/pkg/git v0.49.1-0.20260529122759-f46ad90373c5
github.com/fluxcd/pkg/kustomize v1.25.0 github.com/fluxcd/pkg/kustomize v1.32.0
github.com/fluxcd/pkg/oci v0.59.0 github.com/fluxcd/pkg/oci v0.66.0
github.com/fluxcd/pkg/runtime v0.96.0 github.com/fluxcd/pkg/runtime v0.106.0
github.com/fluxcd/pkg/sourceignore v0.16.0 github.com/fluxcd/pkg/sourceignore v0.18.0
github.com/fluxcd/pkg/ssa v0.64.0 github.com/fluxcd/pkg/ssa v0.74.0
github.com/fluxcd/pkg/ssh v0.24.0 github.com/fluxcd/pkg/ssh v0.25.0
github.com/fluxcd/pkg/tar v0.17.0 github.com/fluxcd/pkg/tar v1.2.0
github.com/fluxcd/pkg/version v0.12.0 github.com/fluxcd/pkg/version v0.15.0
github.com/fluxcd/source-controller/api v1.7.4 github.com/fluxcd/source-controller/api v1.8.5
github.com/fluxcd/source-watcher/api/v2 v2.0.3 github.com/fluxcd/source-watcher/api/v2 v2.1.1
github.com/go-git/go-git/v5 v5.16.4 github.com/go-git/go-git/v5 v5.19.1
github.com/go-logr/logr v1.4.3 github.com/go-logr/logr v1.4.3
github.com/gonvenience/bunt v1.4.2 github.com/gonvenience/bunt v1.4.2
github.com/gonvenience/ytbx v1.4.7 github.com/gonvenience/ytbx v1.4.7
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.7 github.com/google/go-containerregistry v0.21.5
github.com/hashicorp/go-cleanhttp v0.5.2 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/homeport/dyff v1.10.2
github.com/lucasb-eyer/go-colorful v1.2.0 github.com/lucasb-eyer/go-colorful v1.2.0
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/notaryproject/notation-go v1.3.2 github.com/notaryproject/notation-go v1.3.2
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.39.0 github.com/onsi/gomega v1.40.0
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/theckman/yacspin v0.13.12 golang.org/x/crypto v0.50.0
golang.org/x/crypto v0.47.0 golang.org/x/term v0.42.0
golang.org/x/term v0.39.0 golang.org/x/text v0.36.0
golang.org/x/text v0.33.0 k8s.io/api v0.36.0
k8s.io/api v0.35.0 k8s.io/apiextensions-apiserver v0.36.0
k8s.io/apiextensions-apiserver v0.35.0 k8s.io/apimachinery v0.36.0
k8s.io/apimachinery v0.35.0 k8s.io/cli-runtime v0.36.0
k8s.io/cli-runtime v0.35.0 k8s.io/client-go v0.36.0
k8s.io/client-go v0.35.0 k8s.io/kubectl v0.36.0
k8s.io/kubectl v0.35.0 sigs.k8s.io/controller-runtime v0.24.0
sigs.k8s.io/controller-runtime v0.23.0 sigs.k8s.io/kustomize/api v0.21.1
sigs.k8s.io/kustomize/api v0.21.0 sigs.k8s.io/kustomize/kyaml v0.21.1
sigs.k8s.io/kustomize/kyaml v0.21.0
sigs.k8s.io/yaml v1.6.0 sigs.k8s.io/yaml v1.6.0
) )
require ( require (
cloud.google.com/go/auth v0.18.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/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // 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 dario.cat/mergo v1.0.1 // indirect
github.com/42wim/httpsig v1.2.3 // indirect github.com/42wim/httpsig v1.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.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/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/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/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-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
@@ -83,66 +84,66 @@ require (
github.com/BurntSushi/toml v1.6.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.7 // 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.17 // 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.17 // 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.17 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 // 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.9 // indirect github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.77.0 // 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.4 // 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.17 // 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.5 // 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.9 // 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.13 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
github.com/aws/smithy-go v1.24.0 // 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/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // 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/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v29.1.5+incompatible // indirect github.com/docker/cli v29.4.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // 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/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect github.com/fluxcd/pkg/apis/acl v0.10.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v1.15.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.18.0 // indirect
github.com/fluxcd/pkg/cache v0.13.0 // indirect github.com/fluxcd/pkg/cache v0.14.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.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-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-errors/errors v1.5.1 // indirect github.com/go-errors/errors v1.5.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/go-ldap/ldap/v3 v3.4.10 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag v0.23.1 // 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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/gonvenience/idem v0.0.2 // indirect github.com/gonvenience/idem v0.0.2 // indirect
github.com/gonvenience/neat v1.3.16 // indirect github.com/gonvenience/neat v1.3.16 // indirect
@@ -150,29 +151,29 @@ require (
github.com/gonvenience/text v1.0.9 // indirect github.com/gonvenience/text v1.0.9 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-github/v75 v75.0.0 // indirect github.com/google/go-github/v82 v82.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.2.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/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/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // 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/go-version v1.7.0 // indirect
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hiddeco/sshsig v0.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/compress v1.18.1 // 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/kylelemons/godebug v1.1.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
@@ -193,15 +194,15 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // 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/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/otlptranslator v0.0.2 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.17.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/rediscmd/v9 v9.0.5 // indirect
github.com/redis/go-redis/extra/redisotel/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/redis/go-redis/v9 v9.7.3 // indirect
@@ -212,59 +213,64 @@ require (
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/texttheater/golang-levenshtein v1.0.1 // indirect github.com/texttheater/golang-levenshtein v1.0.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/vbatts/tar-split v0.12.2 // indirect
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // 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/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
gitlab.com/gitlab-org/api/client-go v0.142.5 // indirect gitlab.com/gitlab-org/api/client-go v1.29.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect
go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.8.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.49.0 // indirect golang.org/x/net v0.53.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.43.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.15.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.261.0 // indirect google.golang.org/api v0.278.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/grpc v1.78.0 // indirect google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v4 v4.1.0 // indirect helm.sh/helm/v4 v4.1.4 // indirect
k8s.io/component-base v0.35.0 // indirect k8s.io/component-base v0.36.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // 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
) )
+293 -277
View File
@@ -1,27 +1,27 @@
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= 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 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 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 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= 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.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg=
code.gitea.io/sdk/gitea v0.22.0/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= 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 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 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 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= 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 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 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.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= 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.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= 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 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 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 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/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 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/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.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
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/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 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/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 h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
@@ -41,58 +41,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.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 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/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/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 h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 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 h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 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 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= 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.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= 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.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= 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.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= 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.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= 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.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= 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.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= 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.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= 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.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= 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/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= 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.55.1 h1:B7f9R99lCF83XlolTg6d6Lvghyto+/VU83ZrneAVfK8= 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.55.1/go.mod h1:cpYRXx5BkmS3mwWRKPbWSPKmyAUNL7aLWAPiiinwk/U= 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.9 h1:WxoqdNfGWj668u/NX7qBMPevmJu14LYNMMTRZthoclc= 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.9/go.mod h1:4oMS/bVKMnYIIBgkcHPoru4DVeMGutHv03FZUTjvsvI= 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.77.0 h1:Z5mTpmbJKU7jEM7xoXI5tO4Nm0JUZSgVSFkpYuu6Ic0= 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.77.0/go.mod h1:Qg678m+87sCuJhcsZojenz8mblYG+Tq86V4m3hjVz0s= 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.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= 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.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= 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.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= 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.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= 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.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= 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.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= 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.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= 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.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= 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.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= 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.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= 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.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= 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.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= 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 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.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/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 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 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/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= 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.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 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
@@ -115,14 +121,14 @@ 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 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 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 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.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= 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 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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.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 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -138,24 +144,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/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 h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 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.1.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0=
github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= 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 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao= github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
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/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= 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/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/elazarl/goproxy v1.8.0 h1:dt561rX7UAYMeFRLtzFx6uQGl2TpL1dr6uCG23nFQSY= github.com/elazarl/goproxy v1.8.0 h1:dt561rX7UAYMeFRLtzFx6uQGl2TpL1dr6uCG23nFQSY=
github.com/elazarl/goproxy v1.8.0/go.mod h1:b5xm6W48AUHNpRTCvlnd0YVh+JafCCtsLsJZvvNTz+E= github.com/elazarl/goproxy v1.8.0/go.mod h1:b5xm6W48AUHNpRTCvlnd0YVh+JafCCtsLsJZvvNTz+E=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
@@ -168,62 +172,62 @@ 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/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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/cli-utils v0.37.1-flux.1 h1:WnG2mHxCPZMj/soIq/S/1zvbrGCJN3GJGbNfG06X55M= github.com/fluxcd/cli-utils v1.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI=
github.com/fluxcd/cli-utils v0.37.1-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= 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 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= 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.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw=
github.com/fluxcd/go-git-providers v0.25.0/go.mod h1:8Mx5WRYb61FIjOA26DAi4Ls2rZUHSxP8Nl9qkQHDch8= github.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo=
github.com/fluxcd/helm-controller/api v1.4.5 h1:hMEBtgXUbJjp+ah0jPI3OOQNVngoToOQvTgFgVpAjNg= github.com/fluxcd/helm-controller/api v1.5.5 h1:xQA/9gbifMvZPGhSNKHsrkq829dI/yTBASVdYp9/s4Y=
github.com/fluxcd/helm-controller/api v1.4.5/go.mod h1:rCgx3qhjjtoIH+1EbzFC2vN71/pp0PgMDrZnGCZX5XY= github.com/fluxcd/helm-controller/api v1.5.5/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI=
github.com/fluxcd/image-automation-controller/api v1.0.4 h1:Fgdy97hXkyh/JFjxLIyq4ZDHsKsa49aumtrvIyjVd08= github.com/fluxcd/image-automation-controller/api v1.0.1-0.20260529125431-20ebc65ab20f h1:pjHh/w2xRd9u20J0c8H0EEoPAurJndm2XNF+/mem3EE=
github.com/fluxcd/image-automation-controller/api v1.0.4/go.mod h1:LLBf4XQJAgnpIMlZUwfpVIkCdUtBOi31B6fDbPwBCq4= github.com/fluxcd/image-automation-controller/api v1.0.1-0.20260529125431-20ebc65ab20f/go.mod h1:XNWgNSF7GVZgGx6qTI+8jhiH0S+a/hNtNvBwPcxOotw=
github.com/fluxcd/image-reflector-controller/api v1.0.4 h1:/JGpTZf4eMcKG2FpWfP5H7SneSrD5P8EvwGnHiH/WLY= github.com/fluxcd/image-reflector-controller/api v1.1.2 h1:VPwUgA8WyPVVs16uSkwvjOAY6pvTYgAb0fL90t0RKLE=
github.com/fluxcd/image-reflector-controller/api v1.0.4/go.mod h1:5GS4ojHaz+W6hK80WakGIOYk8sn93AyV5X+YOne1XMw= github.com/fluxcd/image-reflector-controller/api v1.1.2/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0=
github.com/fluxcd/kustomize-controller/api v1.7.3 h1:g+C9Il+H33DQi/ZiQ8KpTvL9KXebXnS4oM/0uJ/C8Gw= github.com/fluxcd/kustomize-controller/api v1.8.5 h1:4fGPh6foGVKUUbt5OjVzbC5iTyX+Q+NS50atPboDC4w=
github.com/fluxcd/kustomize-controller/api v1.7.3/go.mod h1:Yj80JyfQpBUgLhsUZ/c86qcvPGO2+P1VCKsb8fL+L/k= github.com/fluxcd/kustomize-controller/api v1.8.5/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc=
github.com/fluxcd/notification-controller/api v1.7.5 h1:6CO5bKyjodiK9exQFOdBcz0XLeo17rrrWQBTJL9NNa8= github.com/fluxcd/notification-controller/api v1.8.4 h1:KhHHVhQNtQsY+cVm/Y/8vhhFfrEOxM2AL/8JF8LAjMg=
github.com/fluxcd/notification-controller/api v1.7.5/go.mod h1:IciwSg8Q0pVtdbsyDyEXx/MxBKWeagxAazpm64C8oCE= github.com/fluxcd/notification-controller/api v1.8.4/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA=
github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.10.0 h1:KPfAmELNvtvaz8wixnm/MYXqa+MJf7ntVVMUU93Aenk=
github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/acl v0.10.0/go.mod h1:a87i2A7AlFO5N2J8CxtzaUCCDmuLLWOHwkKu3eJF5fY=
github.com/fluxcd/pkg/apis/event v0.22.0 h1:nCW0TnneMnscSnj9NlaSKcvyC+436MbY1GyKn/4YnII= github.com/fluxcd/pkg/apis/event v0.26.0 h1:QzBRz9Qy91jzJmLlOhd4ecp6OWDpMVFvm311AwxCXzY=
github.com/fluxcd/pkg/apis/event v0.22.0/go.mod h1:Hoi4DejaNKVahGkRXqGBjT9h1aKmhc7RCYcsgoTieqc= github.com/fluxcd/pkg/apis/event v0.26.0/go.mod h1:0yy7FMJABzq8PP5/VEi1Gro6ssPaPlH9xuPIoF+Rm6M=
github.com/fluxcd/pkg/apis/kustomize v1.15.0 h1:p8wPIxdmn0vy0a664rsE9JKCfnliZz4HUsDcTy4ZOxA= github.com/fluxcd/pkg/apis/kustomize v1.18.0 h1:FCNjViCLyKYj6lddpnjXybKBTC2eK6eXK9YOaNwLVTM=
github.com/fluxcd/pkg/apis/kustomize v1.15.0/go.mod h1:XWdsx8P15OiMaQIvmUjYWdmD3zAwhl5q9osl5iCqcOk= github.com/fluxcd/pkg/apis/kustomize v1.18.0/go.mod h1:mvtMtM4NNLipdCna6DYPC6Bd42xeaF15N+tNO+F6kxY=
github.com/fluxcd/pkg/apis/meta v1.25.0 h1:fmZgMoe7yITGfhFqdOs7w2GOu3Y/2Vvz4+4p/eay3eA= github.com/fluxcd/pkg/apis/meta v1.27.0 h1:EspByEk5j8w3rs1cGbEh9AjSmpDwQIz7DFG/zzqf6uI=
github.com/fluxcd/pkg/apis/meta v1.25.0/go.mod h1:1D92RqAet0/n/cH5S0khBXweirHWkw9rCO0V4NCY6xc= github.com/fluxcd/pkg/apis/meta v1.27.0/go.mod h1:2t6JyrRfvIBhx6EBnXfFh/6sCCJ1db9WGaqko0JmNOE=
github.com/fluxcd/pkg/auth v0.36.0 h1:4T61EOyRAElhJedwglfa68OxsD6GiNPGGTMZIeYE3sM= github.com/fluxcd/pkg/auth v0.45.0 h1:3p/CMdFJ1c8LevdLd2cikackaTW1Tw8JB2xg4YqpP8A=
github.com/fluxcd/pkg/auth v0.36.0/go.mod h1:pRet9dmeOW3iHEh9BwCvhvjEQ5HjQLi4lblaIfR/yJg= github.com/fluxcd/pkg/auth v0.45.0/go.mod h1:/ijjR9G/l6URmEo/zWzpJ3XIMIXWP1Ad7AXTCqmWioY=
github.com/fluxcd/pkg/cache v0.13.0 h1:MqtlgOwIVcGKKgV422e39O+KFSVMWuExKeRaMDBjJlk= github.com/fluxcd/pkg/cache v0.14.0 h1:wEwJA8NhYj+nH9P6ifcsglDZARWlcbxbmwngGOzfU4c=
github.com/fluxcd/pkg/cache v0.13.0/go.mod h1:0xRZ1hitrIFQ6pl68ke2wZLbIqA2VLzY78HpDo9DVxs= github.com/fluxcd/pkg/cache v0.14.0/go.mod h1:KwzU2gyVQ83YOHJsbBeveJ0HsXmLrH0I668zX19d/+s=
github.com/fluxcd/pkg/chartutil v1.21.0 h1:NJYhlekwBwuqMpRgsOlcsJrw2Xq0cBJW0Nmvz2oMluA= github.com/fluxcd/pkg/chartutil v1.24.0 h1:Rh9o50eJUnAioL5q2JVPxO4DjFjwHwPE5K9LcBy7lqA=
github.com/fluxcd/pkg/chartutil v1.21.0/go.mod h1:Gv50bF3SS4OvvKCyyIMRkGeNzZk6Fsh4+lAdrjx97T4= github.com/fluxcd/pkg/chartutil v1.24.0/go.mod h1:oycULCP00m46dxiskme1Yawe74UFLZzX0jqHb6xzdmQ=
github.com/fluxcd/pkg/envsubst v1.5.0 h1:S07mo+MkGhptdHA4pRze5HPKlc8tHxKswNdcMZi1WDY= github.com/fluxcd/pkg/envsubst v1.7.0 h1:PL9Nj/V2fgaMR9KYZR7mEEw+vlYgP80nFZjOQQKAfJs=
github.com/fluxcd/pkg/envsubst v1.5.0/go.mod h1:c3a8DYI855sZUubHFYQbjfjop6Wu4/zg1cLyf7SnCes= github.com/fluxcd/pkg/envsubst v1.7.0/go.mod h1:aoWeSIOamhqBZ3bHVj1GDwpdA10DXrI8yYbyjPiFly0=
github.com/fluxcd/pkg/git v0.41.0 h1:WvvIUFssFDKpRrptJjDf0B4mrUCwhesv1Txu3DzTsl8= github.com/fluxcd/pkg/git v0.49.1-0.20260529122759-f46ad90373c5 h1:ZPpsEw33U/2JmgeGIbgdAN0UX+GM01sE+cb1OuuSQwY=
github.com/fluxcd/pkg/git v0.41.0/go.mod h1:iqR4eZEhd3gdRSkv+VDP3Qz9WCner3aZ5ClkOUe+3fc= github.com/fluxcd/pkg/git v0.49.1-0.20260529122759-f46ad90373c5/go.mod h1:OgaHoS0iR0GuLl+f778X7NrGy1pDH7xcpF/nsCRgJ9g=
github.com/fluxcd/pkg/gittestserver v0.24.0 h1:ZIksyENX8yPlB95GJGoUIT171o2oKFJvFSXu+4mEmzU= github.com/fluxcd/pkg/gittestserver v0.29.0 h1:2j03zKVL6iVn6oiUuecG/O/3Q1pULWM9JrF/HSjkpnc=
github.com/fluxcd/pkg/gittestserver v0.24.0/go.mod h1:9l+gwEfqqe/WxiRvIrQxircgDcXUF3/tw/1Bie/XwJc= github.com/fluxcd/pkg/gittestserver v0.29.0/go.mod h1:O8151jV0ppBZTb9IUXMjxh6hZpkiuLq8JQHDBPOkZFw=
github.com/fluxcd/pkg/kustomize v1.25.0 h1:0jjACHxaMif+RYwrlDDqA09vRtib7WbqU8MmF0k91bM= github.com/fluxcd/pkg/kustomize v1.32.0 h1:5lLT2dgR+JrcoJHB7/K50o0AcJikKvXcRd3r7jIYZC8=
github.com/fluxcd/pkg/kustomize v1.25.0/go.mod h1:253Y78WyQJ+cD1krdoysluy9bsm5yee6SdmA4xf1hnk= github.com/fluxcd/pkg/kustomize v1.32.0/go.mod h1:Xz1QIUIKexXuSolRQY63843wSycPVuIsVhE9C+aJWl8=
github.com/fluxcd/pkg/oci v0.59.0 h1:0b+iy52QEjGE5vZzmlqjlcTTUYtNZ3F70yG6cyKR+Mg= github.com/fluxcd/pkg/oci v0.66.0 h1:nlBA3/MvJShjA+VFPKDRoXLUpH+e8THVvCMST+qUTlI=
github.com/fluxcd/pkg/oci v0.59.0/go.mod h1:sh3UhBhhKiHBX2Tjnrpq8qPvk28OxPz3hS0iMW6JdOY= github.com/fluxcd/pkg/oci v0.66.0/go.mod h1:9Z4Juu/BhLLKHKOkOalfwIkZYsjTViD0YDHzW9w5R9c=
github.com/fluxcd/pkg/runtime v0.96.0 h1:sF4ic8131BwbOE+T2pkiXlkr2gCaxAho500zlZJJLck= github.com/fluxcd/pkg/runtime v0.106.0 h1:SGzUaO/G6bUBLkZIxLGnCVEEURSPuldSG2exYjlrsQk=
github.com/fluxcd/pkg/runtime v0.96.0/go.mod h1:FyjNMFNAERkCsF/muTWJYU9MZOsq/m4Sc4aQk/EgQ9E= github.com/fluxcd/pkg/runtime v0.106.0/go.mod h1:7lCogUwTGKoG3bfBvCn6eQgPYF7cvVp5R+gL3bB1HX8=
github.com/fluxcd/pkg/sourceignore v0.16.0 h1:28+IBmNM1rGNQysiAZXyilFMgS0kno/aJM4zSPgqu2A= github.com/fluxcd/pkg/sourceignore v0.18.0 h1:WU2tPKasG9AM7/H/LlqdjULyaSknnZBTrpHsDDtOuns=
github.com/fluxcd/pkg/sourceignore v0.16.0/go.mod h1:Enjrk4gdk8t9VEp0dU3OHvMiS5ZHafZiL4H/FGNluh0= github.com/fluxcd/pkg/sourceignore v0.18.0/go.mod h1:mnH7rFFlEbMTclhz7JZP7tiHssKdXRNpCqnly2JGvaI=
github.com/fluxcd/pkg/ssa v0.64.0 h1:B/8VYMIYMeRmolup2HOoWNqXh4UeXi6w2LvXXvl6MZM= github.com/fluxcd/pkg/ssa v0.74.0 h1:a5VqQXBQ5TQlzucwZ9l37bshzR3IPK5awzfMEBF3pdM=
github.com/fluxcd/pkg/ssa v0.64.0/go.mod h1:RjvVjJIoRo1ecsv91yMuiqzO6cpNag80M6MOB/vrJdc= github.com/fluxcd/pkg/ssa v0.74.0/go.mod h1:6mdhG+O1db8oXsP7Ex3buJTexM5r9F7+LzSybBgT1f0=
github.com/fluxcd/pkg/ssh v0.24.0 h1:hrPlxs0hhXf32DRqs68VbsXs0XfQMphyRVIk0rYYJa4= github.com/fluxcd/pkg/ssh v0.25.0 h1:4Y9WmuNqyKvH759UznU5DGHRcOuoJ/dQM6sbsaDZYYM=
github.com/fluxcd/pkg/ssh v0.24.0/go.mod h1:xWammEqalrpurpcMiixJRXtynRQtBEoqheyU5F/vWrg= github.com/fluxcd/pkg/ssh v0.25.0/go.mod h1:Fli2Ogu4uaIVGbCy+r0vvZlMO0RfuInyNY1q2FVIx0o=
github.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY= github.com/fluxcd/pkg/tar v1.2.0 h1:T6WFB5M0YRHktlrgdKNskqpdp76TVDdWTOeuWz33CFs=
github.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls= github.com/fluxcd/pkg/tar v1.2.0/go.mod h1:Wlalp5vIVe+BbckkKkqExKcoHAeeWJPAzwK7ONeFcS0=
github.com/fluxcd/pkg/version v0.12.0 h1:MGbdbNf2D5wazMqAkNPn+Lh5j+oY0gxQJFTGyet5Hfc= github.com/fluxcd/pkg/version v0.15.0 h1:E2Ju4i0vj8ZXLHKz/F4a8JTmDh7Jcg8okB0hK5rEoTM=
github.com/fluxcd/pkg/version v0.12.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8= github.com/fluxcd/pkg/version v0.15.0/go.mod h1:LEHnvLMgbTk4kelF+JHHzaG77kY9uTWodMtadPRMEW8=
github.com/fluxcd/source-controller/api v1.7.4 h1:+EOVnRA9LmLxOx7J273l7IOEU39m+Slt/nQGBy69ygs= github.com/fluxcd/source-controller/api v1.8.5 h1:mLKc9YVMk46JCt1BQbkG6irkrpBZp95kiXh2+GYB6KQ=
github.com/fluxcd/source-controller/api v1.7.4/go.mod h1:ruf49LEgZRBfcP+eshl2n9SX1MfHayCcViAIGnZcaDY= github.com/fluxcd/source-controller/api v1.8.5/go.mod h1:sio4t49RDx+S1etHRFAEEw8qfVuw0KKlOg8bRVlEYPM=
github.com/fluxcd/source-watcher/api/v2 v2.0.3 h1:SsVGAaMBxzvcgrOz/Kl6c2ybMHVqoiEFwtI+bDuSeSs= github.com/fluxcd/source-watcher/api/v2 v2.1.1 h1:1LfT50ty+78MKKbschAZl28QbVqIyjaNq17KmW5wPJI=
github.com/fluxcd/source-watcher/api/v2 v2.0.3/go.mod h1:Nx3QZweVyuhaOtSNrw+oxifG+qrakPvjgNAN9qlUTb0= 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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -238,14 +242,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-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 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= 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 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-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= 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-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 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
@@ -267,12 +271,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-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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= 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/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.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 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 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 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= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -297,41 +300,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 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 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.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.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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= github.com/google/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=
github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=
github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic= github.com/google/go-github/v82 v82.0.0 h1:OH09ESON2QwKCUVMYmMcVu1IFKFoaZHwqYaUtr/MVfk=
github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI= github.com/google/go-github/v82 v82.0.0/go.mod h1:hQ6Xo0VKfL8RZ7z1hSfB4fvISg0QqHOqe9BP0qo+WvM=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= 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 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= 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 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= 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 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 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/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/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 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.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= 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.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 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -352,6 +352,8 @@ github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGN
github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw=
github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE=
github.com/homeport/dyff v1.10.2 h1:XyB+D0KVwjbUFTZYIkvPtsImwkfh+ObH2CEdEHTqdr4= github.com/homeport/dyff v1.10.2 h1:XyB+D0KVwjbUFTZYIkvPtsImwkfh+ObH2CEdEHTqdr4=
github.com/homeport/dyff v1.10.2/go.mod h1:0kIjL/JOGaXigzrLY6kcl5esSStbAa99r6GzEvr7lrs= github.com/homeport/dyff v1.10.2/go.mod h1:0kIjL/JOGaXigzrLY6kcl5esSStbAa99r6GzEvr7lrs=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -381,8 +383,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/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 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= 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/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/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -448,10 +452,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/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 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= 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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -464,8 +468,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/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 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= 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.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 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 h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 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= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -485,15 +489,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/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.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.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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= 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.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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 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.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= 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 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/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
@@ -511,6 +515,7 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= 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 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
@@ -538,12 +543,22 @@ 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/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 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U=
github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= 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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg= 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 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= 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 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= 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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
@@ -551,64 +566,66 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/gitlab-org/api/client-go v0.142.5 h1:zvengEU958Fjwasi1V+9QNRw0viqNKkqUwvFD15XDZI= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
gitlab.com/gitlab-org/api/client-go v0.142.5/go.mod h1:Ru5IRauphXt9qwmTzJD7ou1dH7Gc6pnsdFWEiMMpmB0= 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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 h1:/Rij/t18Y7rUayNg7Id6rPrEnHgorxYabm2E6wUdPP4= go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0/go.mod h1:AdyDPn6pkbkt2w01n3BubRVk7xAsCRq1Yg1mpfyA/0E= go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 h1:NLnZybb9KkfMXPwZhd5diBYJoVxiO9Qa06dacEA7ySY= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0/go.mod h1:OvRg7gm5WRSCtxzGSsrFHbDLToYlStHNZQ+iPNIyD6g= 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.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= 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.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= 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.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= 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.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= 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.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= 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.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= 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.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE=
go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU=
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= 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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -622,15 +639,15 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= 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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.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.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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= 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-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-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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -646,10 +663,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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-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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -659,8 +676,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.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.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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-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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -686,8 +703,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.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.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.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -697,8 +714,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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 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.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -709,36 +726,35 @@ 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.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.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= 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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 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.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.261.0 h1:3DoJ2GGibaCxNi1lhdScNMx9fTW87ujKHDgyHMMYdoA= google.golang.org/api v0.278.0 h1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E=
google.golang.org/api v0.261.0/go.mod h1:nVH0ZK5C4tO0RdsMscleeTLY7I8m/Nt9IXxcXD2tfts= google.golang.org/api v0.278.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= 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-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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/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 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -758,41 +774,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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
helm.sh/helm/v4 v4.1.0 h1:ytBbmQ7W2h1BLMyvkexnoG52JEDbYj9LTnnNgKRhiCI= helm.sh/helm/v4 v4.1.4 h1:zwTrNkalG4f7SYigRSdQnYrTj0QEz1qzetzAlYoDVSo=
helm.sh/helm/v4 v4.1.0/go.mod h1:yH4qpYvTNBTHnkRSenhi1m7oEFKoN6iK3/rYyFJ00IQ= helm.sh/helm/v4 v4.1.4/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI=
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE= k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg=
k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY= k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA=
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo=
k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc= k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms=
k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo= k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/controller-runtime v0.23.0 h1:Ubi7klJWiwEWqDY+odSVZiFA0aDSevOCXpa38yCSYu8= sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4=
sigs.k8s.io/controller-runtime v0.23.0/go.mod h1:DBOIr9NsprUqCZ1ZhsuJ0wAnQSIxY/C6VjZbmLgw0j0= 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 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 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.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
sigs.k8s.io/kustomize/api v0.21.0/go.mod h1:XGVQuR5n2pXKWbzXHweZU683pALGw/AMVO4zU4iS8SE= sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI=
sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= 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 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 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.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
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/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+119 -53
View File
@@ -30,7 +30,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/theckman/yacspin" "github.com/briandowns/spinner"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -45,6 +45,7 @@ import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/pkg/kustomize" "github.com/fluxcd/pkg/kustomize"
buildfs "github.com/fluxcd/pkg/kustomize/filesys"
runclient "github.com/fluxcd/pkg/runtime/client" runclient "github.com/fluxcd/pkg/runtime/client"
ssautil "github.com/fluxcd/pkg/ssa/utils" ssautil "github.com/fluxcd/pkg/ssa/utils"
"sigs.k8s.io/kustomize/kyaml/filesys" "sigs.k8s.io/kustomize/kyaml/filesys"
@@ -65,6 +66,65 @@ const (
var defaultTimeout = 80 * time.Second 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 // Builder builds yaml manifests
// It retrieves the kustomization object from the k8s cluster // It retrieves the kustomization object from the k8s cluster
// and overlays the manifests with the resources specified in the resourcesPath // and overlays the manifests with the resources specified in the resourcesPath
@@ -81,13 +141,15 @@ type Builder struct {
action kustomize.Action action kustomize.Action
kustomization *kustomizev1.Kustomization kustomization *kustomizev1.Kustomization
timeout time.Duration timeout time.Duration
spinner *yacspin.Spinner spinner *spinner.Spinner
dryRun bool dryRun bool
strictSubst bool strictSubst bool
recursive bool recursive bool
localSources map[string]string 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 singleKustomization bool
ignoreNotFound bool
fsBackend fsBackend
} }
// BuilderOptionFunc is a function that configures a Builder // BuilderOptionFunc is a function that configures a Builder
@@ -111,22 +173,9 @@ func WithTimeout(timeout time.Duration) BuilderOptionFunc {
func WithProgressBar() BuilderOptionFunc { func WithProgressBar() BuilderOptionFunc {
return func(b *Builder) error { return func(b *Builder) error {
// Add a spinner s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
cfg := yacspin.Config{ s.Suffix = " Kustomization diffing... " + spinnerDryRunMessage
Frequency: 100 * time.Millisecond, b.spinner = s
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
return nil 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 // WithIgnore sets ignore field
func WithIgnore(ignore []string) BuilderOptionFunc { func WithIgnore(ignore []string) BuilderOptionFunc {
return func(b *Builder) error { 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 // WithSingleKustomization sets the single kustomization field to true
func WithSingleKustomization() BuilderOptionFunc { func WithSingleKustomization() BuilderOptionFunc {
return func(b *Builder) error { 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 { func withSpinnerFrom(in *Builder) BuilderOptionFunc {
return func(b *Builder) error { return func(b *Builder) error {
b.spinner = in.spinner 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 // withKustomization sets the kustomization field
func withKustomization(k *kustomizev1.Kustomization) BuilderOptionFunc { func withKustomization(k *kustomizev1.Kustomization) BuilderOptionFunc {
return func(b *Builder) error { return func(b *Builder) error {
@@ -258,10 +334,18 @@ func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, er
b.timeout = defaultTimeout b.timeout = defaultTimeout
} }
if b.fsBackend == nil {
b.fsBackend = onDiskFsBackend{}
}
if b.dryRun && b.kustomizationFile == "" && b.kustomization == nil { if b.dryRun && b.kustomizationFile == "" && b.kustomization == nil {
return nil, fmt.Errorf("kustomization file is required for dry-run") 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 { if !b.dryRun && b.client == nil {
return nil, fmt.Errorf("client is required for live run") return nil, fmt.Errorf("client is required for live run")
} }
@@ -360,10 +444,11 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
} else { } else {
liveKus, err = b.getKustomization(ctx) liveKus, err = b.getKustomization(ctx)
if err != nil { 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) return nil, fmt.Errorf("failed to get kustomization object: %w", err)
} }
// use provided Kustomization
liveKus = b.kustomization liveKus = b.kustomization
} }
} }
@@ -378,9 +463,9 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
b.kustomization = k b.kustomization = k
// generate kustomization.yaml if needed // generate kustomization.yaml if needed
action, er := b.generate(*k, b.resourcesPath) buildFS, buildDir, action, er := b.generate(*k, b.resourcesPath)
if er != nil { 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)) err = fmt.Errorf("failed to generate kustomization.yaml: %w", fmt.Errorf("%v %v", er, errf))
return return
} }
@@ -388,14 +473,14 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
b.action = action b.action = action
defer func() { defer func() {
errf := b.Cancel() errf := b.fsBackend.Cleanup(b.resourcesPath, b.action)
if err == nil { if err == nil {
err = errf err = errf
} }
}() }()
// build the kustomization // build the kustomization
m, err = b.do(ctx, *k, b.resourcesPath) m, err = b.do(ctx, *k, buildFS, buildDir)
if err != nil { if err != nil {
return return
} }
@@ -436,6 +521,7 @@ func (b *Builder) kustomizationBuild(k *kustomizev1.Kustomization) ([]*unstructu
WithRecursive(b.recursive), WithRecursive(b.recursive),
WithLocalSources(b.localSources), WithLocalSources(b.localSources),
WithDryRun(b.dryRun), WithDryRun(b.dryRun),
withFsBackend(b.fsBackend),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -490,10 +576,10 @@ func (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error)
return k, nil 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) data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)
if err != nil { if err != nil {
return "", err return nil, "", kustomize.UnchangedAction, err
} }
// a scanner will be used down the line to parse the list // 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() b.mu.Lock()
defer b.mu.Unlock() 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) { func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, fs filesys.FileSystem, dirPath string) (resmap.ResMap, error) {
fs := filesys.MakeFsOnDisk()
// acquire the lock // acquire the lock
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
@@ -734,24 +818,14 @@ func (b *Builder) Cancel() error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
err := kustomize.CleanDirectory(b.resourcesPath, b.action) return b.fsBackend.Cleanup(b.resourcesPath, b.action)
if err != nil {
return err
}
return nil
} }
func (b *Builder) StartSpinner() error { func (b *Builder) StartSpinner() error {
if b.spinner == nil { if b.spinner == nil {
return nil return nil
} }
b.spinner.Start()
err := b.spinner.Start()
if err != nil {
return fmt.Errorf("failed to start spinner: %w", err)
}
return nil return nil
} }
@@ -759,14 +833,6 @@ func (b *Builder) StopSpinner() error {
if b.spinner == nil { if b.spinner == nil {
return nil return nil
} }
b.spinner.Stop()
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)
}
}
return nil return nil
} }
+229
View File
@@ -18,12 +18,15 @@ package build
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"strings" "strings"
"testing" "testing"
"time" "time"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/kustomize"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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")
}
}
+4 -3
View File
@@ -173,14 +173,14 @@ func (b *Builder) diff() (string, bool, error) {
// finished with Kustomization diff // finished with Kustomization diff
if b.spinner != nil { if b.spinner != nil {
b.spinner.Message(spinnerDryRunMessage) b.spinner.Suffix = " " + spinnerDryRunMessage
} }
} }
} }
} }
if b.spinner != nil { if b.spinner != nil {
b.spinner.Message("processing inventory") b.spinner.Suffix = " processing inventory"
} }
if b.kustomization.Spec.Prune && len(diffErrs) == 0 { 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) { func (b *Builder) kustomizationDiff(kustomization *kustomizev1.Kustomization) (string, bool, error) {
if b.spinner != nil { 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() sourceRef := kustomization.Spec.SourceRef.DeepCopy()
@@ -230,6 +230,7 @@ func (b *Builder) kustomizationDiff(kustomization *kustomizev1.Kustomization) (s
WithRecursive(b.recursive), WithRecursive(b.recursive),
WithLocalSources(b.localSources), WithLocalSources(b.localSources),
WithSingleKustomization(), WithSingleKustomization(),
withFsBackend(b.fsBackend),
) )
if err != nil { if err != nil {
return "", false, err return "", false, err

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