Compare commits

...

144 commits
v2.8.8 ... main

Author SHA1 Message Date
Matheus Pimenta
4d41251dbc
Merge pull request #5957 from fluxcd/bug-ag
Some checks are pending
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.2) (push) Waiting to run
conformance / conform-kubernetes (1.36.1) (push) Waiting to run
conformance / conform-k3s (1.34.8) (push) Waiting to run
conformance / conform-k3s (1.35.5) (push) Waiting to run
conformance / conform-k3s (1.36.1) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
conformance / conform-openshift (4.21.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
Fix using Receiver adapter for ArtifactGenerator
2026-06-28 22:21:55 +01:00
Matheus Pimenta
9c610bacd2
Fix using Receiver adapter for ArtifactGenerator
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-28 20:57:43 +01:00
Matheus Pimenta
94c079c109
Merge pull request #5956 from fluxcd/upgrade-gg-prov
Some checks failed
e2e / e2e-amd64-kubernetes (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
Upgrade go-git-providers to v0.27.0
2026-06-27 11:18:52 +01:00
Matheus Pimenta
c04738c543
Upgrade go-git-providers to v0.27.0
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-27 09:13:07 +01:00
Matheus Pimenta
6fe4f7b502
Merge pull request #5954 from fluxcd/fix-5949-5953
Some checks are pending
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.2) (push) Waiting to run
conformance / conform-kubernetes (1.36.1) (push) Waiting to run
conformance / conform-k3s (1.34.8) (push) Waiting to run
conformance / conform-k3s (1.35.5) (push) Waiting to run
conformance / conform-k3s (1.36.1) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
conformance / conform-openshift (4.21.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
update / update-components (push) Waiting to run
Fix `flux get all --status-selector` for empty results and notification resources
2026-06-26 11:11:37 +01:00
Matheus Pimenta
65d4635709
Fix 'flux get all --status-selector' for Alert and Provider
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-25 12:16:35 +01:00
Matheus Pimenta
cec25b5d1e
Fix 'flux get all --status-selector' for empty results
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-25 12:16:24 +01:00
Matheus Pimenta
cd0ffe0151
Merge pull request #5952 from 3uzbcqje/status-selector-negation
Some checks failed
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
cmd: support `type!=status` in get --status-selector
2026-06-25 08:41:21 +01:00
Matheus Pimenta
f234f2f26f
Simplify status filter in get command
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-25 08:27:45 +01:00
3uzbcqje
5afd1d8728
cmd: support type!=status in get --status-selector
`flux get --status-selector` only supported equality (`type=status`),
so finding objects that are not in a given state required multiple
invocations, e.g. listing everything that is not ready needed both
`Ready=False` and `Ready=Unknown`.

Add support for a negated selector `type!=status`. Since all resource
adapters delegate matching to the shared `statusMatches` helper and
filtering is centralised in `getRowsToPrint`, negation is implemented
purely in the parse/filter layer by inverting the match result. This
covers every resource type and the `--watch` path without touching the
per-resource adapters.

A missing condition is treated as not-matching by `statusMatches` (Flux
considers it "waiting to be reconciled"), so `Ready!=True` also surfaces
objects that have no Ready condition yet, i.e. the complete not-ready set:

    flux get all -A --status-selector Ready!=True

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: 3uzbcqje <3uzbcqje@addy.to>
2026-06-24 10:53:37 -07:00
Matheus Pimenta
e833099e1d
Merge pull request #5937 from fluxcd/update-pkg-deps/main
Some checks failed
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.2) (push) Waiting to run
conformance / conform-kubernetes (1.36.1) (push) Waiting to run
conformance / conform-k3s (1.34.8) (push) Waiting to run
conformance / conform-k3s (1.35.5) (push) Waiting to run
conformance / conform-k3s (1.36.1) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
conformance / conform-openshift (4.21.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
e2e-gcp / e2e-gcp (push) Has been cancelled
e2e-azure / e2e-aks (push) Has been cancelled
Update fluxcd/pkg dependencies
2026-06-24 16:55:28 +01:00
matheuscscp
b4cf45fc95 Update fluxcd/pkg dependencies
Signed-off-by: GitHub <noreply@github.com>
2026-06-24 15:27:06 +00:00
Matheus Pimenta
3e49729349
Merge pull request #5950 from fluxcd/update-components-main
Update toolkit components
2026-06-24 16:16:20 +01:00
Matheus Pimenta
9a68454996
Fix Receiver type
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-24 16:00:20 +01:00
fluxcdbot
9f995dfec0 Update toolkit components
- notification-controller to v1.9.0
  https://github.com/fluxcd/notification-controller/blob/v1.9.0/CHANGELOG.md
- source-watcher to v2.2.0
  https://github.com/fluxcd/source-watcher/blob/v2.2.0/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2026-06-24 14:50:36 +00:00
Matheus Pimenta
65d975b490
Merge pull request #5920 from fluxcd/feat/ssh-commit-signing
Some checks failed
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
Allow signing commits using SSH key
2026-06-19 18:52:13 +01:00
Hidde Beydals
96fda4cd56
Reject ssh-signing-reuse early in github and gitea
`bootstrap github` and `bootstrap gitea` generate the SSH transport
key in-process, so they have no operator-supplied key to reuse for
commit signing. Both subcommands already reject
`--ssh-signing-reuse-private-key` with a provider-specific
"not supported" error, but the check sat after `bootstrapValidate`,
which fails first with the generic
"--ssh-signing-reuse-private-key requires --private-key-file"
message. A user invoking e.g. `flux bootstrap github
--ssh-signing-reuse-private-key` is told to set a flag that the
subcommand cannot honour anyway, masking the real problem.

Move the unsupported-flag rejection to the top of each `RunE` —
before the interactive PAT prompt and before `bootstrapValidate` —
so the provider-specific error wins. The deeper, now-redundant
check is dropped. `TestBootstrapProviderRejectsReuseBeforeValidate`
exercises both subcommands with the reuse flag set and no
`--private-key-file` to lock in the precedence.

Assisted-by: claude/opus-4.7
Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-06-19 15:03:54 +02:00
Hidde Beydals
2ca3468423
Return error for public-only GPG signing keyring
`SelectOpenPGPSigningEntity` selects `keyRing[0]` when no key id is
supplied and then calls `entity.PrivateKey.Decrypt` directly. For a
keyring that contains only public keys — e.g. an armor-exported
public key file — `PrivateKey` is `nil` and the call panics with a
nil pointer dereference rather than surfacing an actionable error.
The keyed branch already guards against this; the default branch
did not.

Guard the default branch with the same nil check and return an
error pointing at `gpg --export-secret-keys` or `--gpg-key-id` so
the user knows how to recover. Cover the public-only-keyring case
in `TestSelectOpenPGPSigningEntity` so a future regression cannot
re-introduce the panic.

Assisted-by: claude/opus-4.7
Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-06-19 15:03:54 +02:00
Hidde Beydals
4f45409697
Seed defaultComponents in bootstrap signing tests
`resetCmdArgs` in `main_test.go` rebuilds `bootstrapArgs` from
`NewBootstrapFlags`, which deliberately omits the cobra-populated
`defaultComponents`. In the `e2e` build, `TestMain` runs `flux install
…` before any test executes; that call triggers the reset and leaves
`bootstrapArgs.defaultComponents` empty for the lifetime of the
process. `bootstrapValidate` then trips on its `requiredComponents`
pre-check and fails with "component source-controller is required"
before it ever reaches the SSH/GPG signing flag validation that this
test cares about.

Save, seed, and restore `defaultComponents` per subtest so the
required-component check passes regardless of whether the test runs
under the plain or the `e2e` build tag.

Assisted-by: claude/opus-4.7
Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-06-19 15:03:46 +02:00
Hidde Beydals
923a8ae394
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.

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-06-18 16:21:19 +02:00
Hidde Beydals
4e8c13ba59
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 is omitted when only the secret is set, deferring to the
controller's gpg default), 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-06-18 16:21:18 +02:00
Hidde Beydals
61316ccca7
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 leaves spec.git.commit.signingKey.type empty so the
controller's documented default ('gpg' when type is unset[1]) applies
server-side rather than baking the choice into the rendered YAML.
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.

[1]: https://github.com/fluxcd/image-automation-controller/pull/1035

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-06-18 16:21:14 +02:00
Hidde Beydals
43574215a6
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-06-18 14:40:43 +02:00
Hidde Beydals
de76bb4725
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-06-18 14:40:43 +02:00
Hidde Beydals
b767c68876
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-06-18 14:40:43 +02:00
Hidde Beydals
a84934311a
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-06-18 14:40:43 +02:00
Hidde Beydals
4810828b53
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-06-18 14:40:42 +02:00
Hidde Beydals
e6ac1390d0
Migrate bootstrap signing to generic Signer
Bumps fluxcd/pkg/git to v0.52.0, which exposes the generic
signature.Signer interface and the NewOpenPGPSigner / NewSSHSigner
constructors, and migrates pkg/bootstrap's two WithSigner call sites
accordingly. Refs fluxcd/pkg#398[1].

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.

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

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
2026-06-18 14:40:30 +02:00
Matheus Pimenta
6f803d47bc
Merge pull request #5923 from dipti-pai/drift-ignore-rules
Some checks failed
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
Add DriftIgnoreRules support to flux diff kustomization
2026-06-17 18:40:49 +01:00
Dipti Pai
4e815ab5e2 Add DriftIgnoreRules support to flux diff kustomization
Signed-off-by: Dipti Pai <diptipai89@outlook.com>
Assisted-by: GitHub Copilot/Claude Opus 4.7
2026-06-17 09:51:02 -07:00
Matheus Pimenta
a969646a56
Merge pull request #5945 from fluxcd/substitute-always
Honor `ks.spec.postBuild.substituteStrategy`
2026-06-17 17:32:46 +01:00
Matheus Pimenta
1e104631e4
Honor ks.spec.postBuild.substituteStrategy
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-17 17:25:41 +01:00
Matheus Pimenta
44612a750d
Merge pull request #5944 from fluxcd/update-components-main
Update toolkit components
2026-06-17 15:45:44 +01:00
Matheus Pimenta
e31c1a4f7d
Fix breaking change from source-controller 1.9
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-06-17 14:52:00 +01:00
fluxcdbot
8f5b850727 Update toolkit components
- helm-controller to v1.6.0
  https://github.com/fluxcd/helm-controller/blob/v1.6.0/CHANGELOG.md
- kustomize-controller to v1.9.0
  https://github.com/fluxcd/kustomize-controller/blob/v1.9.0/CHANGELOG.md
- source-controller to v1.9.0
  https://github.com/fluxcd/source-controller/blob/v1.9.0/CHANGELOG.md
- image-reflector-controller to v1.2.0
  https://github.com/fluxcd/image-reflector-controller/blob/v1.2.0/CHANGELOG.md
- image-automation-controller to v1.2.0
  https://github.com/fluxcd/image-automation-controller/blob/v1.2.0/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2026-06-17 13:50:53 +00:00
Matheus Pimenta
7a725fc3ad
Merge pull request #5918 from piny940/main
Some checks failed
e2e / e2e-amd64-kubernetes (push) Has been cancelled
scan / analyze (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
Support specifing sparseCheckout in flux bootstrap
2026-06-16 12:55:36 +01:00
piny940
56166fd90c
Support specifing sparseCheckout in flux bootstrap
Signed-off-by: piny940 <83708535+piny940@users.noreply.github.com>
Assisted-by: claude/opus-4.7
2026-06-16 20:33:11 +09:00
Matheus Pimenta
c438a10efc
Merge pull request #5938 from fluxcd/dependabot/github_actions/ci-5a41c51c5c
Some checks failed
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
build(deps): bump the ci group with 6 updates
2026-06-12 13:16:46 +01:00
dependabot[bot]
7a53052d06
build(deps): bump the ci group with 6 updates
Bumps the ci group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [fluxcd/gha-workflows/.github/workflows/backport.yaml](https://github.com/fluxcd/gha-workflows) | `0.10.0` | `0.11.0` |
| [fluxcd/pkg](https://github.com/fluxcd/pkg) | `1.32.0` | `1.33.0` |
| [replicatedhq/replicated-actions](https://github.com/replicatedhq/replicated-actions) | `1.26.0` | `1.27.0` |
| [fluxcd/gha-workflows/.github/workflows/code-scan.yaml](https://github.com/fluxcd/gha-workflows) | `0.10.0` | `0.11.0` |
| [fluxcd/gha-workflows/.github/workflows/labels-sync.yaml](https://github.com/fluxcd/gha-workflows) | `0.10.0` | `0.11.0` |
| [fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml](https://github.com/fluxcd/gha-workflows) | `0.10.0` | `0.11.0` |


Updates `fluxcd/gha-workflows/.github/workflows/backport.yaml` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.10.0...v0.11.0)

Updates `fluxcd/pkg` from 1.32.0 to 1.33.0
- [Commits](f3ad4b56ad...5a7f3ce0de)

Updates `replicatedhq/replicated-actions` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/replicatedhq/replicated-actions/releases)
- [Commits](291bef61a0...6803131db7)

Updates `fluxcd/gha-workflows/.github/workflows/code-scan.yaml` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.10.0...v0.11.0)

Updates `fluxcd/gha-workflows/.github/workflows/labels-sync.yaml` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.10.0...v0.11.0)

Updates `fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: fluxcd/gha-workflows/.github/workflows/backport.yaml
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/pkg
  dependency-version: 1.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: replicatedhq/replicated-actions
  dependency-version: 1.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/code-scan.yaml
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-12 12:14:39 +00:00
leigh capili
b1b4438ae9
Merge pull request #5932 from fluxcd/build-native-arch
Some checks failed
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
build: target host arch for local builds/envtest
2026-06-08 03:44:48 -06:00
leigh capili
862ab9b370
build: target host architecture for local builds and envtest
Local container image builds and envtest binaries were pinned to amd64,
forcing emulation (e.g. Rosetta) on Apple Silicon and other arm64 hosts. This
produced amd64 images/test binaries locally, which can surface subtle runtime
bugs. envtest now publishes arm64 binaries (including darwin/arm64), so the
historical amd64 pin (and the Darwin-specific override) is no longer needed.

Derive the architecture from the host Go toolchain (go env GOARCH) so local
builds are native, while keeping both values overridable for cross-arch builds.
Multi-arch release images are built by the fluxcd/gha-workflows release
workflow, not by "make docker-build", so release artifacts are unaffected.

Signed-off-by: leigh capili <leigh@null.net>
2026-06-08 03:25:15 -06:00
Stefan Prodan
c1355c1e72
Merge pull request #5906 from raffis/fix-preserve-invalid-labels
Some checks failed
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
conformance / conform-kubernetes (1.35.2) (push) Has been cancelled
conformance / conform-kubernetes (1.36.1) (push) Has been cancelled
conformance / conform-k3s (1.34.8) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
conformance / conform-k3s (1.35.5) (push) Has been cancelled
conformance / conform-k3s (1.36.1) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
conformance / conform-openshift (4.21.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
fix: preserve invalid metadata.labels in `flux build ks`
2026-06-05 10:00:29 +03:00
Raffael Sahli
e0803ee689
fix: preserve invalid label type
Signed-off-by: Raffael Sahli <raffael.sahli@doodle.com>
2026-06-05 08:46:15 +02:00
Matheus Pimenta
04b23241e1
Merge pull request #5928 from fluxcd/update-pkg-deps/main
Some checks failed
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.2) (push) Waiting to run
conformance / conform-kubernetes (1.36.1) (push) Waiting to run
conformance / conform-k3s (1.34.8) (push) Waiting to run
conformance / conform-k3s (1.35.5) (push) Waiting to run
conformance / conform-k3s (1.36.1) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
conformance / conform-openshift (4.21.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
e2e-gcp / e2e-gcp (push) Has been cancelled
e2e-azure / e2e-aks (push) Has been cancelled
Update fluxcd/pkg dependencies
2026-06-04 23:54:40 +01:00
matheuscscp
3aaa5fd4ef Update fluxcd/pkg dependencies
Signed-off-by: GitHub <noreply@github.com>
2026-06-04 22:53:56 +00:00
Stefan Prodan
f265800a87
Merge pull request #5927 from fluxcd/fix-plugin-path
Validate plugin binary path
2026-06-04 21:39:10 +03:00
Stefan Prodan
0afcda1a50
Validate plugin binary path
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-06-04 21:25:37 +03:00
Stefan Prodan
d78d406a52
Merge pull request #5868 from taraspos/taras/aws-codecommit
Add support for AWS CodeCommit to `flux bootstrap git`
2026-06-04 21:25:21 +03:00
Taras
5999cd4b9a
feat: add support of aws codecommit bootstrap
Signed-off-by: Taras <9948629+taraspos@users.noreply.github.com>
2026-06-04 19:02:56 +01:00
Stefan Prodan
3c2fe83dc2
Merge pull request #5926 from fluxcd/conform-k8s-1.36
Run conformance tests for Kubernetes 1.36
2026-06-04 20:29:57 +03:00
Stefan Prodan
9351ff68af
Run conformance tests for Kubernetes 1.36
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-06-04 20:12:46 +03:00
Stefan Prodan
3fe2820cf0
Merge pull request #5925 from fluxcd/dependabot/github_actions/ci-911b504c74
build(deps): bump the ci group across 1 directory with 19 updates
2026-06-04 20:01:50 +03:00
dependabot[bot]
166cc7ca72
build(deps): bump the ci group across 1 directory with 19 updates
Bumps the ci group with 19 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `6.0.2` | `6.0.3` |
| [fluxcd/gha-workflows/.github/workflows/backport.yaml](https://github.com/fluxcd/gha-workflows) | `0.9.0` | `0.10.0` |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.3.0` | `6.4.0` |
| [fluxcd/pkg](https://github.com/fluxcd/pkg) | `1.27.0` | `1.32.0` |
| [replicatedhq/replicated-actions](https://github.com/replicatedhq/replicated-actions) | `1.20.0` | `1.26.0` |
| [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) | `4.0.0` | `4.0.1` |
| [Azure/login](https://github.com/azure/login) | `2.3.0` | `3.0.0` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `4.0.0` | `4.1.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.0.0` | `4.1.0` |
| [docker/login-action](https://github.com/docker/login-action) | `4.0.0` | `4.2.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.0` | `7.0.1` |
| [github/codeql-action](https://github.com/github/codeql-action) | `4.32.6` | `4.36.2` |
| [anchore/sbom-action](https://github.com/anchore/sbom-action) | `0.23.1` | `0.24.0` |
| [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) | `4.1.0` | `4.1.2` |
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `7.0.0` | `7.2.2` |
| [fluxcd/gha-workflows/.github/workflows/code-scan.yaml](https://github.com/fluxcd/gha-workflows) | `0.9.0` | `0.10.0` |
| [fluxcd/gha-workflows/.github/workflows/labels-sync.yaml](https://github.com/fluxcd/gha-workflows) | `0.9.0` | `0.10.0` |
| [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) | `8.1.0` | `8.1.1` |
| [fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml](https://github.com/fluxcd/gha-workflows) | `0.9.0` | `0.10.0` |



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

Updates `fluxcd/gha-workflows/.github/workflows/backport.yaml` from 0.9.0 to 0.10.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.9.0...v0.10.0)

Updates `actions/setup-go` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](4b73464bb3...4a3601121d)

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

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

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

Updates `Azure/login` from 2.3.0 to 3.0.0
- [Release notes](https://github.com/azure/login/releases)
- [Commits](a457da9ea1...532459ea53)

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

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

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

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

Updates `github/codeql-action` from 4.32.6 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](0d579ffd05...8aad20d150)

Updates `anchore/sbom-action` from 0.23.1 to 0.24.0
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](57aae52805...e22c389904)

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

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

Updates `fluxcd/gha-workflows/.github/workflows/code-scan.yaml` from 0.9.0 to 0.10.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.9.0...v0.10.0)

Updates `fluxcd/gha-workflows/.github/workflows/labels-sync.yaml` from 0.9.0 to 0.10.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.9.0...v0.10.0)

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

Updates `fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml` from 0.9.0 to 0.10.0
- [Release notes](https://github.com/fluxcd/gha-workflows/releases)
- [Commits](https://github.com/fluxcd/gha-workflows/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/backport.yaml
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: actions/setup-go
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/pkg
  dependency-version: 1.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: replicatedhq/replicated-actions
  dependency-version: 1.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: hashicorp/setup-terraform
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: Azure/login
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: anchore/sbom-action
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.2.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/code-scan.yaml
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: peter-evans/create-pull-request
  dependency-version: 8.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci
- dependency-name: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 16:51:39 +00:00
Stefan Prodan
9daccd1847
Merge pull request #5924 from fluxcd/k8s-1.36
Update to Kubernetes 1.36 and Go 1.26
2026-06-04 19:36:46 +03:00
Stefan Prodan
3e21c27749
Update to Kubernetes 1.36 and Go 1.26
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-06-04 19:09:29 +03:00
Matheus Pimenta
ed778892df
Merge pull request #5912 from vecil/refactor/import-dependencyreference-type-from-meta
Some checks are pending
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
update / update-components (push) Waiting to run
refactor(api): migrate MakeDependsOn to shared apis/meta func
2026-06-04 08:25:01 +01:00
vecil
22953596c6
refactor(api): migrate MakeDependsOn to shared apis/meta func
Signed-off-by: Vincent Dely <vincent.dely@ik.me>
2026-05-26 07:14:33 +02:00
Matheus Pimenta
8c41d5b56d
Merge pull request #5908 from fluxcd/trigger-receiver
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
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
Some checks are pending
ossf / scorecard (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
e2e-gcp / e2e-gcp (push) Has been cancelled
e2e-azure / e2e-aks (push) Has been cancelled
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
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
scan / analyze (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
update / update-components (push) Has been cancelled
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
Some checks failed
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
update / update-components (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
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
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
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
Some checks failed
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
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
Some checks are pending
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks failed
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
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
Some checks failed
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
[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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
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
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-azure / e2e-aks (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e-gcp / e2e-gcp (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
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
Some checks failed
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
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
Some checks failed
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
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
Some checks failed
e2e / e2e-amd64-kubernetes (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
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
Some checks failed
e2e / e2e-amd64-kubernetes (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks failed
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
e2e-azure / e2e-aks (push) Has been cancelled
e2e-gcp / e2e-gcp (push) Has been cancelled
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-azure / e2e-aks (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
e2e-gcp / e2e-gcp (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
ossf / scorecard (push) Waiting to run
scan / analyze (push) Waiting to run
update / update-components (push) Waiting to run
build(deps): bump the ci group across 1 directory with 11 updates
2026-03-11 19:02:08 +02:00
dependabot[bot]
b60dfbe970
build(deps): bump the ci group across 1 directory with 11 updates
Bumps the ci group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.2.0` | `6.3.0` |
| [replicatedhq/replicated-actions](https://github.com/replicatedhq/replicated-actions) | `1.19.0` | `1.20.0` |
| [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) | `3.1.2` | `4.0.0` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `3.7.0` | `4.0.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3.12.0` | `4.0.0` |
| [docker/login-action](https://github.com/docker/login-action) | `3.7.0` | `4.0.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `6.0.0` | `7.0.0` |
| [github/codeql-action](https://github.com/github/codeql-action) | `4.32.4` | `4.32.6` |
| [anchore/sbom-action](https://github.com/anchore/sbom-action) | `0.22.2` | `0.23.1` |
| [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) | `4.0.0` | `4.1.0` |
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `6.4.0` | `7.0.0` |



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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-11 16:46:48 +00:00
Stefan Prodan
ee8bb8d8a0
Merge pull request #5763 from gaganhr94/fix/token-permissions
ci: add top-level permissions to upgrade-fluxcd-pkg workflow
2026-03-11 18:42:27 +02:00
Gagan H R
5f3098477e ci: add top-level permissions to upgrade-fluxcd-pkg workflow
Add explicit top-level `permissions: contents: read` to the
upgrade-fluxcd-pkg workflow to follow the principle of least privilege
and fix the OpenSSF Scorecard Token-Permissions warning.

Signed-off-by: Gagan H R <hrgagan4@gmail.com>
2026-03-11 21:40:14 +05:30
Matheus Pimenta
4c79a76e94
Merge pull request #5743 from fluxcd/rn-template
Some checks failed
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
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
Some checks are pending
conformance / conform-kubernetes (1.33.0) (push) Waiting to run
conformance / conform-k3s (1.34.3) (push) Waiting to run
conformance / conform-kubernetes (1.34.1) (push) Waiting to run
conformance / conform-k3s (1.35.0) (push) Waiting to run
conformance / conform-kubernetes (1.35.0) (push) Waiting to run
conformance / conform-k3s (1.33.7) (push) Waiting to run
e2e / e2e-amd64-kubernetes (push) Waiting to run
scan / analyze (push) Waiting to run
conformance / conform-openshift (4.20.0-okd) (push) Waiting to run
e2e-bootstrap / e2e-boostrap-github (push) Waiting to run
ossf / scorecard (push) Waiting to run
update / update-components (push) Waiting to run
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
Some checks failed
scan / analyze (push) Has been cancelled
update / update-components (push) Has been cancelled
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
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
Some checks failed
conformance / conform-kubernetes (1.35.0) (push) Has been cancelled
ossf / scorecard (push) Has been cancelled
e2e-azure / e2e-aks (push) Has been cancelled
e2e-bootstrap / e2e-boostrap-github (push) Has been cancelled
e2e-gcp / e2e-gcp (push) Has been cancelled
e2e / e2e-amd64-kubernetes (push) Has been cancelled
scan / analyze (push) Has been cancelled
conformance / conform-openshift (4.20.0-okd) (push) Has been cancelled
update / update-components (push) Has been cancelled
sync-labels / sync-labels (push) Has been cancelled
conformance / conform-k3s (1.33.7) (push) Has been cancelled
conformance / conform-k3s (1.34.3) (push) Has been cancelled
conformance / conform-kubernetes (1.33.0) (push) Has been cancelled
conformance / conform-kubernetes (1.34.1) (push) Has been cancelled
conformance / conform-k3s (1.35.0) (push) Has been cancelled
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
161 changed files with 9302 additions and 1250 deletions

6
.github/labels.yaml vendored
View file

@ -44,12 +44,12 @@
description: Feature request proposals in the RFC format
color: '#D621C3'
aliases: ['area/RFC']
- name: backport:release/v2.5.x
description: To be backported to release/v2.5.x
color: '#ffd700'
- name: backport:release/v2.6.x
description: To be backported to release/v2.6.x
color: '#ffd700'
- name: backport:release/v2.7.x
description: To be backported to release/v2.7.x
color: '#ffd700'
- name: backport:release/v2.8.x
description: To be backported to release/v2.8.x
color: '#ffd700'

View file

@ -23,7 +23,7 @@ amd when it finds a new controller version, the workflow performs the following
- Updates the controller API package version in `go.mod`.
- Patches the controller CRDs version in the `manifests/crds` overlay.
- Patches the controller Deployment version in `manifests/bases` overlay.
- Opens a Pull Request against the `main` branch.
- Opens a Pull Request against the checked out branch.
- Triggers the e2e test suite to run for the opened PR.

View file

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

View file

@ -8,6 +8,6 @@ jobs:
permissions:
contents: write # for reading and creating branches.
pull-requests: write # for creating pull requests against release branches.
uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.8.0
uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.11.0
secrets:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}

View file

@ -3,7 +3,7 @@ name: conformance
on:
workflow_dispatch:
push:
branches: [ 'main', 'update-components', 'release/**', 'conform*' ]
branches: [ 'main', 'update-components-**', 'release/**', 'conform*' ]
permissions:
contents: read
@ -19,13 +19,13 @@ jobs:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml
KUBERNETES_VERSION: [1.33.0, 1.34.1, 1.35.0]
KUBERNETES_VERSION: [1.34.1, 1.35.2, 1.36.1]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@ -42,7 +42,7 @@ jobs:
- name: Setup Kubernetes
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.30.0
version: v0.32.0
cluster_name: ${{ steps.prep.outputs.CLUSTER }}
node_image: ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64
- name: Run e2e tests
@ -76,13 +76,13 @@ jobs:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Available versions can be found with "replicated cluster versions"
K3S_VERSION: [ 1.33.7, 1.34.3, 1.35.0 ]
K3S_VERSION: [ 1.34.8, 1.35.5, 1.36.1 ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@ -97,7 +97,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/kustomize@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Build
run: make build-dev
- name: Create repository
@ -107,7 +107,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Create cluster
id: create-cluster
uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/replicated-actions/create-cluster@6803131db735f7cc067de88fa14237c7462b247a # v1.27.0
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: "k3s"
@ -150,7 +150,7 @@ jobs:
kubectl delete ns flux-system --wait
- name: Delete cluster
if: ${{ always() }}
uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/replicated-actions/remove-cluster@6803131db735f7cc067de88fa14237c7462b247a # v1.27.0
continue-on-error: true
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
@ -168,13 +168,13 @@ jobs:
strategy:
matrix:
# Keep this list up-to-date with https://endoflife.date/red-hat-openshift
OPENSHIFT_VERSION: [ 4.20.0-okd ]
OPENSHIFT_VERSION: [ 4.20.0-okd, 4.21.0-okd ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@ -189,7 +189,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/kustomize@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Build
run: make build-dev
- name: Create repository
@ -199,7 +199,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Create cluster
id: create-cluster
uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/replicated-actions/create-cluster@6803131db735f7cc067de88fa14237c7462b247a # v1.27.0
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: "openshift"
@ -240,7 +240,7 @@ jobs:
kubectl delete ns flux-system --wait
- name: Delete cluster
if: ${{ always() }}
uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/replicated-actions/remove-cluster@6803131db735f7cc067de88fa14237c7462b247a # v1.27.0
continue-on-error: true
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}

View file

@ -29,14 +29,14 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: CheckoutD
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.26.x
cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
- name: Setup Flux CLI
run: make build
working-directory: ./
@ -48,7 +48,7 @@ jobs:
env:
SOPS_VER: 3.7.1
- name: Authenticate to Azure
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v1.4.6
uses: Azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v1.4.6
with:
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
- name: Set dynamic variables in .env

View file

@ -17,9 +17,9 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.26.x
cache-dependency-path: |
@ -28,16 +28,16 @@ jobs:
- name: Setup Kubernetes
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.30.0
version: v0.32.0
cluster_name: kind
# The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.33.0
node_image: ghcr.io/fluxcd/kindest/node:v1.36.1-amd64
kubectl_version: v1.36.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/kustomize@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Setup yq
uses: fluxcd/pkg/actions/yq@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/yq@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Build
run: make build-dev
- name: Set outputs

View file

@ -29,14 +29,14 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.26.x
cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
- name: Setup Flux CLI
run: make build
working-directory: ./
@ -56,11 +56,11 @@ jobs:
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
- name: Setup QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Log into us-central1-docker.pkg.dev
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: us-central1-docker.pkg.dev
username: oauth2accesstoken

View file

@ -18,14 +18,14 @@ jobs:
labels: ubuntu-latest-16-cores
services:
registry:
image: registry:2
image: registry:3
ports:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.26.x
cache-dependency-path: |
@ -34,19 +34,19 @@ jobs:
- name: Setup Kubernetes
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.30.0
version: v0.32.0
cluster_name: kind
wait: 5s
config: .github/kind/config.yaml # disable KIND-net
# The versions below should target the oldest supported Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.33.0
node_image: ghcr.io/fluxcd/kindest/node:v1.34.1-amd64
kubectl_version: v1.34.0
- name: Setup Calico for network policy
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.32.0/manifests/calico.yaml
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/kustomize@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Run tests
run: make test
- name: Run e2e tests

View file

@ -19,7 +19,7 @@ jobs:
actions: read
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
@ -28,12 +28,12 @@ jobs:
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: results.sarif

View file

@ -22,35 +22,35 @@ jobs:
packages: write # needed for ghcr access
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Unshallow
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.26.x
cache: false
- name: Setup QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Setup Syft
uses: anchore/sbom-action/download-syft@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
- name: Setup Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/kustomize@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@ -63,7 +63,7 @@ jobs:
run: |
kustomize build manifests/crds > all-crds.yaml
- name: Generate OpenAPI JSON schemas from CRDs
uses: fluxcd/pkg/actions/crdjsonschema@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/crdjsonschema@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
with:
crd: all-crds.yaml
output: schemas
@ -72,7 +72,7 @@ jobs:
tar -czvf ./output/crd-schemas.tar.gz -C schemas .
- name: Run GoReleaser
id: run-goreleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
with:
version: latest
args: release --skip=validate
@ -103,9 +103,9 @@ jobs:
id-token: write
packages: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
uses: fluxcd/pkg/actions/kustomize@5a7f3ce0de742b6c561a50f90940d81cf6fc698d # main
- name: Setup Flux CLI
uses: ./action/
with:
@ -116,13 +116,13 @@ jobs:
VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@ -150,7 +150,7 @@ jobs:
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
- name: Sign manifests

View file

@ -13,7 +13,7 @@ jobs:
permissions:
contents: read # for reading the repository code.
security-events: write # for uploading the CodeQL analysis results.
uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.8.0
uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.11.0
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}
fossa-token: ${{ secrets.FOSSA_TOKEN }}

View file

@ -12,6 +12,6 @@ jobs:
permissions:
contents: read # for reading the labels file.
issues: write # for creating and updating labels.
uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.8.0
uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.11.0
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -16,9 +16,9 @@ jobs:
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.26.x
cache-dependency-path: |
@ -96,7 +96,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: |
@ -106,7 +106,7 @@ jobs:
committer: GitHub <noreply@github.com>
author: fluxcdbot <fluxcdbot@users.noreply.github.com>
signoff: true
branch: update-components
branch: update-components-${{ github.ref_name }}
title: Update toolkit components
body: |
${{ steps.update.outputs.pr_body }}

View file

@ -2,20 +2,12 @@ name: upgrade-fluxcd-pkg
on:
workflow_dispatch:
inputs:
pre-release-pkg:
description: >-
Temporary flag for Flux 2.8: use the flux/v2.8.x pkg branch for main branches
because the pkg release branch was cut before the Flux distribution release.
Remove this input once Flux 2.8.0 is released.
required: false
default: false
type: boolean
permissions:
contents: read
jobs:
upgrade-fluxcd-pkg:
uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.8.0
with:
pre-release-pkg: ${{ inputs.pre-release-pkg }}
uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.11.0
secrets:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}

151
AGENTS.md Normal file
View file

@ -0,0 +1,151 @@
# AGENTS.md
Guidance for AI coding assistants working in `fluxcd/flux2`. Read this file before making changes.
## Contribution workflow for AI agents
These rules come from [`fluxcd/flux2/CONTRIBUTING.md`](https://github.com/fluxcd/flux2/blob/main/CONTRIBUTING.md) and apply to every Flux repository.
- **Do not add `Signed-off-by` or `Co-authored-by` trailers with your agent name.** Only a human can legally certify the DCO.
- **Disclose AI assistance** with an `Assisted-by` trailer naming your agent and model:
```sh
git commit -s -m "Add support for X" --trailer "Assisted-by: <agent-name>/<model-id>"
```
The `-s` flag adds the human's `Signed-off-by` from their git config — do not remove it.
- **Commit message format:** Subject in imperative mood ("Add feature X" instead of "Adding feature X"), capitalized, no trailing period, ≤50 characters. Body wrapped at 72 columns, explaining what and why. No `@mentions` or `#123` issue references in the commit — put those in the PR description.
- **Trim verbiage:** in PR descriptions, commit messages, and code comments. No marketing prose, no restating the diff, no emojis.
- **Rebase, don't merge:** Never merge `main` into the feature branch; rebase onto the latest `main` and push with `--force-with-lease`. Squash before merge when asked.
- **Pre-PR gate:** `make tidy fmt vet && make test` must pass and the working tree must be clean.
- **Flux is GA:** Backward compatibility is mandatory. Breaking changes to CLI flags, output format, or behavior will be rejected. Design additive changes.
- **Copyright:** All new `.go` files must begin with the header from `cmd/flux/main.go` (Apache 2.0). Update the year to the current year when copying.
- **Tests:** New features, improvements and fixes must have test coverage. Add unit tests in `cmd/flux/*_test.go` tagged `//go:build unit`. Follow the existing `cmdTestCase` + golden file patterns. Run tests locally before pushing.
## Code quality
Before submitting code, review your changes for the following:
- **No secrets in logs or output.** Never surface auth tokens, passwords, deploy keys, or credential URLs in error messages, log lines, or CLI output. Bootstrap and source-secret commands handle sensitive material — take extra care.
- **No unchecked I/O.** Close HTTP response bodies, file handles, and tar readers in `defer` statements. Check and propagate errors from I/O operations.
- **No path traversal.** Validate and sanitize file paths extracted from archives or user input. Never `filepath.Join` with untrusted components without validation.
- **No command injection.** Do not shell out via `os/exec` for git, helm, or kustomize operations. Use the Go libraries already in use (`fluxcd/pkg/git`, `fluxcd/pkg/kustomize`, `fluxcd/pkg/ssa`).
- **No hardcoded defaults for security settings.** TLS verification must remain enabled by default. Git auth settings come from user-provided secrets.
- **Error handling.** Wrap errors with `%w` for chain inspection. Do not swallow errors silently. CLI errors must be actionable — tell the user what went wrong and how to fix it without leaking internal state.
- **Resource cleanup.** Ensure temporary files and directories (manifest staging, downloaded tarballs) are cleaned up on all code paths (success and error). Use `defer` and `t.TempDir()` in tests.
- **No panics.** Never use `panic` in runtime code paths. Return errors and let the CLI handle them gracefully.
- **Output discipline.** Machine-readable data (tables, YAML, JSON) goes to stdout via `rootCmd.OutOrStdout()`. Human-readable status messages go to stderr via the `stderrLogger`.
- **Minimal surface.** Keep new exported APIs in `pkg/` to the minimum needed. Every export is a backward-compatibility commitment.
## Project overview
flux2 is the Flux CLI (`flux` command) and distribution repository. It is **not** a controller — it consumes CRD APIs from six independent controller repos (source-controller, kustomize-controller, helm-controller, notification-controller, image-reflector-controller, image-automation-controller). It serves two purposes:
1. **CLI tool** — a Cobra-based binary that installs Flux onto Kubernetes clusters, bootstraps GitOps pipelines, and manages all Flux CRD objects (create, get, export, reconcile, suspend, resume, delete, diff, build, etc.).
2. **Distribution hub** — it bundles the Kustomize manifests for all Flux controllers and releases them as `manifests.tar.gz` on GitHub. Those manifests are also compiled into the binary itself via `//go:embed`.
## Repository layout
- `cmd/flux/` — all CLI source. Single `main` package with one file per command or resource type. `main.go` defines the root cobra command with global flags. `manifests.embed.go` embeds the generated controller manifests via `//go:embed`.
- `internal/build/` — `flux build kustomization` logic (kustomize-based diff/build, SOPS secret masking).
- `internal/flags/` — custom `pflag.Value` types providing enum validation (e.g. `LogLevel`, `ECDSACurve`, `RSAKeyBits`, `PublicKeyAlgorithm`, `DecryptionProvider`).
- `internal/tree/` — tree-printing helper for `flux tree kustomization`.
- `internal/utils/` — shared helpers: `KubeClient`, `KubeConfig`, `NewScheme` (registers all controller API groups), `Apply` (SSA-based two-phase apply), `ExecKubectlCommand`, `ValidateComponents`.
- `pkg/bootstrap/` — bootstrap orchestration: `Run()`, `PlainGitBootstrapper`, `ProviderBootstrapper`. `provider/` has the git provider factory (GitHub, GitLab, Gitea, Bitbucket).
- `pkg/log/` — `Logger` interface (`Actionf`, `Generatef`, `Waitingf`, `Successf`, `Warningf`, `Failuref`).
- `pkg/manifestgen/` — manifest generation for install, sync, kustomization, and source secrets.
- `pkg/printers/` — specialized printers `TablePrinter` and `DyffPrinter`.
- `pkg/status/` — `StatusChecker` using `fluxcd/cli-utils` kstatus polling.
- `pkg/uninstall/` — `flux uninstall` logic.
- `manifests/` — Kustomize bases per controller, RBAC, network policies, CRD references, and `scripts/bundle.sh` which runs `kustomize build` to generate `cmd/flux/manifests/`.
- `tests/integration/` — cloud e2e tests (Azure/GCP) with their own `go.mod` and Terraform infrastructure.
- `rfcs/` — Request for Comments documents for major design proposals and changes.
## CLI architecture
Commands are Cobra-based, organized as parent + per-resource children:
- Group files (`create.go`, `get.go`, `reconcile.go`, etc.) register the parent subcommand.
- Per-resource files (`create_kustomization.go`, `get_helmrelease.go`, etc.) register children.
Core interfaces in `cmd/flux/` enable generic command implementations:
- `adapter` / `copyable` / `listAdapter` — wrap controller API types for generic CRUD.
- `reconcilable` — annotate-and-poll pattern for triggering reconciliation.
- `summarisable` — generic table output for `get` commands.
Each resource type (e.g. `kustomizationAdapter` in `kustomization.go`) wraps the controller API type and implements these interfaces. Follow this pattern when adding new resource support.
Commands interact with the Kubernetes API via `internal/utils.KubeClient()` → `client.WithWatch`. `internal/utils.NewScheme()` registers all six controller API groups plus core k8s types. `internal/utils.Apply()` implements SSA-based two-phase apply (CRDs/Namespaces first, then remaining objects).
## Manifest pipeline
1. `manifests/bases/<controller>/` contains a Kustomize base per controller referencing the controller's GitHub release for CRDs and deployment manifests.
2. `manifests/install/kustomization.yaml` assembles all bases plus RBAC and policies.
3. `manifests/scripts/bundle.sh` runs `kustomize build` on each base, writing output to `cmd/flux/manifests/` (not checked in — generated).
4. The Makefile `$(EMBEDDED_MANIFESTS_TARGET)` runs `bundle.sh` and creates a sentinel file `cmd/flux/.manifests.done`.
5. `cmd/flux/manifests.embed.go` uses `//go:embed manifests/*.yaml` to compile everything into the binary.
When modifying `manifests/`, always run `make build` and verify the generated output before committing. Never hand-edit files under `cmd/flux/manifests/`.
## Build, test, lint
All targets in the root `Makefile`. Go version tracks `go.mod`.
- `make tidy` — tidy the root module and `tests/integration/`.
- `make fmt` / `make vet` — run in the root module.
- `make build` — builds `bin/flux` (CGO disabled, version injected via ldflags). Depends on embedded manifests being generated.
- `make build-dev` — builds with `DEV_VERSION`.
- `make install` / `make install-dev` — `go install` or copy to `/usr/local/bin`.
- `make test` — unit tests with envtest: runs `tidy fmt vet install-envtest`, then `go test ./... -coverprofile cover.out --tags=unit $(TEST_ARGS)`.
- `make e2e` — e2e tests against a live cluster: `go test ./cmd/flux/... --tags=e2e -v -failfast`.
- `make test-with-kind` — sets up a kind cluster, runs e2e, tears it down.
- `make install-envtest` — downloads `setup-envtest` and fetches k8s binaries into `testbin/`.
Run a single test: `make test TEST_ARGS='-run TestCreate -v'`.
## Codegen and generated files
Check `go.mod` and the `Makefile` for current dependency and tool versions. The main codegen pipeline is the manifest bundle:
```sh
./manifests/scripts/bundle.sh
```
Generated files (never hand-edit):
- `cmd/flux/manifests/*.yaml` — generated by `bundle.sh` from `manifests/` sources.
- `cmd/flux/.manifests.done` — sentinel file tracking bundle state.
Bump `fluxcd/pkg/*` and controller `api` modules as a set. Run `make tidy` after any bump.
## Conventions
- Standard `gofmt`. All exported names need doc comments.
- **Command pattern:** follow the existing group-parent + per-resource-child cobra structure. New resources need an adapter type implementing `adapter`, `copyable`, and the relevant command interfaces (`reconcilable`, `summarisable`, etc.).
- **Output:** stderr for human status messages via `stderrLogger` (Unicode symbols: `►` action, `✔` success, `✗` failure, `◎` waiting, `⚠️` warning, `✚` generate). Stdout for machine-readable data (tables, YAML, JSON) via `rootCmd.OutOrStdout()`.
- **Global flags:** kubeconfig flags come from `k8s.io/cli-runtime/pkg/genericclioptions.ConfigFlags`. Client tuning comes from `fluxcd/pkg/runtime/client.Options`. `FLUX_SYSTEM_NAMESPACE` env var overrides the default namespace.
- **SSA apply:** always use `internal/utils.Apply()` (two-phase: CRDs/Namespaces first, then rest). Do not apply manifests directly via the k8s client.
- **Reconcile triggering:** patch `meta.ReconcileRequestAnnotation` with a timestamp, then poll with `kstatus.Compute()` until ready. See `reconcile.go`.
- **Error handling:** return errors from `RunE`. Use `*RequestError` with exit codes for actionable CLI errors. Exit code 1 = warning, anything else = failure.
- **Flags:** use `internal/flags/` custom `pflag.Value` types for enum flags (providers, algorithms, sources). Add new enum types there.
## Testing
Three test suites with build tags:
- **Unit** (`//go:build unit`): lives in `cmd/flux/*_test.go`. Uses `controller-runtime/envtest` for an in-process fake k8s API. CRDs are loaded from `cmd/flux/manifests/` (embedded manifests). Pattern: `cmdTestCase{args: "...", assert: assertGoldenFile("testdata/...")}`. The `executeCommand()` helper captures stdout.
- **E2e** (`//go:build e2e`): lives in `cmd/flux/*_test.go`. Requires a live cluster via `TEST_KUBECONFIG`. `TestMain` runs `flux install` for setup and teardown.
- **Integration** (`//go:build integration`): lives in `tests/integration/` with its own `go.mod`. Uses Terraform-provisioned cloud clusters.
Golden files live in `cmd/flux/testdata/`. Update them with `go test ./cmd/flux/... --tags=unit -update`.
Run a single unit test: `make test TEST_ARGS='-run TestInstall -v'`.
## Gotchas and non-obvious rules
- The `cmd/flux/manifests/` directory is **generated, not checked in**. It is created by `manifests/scripts/bundle.sh` and embedded into the binary. `make build` and `make test` both trigger the bundle if the sentinel file is stale.
- `kustomize` must be on `PATH` for `bundle.sh` to work. If you see "command not found" errors during build, install kustomize.
- `internal/utils.NewScheme()` registers all six controller API groups. Adding support for a new CRD type means updating the scheme registration there.
- The `VERSION` constant is injected via `-ldflags` at build time. In dev builds it defaults to `0.0.0-dev.0`. The embedded manifest version check (`isEmbeddedVersion`) determines whether `flux install` uses compiled-in manifests or downloads from GitHub.
- `resetCmdArgs()` in tests is critical — Cobra persists flag state between test runs. Every test case must reset to avoid pollution.
- `executeCommand()` captures stdout only. Stderr output (from `stderrLogger`) is not captured in test assertions. If your command's output goes to the wrong stream, tests will silently pass with empty golden files.
- The `adapter` / `listAdapter` interfaces use type assertions internally. If you add a new resource type and forget to implement an interface method, you'll get a runtime panic in the generic command handler, not a compile error. Add interface compliance checks (`var _ reconcilable = ...`).
- Bootstrap commands create real Git commits and push to real repos. E2e tests for bootstrap need careful cleanup. Do not add bootstrap e2e tests without a corresponding teardown.
- `pkg/` packages are importable by external consumers (e.g. Terraform provider, other tools). Treat their exported surface as public API.

View file

@ -1,154 +1,129 @@
# Contributing
Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and
accepts contributions via GitHub pull requests. This document outlines
some of the conventions on to make it easier to get your contribution
accepted.
Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and accepts contributions via GitHub pull requests.
This document outlines the conventions to get your contribution accepted.
We gratefully welcome improvements to documentation as well as code contributions.
We gratefully welcome improvements to issues and documentation as well as to
code.
If you are new to the project, we recommend starting with documentation improvements or
small bug fixes to get familiar with the codebase and the contribution process.
## Project Structure
The Flux project consists of a set of Kubernetes controllers and tools that implement the GitOps pattern.
The main repositories in the Flux project are:
- [fluxcd/flux2](https://github.com/fluxcd/flux2): The Flux distribution and command-line interface (CLI)
- [fluxcd/pkg](https://github.com/fluxcd/pkg): The GitOps Toolkit Go SDK for building Flux controllers and CLI plugins
- [fluxcd/source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets)
- [fluxcd/source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns
- [fluxcd/kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize
- [fluxcd/helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for lifecycle management of Helm releases
- [fluxcd/notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events (alerts and webhook receivers)
- [fluxcd/image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries for new image tags and digests
- [fluxcd/image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patching container image tags and digests in Git repositories
- [fluxcd/website](https://github.com/fluxcd/website): The Flux documentation website accessible at <https://fluxcd.io/>
## AI Coding Assistants Guidance
Using AI Agents to help write your PR is acceptable, but as the author, you are responsible
for understanding the code and the documentation you submit. Please review all the AI-generated
content and make sure it follows the guidelines in this document before submitting your PR.
All Flux repositories contain an `AGENTS.md` file. You must point your AI Agent to
`AGENTS.md` and ask it to follow the guidelines and conventions described there.
Trim down the verbiage in the PR description, commit messages and code comments.
When engaging with Flux maintainers please refrain from using AI Agents to
generate responses, we want to talk to you, not to your AI Agent.
AI Agents **must not** add `Signed-off-by` or `Co-authored-by` tags to the commit message.
Only humans can legally certify the Developer Certificate of Origin ([DCO](https://developercertificate.org/)).
You should disclose the use of AI Agents in the description of your PR and
in the commit message using the `Assisted-by: AGENT_NAME/LLM_VERSION` tag.
Adding the `Assisted-by` tag to the commit message can be done with:
```sh
git commit -s -m "Your commit message" --trailer "Assisted-by: <agent>/<model>"
```
**Note** that the `Signed-off-by` tag is set via the `-s` flag using your real name and email
(`user.name` and `user.email` must be set in Git config).
Example of a commit message disclosing the use of AI assistance:
```text
Add version info to plugin listing
Add a version column to the `flux plugin list` table output and populate
it with the semantic version info extracted from the plugin's recipe file.
For plugins installed via symlinks, the version is set to `unknown`.
Signed-off-by: Jane Doe <jane.doe@example.com>
Assisted-by: copilot/gpt-5.4
```
## Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution.
By contributing to this project you agree to the Developer Certificate of Origin (DCO).
This document was created by the Linux Kernel community and is a simple statement that you,
as a contributor, have the legal right to make the contribution.
We require all commits to be signed. By signing off with your signature, you
certify that you wrote the patch or otherwise have the right to contribute the
material by the rules of the [DCO](DCO):
We require all commits to be signed. By signing off with your signature, you certify that you wrote
the patch or otherwise have the right to contribute the material by the rules of the [DCO](https://raw.githubusercontent.com/fluxcd/flux2/refs/heads/main/DCO):
`Signed-off-by: Jane Doe <jane.doe@example.com>`
The signature must contain your real name
(sorry, no pseudonyms or anonymous contributions)
If your `user.name` and `user.email` are configured in your Git config,
The signature must contain your real name (sorry, no pseudonyms or anonymous contributions).
If your `user.name` and `user.email` are set in your Git config,
you can sign your commit automatically with `git commit -s`.
## Communications
For realtime communications we use Slack: To join the conversation, simply
join the [CNCF](https://slack.cncf.io/) Slack workspace and use the
[#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel.
To discuss ideas and specifications we use [Github
Discussions](https://github.com/fluxcd/flux2/discussions).
For announcements we use a mailing list as well. Simply subscribe to
[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev)
to join the conversation (there you can also add calendar invites
to your Google calendar for our [Flux
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)).
## Understanding Flux and the GitOps Toolkit
If you are entirely new to Flux and the GitOps Toolkit,
you might want to take a look at the [introductory talk and demo](https://www.youtube.com/watch?v=qQBtSkgl7tI).
This project is composed of:
- [flux2](https://github.com/fluxcd/flux2): The Flux CLI
- [source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets)
- [source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns
- [kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize
- [helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm
- [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events
- [image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries
- [image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patches container image tags in Git
### Understanding the code
To get started with developing controllers, you might want to review
[our guide](https://fluxcd.io/flux/gitops-toolkit/source-watcher/) which
walks you through writing a short and concise controller that watches out
for source changes.
## How to run the test suite
Prerequisites:
* go >= 1.26
* kubectl >= 1.33
* kustomize >= 5.0
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
```bash
make install-envtest
```
Then you can run the unit tests with:
```bash
make test
```
After [installing Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation) on your machine,
create a cluster for testing with:
```bash
make setup-kind
```
Then you can run the end-to-end tests with:
```bash
make e2e
```
When the output of the Flux CLI changes, to automatically update the golden
files used in the test, pass `-update` flag to the test as:
```bash
make e2e TEST_ARGS="-update"
```
Since not all packages use golden files for testing, `-update` argument must be
passed only for the packages that use golden files. Use the variables
`TEST_PKG_PATH` for unit tests and `E2E_TEST_PKG_PATH` for e2e tests, to set the
path of the target test package:
```bash
# Unit test
make test TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
# e2e test
make e2e E2E_TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
```
Teardown the e2e environment with:
```bash
make cleanup-kind
```
## Acceptance policy
These things will make a PR more likely to be accepted:
- a well-described requirement
- tests for new code
- tests for old code!
- new code and tests follow the conventions in old code and tests
- a good commit message (see below)
- all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1)
- code must build on both Linux and Darwin, via plain `go build`
- code should have appropriate test coverage and tests should be written
to work with `go test`
- Addressing an open issue, if one doesn't exist, please open an issue to discuss the problem and the proposed solution before submitting a PR.
- Flux is GA software and we are committed to maintaining backward compatibility. If your contribution introduces a breaking change, expect for your PR to be rejected.
- New code and tests must follow the conventions in the existing code and tests. All new code must have good test coverage and be well documented.
- All top-level Go code and exported names should have doc comments, as should non-trivial unexported type or function declarations.
- Before submitting a PR, make sure that your code is properly formatted by running `make tidy fmt vet` and that all tests are passing by running `make test`.
In general, we will merge a PR once one maintainer has endorsed it.
For substantial changes, more people may become involved, and you might
get asked to resubmit the PR or divide the changes into more than one PR.
### Format of the Commit Message
## Format of the Commit Message
For the GitOps Toolkit controllers we prefer the following rules for good commit messages:
For the Flux project we prefer the following rules:
- Limit the subject to 50 characters and write as the continuation
of the sentence "If applied, this commit will ..."
- Explain what and why in the body, if more than a trivial change;
wrap it at 72 characters.
- Limit the subject to 50 characters, start with a capital letter and do not end with a period.
- Explain what and why in the body, if more than a trivial change; wrap it at 72 characters.
- Use the imperative mood in the subject line (e.g., "Add support for X" instead of "Added support for X" or "Adds support for X").
- Do not include GitHub mentions to issues in the commit message, use the PR description instead (e.g., "Fixes #123" or "Closes #123").
- Do not include GitHub mentions to accounts (e.g., `@username` or `@team`) within the commit message.
The [following article](https://chris.beams.io/posts/git-commit/#seven-rules)
has some more helpful advice on documenting your work.
## Pull Request Process
Fork the repository and create a new branch for your changes, do not commit directly to the `main` branch.
Once you have made your changes and committed them, push your branch to your fork and open a pull request
against the `main` branch of the Flux repository.
During the review process, you may be asked to make changes to your PR. Add commits to address the feedback
without force pushing, as this will make it easier for reviewers to see the changes.
Before committing, make sure to run `make test` to ensure that your code will pass the CI checks.
When the review process is complete, you will be asked to **squash** the commits and **rebase** your branch.
**Do not merge** the `main` branch into your branch, instead, rebase your branch on top of the latest `main`
branch after **syncing your fork** with the latest changes from the Flux repository. After rebasing,
you can push your branch with the `--force-with-lease` option to update the PR.
## Communications
For realtime communications we use Slack. To reach out to the Flux maintainers and contributors,
join the [CNCF](https://slack.cncf.io/) Slack workspace and use the [#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel.
To discuss ideas and specifications we use [GitHub Discussions](https://github.com/fluxcd/flux2/discussions).
For announcements, we use a mailing list as well. Subscribe to
[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev), there you can also add calendar invites
to your Google calendar for our [Flux dev meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view).

View file

@ -3,7 +3,7 @@ FROM alpine:3.23 AS builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.35.0
ARG KUBECTL_VER=1.36.1
RUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl

View file

@ -2,8 +2,9 @@ VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n
DEV_VERSION?=0.0.0-$(shell git rev-parse --abbrev-ref HEAD)-$(shell git rev-parse --short HEAD)-$(shell date +%s)
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done
TEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig
# Architecture to use envtest with
ENVTEST_ARCH ?= amd64
# Architecture to use envtest with; defaults to the host architecture.
LOCALARCH ?= $(shell go env GOARCH)
ENVTEST_ARCH ?= $(LOCALARCH)
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))

View file

@ -20,9 +20,11 @@ import (
"context"
"crypto/elliptic"
"fmt"
"os"
"strings"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/signature"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
@ -30,6 +32,7 @@ import (
"github.com/fluxcd/flux2/v2/internal/flags"
"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/sourcesecret"
)
@ -47,6 +50,7 @@ type bootstrapFlags struct {
branch string
recurseSubmodules bool
sparseCheckout []string
manifestsPath string
defaultComponents []string
@ -79,6 +83,11 @@ type bootstrapFlags struct {
gpgPassphrase string
gpgKeyID string
sshSigningKeyFile string
sshSigningPassword string
sshSigningPassphrase string
sshSigningReusePrivateKey bool
force bool
commitMessageAppendix string
@ -109,6 +118,8 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, "branch", bootstrapDefaultBranch, "Git branch")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.recurseSubmodules, "recurse-submodules", false,
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.sparseCheckout, "sparse-checkout", nil,
"list of directories to be included in the GitRepository sparse checkout, the configured --path must be one of them, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory")
@ -139,6 +150,12 @@ func init() {
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.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().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a different tool such as Helm")
@ -195,6 +212,31 @@ func bootstrapValidate() error {
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 {
git.HostKeyAlgos = bootstrapArgs.sshHostKeyAlgorithms
}
@ -214,6 +256,57 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
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.
// 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 {

View file

@ -24,6 +24,7 @@ import (
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/fluxcd/pkg/git/signature"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags"
@ -253,6 +254,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
TargetPath: bServerArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
SparseCheckout: bootstrapArgs.sparseCheckout,
}
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
@ -287,6 +289,31 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
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
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil {

View file

@ -28,8 +28,12 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/auth/aws"
authutils "github.com/fluxcd/pkg/auth/utils"
"github.com/fluxcd/pkg/git"
"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/utils"
@ -62,9 +66,12 @@ command will perform an upgrade if needed.`,
# Run bootstrap for a Git repository with a private key and password
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password> --path=clusters/my-cluster
# Run bootstrap for a Git repository on AWS CodeCommit
# Run bootstrap for a Git repository on AWS CodeCommit using SSH
flux bootstrap git --url=ssh://<SSH-Key-ID>@git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --private-key-file=<path/to/private.key> --password=<SSH-passphrase> --path=clusters/my-cluster
# Run bootstrap for a Git repository on AWS CodeCommit using HTTPS (requires AWS IAM credentials)
flux bootstrap git --url=https://git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --path=clusters/my-cluster
# Run bootstrap for a Git repository on Azure Devops
flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --private-key-file=<path/to/rsa-sha2-private.key> --ssh-hostkey-algos=rsa-sha2-512,rsa-sha2-256 --path=clusters/my-cluster
@ -109,6 +116,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
bootstrapArgs.tokenAuth = true
}
var gitProvider string
gitPassword := os.Getenv(gitPasswordEnvVar)
if gitPassword != "" && gitArgs.password == "" {
gitArgs.password = gitPassword
@ -131,8 +139,12 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
if strings.Contains(repositoryURL.Hostname(), "git-codecommit") && strings.Contains(repositoryURL.Hostname(), "amazonaws.com") {
if repositoryURL.Scheme == string(git.SSH) {
// https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control.html
if repositoryURL.Scheme == string(git.SSH) { // IAM user + SSH
if repositoryURL.User == nil {
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be specified in the url")
}
@ -142,14 +154,18 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.privateKeyFile == "" {
return fmt.Errorf("private key file is required for bootstrapping against AWS CodeCommit using ssh")
}
} else if repositoryURL.Scheme == string(git.HTTPS) && !bootstrapArgs.tokenAuth { // IAM role + HTTPS
creds, err := authutils.GetGitCredentials(ctx, "aws", auth.WithGitURL(*repositoryURL))
if err != nil {
return fmt.Errorf("failed to get AWS CodeCommit IAM git credentials: %w", err)
}
gitArgs.username = creds.Username
gitArgs.password = creds.Password
bootstrapArgs.tokenAuth = true
gitProvider = aws.ProviderName
}
if repositoryURL.Scheme == string(git.HTTPS) && !bootstrapArgs.tokenAuth {
return fmt.Errorf("--token-auth=true must be specified for using an HTTPS AWS CodeCommit url")
}
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
}
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
@ -296,6 +312,10 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
TargetPath: gitArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
SparseCheckout: bootstrapArgs.sparseCheckout,
}
if gitProvider != "" {
syncOpts.Provider = gitProvider
}
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
@ -315,6 +335,33 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
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
b, err := bootstrap.NewPlainGitProvider(gitClient, kubeClient, bootstrapOpts...)
if err != nil {

View file

@ -107,6 +107,11 @@ func init() {
}
func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
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")
}
gtToken := os.Getenv(gtTokenEnvVar)
if gtToken == "" {
var err error
@ -232,6 +237,7 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
TargetPath: giteaArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
SparseCheckout: bootstrapArgs.sparseCheckout,
}
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
@ -252,6 +258,7 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
}
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
@ -265,6 +272,19 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
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
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil {

View file

@ -107,6 +107,11 @@ func init() {
}
func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
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")
}
ghToken := os.Getenv(ghTokenEnvVar)
if ghToken == "" {
var err error
@ -239,6 +244,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
TargetPath: githubArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
SparseCheckout: bootstrapArgs.sparseCheckout,
}
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
@ -259,6 +265,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
}
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
@ -272,6 +279,19 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
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
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil {

View file

@ -27,6 +27,7 @@ import (
"github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/fluxcd/pkg/git/signature"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags"
@ -287,6 +288,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
TargetPath: gitlabArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
SparseCheckout: bootstrapArgs.sparseCheckout,
}
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
@ -321,6 +323,31 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
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
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil {

208
cmd/flux/bootstrap_test.go Normal file
View file

@ -0,0 +1,208 @@
/*
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) {
savedDefaultComponents := bootstrapArgs.defaultComponents
savedGpgRing := bootstrapArgs.gpgKeyRingPath
savedGpgPass := bootstrapArgs.gpgPassphrase
savedSshKey := bootstrapArgs.sshSigningKeyFile
savedSshPass := bootstrapArgs.sshSigningPassword
savedSshPassp := bootstrapArgs.sshSigningPassphrase
savedPrivKey := bootstrapArgs.privateKeyFile
savedReuse := bootstrapArgs.sshSigningReusePrivateKey
defer func() {
bootstrapArgs.defaultComponents = savedDefaultComponents
bootstrapArgs.gpgKeyRingPath = savedGpgRing
bootstrapArgs.gpgPassphrase = savedGpgPass
bootstrapArgs.sshSigningKeyFile = savedSshKey
bootstrapArgs.sshSigningPassword = savedSshPass
bootstrapArgs.sshSigningPassphrase = savedSshPassp
bootstrapArgs.privateKeyFile = savedPrivKey
bootstrapArgs.sshSigningReusePrivateKey = savedReuse
}()
// The e2e TestMain calls resetCmdArgs which clears the
// cobra-populated default components, so seed them here to
// satisfy the requiredComponents pre-check in bootstrapValidate.
bootstrapArgs.defaultComponents = bootstrapArgs.requiredComponents
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)
}
})
}
}
// Providers that generate the SSH transport key in-process (github, gitea)
// must reject --ssh-signing-reuse-private-key with their own, provider-
// specific error before bootstrapValidate runs — otherwise the generic
// "--ssh-signing-reuse-private-key requires --private-key-file" error
// shadows the fact that the flag is fundamentally unsupported there.
func TestBootstrapProviderRejectsReuseBeforeValidate(t *testing.T) {
tests := []struct {
name string
runE func() error
wantErr string
}{
{
name: "github rejects reuse with provider-specific error",
runE: func() error { return bootstrapGitHubCmdRun(nil, nil) },
wantErr: "not supported by 'bootstrap github'",
},
{
name: "gitea rejects reuse with provider-specific error",
runE: func() error { return bootstrapGiteaCmdRun(nil, nil) },
wantErr: "not supported by 'bootstrap gitea'",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
savedReuse := bootstrapArgs.sshSigningReusePrivateKey
savedPrivKey := bootstrapArgs.privateKeyFile
defer func() {
bootstrapArgs.sshSigningReusePrivateKey = savedReuse
bootstrapArgs.privateKeyFile = savedPrivKey
}()
// Reuse flag set, no --private-key-file: bootstrapValidate
// would otherwise return "requires --private-key-file".
bootstrapArgs.sshSigningReusePrivateKey = true
bootstrapArgs.privateKeyFile = ""
err := tt.runE()
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)
}
})
}
}

View file

@ -22,6 +22,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
@ -48,9 +49,10 @@ from the given directory or a single manifest file.`,
}
type buildArtifactFlags struct {
output string
path string
ignorePaths []string
output string
path string
ignorePaths []string
resolveSymlinks bool
}
var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...)
@ -61,6 +63,7 @@ func init() {
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, "path", "p", "", "Path to the directory where the Kubernetes manifests are located.")
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.")
buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
buildArtifactCmd.Flags().BoolVar(&buildArtifactArgs.resolveSymlinks, "resolve-symlinks", false, "resolve symlinks by copying their targets into the artifact")
buildCmd.AddCommand(buildArtifactCmd)
}
@ -85,6 +88,15 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", path)
}
if buildArtifactArgs.resolveSymlinks {
resolved, cleanupDir, err := resolveSymlinks(path)
if err != nil {
return fmt.Errorf("resolving symlinks failed: %w", err)
}
defer os.RemoveAll(cleanupDir)
path = resolved
}
logger.Actionf("building artifact from %s", path)
ociClient := oci.NewClient(oci.DefaultOptions())
@ -96,6 +108,141 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
// resolveSymlinks creates a temporary directory with symlinks resolved to their
// real file contents. This allows building artifacts from symlink trees (e.g.,
// those created by Nix) where the actual files live outside the source directory.
// It returns the resolved path and the temporary directory path for cleanup.
func resolveSymlinks(srcPath string) (string, string, error) {
absPath, err := filepath.Abs(srcPath)
if err != nil {
return "", "", err
}
info, err := os.Stat(absPath)
if err != nil {
return "", "", err
}
// For a single file, resolve the symlink and return the path to the
// copied file within the temp dir, preserving file semantics for callers.
if !info.IsDir() {
resolved, err := filepath.EvalSymlinks(absPath)
if err != nil {
return "", "", fmt.Errorf("resolving symlink for %s: %w", absPath, err)
}
tmpDir, err := os.MkdirTemp("", "flux-artifact-*")
if err != nil {
return "", "", err
}
dst := filepath.Join(tmpDir, filepath.Base(absPath))
if err := copyFile(resolved, dst); err != nil {
os.RemoveAll(tmpDir)
return "", "", err
}
return dst, tmpDir, nil
}
tmpDir, err := os.MkdirTemp("", "flux-artifact-*")
if err != nil {
return "", "", err
}
visited := make(map[string]bool)
if err := copyDir(absPath, tmpDir, visited); err != nil {
os.RemoveAll(tmpDir)
return "", "", err
}
return tmpDir, tmpDir, nil
}
// copyDir recursively copies the contents of srcDir to dstDir, resolving any
// symlinks encountered along the way. The visited map tracks resolved real
// directory paths to detect and break symlink cycles.
func copyDir(srcDir, dstDir string, visited map[string]bool) error {
real, err := filepath.EvalSymlinks(srcDir)
if err != nil {
return fmt.Errorf("resolving symlink %s: %w", srcDir, err)
}
abs, err := filepath.Abs(real)
if err != nil {
return fmt.Errorf("getting absolute path for %s: %w", real, err)
}
if visited[abs] {
return nil // break the cycle
}
visited[abs] = true
defer delete(visited, abs)
entries, err := os.ReadDir(srcDir)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())
// Resolve symlinks to get the real path and info.
realPath, err := filepath.EvalSymlinks(srcPath)
if err != nil {
return fmt.Errorf("resolving symlink %s: %w", srcPath, err)
}
realInfo, err := os.Stat(realPath)
if err != nil {
return fmt.Errorf("stat resolved path %s: %w", realPath, err)
}
if realInfo.IsDir() {
if err := os.MkdirAll(dstPath, realInfo.Mode()); err != nil {
return err
}
// Recursively copy the resolved directory contents.
if err := copyDir(realPath, dstPath, visited); err != nil {
return err
}
continue
}
if !realInfo.Mode().IsRegular() {
continue
}
if err := copyFile(realPath, dstPath); err != nil {
return err
}
}
return nil
}
func copyFile(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}
out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode())
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
return out.Close()
}
func saveReaderToFile(reader io.Reader) (string, error) {
b, err := io.ReadAll(bufio.NewReader(reader))
if err != nil {

View file

@ -18,6 +18,7 @@ package main
import (
"os"
"path/filepath"
"strings"
"testing"
@ -68,3 +69,149 @@ data:
}
}
func Test_resolveSymlinks(t *testing.T) {
g := NewWithT(t)
// Create source directory with a real file
srcDir := t.TempDir()
realFile := filepath.Join(srcDir, "real.yaml")
g.Expect(os.WriteFile(realFile, []byte("apiVersion: v1\nkind: Namespace\nmetadata:\n name: test\n"), 0o644)).To(Succeed())
// Create a directory with symlinks pointing to files outside it
symlinkDir := t.TempDir()
symlinkFile := filepath.Join(symlinkDir, "linked.yaml")
g.Expect(os.Symlink(realFile, symlinkFile)).To(Succeed())
// Also add a regular file in the symlink dir
regularFile := filepath.Join(symlinkDir, "regular.yaml")
g.Expect(os.WriteFile(regularFile, []byte("apiVersion: v1\nkind: ConfigMap\n"), 0o644)).To(Succeed())
// Create a symlinked subdirectory
subDir := filepath.Join(srcDir, "subdir")
g.Expect(os.MkdirAll(subDir, 0o755)).To(Succeed())
g.Expect(os.WriteFile(filepath.Join(subDir, "nested.yaml"), []byte("nested"), 0o644)).To(Succeed())
g.Expect(os.Symlink(subDir, filepath.Join(symlinkDir, "linkeddir"))).To(Succeed())
// Resolve symlinks
resolved, cleanupDir, err := resolveSymlinks(symlinkDir)
g.Expect(err).To(BeNil())
t.Cleanup(func() { os.RemoveAll(cleanupDir) })
// Verify the regular file was copied
content, err := os.ReadFile(filepath.Join(resolved, "regular.yaml"))
g.Expect(err).To(BeNil())
g.Expect(string(content)).To(Equal("apiVersion: v1\nkind: ConfigMap\n"))
// Verify the symlinked file was resolved and copied
content, err = os.ReadFile(filepath.Join(resolved, "linked.yaml"))
g.Expect(err).To(BeNil())
g.Expect(string(content)).To(ContainSubstring("kind: Namespace"))
// Verify that the resolved file is a regular file, not a symlink
info, err := os.Lstat(filepath.Join(resolved, "linked.yaml"))
g.Expect(err).To(BeNil())
g.Expect(info.Mode().IsRegular()).To(BeTrue())
// Verify that the symlinked directory was resolved and its contents were copied
content, err = os.ReadFile(filepath.Join(resolved, "linkeddir", "nested.yaml"))
g.Expect(err).To(BeNil())
g.Expect(string(content)).To(Equal("nested"))
// Verify that the file inside the symlinked directory is a regular file
info, err = os.Lstat(filepath.Join(resolved, "linkeddir", "nested.yaml"))
g.Expect(err).To(BeNil())
g.Expect(info.Mode().IsRegular()).To(BeTrue())
}
func Test_resolveSymlinks_singleFile(t *testing.T) {
g := NewWithT(t)
// Create a real file
srcDir := t.TempDir()
realFile := filepath.Join(srcDir, "manifest.yaml")
g.Expect(os.WriteFile(realFile, []byte("kind: ConfigMap"), 0o644)).To(Succeed())
// Create a symlink to the real file
linkDir := t.TempDir()
linkFile := filepath.Join(linkDir, "link.yaml")
g.Expect(os.Symlink(realFile, linkFile)).To(Succeed())
// Resolve the single symlinked file
resolved, cleanupDir, err := resolveSymlinks(linkFile)
g.Expect(err).To(BeNil())
t.Cleanup(func() { os.RemoveAll(cleanupDir) })
// The returned path should be a file, not a directory
info, err := os.Stat(resolved)
g.Expect(err).To(BeNil())
g.Expect(info.IsDir()).To(BeFalse())
// Verify contents
content, err := os.ReadFile(resolved)
g.Expect(err).To(BeNil())
g.Expect(string(content)).To(Equal("kind: ConfigMap"))
}
func Test_resolveSymlinks_cycle(t *testing.T) {
g := NewWithT(t)
// Create a directory with a symlink cycle: dir/link -> dir
dir := t.TempDir()
g.Expect(os.WriteFile(filepath.Join(dir, "file.yaml"), []byte("data"), 0o644)).To(Succeed())
g.Expect(os.Symlink(dir, filepath.Join(dir, "cycle"))).To(Succeed())
// resolveSymlinks should not infinite-loop
resolved, cleanupDir, err := resolveSymlinks(dir)
g.Expect(err).To(BeNil())
t.Cleanup(func() { os.RemoveAll(cleanupDir) })
// The file should be copied
content, err := os.ReadFile(filepath.Join(resolved, "file.yaml"))
g.Expect(err).To(BeNil())
g.Expect(string(content)).To(Equal("data"))
// The cycle directory should exist but not cause infinite nesting
_, err = os.Stat(filepath.Join(resolved, "cycle"))
g.Expect(err).To(BeNil())
// There should NOT be deeply nested cycle/cycle/cycle/... paths
_, err = os.Stat(filepath.Join(resolved, "cycle", "cycle", "cycle"))
g.Expect(os.IsNotExist(err)).To(BeTrue())
}
func Test_resolveSymlinks_multipleLinksSameTarget(t *testing.T) {
g := NewWithT(t)
// Create source directory with a real file inside a dir
srcDir := t.TempDir()
targetDir := filepath.Join(srcDir, "target")
g.Expect(os.MkdirAll(targetDir, 0o755)).To(Succeed())
g.Expect(os.WriteFile(filepath.Join(targetDir, "file.yaml"), []byte("data"), 0o644)).To(Succeed())
// Create a directory with multiple symlinks pointing to targetDir
symlinkDir := t.TempDir()
// Link 1
link1 := filepath.Join(symlinkDir, "link1")
g.Expect(os.Symlink(targetDir, link1)).To(Succeed())
// Link 2
link2 := filepath.Join(symlinkDir, "link2")
g.Expect(os.Symlink(targetDir, link2)).To(Succeed())
// Resolve symlinks
resolved, cleanupDir, err := resolveSymlinks(symlinkDir)
g.Expect(err).To(BeNil())
t.Cleanup(func() { os.RemoveAll(cleanupDir) })
// Verify link1 has the file
content, err := os.ReadFile(filepath.Join(resolved, "link1", "file.yaml"))
g.Expect(err).To(BeNil())
g.Expect(string(content)).To(Equal("data"))
// Verify link2 ALSO has the file
content2, err := os.ReadFile(filepath.Join(resolved, "link2", "file.yaml"))
g.Expect(err).To(BeNil())
g.Expect(string(content2)).To(Equal("data"))
}

View file

@ -72,6 +72,7 @@ type buildKsFlags struct {
strictSubst bool
recursive bool
localSources map[string]string
inMemoryBuild bool
}
var buildKsArgs buildKsFlags
@ -85,6 +86,8 @@ func init() {
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.")
buildKsCmd.Flags().BoolVarP(&buildKsArgs.recursive, "recursive", "r", false, "Recursively build Kustomizations")
buildKsCmd.Flags().StringToStringVar(&buildKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path")
buildKsCmd.Flags().BoolVar(&buildKsArgs.inMemoryBuild, "in-memory-build", true,
"Use in-memory filesystem during build.")
buildCmd.AddCommand(buildKsCmd)
}
@ -130,6 +133,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources),
build.WithInMemoryBuild(buildKsArgs.inMemoryBuild),
)
} else {
builder, err = build.NewBuilder(name, buildKsArgs.path,
@ -140,6 +144,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources),
build.WithInMemoryBuild(buildKsArgs.inMemoryBuild),
)
}

View file

@ -52,6 +52,12 @@ func TestBuildKustomization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo (on-disk)",
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo --in-memory-build=false",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo without service",
args: "build kustomization podinfo --path ./testdata/build-kustomization/delete-service",
@ -70,12 +76,24 @@ func TestBuildKustomization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build ignore (on-disk)",
args: "build kustomization podinfo --path ./testdata/build-kustomization/ignore --ignore-paths \"!configmap.yaml,!secret.yaml\" --in-memory-build=false",
resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive",
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive (on-disk)",
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --in-memory-build=false",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
@ -145,6 +163,12 @@ spec:
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo (on-disk)",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo --in-memory-build=false",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo without service",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/delete-service",
@ -175,6 +199,18 @@ spec:
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive (on-disk)",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --in-memory-build=false",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive in dry-run mode (on-disk)",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --in-memory-build=false --dry-run",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
@ -241,6 +277,12 @@ func TestBuildKustomizationPathNormalization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with absolute path (on-disk)",
args: "build kustomization podinfo --path " + absTestDataPath + " --in-memory-build=false",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with complex relative path (parent dir)",
args: "build kustomization podinfo --path ./testdata/build-kustomization/../build-kustomization/podinfo",

View file

@ -218,7 +218,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
if len(helmReleaseArgs.dependsOn) > 0 {
ls := utils.MakeDependsOn(helmReleaseArgs.dependsOn)
ls := meta.MakeDependsOn(helmReleaseArgs.dependsOn)
hrDependsOn := make([]helmv2.DependencyReference, 0, len(ls))
for _, d := range ls {
hrDependsOn = append(hrDependsOn, helmv2.DependencyReference{

View file

@ -23,6 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
)
@ -75,6 +76,8 @@ type imageUpdateFlags struct {
commitTemplate string
authorName string
authorEmail string
signingKeySecret string
signingKeyType string
}
var imageUpdateArgs = imageUpdateFlags{}
@ -89,6 +92,8 @@ func init() {
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.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)
}
@ -112,6 +117,15 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
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()
if err != nil {
return err
@ -163,6 +177,13 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
}
}
if imageUpdateArgs.signingKeySecret != "" {
update.Spec.GitSpec.Commit.SigningKey = &autov1.SigningKey{
SecretRef: meta.LocalObjectReference{Name: imageUpdateArgs.signingKeySecret},
Type: autov1.SigningKeyType(imageUpdateArgs.signingKeyType),
}
}
if createArgs.export {
return printExport(exportImageUpdate(&update))
}

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

View file

@ -136,6 +136,9 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
if !strings.HasPrefix(kustomizationArgs.path.String(), "./") {
return fmt.Errorf("path must begin with ./")
}
if kustomizationArgs.source.Name == "" {
return fmt.Errorf("source is required")
}
if !createArgs.export {
logger.Generatef("generating Kustomization")
@ -169,7 +172,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}
if len(kustomizationArgs.dependsOn) > 0 {
ls := utils.MakeDependsOn(kustomizationArgs.dependsOn)
ls := meta.MakeDependsOn(kustomizationArgs.dependsOn)
ksDependsOn := make([]kustomizev1.DependencyReference, 0, len(ls))
for _, d := range ls {
ksDependsOn = append(ksDependsOn, kustomizev1.DependencyReference{

View file

@ -0,0 +1,48 @@
//go:build unit
// +build unit
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import "testing"
func TestCreateKustomization(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
// A user creating a kustomization without --source gets a confusing
// API-level error about spec.sourceRef.kind instead of a clear message.
name: "missing source",
args: "create kustomization my-app --path=./deploy --export",
assert: assertError("source is required"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

View file

@ -30,6 +30,7 @@ import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
)
@ -49,7 +50,7 @@ var createReceiverCmd = &cobra.Command{
}
type receiverFlags struct {
receiverType string
receiverType flags.ReceiverType
secretRef string
events []string
resources []string
@ -58,7 +59,7 @@ type receiverFlags struct {
var receiverArgs receiverFlags
func init() {
createReceiverCmd.Flags().StringVar(&receiverArgs.receiverType, "type", "", "")
createReceiverCmd.Flags().Var(&receiverArgs.receiverType, "type", receiverArgs.receiverType.Description())
createReceiverCmd.Flags().StringVar(&receiverArgs.secretRef, "secret-ref", "", "")
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, "event", []string{}, "also accepts comma-separated values")
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.resources, "resource", []string{}, "also accepts comma-separated values")
@ -76,16 +77,18 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("secret ref is required")
}
resources := []notificationv1.CrossNamespaceObjectReference{}
resources := []notificationv1.ReceiverResource{}
for _, resource := range receiverArgs.resources {
kind, name := utils.ParseObjectKindName(resource)
if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", resource)
}
resources = append(resources, notificationv1.CrossNamespaceObjectReference{
Kind: kind,
Name: name,
resources = append(resources, notificationv1.ReceiverResource{
CrossNamespaceObjectReference: notificationv1.CrossNamespaceObjectReference{
Kind: kind,
Name: name,
},
})
}
@ -109,10 +112,10 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
Labels: sourceLabels,
},
Spec: notificationv1.ReceiverSpec{
Type: receiverArgs.receiverType,
Type: receiverArgs.receiverType.String(),
Events: receiverArgs.events,
Resources: resources,
SecretRef: meta.LocalObjectReference{
SecretRef: &meta.LocalObjectReference{
Name: receiverArgs.secretRef,
},
Suspend: false,

View file

@ -56,6 +56,22 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
}
existing.StringData = secret.StringData
if secret.Annotations != nil {
if existing.Annotations == nil {
existing.Annotations = make(map[string]string)
}
for k, v := range secret.Annotations {
existing.Annotations[k] = v
}
}
if secret.Labels != nil {
if existing.Labels == nil {
existing.Labels = make(map[string]string)
}
for k, v := range secret.Labels {
existing.Labels[k] = v
}
}
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
}

View file

@ -0,0 +1,134 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
)
var createSecretReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Create or update a Kubernetes secret for a Receiver webhook",
Long: `The create secret receiver command generates a Kubernetes secret with
the token used for webhook payload validation and an annotation with the
computed webhook URL.`,
Example: ` # Create a receiver secret for a GitHub webhook
flux create secret receiver github-receiver \
--namespace=my-namespace \
--type=github \
--hostname=flux.example.com \
--export
# Create a receiver secret for GCR with email claim
flux create secret receiver gcr-receiver \
--namespace=my-namespace \
--type=gcr \
--hostname=flux.example.com \
--email-claim=sa@project.iam.gserviceaccount.com \
--export`,
RunE: createSecretReceiverCmdRun,
}
type secretReceiverFlags struct {
receiverType flags.ReceiverType
token string
hostname string
emailClaim string
audienceClaim string
}
var secretReceiverArgs secretReceiverFlags
func init() {
createSecretReceiverCmd.Flags().Var(&secretReceiverArgs.receiverType, "type", secretReceiverArgs.receiverType.Description())
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.token, "token", "", "webhook token used for payload validation and URL computation, auto-generated if not specified")
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.hostname, "hostname", "", "hostname for the webhook URL e.g. flux.example.com")
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.emailClaim, "email-claim", "", "IAM service account email, required for gcr type")
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.audienceClaim, "audience-claim", "", "custom OIDC token audience for gcr type, defaults to the webhook URL")
createSecretCmd.AddCommand(createSecretReceiverCmd)
}
func createSecretReceiverCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if secretReceiverArgs.receiverType == "" {
return fmt.Errorf("--type is required")
}
if secretReceiverArgs.hostname == "" {
return fmt.Errorf("--hostname is required")
}
if secretReceiverArgs.receiverType.String() == notificationv1.GCRReceiver && secretReceiverArgs.emailClaim == "" {
return fmt.Errorf("--email-claim is required for gcr receiver type")
}
labels, err := parseLabels()
if err != nil {
return err
}
opts := sourcesecret.Options{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Labels: labels,
ReceiverType: secretReceiverArgs.receiverType.String(),
Token: secretReceiverArgs.token,
Hostname: secretReceiverArgs.hostname,
EmailClaim: secretReceiverArgs.emailClaim,
AudienceClaim: secretReceiverArgs.audienceClaim,
}
secret, err := sourcesecret.GenerateReceiver(opts)
if err != nil {
return err
}
if createArgs.export {
rootCmd.Println(secret.Content)
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
logger.Actionf("receiver secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
return nil
}

View file

@ -0,0 +1,74 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestCreateReceiverSecret(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "missing type",
args: "create secret receiver test-secret --token=t --hostname=h",
assert: assertError("--type is required"),
},
{
name: "invalid type",
args: "create secret receiver test-secret --type=invalid --token=t --hostname=h",
assert: assertError("invalid argument \"invalid\" for \"--type\" flag: receiver type 'invalid' is not supported, must be one of: generic, generic-hmac, github, gitlab, bitbucket, harbor, dockerhub, quay, gcr, nexus, acr, cdevents"),
},
{
name: "missing hostname",
args: "create secret receiver test-secret --type=github --token=t",
assert: assertError("--hostname is required"),
},
{
name: "gcr missing email-claim",
args: "create secret receiver test-secret --type=gcr --token=t --hostname=h",
assert: assertError("--email-claim is required for gcr receiver type"),
},
{
name: "github receiver secret",
args: "create secret receiver receiver-secret --type=github --token=test-token --hostname=flux.example.com --namespace=my-namespace --export",
assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver.yaml"),
},
{
name: "gcr receiver secret",
args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --namespace=my-namespace --export",
assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr.yaml"),
},
{
name: "gcr receiver secret with custom audience",
args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --audience-claim=https://custom.audience.example.com --namespace=my-namespace --export",
assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

View file

@ -130,7 +130,7 @@ func createSourceChartCmdRun(cmd *cobra.Command, args []string) error {
}
if provider := sourceChartArgs.verifyProvider.String(); provider != "" {
helmChart.Spec.Verify = &sourcev1.OCIRepositoryVerification{
helmChart.Spec.Verify = &sourcev1.HelmChartVerification{
Provider: provider,
}
if secretName := sourceChartArgs.verifySecretRef; secretName != "" {

View file

@ -124,6 +124,12 @@ For private Git repositories, the basic authentication credentials are stored in
--username=username \
--password=password
# Create a source for a Git repository using AWS CodeCommit with IAM credentials
flux create source git podinfo \
--url=https://git-codecommit.<region>.amazonaws.com/v1/repos/podinfo \
--branch=master \
--provider=aws
# Create a source for a Git repository using azure provider
flux create source git podinfo \
--url=https://dev.azure.com/foo/bar/_git/podinfo \

View file

@ -152,7 +152,7 @@ func TestCreateSourceGitExport(t *testing.T) {
{
name: "source with invalid provider",
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider dummy --branch=test --interval=1m0s --export",
assert: assertError("invalid argument \"dummy\" for \"--provider\" flag: source Git provider 'dummy' is not supported, must be one of: generic|azure|github"),
assert: assertError("invalid argument \"dummy\" for \"--provider\" flag: source Git provider 'dummy' is not supported, must be one of: generic|github|aws|azure"),
},
{
name: "source with empty provider",

View file

@ -114,9 +114,16 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
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)
}
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{
ObjectMeta: metav1.ObjectMeta{
@ -132,11 +139,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
},
}
url, err := url.Parse(sourceHelmArgs.url)
if err != nil {
return fmt.Errorf("failed to parse URL: %w", err)
}
if url.Scheme == sourcev1.HelmRepositoryTypeOCI {
if helmURL.Scheme == sourcev1.HelmRepositoryTypeOCI {
helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI
helmRepository.Spec.Provider = sourceHelmArgs.ociProvider
}

View file

@ -36,6 +36,12 @@ func TestCreateSourceHelm(t *testing.T) {
resultFile: "name is required",
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",
args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export",

View file

@ -62,6 +62,8 @@ type diffKsFlags struct {
strictSubst bool
recursive bool
localSources map[string]string
inMemoryBuild bool
ignoreNotFound bool
}
var diffKsArgs diffKsFlags
@ -75,6 +77,10 @@ func init() {
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.")
diffKsCmd.Flags().BoolVarP(&diffKsArgs.recursive, "recursive", "r", false, "Recursively diff Kustomizations")
diffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path")
diffKsCmd.Flags().BoolVar(&diffKsArgs.inMemoryBuild, "in-memory-build", true,
"Use in-memory filesystem during build.")
diffKsCmd.Flags().BoolVar(&diffKsArgs.ignoreNotFound, "ignore-not-found", false,
"Ignore Kustomization not found errors on the cluster when diffing.")
diffCmd.AddCommand(diffKsCmd)
}
@ -113,6 +119,8 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
build.WithInMemoryBuild(diffKsArgs.inMemoryBuild),
build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound),
)
} else {
builder, err = build.NewBuilder(name, diffKsArgs.path,
@ -124,6 +132,8 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
build.WithInMemoryBuild(diffKsArgs.inMemoryBuild),
build.WithIgnoreNotFound(diffKsArgs.ignoreNotFound),
)
}

View file

@ -48,7 +48,7 @@ func TestDiffKustomization(t *testing.T) {
name: "diff nothing deployed",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
objectFile: "",
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"),
},
{
name: "diff with a deployment object",
@ -96,7 +96,7 @@ func TestDiffKustomization(t *testing.T) {
name: "diff where kustomization file has multiple objects with the same name",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml",
objectFile: "",
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"),
},
{
name: "diff with recursive",
@ -138,6 +138,118 @@ func TestDiffKustomization(t *testing.T) {
}
}
// TestDiffKustomizationNotDeployed tests `flux diff ks` when the Kustomization
// CR does not exist in the cluster but is provided via --kustomization-file.
// Reproduces https://github.com/fluxcd/flux2/issues/5439
func TestDiffKustomizationNotDeployed(t *testing.T) {
// Use a dedicated namespace with NO setup() -- the Kustomization CR
// intentionally does not exist in the cluster.
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setupTestNamespace(tmpl["fluxns"], t)
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "fails without --ignore-not-found",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false " +
"--kustomization-file ./testdata/diff-kustomization/flux-kustomization-local-only.yaml",
assert: assertError("failed to get kustomization object: kustomizations.kustomize.toolkit.fluxcd.io \"podinfo\" not found"),
},
{
name: "succeeds with --ignore-not-found and --kustomization-file",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false " +
"--kustomization-file ./testdata/diff-kustomization/flux-kustomization-local-only.yaml " +
"--ignore-not-found",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-kustomization.golden"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"],
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}
// TestDiffKustomizationTakeOwnership tests `flux diff ks` when taking ownership
// of existing resources on the cluster. A "pre-existing" configmap is applied
// to the cluster, and the kustomization contains a matching configmap; the
// diff should show the labels added by flux
func TestDiffKustomizationTakeOwnership(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setupTestNamespace(tmpl["fluxns"], t)
b, _ := build.NewBuilder("configmaps", "", build.WithClientConfig(kubeconfigArgs, kubeclientOptions))
resourceManager, err := b.Manager()
if err != nil {
t.Fatal(err)
}
// Pre-create the "existing" configmap in the cluster without Flux labels
if _, err := resourceManager.ApplyAll(context.Background(), createObjectFromFile("./testdata/diff-kustomization/existing-configmap.yaml", tmpl, t), ssa.DefaultApplyOptions()); err != nil {
t.Fatal(err)
}
cmd := cmdTestCase{
args: "diff kustomization configmaps --path ./testdata/build-kustomization/configmaps --progress-bar=false " +
"--kustomization-file ./testdata/diff-kustomization/flux-kustomization-configmaps.yaml " +
"--ignore-not-found" +
" -n " + tmpl["fluxns"],
assert: assertGoldenFile("./testdata/diff-kustomization/diff-taking-ownership.golden"),
}
cmd.runTestCmd(t)
}
// TestDiffKustomizationNewNamespaceAndConfigmap runs `flux diff ks` when the
// kustomization creates a new namespace and resources inside it. The server-side
// dry-run cannot resolve resources in a namespace that doesn't exist yet,
// consistent with `kubectl diff --server-side` behavior.
func TestDiffKustomizationNewNamespaceAndConfigmap(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setupTestNamespace(tmpl["fluxns"], t)
cmd := cmdTestCase{
args: "diff kustomization new-namespace-and-configmap --path ./testdata/build-kustomization/new-namespace-and-configmap --progress-bar=false " +
"--kustomization-file ./testdata/diff-kustomization/flux-kustomization-new-namespace-and-configmap.yaml " +
"--ignore-not-found" +
" -n " + tmpl["fluxns"],
assert: assertError("ConfigMap/new-ns/app-config not found: namespaces \"new-ns\" not found"),
}
cmd.runTestCmd(t)
}
// TestDiffKustomizationNewNamespaceOnly runs `flux diff ks` when the
// kustomization creates only a new namespace. The diff should show the
// namespace as created.
func TestDiffKustomizationNewNamespaceOnly(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setupTestNamespace(tmpl["fluxns"], t)
cmd := cmdTestCase{
args: "diff kustomization new-namespace-only --path ./testdata/build-kustomization/new-namespace-only --progress-bar=false " +
"--kustomization-file ./testdata/diff-kustomization/flux-kustomization-new-namespace-only.yaml " +
"--ignore-not-found" +
" -n " + tmpl["fluxns"],
assert: assertGoldenFile("./testdata/diff-kustomization/diff-new-namespace-only.golden"),
}
cmd.runTestCmd(t)
}
func createObjectFromFile(objectFile string, templateValues map[string]string, t *testing.T) []*unstructured.Unstructured {
buf, err := os.ReadFile(objectFile)
if err != nil {
@ -158,3 +270,36 @@ func createObjectFromFile(objectFile string, templateValues map[string]string, t
return clientObjects
}
// TestDiffKustomizationDriftIgnoreRules tests `flux diff ks` with drift ignore
// rules. A service with a drifted port is pre-applied to the cluster, and the
// kustomization specifies driftIgnoreRules that ignore /spec/ports on Services.
// The diff should not show the service as drifted.
func TestDiffKustomizationDriftIgnoreRules(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setupTestNamespace(tmpl["fluxns"], t)
b, _ := build.NewBuilder("podinfo", "", build.WithClientConfig(kubeconfigArgs, kubeclientOptions))
resourceManager, err := b.Manager()
if err != nil {
t.Fatal(err)
}
// Pre-apply the drifted service (port 9899 instead of 9898) without Flux labels.
if _, err := resourceManager.ApplyAll(context.Background(), createObjectFromFile("./testdata/diff-kustomization/drifted-service-no-labels.yaml", tmpl, t), ssa.DefaultApplyOptions()); err != nil {
t.Fatal(err)
}
cmd := cmdTestCase{
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false " +
"--kustomization-file ./testdata/diff-kustomization/flux-kustomization-drift-ignore.yaml " +
"--ignore-not-found" +
" -n " + tmpl["fluxns"],
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drift-ignore.golden"),
}
cmd.runTestCmd(t)
testEnv.DeleteObjectFile("./testdata/diff-kustomization/drifted-service-no-labels.yaml", tmpl, t)
}

View file

@ -78,7 +78,7 @@ func init() {
getCmd.PersistentFlags().BoolVarP(&getArgs.noHeader, "no-header", "", false, "skip the header when printing the results")
getCmd.PersistentFlags().BoolVarP(&getArgs.watch, "watch", "w", false, "After listing/getting the requested object, watch for changes.")
getCmd.PersistentFlags().StringVar(&getArgs.statusSelector, "status-selector", "",
"specify the status condition name and the desired state to filter the get result, e.g. ready=false")
"specify the status condition name and the desired state to filter the get result, e.g. ready=false or ready!=true")
getCmd.PersistentFlags().StringVarP(&getArgs.labelSelector, "label-selector", "l", "",
"filter objects by label selector")
rootCmd.AddCommand(getCmd)
@ -114,6 +114,11 @@ func statusMatches(conditionType, conditionStatus string, conditions []metav1.Co
return false
}
func readyStatusMatches(conditionType, conditionStatus string) bool {
return strings.EqualFold(conditionType, meta.ReadyCondition) &&
strings.EqualFold(conditionStatus, string(metav1.ConditionTrue))
}
func nameColumns(item named, includeNamespace bool, includeKind bool) []string {
name := item.GetName()
if includeKind {
@ -207,6 +212,9 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if getAll && len(rows) == 0 {
return nil
}
err = printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)
if err != nil {
@ -228,20 +236,31 @@ func namespaceNameOrAny(allNamespaces bool, namespaceName string) string {
}
func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {
noFilter := true
filter := func(i int) bool { return true }
var conditionType, conditionStatus string
if getArgs.statusSelector != "" {
parts := strings.SplitN(getArgs.statusSelector, "=", 2)
// Support both type=status (match) and type!=status (negated match).
// "!=" must be checked first since it also contains "=".
separator := "="
filter = func(i int) bool {
return list.statusSelectorMatches(i, conditionType, conditionStatus)
}
if strings.Contains(getArgs.statusSelector, "!=") {
separator = "!="
filter = func(i int) bool {
return !list.statusSelectorMatches(i, conditionType, conditionStatus)
}
}
parts := strings.SplitN(getArgs.statusSelector, separator, 2)
if len(parts) != 2 {
return nil, fmt.Errorf("expected status selector in type=status format, but found: %s", getArgs.statusSelector)
return nil, fmt.Errorf("expected status selector in type=status or type!=status format, but found: %s", getArgs.statusSelector)
}
conditionType = parts[0]
conditionStatus = parts[1]
noFilter = false
}
var rows [][]string
for i := 0; i < list.len(); i++ {
if noFilter || list.statusSelectorMatches(i, conditionType, conditionStatus) {
if filter(i) {
row := list.summariseItem(i, getArgs.allNamespaces, getAll)
rows = append(rows, row)
}

View file

@ -92,5 +92,5 @@ func (s alertListAdapter) headers(includeNamespace bool) []string {
}
func (s alertListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
return false
return readyStatusMatches(conditionType, conditionStatus)
}

View file

@ -88,5 +88,5 @@ func (s alertProviderListAdapter) headers(includeNamespace bool) []string {
}
func (s alertProviderListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
return false
return readyStatusMatches(conditionType, conditionStatus)
}

View file

@ -38,7 +38,7 @@ var getArtifactGeneratorCmd = &cobra.Command{
ValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
apiType: receiverType,
apiType: artifactGeneratorType,
list: artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},
funcMap: make(typeMap),
}

View file

@ -28,13 +28,22 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2"
)
type getHelmReleaseFlags struct {
showSource bool
}
var getHrArgs getHelmReleaseFlags
var getHelmReleaseCmd = &cobra.Command{
Use: "helmreleases",
Aliases: []string{"hr", "helmrelease"},
Short: "Get HelmRelease statuses",
Long: "The get helmreleases command prints the statuses of the resources.",
Example: ` # List all Helm releases and their status
flux get helmreleases`,
flux get helmreleases
# List all Helm releases with source information
flux get helmreleases --show-source`,
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
@ -69,6 +78,7 @@ var getHelmReleaseCmd = &cobra.Command{
}
func init() {
getHelmReleaseCmd.Flags().BoolVar(&getHrArgs.showSource, "show-source", false, "show the source reference for each helmrelease")
getCmd.AddCommand(getHelmReleaseCmd)
}
@ -79,16 +89,45 @@ func getHelmReleaseRevision(helmRelease helmv2.HelmRelease) string {
return helmRelease.Status.LastAttemptedRevision
}
func getHelmReleaseSource(item helmv2.HelmRelease) string {
if item.Spec.ChartRef != nil {
ns := item.Spec.ChartRef.Namespace
if ns == "" {
ns = item.GetNamespace()
}
return fmt.Sprintf("%s/%s/%s",
item.Spec.ChartRef.Kind,
ns,
item.Spec.ChartRef.Name)
}
ns := item.Spec.Chart.Spec.SourceRef.Namespace
if ns == "" {
ns = item.GetNamespace()
}
return fmt.Sprintf("%s/%s/%s",
item.Spec.Chart.Spec.SourceRef.Kind,
ns,
item.Spec.Chart.Spec.SourceRef.Name)
}
func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := a.Items[i]
revision := getHelmReleaseRevision(item)
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
row := nameColumns(&item, includeNamespace, includeKind)
if getHrArgs.showSource {
row = append(row, getHelmReleaseSource(item))
}
return append(row,
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name"}
if getHrArgs.showSource {
headers = append(headers, "Source")
}
headers = append(headers, "Revision", "Suspended", "Ready", "Message")
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View file

@ -30,13 +30,22 @@ import (
"github.com/fluxcd/flux2/v2/internal/utils"
)
type getKustomizationFlags struct {
showSource bool
}
var getKsArgs getKustomizationFlags
var getKsCmd = &cobra.Command{
Use: "kustomizations",
Aliases: []string{"ks", "kustomization"},
Short: "Get Kustomization statuses",
Long: `The get kustomizations command prints the statuses of the resources.`,
Example: ` # List all kustomizations and their status
flux get kustomizations`,
flux get kustomizations
# List all kustomizations with source information
flux get kustomizations --show-source`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
@ -74,6 +83,7 @@ var getKsCmd = &cobra.Command{
}
func init() {
getKsCmd.Flags().BoolVar(&getKsArgs.showSource, "show-source", false, "show the source reference for each kustomization")
getCmd.AddCommand(getKsCmd)
}
@ -83,12 +93,27 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in
status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
row := nameColumns(&item, includeNamespace, includeKind)
if getKsArgs.showSource {
sourceNs := item.Spec.SourceRef.Namespace
if sourceNs == "" {
sourceNs = item.GetNamespace()
}
row = append(row, fmt.Sprintf("%s/%s/%s",
item.Spec.SourceRef.Kind,
sourceNs,
item.Spec.SourceRef.Name))
}
return append(row,
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name"}
if getKsArgs.showSource {
headers = append(headers, "Source")
}
headers = append(headers, "Revision", "Suspended", "Ready", "Message")
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View file

@ -63,6 +63,141 @@ func Test_GetCmd(t *testing.T) {
}
}
func Test_GetCmdStatusSelector(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/status_objects.yaml", tmpl, t)
tests := []struct {
name string
args string
expected string
}{
{
name: "equal status selector matches one",
args: "--status-selector Ready=True",
expected: "testdata/get/get_status_ready_true.golden",
},
{
name: "equal status selector matches false",
args: "--status-selector Ready=False",
expected: "testdata/get/get_status_ready_false.golden",
},
{
name: "not-equal status selector matches all not-true",
args: "--status-selector Ready!=True",
expected: "testdata/get/get_status_ready_not_true.golden",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: "get sources git " + tt.args + " -n " + tmpl["fluxns"],
assert: assertGoldenTemplateFile(tt.expected, nil),
}
cmd.runTestCmd(t)
})
}
}
func Test_GetCmdStatusSelectorSyntheticReady(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/notification_objects.yaml", tmpl, t)
commands := []string{
"get alerts",
"get alert-providers",
"get all",
}
for _, command := range commands {
t.Run(command, func(t *testing.T) {
unfilteredOutput, err := executeCommand(command + " -n " + tmpl["fluxns"])
if err != nil {
t.Fatalf("%s failed: %v", command, err)
}
if unfilteredOutput == "" {
t.Fatalf("expected %s output for namespace with notification objects", command)
}
filteredOutput, err := executeCommand(command + " --status-selector Ready=True -n " + tmpl["fluxns"])
if err != nil {
t.Fatalf("%s with Ready=True status selector failed: %v", command, err)
}
if filteredOutput != unfilteredOutput {
t.Fatalf("expected Ready=True filtered output to match unfiltered output:\nfiltered:\n%s\nunfiltered:\n%s", filteredOutput, unfilteredOutput)
}
})
}
}
func Test_GetAllCmdStatusSelectorNoMatches(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/status_objects.yaml", tmpl, t)
cmd := cmdTestCase{
args: "get all --status-selector foo=bar -n " + tmpl["fluxns"],
assert: assertGoldenValue(""),
}
cmd.runTestCmd(t)
}
func Test_GetAllCmdStatusSelectorKustomizationOnlyMatches(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/kustomization_only.yaml", tmpl, t)
unfilteredOutput, err := executeCommand("get all -n " + tmpl["fluxns"])
if err != nil {
t.Fatalf("get all failed: %v", err)
}
if unfilteredOutput == "" {
t.Fatal("expected get all output for namespace with one Kustomization")
}
filteredOutput, err := executeCommand("get all --status-selector Ready=True -n " + tmpl["fluxns"])
if err != nil {
t.Fatalf("get all with matching status selector failed: %v", err)
}
if filteredOutput != unfilteredOutput {
t.Fatalf("expected filtered output to match unfiltered output:\nfiltered:\n%s\nunfiltered:\n%s", filteredOutput, unfilteredOutput)
}
}
func Test_GetAllCmdStatusSelectorKustomizationOnlyNoMatch(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/kustomization_only.yaml", tmpl, t)
emptyNamespace := allocateNamespace("empty")
setupTestNamespace(emptyNamespace, t)
emptyOutput, err := executeCommand("get all -n " + emptyNamespace)
if err != nil {
t.Fatalf("get all in empty namespace failed: %v", err)
}
filteredOutput, err := executeCommand("get all --status-selector Ready=False -n " + tmpl["fluxns"])
if err != nil {
t.Fatalf("get all with non-matching status selector failed: %v", err)
}
if filteredOutput != emptyOutput {
t.Fatalf("expected filtered output to match empty namespace output:\nfiltered:\n%s\nempty namespace:\n%s", filteredOutput, emptyOutput)
}
}
func Test_GetCmdErrors(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
@ -84,6 +219,16 @@ func Test_GetCmdErrors(t *testing.T) {
args: "get helmrelease -n " + tmpl["fluxns"],
assert: assertError(fmt.Sprintf("no HelmRelease objects found in \"%s\" namespace", tmpl["fluxns"])),
},
{
name: "no artifact generators found in namespace",
args: "get artifact generators -n " + tmpl["fluxns"],
assert: assertError(fmt.Sprintf("no ArtifactGenerator objects found in \"%s\" namespace", tmpl["fluxns"])),
},
{
name: "malformed status selector",
args: "get sources git --status-selector Ready -n " + tmpl["fluxns"],
assert: assertError("expected status selector in type=status or type!=status format, but found: Ready"),
},
}
for _, tt := range tests {

View file

@ -100,6 +100,16 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
# Uninstall Flux and delete CRDs
flux uninstall`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// If opted in via --ns-follows-kube-context flag or
// FLUX_NS_FOLLOWS_KUBE_CONTEXT env var, and --namespace was not
// explicitly set, respect the namespace from the kubeconfig context.
if !cmd.Flags().Changed("namespace") &&
(rootArgs.nsFollowsKubeContext || os.Getenv("FLUX_NS_FOLLOWS_KUBE_CONTEXT") != "") {
if ctxNs := getKubeconfigContextNamespace(kubeconfigArgs); ctxNs != "" {
*kubeconfigArgs.Namespace = ctxNs
}
}
ns, err := cmd.Flags().GetString("namespace")
if err != nil {
return fmt.Errorf("error getting namespace: %w", err)
@ -116,10 +126,11 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
var logger = stderrLogger{stderr: os.Stderr}
type rootFlags struct {
timeout time.Duration
verbose bool
pollInterval time.Duration
defaults install.Options
timeout time.Duration
verbose bool
pollInterval time.Duration
nsFollowsKubeContext bool
defaults install.Options
}
// RequestError is a custom error type that wraps an error returned by the flux api.
@ -139,6 +150,8 @@ var kubeclientOptions = new(runclient.Options)
func init() {
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
rootCmd.PersistentFlags().BoolVar(&rootArgs.nsFollowsKubeContext, "ns-follows-kube-context", false,
"use the namespace from the kubeconfig context instead of the default flux-system namespace, can also be set via FLUX_NS_FOLLOWS_KUBE_CONTEXT env var")
configureDefaultNamespace()
kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag
@ -186,6 +199,8 @@ func main() {
// logger, we configure it's logger to do nothing.
ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{}))
registerPlugins()
if err := rootCmd.Execute(); err != nil {
if err, ok := err.(*RequestError); ok {
@ -203,6 +218,26 @@ func main() {
}
}
// getKubeconfigContextNamespace returns the namespace from the current
// kubeconfig context, or an empty string if it cannot be determined.
func getKubeconfigContextNamespace(cf *genericclioptions.ConfigFlags) string {
rawConfig, err := cf.ToRawKubeConfigLoader().RawConfig()
if err != nil {
return ""
}
currentContext := rawConfig.CurrentContext
if cf.Context != nil && *cf.Context != "" {
currentContext = *cf.Context
}
if ctx, ok := rawConfig.Contexts[currentContext]; ok {
return ctx.Namespace
}
return ""
}
func configureDefaultNamespace() {
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE")

View file

@ -0,0 +1,221 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"os"
"path/filepath"
"testing"
. "github.com/onsi/gomega"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func TestGetKubeconfigContextNamespace(t *testing.T) {
tests := []struct {
name string
kubeconfig string
context string
expectedResult string
}{
{
name: "returns namespace from current context",
kubeconfig: `apiVersion: v1
kind: Config
current-context: my-context
contexts:
- name: my-context
context:
cluster: my-cluster
namespace: custom-ns
clusters:
- name: my-cluster
cluster:
server: https://localhost:6443
`,
expectedResult: "custom-ns",
},
{
name: "returns empty when context has no namespace",
kubeconfig: `apiVersion: v1
kind: Config
current-context: my-context
contexts:
- name: my-context
context:
cluster: my-cluster
clusters:
- name: my-cluster
cluster:
server: https://localhost:6443
`,
expectedResult: "",
},
{
name: "returns namespace from context specified via --context flag",
kubeconfig: `apiVersion: v1
kind: Config
current-context: default-context
contexts:
- name: default-context
context:
cluster: my-cluster
namespace: default-ns
- name: other-context
context:
cluster: my-cluster
namespace: other-ns
clusters:
- name: my-cluster
cluster:
server: https://localhost:6443
`,
context: "other-context",
expectedResult: "other-ns",
},
{
name: "returns empty when context does not exist",
kubeconfig: `apiVersion: v1
kind: Config
current-context: non-existent
contexts: []
clusters: []
`,
expectedResult: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
// Write temporary kubeconfig.
tmpDir := t.TempDir()
kcPath := filepath.Join(tmpDir, "kubeconfig")
g.Expect(os.WriteFile(kcPath, []byte(tt.kubeconfig), 0o600)).To(Succeed())
// Use a local ConfigFlags instance to avoid polluting the
// package-global kubeconfigArgs (which caches a clientConfig
// internally and would leak state across tests).
cf := genericclioptions.NewConfigFlags(false)
cf.KubeConfig = &kcPath
cf.Context = &tt.context
got := getKubeconfigContextNamespace(cf)
g.Expect(got).To(Equal(tt.expectedResult))
})
}
}
func TestContextNamespaceOptIn(t *testing.T) {
kubeconfig := `apiVersion: v1
kind: Config
current-context: my-context
contexts:
- name: my-context
context:
cluster: my-cluster
namespace: context-ns
clusters:
- name: my-cluster
cluster:
server: https://localhost:6443
`
tests := []struct {
name string
nsFollowsFlag bool
nsFollowsEnv string
envNamespace string
flagNamespace string
expectedNamespace string
}{
{
name: "ignores context namespace when not opted in",
expectedNamespace: rootArgs.defaults.Namespace,
},
{
name: "uses context namespace when opted in via flag",
nsFollowsFlag: true,
expectedNamespace: "context-ns",
},
{
name: "uses context namespace when opted in via env var",
nsFollowsEnv: "1",
expectedNamespace: "context-ns",
},
{
name: "context namespace takes precedence over FLUX_SYSTEM_NAMESPACE when opted in",
nsFollowsFlag: true,
envNamespace: "env-ns",
expectedNamespace: "context-ns",
},
{
name: "FLUX_SYSTEM_NAMESPACE used when not opted in",
envNamespace: "env-ns",
expectedNamespace: "env-ns",
},
{
name: "--namespace flag takes precedence over context namespace",
nsFollowsFlag: true,
flagNamespace: "flag-ns",
expectedNamespace: "flag-ns",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
// Write temporary kubeconfig.
tmpDir := t.TempDir()
kcPath := filepath.Join(tmpDir, "kubeconfig")
g.Expect(os.WriteFile(kcPath, []byte(kubeconfig), 0o600)).To(Succeed())
// Use a local ConfigFlags instance to avoid polluting the
// package-global kubeconfigArgs.
cf := genericclioptions.NewConfigFlags(false)
cf.KubeConfig = &kcPath
emptyCtx := ""
cf.Context = &emptyCtx
// Mirror configureDefaultNamespace behavior on the local instance.
defaultNs := rootArgs.defaults.Namespace
cf.Namespace = &defaultNs
if tt.envNamespace != "" {
t.Setenv("FLUX_SYSTEM_NAMESPACE", tt.envNamespace)
envNs := tt.envNamespace
cf.Namespace = &envNs
}
if tt.nsFollowsEnv != "" {
t.Setenv("FLUX_NS_FOLLOWS_KUBE_CONTEXT", tt.nsFollowsEnv)
}
// Simulate PersistentPreRunE behavior.
if tt.flagNamespace != "" {
*cf.Namespace = tt.flagNamespace
} else if tt.nsFollowsFlag || os.Getenv("FLUX_NS_FOLLOWS_KUBE_CONTEXT") != "" {
if ctxNs := getKubeconfigContextNamespace(cf); ctxNs != "" {
*cf.Namespace = ctxNs
}
}
g.Expect(*cf.Namespace).To(Equal(tt.expectedNamespace))
})
}
}

View file

@ -374,6 +374,12 @@ func executeCommand(cmd string) (string, error) {
// in subsequent executions which causes tests to fail that rely on the value
// of "Changed".
resumeCmd.PersistentFlags().Lookup("wait").Changed = false
// Reset the help flag value and Changed state so that a prior
// "--help" invocation does not leak into subsequent test runs.
if hf := rootCmd.Flags().Lookup("help"); hf != nil {
hf.Value.Set("false")
hf.Changed = false
}
}()
args, err := shellwords.Parse(cmd)
if err != nil {
@ -456,6 +462,7 @@ func resetCmdArgs() {
secretGitArgs = NewSecretGitFlags()
secretGitHubAppArgs = secretGitHubAppFlags{}
secretProxyArgs = secretProxyFlags{}
secretReceiverArgs = secretReceiverFlags{}
secretHelmArgs = secretHelmFlags{}
secretTLSArgs = secretTLSFlags{}
sourceBucketArgs = sourceBucketFlags{}

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

@ -0,0 +1,118 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"strings"
"time"
"github.com/briandowns/spinner"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
)
var pluginHandler = plugin.NewHandler()
var pluginCmd = &cobra.Command{
Use: "plugin",
Short: "Manage Flux CLI plugins",
Long: `The plugin sub-commands manage Flux CLI plugins.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// No-op: skip root's namespace DNS validation for plugin commands.
return nil
},
}
func init() {
rootCmd.AddCommand(pluginCmd)
}
// builtinCommandNames returns the names of all non-plugin commands on rootCmd.
func builtinCommandNames() []string {
var names []string
for _, c := range rootCmd.Commands() {
if c.GroupID != "plugin" {
names = append(names, c.Name())
}
}
return names
}
// registerPlugins scans the plugin directory and registers discovered
// plugins as Cobra subcommands on rootCmd.
func registerPlugins() {
plugins := pluginHandler.Discover(builtinCommandNames())
if len(plugins) == 0 {
return
}
if !rootCmd.ContainsGroup("plugin") {
rootCmd.AddGroup(&cobra.Group{
ID: "plugin",
Title: "Plugin Commands:",
})
}
for _, p := range plugins {
cmd := &cobra.Command{
Use: p.Name,
Short: fmt.Sprintf("Runs the %s plugin", p.Name),
Long: fmt.Sprintf("This command runs the %s plugin.\nUse 'flux %s --help' for full plugin help.", p.Name, p.Name),
DisableFlagParsing: true,
GroupID: "plugin",
ValidArgsFunction: plugin.CompleteFunc(p.Path),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return plugin.Exec(p.Path, args)
},
}
rootCmd.AddCommand(cmd)
}
}
// parseNameVersion splits "operator@0.45.0" into ("operator", "0.45.0").
// If no @ is present, version is empty (latest).
func parseNameVersion(s string) (string, string) {
name, version, found := strings.Cut(s, "@")
if found {
return name, version
}
return s, ""
}
// isDigestRef reports whether ref is a content-addressable digest
// (e.g. "sha256:06e0a38...").
func isDigestRef(ref string) bool {
return strings.HasPrefix(ref, "sha256:")
}
// newCatalogClient creates a CatalogClient that respects FLUXCD_PLUGIN_CATALOG.
func newCatalogClient() *plugin.CatalogClient {
client := plugin.NewCatalogClient()
client.GetEnv = pluginHandler.GetEnv
return client
}
func newPluginSpinner(message string) *spinner.Spinner {
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
s.Suffix = " " + message
return s
}

View file

@ -0,0 +1,96 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"runtime"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
)
var pluginInstallCmd = &cobra.Command{
Use: "install <name>[@<version>|@<digest>]",
Short: "Install a plugin from the catalog",
Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog.
Examples:
# Install the latest version
flux plugin install operator
# Install a specific version
flux plugin install operator@0.45.0
# Install pinned to a specific digest
flux plugin install operator@sha256:06e0a38db4fa6bc9f705a577c7e58dc020bfe2618e45488599e5ef7bb62e3a8a`,
Args: cobra.ExactArgs(1),
RunE: pluginInstallCmdRun,
}
func init() {
pluginCmd.AddCommand(pluginInstallCmd)
}
func pluginInstallCmdRun(cmd *cobra.Command, args []string) error {
nameVersion := args[0]
name, ref := parseNameVersion(nameVersion)
catalogClient := newCatalogClient()
manifest, err := catalogClient.FetchManifest(name)
if err != nil {
return err
}
var pv *plugintypes.Version
var plat *plugintypes.Platform
if isDigestRef(ref) {
dm, err := plugin.ResolveByDigest(manifest, ref, runtime.GOOS, runtime.GOARCH)
if err != nil {
return err
}
pv = dm.Version
plat = dm.Platform
} else {
pv, err = plugin.ResolveVersion(manifest, ref)
if err != nil {
return err
}
plat, err = plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH)
if err != nil {
return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH)
}
}
pluginDir := pluginHandler.EnsurePluginDir()
installer := plugin.NewInstaller()
sp := newPluginSpinner(fmt.Sprintf("installing %s v%s", name, pv.Version))
sp.Start()
if err := installer.Install(pluginDir, manifest, pv, plat); err != nil {
sp.Stop()
return err
}
sp.Stop()
logger.Successf("installed %s v%s", name, pv.Version)
return nil
}

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

@ -0,0 +1,57 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
"github.com/fluxcd/flux2/v2/pkg/printers"
)
var pluginListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List installed plugins",
Long: `The plugin list command shows all installed plugins with their versions and paths.`,
RunE: pluginListCmdRun,
}
func init() {
pluginCmd.AddCommand(pluginListCmd)
}
func pluginListCmdRun(cmd *cobra.Command, args []string) error {
pluginDir := pluginHandler.PluginDir()
plugins := pluginHandler.Discover(builtinCommandNames())
if len(plugins) == 0 {
cmd.Println("No plugins found")
return nil
}
header := []string{"NAME", "VERSION", "PATH"}
var rows [][]string
for _, p := range plugins {
version := "manual"
if receipt := plugin.ReadReceipt(pluginDir, p.Name); receipt != nil {
version = receipt.Version
}
rows = append(rows, []string{p.Name, version, p.Path})
}
return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)
}

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

@ -0,0 +1,81 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"strings"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
"github.com/fluxcd/flux2/v2/pkg/printers"
)
var pluginSearchCmd = &cobra.Command{
Use: "search [query]",
Short: "Search the plugin catalog",
Long: `The plugin search command lists available plugins from the Flux plugin catalog.`,
Args: cobra.MaximumNArgs(1),
RunE: pluginSearchCmdRun,
}
func init() {
pluginCmd.AddCommand(pluginSearchCmd)
}
func pluginSearchCmdRun(cmd *cobra.Command, args []string) error {
catalogClient := newCatalogClient()
catalog, err := catalogClient.FetchCatalog()
if err != nil {
return err
}
var query string
if len(args) == 1 {
query = strings.ToLower(args[0])
}
pluginDir := pluginHandler.PluginDir()
header := []string{"NAME", "DESCRIPTION", "INSTALLED"}
var rows [][]string
for _, entry := range catalog.Plugins {
if query != "" {
if !strings.Contains(strings.ToLower(entry.Name), query) &&
!strings.Contains(strings.ToLower(entry.Description), query) {
continue
}
}
installed := ""
if receipt := plugin.ReadReceipt(pluginDir, entry.Name); receipt != nil {
installed = receipt.Version
}
rows = append(rows, []string{entry.Name, entry.Description, installed})
}
if len(rows) == 0 {
if query != "" {
cmd.Printf("No plugins matching %q found in catalog\n", query)
} else {
cmd.Println("No plugins found in catalog")
}
return nil
}
return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)
}

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

@ -0,0 +1,286 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"os"
"strings"
"testing"
"github.com/fluxcd/flux2/v2/internal/plugin"
)
func TestPluginAppearsInHelp(t *testing.T) {
origHandler := pluginHandler
defer func() { pluginHandler = origHandler }()
pluginDir := t.TempDir()
fakeBin := pluginDir + "/flux-testplugin"
os.WriteFile(fakeBin, []byte("#!/bin/sh\necho test"), 0o755)
pluginHandler = &plugin.Handler{
ReadDir: os.ReadDir,
Stat: os.Stat,
GetEnv: func(key string) string {
if key == "FLUXCD_PLUGINS" {
return pluginDir
}
return ""
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
}
registerPlugins()
defer func() {
cmds := rootCmd.Commands()
for _, cmd := range cmds {
if cmd.Name() == "testplugin" {
rootCmd.RemoveCommand(cmd)
break
}
}
}()
output, err := executeCommand("--help")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(output, "Plugin Commands:") {
t.Error("expected 'Plugin Commands:' in help output")
}
if !strings.Contains(output, "testplugin") {
t.Error("expected 'testplugin' in help output")
}
}
func TestPluginListOutput(t *testing.T) {
origHandler := pluginHandler
defer func() { pluginHandler = origHandler }()
pluginDir := t.TempDir()
fakeBin := pluginDir + "/flux-myplugin"
os.WriteFile(fakeBin, []byte("#!/bin/sh\necho test"), 0o755)
pluginHandler = &plugin.Handler{
ReadDir: os.ReadDir,
Stat: os.Stat,
GetEnv: func(key string) string {
if key == "FLUXCD_PLUGINS" {
return pluginDir
}
return ""
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
}
output, err := executeCommand("plugin list")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(output, "myplugin") {
t.Errorf("expected 'myplugin' in output, got: %s", output)
}
if !strings.Contains(output, "manual") {
t.Errorf("expected 'manual' in output (no receipt), got: %s", output)
}
}
func TestPluginListWithReceipt(t *testing.T) {
origHandler := pluginHandler
defer func() { pluginHandler = origHandler }()
pluginDir := t.TempDir()
fakeBin := pluginDir + "/flux-myplugin"
os.WriteFile(fakeBin, []byte("#!/bin/sh\necho test"), 0o755)
receipt := pluginDir + "/flux-myplugin.yaml"
os.WriteFile(receipt, []byte("name: myplugin\nversion: \"1.2.3\"\n"), 0o644)
pluginHandler = &plugin.Handler{
ReadDir: os.ReadDir,
Stat: os.Stat,
GetEnv: func(key string) string {
if key == "FLUXCD_PLUGINS" {
return pluginDir
}
return ""
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
}
output, err := executeCommand("plugin list")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(output, "1.2.3") {
t.Errorf("expected version '1.2.3' in output, got: %s", output)
}
}
func TestPluginListEmpty(t *testing.T) {
origHandler := pluginHandler
defer func() { pluginHandler = origHandler }()
pluginDir := t.TempDir()
pluginHandler = &plugin.Handler{
ReadDir: os.ReadDir,
Stat: os.Stat,
GetEnv: func(key string) string {
if key == "FLUXCD_PLUGINS" {
return pluginDir
}
return ""
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
}
output, err := executeCommand("plugin list")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(output, "No plugins found") {
t.Errorf("expected 'No plugins found', got: %s", output)
}
}
func TestNoPluginsNoRegistration(t *testing.T) {
origHandler := pluginHandler
defer func() { pluginHandler = origHandler }()
pluginHandler = &plugin.Handler{
ReadDir: func(name string) ([]os.DirEntry, error) {
return nil, fmt.Errorf("no dir")
},
Stat: os.Stat,
GetEnv: func(key string) string {
if key == "FLUXCD_PLUGINS" {
return "/nonexistent"
}
return ""
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
}
// Verify that registerPlugins with no plugins doesn't add any commands.
before := len(rootCmd.Commands())
registerPlugins()
after := len(rootCmd.Commands())
if after != before {
t.Errorf("expected no new commands, got %d new", after-before)
}
}
func TestPluginSkipsPersistentPreRun(t *testing.T) {
// Plugin commands override root's PersistentPreRunE with a no-op,
// so an invalid namespace should not trigger a validation error.
_, err := executeCommand("plugin list")
if err != nil {
t.Fatalf("plugin list should not trigger root's namespace validation: %v", err)
}
}
func TestParseNameVersion(t *testing.T) {
tests := []struct {
input string
wantName string
wantVersion string
}{
{"operator", "operator", ""},
{"operator@0.45.0", "operator", "0.45.0"},
{"my-tool@1.0.0", "my-tool", "1.0.0"},
{"plugin@", "plugin", ""},
{"operator@sha256:abc123", "operator", "sha256:abc123"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
name, version := parseNameVersion(tt.input)
if name != tt.wantName {
t.Errorf("name: got %q, want %q", name, tt.wantName)
}
if version != tt.wantVersion {
t.Errorf("version: got %q, want %q", version, tt.wantVersion)
}
})
}
}
func TestIsDigestRef(t *testing.T) {
tests := []struct {
input string
want bool
}{
{"sha256:06e0a38db4fa6bc9f705a577c7e58dc020bfe2618e45488599e5ef7bb62e3a8a", true},
{"0.45.0", false},
{"", false},
{"sha256", false},
{"SHA256:abc", false}, // case-sensitive
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
if got := isDigestRef(tt.input); got != tt.want {
t.Errorf("isDigestRef(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}
func TestPluginDiscoverSkipsBuiltins(t *testing.T) {
origHandler := pluginHandler
defer func() { pluginHandler = origHandler }()
pluginDir := t.TempDir()
for _, name := range []string{"flux-get", "flux-create", "flux-version"} {
os.WriteFile(pluginDir+"/"+name, []byte("#!/bin/sh"), 0o755)
}
os.WriteFile(pluginDir+"/flux-myplugin", []byte("#!/bin/sh"), 0o755)
pluginHandler = &plugin.Handler{
ReadDir: os.ReadDir,
Stat: os.Stat,
GetEnv: func(key string) string {
if key == "FLUXCD_PLUGINS" {
return pluginDir
}
return ""
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
}
plugins := pluginHandler.Discover(builtinCommandNames())
if len(plugins) != 1 {
names := make([]string, len(plugins))
for i, p := range plugins {
names[i] = p.Name
}
t.Fatalf("expected 1 plugin, got %d: %v", len(plugins), names)
}
if plugins[0].Name != "myplugin" {
t.Errorf("expected 'myplugin', got %q", plugins[0].Name)
}
}

View file

@ -0,0 +1,48 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
)
var pluginUninstallCmd = &cobra.Command{
Use: "uninstall <name>",
Aliases: []string{"delete"},
Short: "Uninstall a plugin",
Long: `The plugin uninstall command removes a plugin binary and its receipt from the plugin directory.`,
Args: cobra.ExactArgs(1),
RunE: pluginUninstallCmdRun,
}
func init() {
pluginCmd.AddCommand(pluginUninstallCmd)
}
func pluginUninstallCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
pluginDir := pluginHandler.PluginDir()
if err := plugin.Uninstall(pluginDir, name); err != nil {
return err
}
logger.Successf("uninstalled %s", name)
return nil
}

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

@ -0,0 +1,102 @@
/*
Copyright 2026 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"runtime"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/plugin"
)
var pluginUpdateCmd = &cobra.Command{
Use: "update [name]",
Aliases: []string{"upgrade"},
Short: "Update installed plugins",
Long: `The plugin update command updates installed plugins to their latest versions.
Examples:
# Update a single plugin
flux plugin update operator
# Update all installed plugins
flux plugin update`,
Args: cobra.MaximumNArgs(1),
RunE: pluginUpdateCmdRun,
}
func init() {
pluginCmd.AddCommand(pluginUpdateCmd)
}
func pluginUpdateCmdRun(cmd *cobra.Command, args []string) error {
catalogClient := newCatalogClient()
plugins := pluginHandler.Discover(builtinCommandNames())
if len(plugins) == 0 {
cmd.Println("No plugins found")
return nil
}
// If a specific plugin is requested, filter to just that one.
if len(args) == 1 {
name := args[0]
var found bool
for _, p := range plugins {
if p.Name == name {
plugins = []plugin.Plugin{p}
found = true
break
}
}
if !found {
return fmt.Errorf("plugin %q is not installed", name)
}
}
pluginDir := pluginHandler.EnsurePluginDir()
installer := plugin.NewInstaller()
for _, p := range plugins {
result := plugin.CheckUpdate(pluginDir, p.Name, catalogClient, runtime.GOOS, runtime.GOARCH)
if result.Err != nil {
logger.Failuref("error checking %s: %v", p.Name, result.Err)
continue
}
if result.Skipped {
if result.SkipReason == plugin.SkipReasonManual {
logger.Warningf("skipping %s (%s)", p.Name, result.SkipReason)
} else {
logger.Successf("%s already up to date (v%s)", p.Name, result.FromVersion)
}
continue
}
sp := newPluginSpinner(fmt.Sprintf("updating %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion))
sp.Start()
if err := installer.Install(pluginDir, result.Manifest, result.Version, result.Platform); err != nil {
sp.Stop()
logger.Failuref("error updating %s: %v", p.Name, err)
continue
}
sp.Stop()
logger.Successf("updated %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion)
}
return nil
}

View file

@ -103,17 +103,18 @@ The command can read the credentials from '~/.docker/config.json' but they can a
}
type pushArtifactFlags struct {
path string
source string
revision string
creds string
provider flags.SourceOCIProvider
ignorePaths []string
annotations []string
output string
debug bool
reproducible bool
insecure bool
path string
source string
revision string
creds string
provider flags.SourceOCIProvider
ignorePaths []string
annotations []string
output string
debug bool
reproducible bool
insecure bool
resolveSymlinks bool
}
var pushArtifactArgs = newPushArtifactFlags()
@ -137,6 +138,7 @@ func init() {
pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pushed without TLS")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.resolveSymlinks, "resolve-symlinks", false, "resolve symlinks by copying their targets into the artifact")
pushCmd.AddCommand(pushArtifactCmd)
}
@ -183,6 +185,15 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid path '%s', must point to an existing directory or file: %w", path, err)
}
if pushArtifactArgs.resolveSymlinks {
resolved, cleanupDir, err := resolveSymlinks(path)
if err != nil {
return fmt.Errorf("resolving symlinks failed: %w", err)
}
defer os.RemoveAll(cleanupDir)
path = resolved
}
annotations := map[string]string{}
for _, annotation := range pushArtifactArgs.annotations {
kv := strings.Split(annotation, "=")

View file

@ -152,7 +152,14 @@ func reconciliationHandled(kubeClient client.Client, namespacedName types.Namesp
return false, err
}
return result.Status == kstatus.CurrentStatus, nil
switch result.Status {
case kstatus.CurrentStatus:
return true, nil
case kstatus.InProgressStatus:
return false, nil
default:
return false, fmt.Errorf("%s", result.Message)
}
}
}

View file

@ -126,6 +126,17 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
resume.printMessage(reconcileResps)
// Return an error if any reconciliation failed
var failedCount int
for _, r := range reconcileResps {
if r.resumable != nil && r.err != nil {
failedCount++
}
}
if failedCount > 0 {
return fmt.Errorf("reconciliation failed for %d %s(s)", failedCount, resume.kind)
}
return nil
}

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

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAWDldtCFdSMXIV1vLwXvRwk4eEmSoDCpxNkcbNph3dCAAAAIjjSDmx40g5
sQAAAAtzc2gtZWQyNTUxOQAAACAWDldtCFdSMXIV1vLwXvRwk4eEmSoDCpxNkcbNph3dCA
AAAEAGpzSFuLkCNDD49+tysxSFFwdOsRnDj67vDT9bfwoSDhYOV20IV1IxchXW8vBe9HCT
h4SZKgMKnE2Rxs2mHd0IAAAABHRlc3QB
-----END OPENSSH PRIVATE KEY-----

Binary file not shown.

BIN
cmd/flux/testdata/bootstrap/gpg.pgp vendored Normal file

Binary file not shown.

View file

@ -0,0 +1 @@
not a real ssh key

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,22 @@
---
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
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system

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

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: Secret
metadata:
annotations:
notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4
name: gcr-secret
namespace: my-namespace
stringData:
audience: https://custom.audience.example.com
email: sa@project.iam.gserviceaccount.com
token: test-token

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: Secret
metadata:
annotations:
notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4
name: gcr-secret
namespace: my-namespace
stringData:
audience: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4
email: sa@project.iam.gserviceaccount.com
token: test-token

View file

@ -0,0 +1,11 @@
---
apiVersion: v1
kind: Secret
metadata:
annotations:
notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/106120121d366c2f67e93200f6c1dbe938235eb588daa5e8c0516d3a77ac1dee
name: receiver-secret
namespace: my-namespace
stringData:
token: test-token

View file

@ -0,0 +1 @@
â–ş Namespace/new-ns created

View file

@ -0,0 +1,9 @@
â–ş ConfigMap/default/existing-config drifted
metadata
+ one map entry added:
labels:
kustomize.toolkit.fluxcd.io/name: configmaps
kustomize.toolkit.fluxcd.io/namespace:
â–ş ConfigMap/default/new-config created

View file

@ -0,0 +1,14 @@
â–ş Deployment/default/podinfo created
â–ş HorizontalPodAutoscaler/default/podinfo created
â–ş Service/default/podinfo drifted
metadata
+ one map entry added:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace:
â–ş Secret/default/docker-secret created
â–ş Secret/default/secret-basic-auth-stringdata created
â–ş Secret/default/podinfo-token-77t89m9b67 created
â–ş Secret/default/db-user-pass-bkbd782d2c created

View file

@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: podinfo
namespace: default
spec:
type: ClusterIP
selector:
app: podinfo
ports:
- name: http
port: 9899
protocol: TCP
targetPort: http
- port: 9999
targetPort: grpc
protocol: TCP
name: grpc

View file

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

View file

@ -0,0 +1,14 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: configmaps
spec:
interval: 5m0s
path: ./kustomize
force: true
prune: true
sourceRef:
kind: GitRepository
name: configmaps
targetNamespace: default

View file

@ -0,0 +1,19 @@
---
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
ignore:
- paths:
- "/spec/ports"
target:
kind: Service

View file

@ -0,0 +1,14 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
spec:
interval: 5m0s
path: ./kustomize
force: true
prune: true
sourceRef:
kind: GitRepository
name: podinfo
targetNamespace: default

View file

@ -0,0 +1,13 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: new-namespace-and-configmap
spec:
interval: 5m0s
path: ./kustomize
force: true
prune: true
sourceRef:
kind: GitRepository
name: new-namespace-and-configmap

View file

@ -0,0 +1,13 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: new-namespace-only
spec:
interval: 5m0s
path: ./kustomize
force: true
prune: true
sourceRef:
kind: GitRepository
name: new-namespace-only

View file

@ -10,6 +10,10 @@ spec:
author:
email: fluxcdbot@users.noreply.github.com
name: fluxcdbot
signingKey:
secretRef:
name: my-signing-key
type: ssh
interval: 1m0s
sourceRef:
kind: GitRepository

View file

@ -67,6 +67,10 @@ spec:
email: fluxcdbot@users.noreply.github.com
name: fluxcdbot
messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
signingKey:
secretRef:
name: my-signing-key
type: ssh
update:
path: ./clusters/my-cluster
strategy: Setters

View file

@ -0,0 +1,2 @@
NAME REVISION SUSPENDED READY MESSAGE
gr-failed False False failed to checkout and determine revision

View file

@ -0,0 +1,3 @@
NAME REVISION SUSPENDED READY MESSAGE
gr-failed False False failed to checkout and determine revision
gr-unknown False Unknown reconciliation in progress

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