Compare commits

..

4 Commits
main ... v2.6.1

Author SHA1 Message Date
Matheus Pimenta b73c7f7191
Merge pull request #5384 from fluxcd/backport-5383-to-release/v2.6.x
[release/v2.6.x] Add digest pinning to image automation testing
7 months ago
Stefan Prodan 7aff0327ad Add digest pinning to image automation testing
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
(cherry picked from commit be8acc0cfb)
7 months ago
Stefan Prodan 3bb3ae3617
Merge pull request #5382 from fluxcd/backport-5381-to-release/v2.6.x
[release/v2.6.x] Update image-reflector-controller to v0.35.1
7 months ago
fluxcdbot bf1af3c120 Update toolkit components
- image-reflector-controller to v0.35.1
  https://github.com/fluxcd/image-reflector-controller/blob/v0.35.1/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
(cherry picked from commit 4172a8a7f9)
7 months ago

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

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

@ -1,13 +1,34 @@
name: backport name: backport
on: on:
pull_request_target: pull_request_target:
types: [closed, labeled] types: [closed, labeled]
permissions: read-all
permissions:
contents: read
jobs: jobs:
backport: pull-request:
runs-on: ubuntu-latest
permissions: permissions:
contents: write # for reading and creating branches. contents: write
pull-requests: write # for creating pull requests against release branches. pull-requests: write
uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.4.0 if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
secrets: steps:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }} - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
# xref: https://github.com/korthout/backport-action#inputs
with:
# Use token to allow workflows to be triggered for the created PR
github_token: ${{ secrets.BOT_GITHUB_TOKEN }}
# Match labels with a pattern `backport:<target-branch>`
label_pattern: '^backport:([^ ]+)$'
# A bit shorter pull-request title than the default
pull_title: '[${target_branch}] ${pull_title}'
# Simpler PR description than default
pull_description: |-
Automated backport to `${target_branch}`, triggered by a label in #${pull_number}.

@ -9,7 +9,7 @@ permissions:
contents: read contents: read
env: env:
GO_VERSION: 1.25.x GO_VERSION: 1.24.x
jobs: jobs:
conform-kubernetes: conform-kubernetes:
@ -19,13 +19,13 @@ jobs:
matrix: matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes # Keep this list up-to-date with https://endoflife.date/kubernetes
# Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml # Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml
KUBERNETES_VERSION: [1.32.1, 1.33.0, 1.34.1] KUBERNETES_VERSION: [1.31.5, 1.32.1, 1.33.0]
fail-fast: false fail-fast: false
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache-dependency-path: | cache-dependency-path: |
@ -42,7 +42,7 @@ jobs:
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with: with:
version: v0.30.0 version: v0.27.0
cluster_name: ${{ steps.prep.outputs.CLUSTER }} cluster_name: ${{ steps.prep.outputs.CLUSTER }}
node_image: ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64 node_image: ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64
- name: Run e2e tests - name: Run e2e tests
@ -76,13 +76,13 @@ jobs:
matrix: matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes # Keep this list up-to-date with https://endoflife.date/kubernetes
# Available versions can be found with "replicated cluster versions" # Available versions can be found with "replicated cluster versions"
K3S_VERSION: [ 1.32.9, 1.33.5, 1.34.1 ] K3S_VERSION: [ 1.31.8, 1.32.4, 1.33.0 ]
fail-fast: false fail-fast: false
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache-dependency-path: | cache-dependency-path: |
@ -97,7 +97,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml" KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Create repository - name: Create repository
@ -120,7 +120,8 @@ jobs:
run: TEST_KUBECONFIG=${{ steps.prep.outputs.kubeconfig-path }} make e2e run: TEST_KUBECONFIG=${{ steps.prep.outputs.kubeconfig-path }} make e2e
- name: Run flux bootstrap - name: Run flux bootstrap
run: | run: |
./bin/flux bootstrap git --manifests ./manifests/test/ \ ./bin/flux bootstrap git --manifests ./manifests/install/ \
--components-extra=image-reflector-controller,image-automation-controller \
--url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \ --url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \
--branch=main \ --branch=main \
--path=clusters/k3s \ --path=clusters/k3s \
@ -168,13 +169,13 @@ jobs:
strategy: strategy:
matrix: matrix:
# Keep this list up-to-date with https://endoflife.date/red-hat-openshift # Keep this list up-to-date with https://endoflife.date/red-hat-openshift
OPENSHIFT_VERSION: [ 4.19.0-okd, 4.20.0-okd ] OPENSHIFT_VERSION: [ 4.18.0-okd ]
fail-fast: false fail-fast: false
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache-dependency-path: | cache-dependency-path: |
@ -189,7 +190,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml" KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Create repository - name: Create repository
@ -211,6 +212,7 @@ jobs:
- name: Run flux bootstrap - name: Run flux bootstrap
run: | run: |
./bin/flux bootstrap git --manifests ./manifests/openshift/ \ ./bin/flux bootstrap git --manifests ./manifests/openshift/ \
--components-extra=image-reflector-controller,image-automation-controller \
--url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \ --url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \
--branch=main \ --branch=main \
--path=clusters/openshift \ --path=clusters/openshift \

@ -22,18 +22,19 @@ permissions:
jobs: jobs:
e2e-aks: e2e-aks:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
defaults: defaults:
run: run:
working-directory: ./tests/integration working-directory: ./tests/integration
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' # This job is currently disabled. Remove the false check when Azure subscription is enabled.
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: CheckoutD - name: CheckoutD
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: 1.25.x go-version: 1.24.x
cache-dependency-path: tests/integration/go.sum cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform - name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
@ -50,7 +51,7 @@ jobs:
- name: Authenticate to Azure - name: Authenticate to Azure
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v1.4.6 uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v1.4.6
with: with:
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}' creds: '{"clientId":"${{ secrets.AZ_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.AZ_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZ_ARM_TENANT_ID }}"}'
- name: Set dynamic variables in .env - name: Set dynamic variables in .env
run: | run: |
cat > .env <<EOF cat > .env <<EOF
@ -60,35 +61,33 @@ jobs:
run: cat .env run: cat .env
- name: Run Azure e2e tests - name: Run Azure e2e tests
env: env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} ARM_CLIENT_ID: ${{ secrets.AZ_ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} ARM_CLIENT_SECRET: ${{ secrets.AZ_ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} ARM_SUBSCRIPTION_ID: ${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} ARM_TENANT_ID: ${{ secrets.AZ_ARM_TENANT_ID }}
TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }} TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}
TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }} TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}
TF_VAR_azure_location: ${{ vars.TF_VAR_azure_location }} TF_VAR_location: ${{ vars.TF_VAR_azure_location }}
GITREPO_SSH_CONTENTS: ${{ secrets.GIT_SSH_IDENTITY }} GITREPO_SSH_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_CONTENTS }}
GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GIT_SSH_IDENTITY_PUB }} GITREPO_SSH_PUB_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_PUB_CONTENTS }}
run: | run: |
source .env source .env
mkdir -p ./build/ssh mkdir -p ./build/ssh
cat <<EOF > build/ssh/key touch ./build/ssh/key
$GITREPO_SSH_CONTENTS echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key
EOF
export GITREPO_SSH_PATH=build/ssh/key export GITREPO_SSH_PATH=build/ssh/key
cat <<EOF > build/ssh/key.pub touch ./build/ssh/key.pub
$GITREPO_SSH_PUB_CONTENTS echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub
EOF
export GITREPO_SSH_PUB_PATH=build/ssh/key.pub export GITREPO_SSH_PUB_PATH=build/ssh/key.pub
make test-azure make test-azure
- name: Ensure resource cleanup - name: Ensure resource cleanup
if: ${{ always() }} if: ${{ always() }}
env: env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} ARM_CLIENT_ID: ${{ secrets.AZ_ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} ARM_CLIENT_SECRET: ${{ secrets.AZ_ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} ARM_SUBSCRIPTION_ID: ${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} ARM_TENANT_ID: ${{ secrets.AZ_ARM_TENANT_ID }}
TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }} TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}
TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }} TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}
TF_VAR_azure_location: ${{ vars.TF_VAR_azure_location }} TF_VAR_location: ${{ vars.TF_VAR_azure_location }}
run: source .env && make destroy-azure run: source .env && make destroy-azure

@ -17,27 +17,27 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: 1.25.x go-version: 1.24.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with: with:
version: v0.30.0 version: v0.24.0
cluster_name: kind cluster_name: kind
# The versions below should target the newest Kubernetes version # The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes # Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.32.1-amd64 node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.32.0 kubectl_version: v1.32.0
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Setup yq - name: Setup yq
uses: fluxcd/pkg/actions/yq@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/yq@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Set outputs - name: Set outputs
@ -51,7 +51,7 @@ jobs:
echo "test_repo_name=$TEST_REPO_NAME" >> $GITHUB_OUTPUT echo "test_repo_name=$TEST_REPO_NAME" >> $GITHUB_OUTPUT
- name: bootstrap init - name: bootstrap init
run: | run: |
./bin/flux bootstrap github --manifests ./manifests/test/ \ ./bin/flux bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--image-pull-secret=ghcr-auth \ --image-pull-secret=ghcr-auth \
--registry-creds=fluxcd:$GITHUB_TOKEN \ --registry-creds=fluxcd:$GITHUB_TOKEN \
@ -66,7 +66,7 @@ jobs:
kubectl -n flux-system get secret ghcr-auth | grep dockerconfigjson kubectl -n flux-system get secret ghcr-auth | grep dockerconfigjson
- name: bootstrap no-op - name: bootstrap no-op
run: | run: |
./bin/flux bootstrap github --manifests ./manifests/test/ \ ./bin/flux bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--image-pull-secret=ghcr-auth \ --image-pull-secret=ghcr-auth \
--repository=${{ steps.vars.outputs.test_repo_name }} \ --repository=${{ steps.vars.outputs.test_repo_name }} \
@ -78,7 +78,7 @@ jobs:
- name: bootstrap customize - name: bootstrap customize
run: | run: |
make setup-bootstrap-patch make setup-bootstrap-patch
./bin/flux bootstrap github --manifests ./manifests/test/ \ ./bin/flux bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--repository=${{ steps.vars.outputs.test_repo_name }} \ --repository=${{ steps.vars.outputs.test_repo_name }} \
--branch=main \ --branch=main \
@ -98,14 +98,13 @@ jobs:
- name: test image automation - name: test image automation
run: | run: |
make setup-image-automation make setup-image-automation
./bin/flux bootstrap github --manifests ./manifests/test/ \ ./bin/flux bootstrap github --manifests ./manifests/install/ \
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--repository=${{ steps.vars.outputs.test_repo_name }} \ --repository=${{ steps.vars.outputs.test_repo_name }} \
--branch=main \ --branch=main \
--path=test-cluster \ --path=test-cluster \
--read-write-key --read-write-key
./bin/flux reconcile image repository podinfo ./bin/flux reconcile image repository podinfo
./bin/flux reconcile image policy podinfo
./bin/flux reconcile image update flux-system ./bin/flux reconcile image update flux-system
./bin/flux get images all ./bin/flux get images all
./bin/flux -n flux-system events --for ImageUpdateAutomation/flux-system ./bin/flux -n flux-system events --for ImageUpdateAutomation/flux-system

@ -22,18 +22,18 @@ permissions:
jobs: jobs:
e2e-gcp: e2e-gcp:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
defaults: defaults:
run: run:
working-directory: ./tests/integration working-directory: ./tests/integration
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: 1.25.x go-version: 1.24.x
cache-dependency-path: tests/integration/go.sum cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform - name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
@ -48,19 +48,19 @@ jobs:
env: env:
SOPS_VER: 3.7.1 SOPS_VER: 3.7.1
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 uses: google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193 # v2.1.10
id: 'auth' id: 'auth'
with: with:
credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}' credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'
token_format: 'access_token' token_format: 'access_token'
- name: Setup gcloud - name: Setup gcloud
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Log into us-central1-docker.pkg.dev - name: Log into us-central1-docker.pkg.dev
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
registry: us-central1-docker.pkg.dev registry: us-central1-docker.pkg.dev
username: oauth2accesstoken username: oauth2accesstoken

@ -23,30 +23,30 @@ jobs:
- 5000:5000 - 5000:5000
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: 1.25.x go-version: 1.24.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with: with:
version: v0.30.0 version: v0.24.0
cluster_name: kind cluster_name: kind
wait: 5s wait: 5s
config: .github/kind/config.yaml # disable KIND-net config: .github/kind/config.yaml # disable KIND-net
# The versions below should target the oldest supported Kubernetes version # The versions below should target the oldest supported Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes # Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.32.1-amd64 node_image: ghcr.io/fluxcd/kindest/node:v1.31.5-amd64
kubectl_version: v1.32.0 kubectl_version: v1.32.0
- name: Setup Calico for network policy - name: Setup Calico for network policy
run: | run: |
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Run tests - name: Run tests
run: make test run: make test
- name: Run e2e tests - name: Run e2e tests
@ -65,7 +65,7 @@ jobs:
./bin/flux check --pre ./bin/flux check --pre
- name: flux install --manifests - name: flux install --manifests
run: | run: |
./bin/flux install --manifests ./manifests/test/ ./bin/flux install --manifests ./manifests/install/
- name: flux create secret - name: flux create secret
run: | run: |
./bin/flux create secret git git-ssh-test \ ./bin/flux create secret git git-ssh-test \
@ -238,9 +238,6 @@ jobs:
- name: flux check - name: flux check
run: | run: |
./bin/flux check ./bin/flux check
- name: flux migrate
run: |
./bin/flux migrate
- name: flux version - name: flux version
run: | run: |
./bin/flux version ./bin/flux version

@ -19,9 +19,9 @@ jobs:
actions: read actions: read
contents: read contents: read
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run analysis - name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@ -34,6 +34,6 @@ jobs:
path: results.sarif path: results.sarif
retention-days: 5 retention-days: 5
- name: Upload SARIF results - name: Upload SARIF results
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
with: with:
sarif_file: results.sarif sarif_file: results.sarif

@ -20,35 +20,33 @@ jobs:
packages: write # needed for ghcr access packages: write # needed for ghcr access
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Unshallow - name: Unshallow
run: git fetch --prune --unshallow run: git fetch --prune --unshallow
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: 1.25.x go-version: 1.24.x
cache: false cache: false
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Setup Docker Buildx - name: Setup Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Setup Syft - name: Setup Syft
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # v0.20.6 uses: anchore/sbom-action/download-syft@9f7302141466aa6482940f15371237e9d9f4c34a # v0.19.0
- name: Setup Cosign - name: Setup Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
with:
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
registry: ghcr.io registry: ghcr.io
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@ -61,7 +59,7 @@ jobs:
run: | run: |
kustomize build manifests/crds > all-crds.yaml kustomize build manifests/crds > all-crds.yaml
- name: Generate OpenAPI JSON schemas from CRDs - name: Generate OpenAPI JSON schemas from CRDs
uses: fluxcd/pkg/actions/crdjsonschema@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/crdjsonschema@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
with: with:
crd: all-crds.yaml crd: all-crds.yaml
output: schemas output: schemas
@ -70,7 +68,7 @@ jobs:
tar -czvf ./output/crd-schemas.tar.gz -C schemas . tar -czvf ./output/crd-schemas.tar.gz -C schemas .
- name: Run GoReleaser - name: Run GoReleaser
id: run-goreleaser id: run-goreleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
with: with:
version: latest version: latest
args: release --skip=validate args: release --skip=validate
@ -101,26 +99,24 @@ jobs:
id-token: write id-token: write
packages: write packages: write
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@bf02f0a2d612cc07e0892166369fa8f63246aabb # main uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
- name: Setup Flux CLI - name: Setup Flux CLI
uses: ./action/ uses: ./action/
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare - name: Prepare
id: prep id: prep
run: | run: |
VERSION=$(flux version --client | awk '{ print $NF }') VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
registry: ghcr.io registry: ghcr.io
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@ -148,9 +144,7 @@ jobs:
--path="./flux-system" \ --path="./flux-system" \
--source=${{ github.repositoryUrl }} \ --source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}" --revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 - uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
with:
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
- name: Sign manifests - name: Sign manifests
env: env:
COSIGN_EXPERIMENTAL: 1 COSIGN_EXPERIMENTAL: 1

@ -1,4 +1,5 @@
name: scan name: scan
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
@ -7,13 +8,46 @@ on:
branches: [ 'main', 'release/**' ] branches: [ 'main', 'release/**' ]
schedule: schedule:
- cron: '18 10 * * 3' - cron: '18 10 * * 3'
permissions: read-all
permissions:
contents: read
jobs: jobs:
analyze: scan-fossa:
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}
scan-codeql:
runs-on: ubuntu-latest
permissions: permissions:
contents: read # for reading the repository code. security-events: write
security-events: write # for uploading the CodeQL analysis results. if: github.actor != 'dependabot[bot]'
uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.4.0 steps:
secrets: - name: Checkout repository
github-token: ${{ secrets.GITHUB_TOKEN }} uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
fossa-token: ${{ secrets.FOSSA_TOKEN }} - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Initialize CodeQL
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
with:
languages: go
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# xref: https://codeql.github.com/codeql-query-help/go/
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17

@ -6,12 +6,23 @@ on:
- main - main
paths: paths:
- .github/labels.yaml - .github/labels.yaml
permissions: read-all
permissions:
contents: read
jobs: jobs:
sync-labels: labels:
name: Run sync
runs-on: ubuntu-latest
permissions: permissions:
contents: read # for reading the labels file. issues: write
issues: write # for creating and updating labels. steps:
uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.4.0 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
secrets: - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
github-token: ${{ secrets.GITHUB_TOKEN }} with:
# Configuration file
config-file: |
https://raw.githubusercontent.com/fluxcd/community/main/.github/standard-labels.yaml
.github/labels.yaml
# Strictly declarative
delete-other-labels: true

@ -2,6 +2,8 @@ name: update
on: on:
workflow_dispatch: workflow_dispatch:
schedule:
- cron: "0 * * * *"
push: push:
branches: [main] branches: [main]
@ -16,37 +18,24 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: 1.25.x go-version: 1.24.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Update component versions - name: Update component versions
id: update id: update
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
PR_BODY=$(mktemp) PR_BODY=$(mktemp)
bump_version() { bump_version() {
local LATEST_VERSION=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name') local LATEST_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
if [[ "$LATEST_VERSION" == *"-rc"* ]]; then
echo "Skipping release candidate version for $1: $LATEST_VERSION"
return
fi
local CTRL_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p;n" manifests/bases/$1/kustomization.yaml) local CTRL_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p;n" manifests/bases/$1/kustomization.yaml)
local CRD_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p" manifests/crds/kustomization.yaml) local CRD_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p" manifests/crds/kustomization.yaml)
local MOD_VERSION=$(go list -m -f '{{ .Version }}' "github.com/fluxcd/$1/api")
local API_PKG="github.com/fluxcd/$1/api"
if [[ "$1" == "source-watcher" ]]; then
API_PKG="github.com/fluxcd/$1/api/v2"
fi
local MOD_VERSION=$(go list -m -f '{{ .Version }}' "$API_PKG")
local changed=false local changed=false
@ -61,7 +50,7 @@ jobs:
fi fi
if [[ "${MOD_VERSION}" != "${LATEST_VERSION}" ]]; then if [[ "${MOD_VERSION}" != "${LATEST_VERSION}" ]]; then
go mod edit -require="$API_PKG@${LATEST_VERSION}" go mod edit -require="github.com/fluxcd/$1/api@${LATEST_VERSION}"
make tidy make tidy
changed=true changed=true
fi fi
@ -80,7 +69,6 @@ jobs:
bump_version notification-controller bump_version notification-controller
bump_version image-reflector-controller bump_version image-reflector-controller
bump_version image-automation-controller bump_version image-automation-controller
bump_version source-watcher
# diff change # diff change
git diff git diff

@ -88,6 +88,22 @@ brews:
generate_completions_from_executable(bin/"flux", "completion") generate_completions_from_executable(bin/"flux", "completion")
test: | test: |
system "#{bin}/flux --version" system "#{bin}/flux --version"
publishers:
- name: aur-pkg-bin
env:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-bin/publish.sh {{ .Version }}
- name: aur-pkg-scm
env:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-scm/publish.sh {{ .Version }}
- name: aur-pkg-go
env:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-go/publish.sh {{ .Version }}
dockers: dockers:
- image_templates: - image_templates:
- 'fluxcd/flux-cli:{{ .Tag }}-amd64' - 'fluxcd/flux-cli:{{ .Tag }}-amd64'

@ -49,8 +49,7 @@ you might want to take a look at the [introductory talk and demo](https://www.yo
This project is composed of: This project is composed of:
- [flux2](https://github.com/fluxcd/flux2): The Flux CLI - [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-manager](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git 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 - [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 - [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 - [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events
@ -68,9 +67,10 @@ for source changes.
Prerequisites: Prerequisites:
* go >= 1.25 * go >= 1.24
* kubectl >= 1.30 * kubectl >= 1.30
* kustomize >= 5.0 * kustomize >= 5.0
* coreutils (on Mac OS)
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with: Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:

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

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

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

@ -16,24 +16,23 @@ inputs:
description: "Alternative location for the Flux binary, defaults to path relative to $RUNNER_TOOL_CACHE." description: "Alternative location for the Flux binary, defaults to path relative to $RUNNER_TOOL_CACHE."
required: false required: false
token: token:
description: "Token used to authenticate against the GitHub.com API." description: "Token used to authentication against the GitHub.com API. Defaults to the token from the GitHub context of the workflow."
required: false required: false
runs: runs:
using: composite using: composite
steps: steps:
- name: "Download the binary to the runner's cache dir" - name: "Download the binary to the runner's cache dir"
shell: bash shell: bash
env:
VERSION: "${{ inputs.version }}"
FLUX_TOOL_DIR: "${{ inputs.bindir }}"
TOKEN: "${{ inputs.token }}"
run: | run: |
VERSION=${{ inputs.version }}
TOKEN=${{ inputs.token }}
if [[ -z "$TOKEN" ]]; then
TOKEN=${{ github.token }}
fi
if [[ -z "$VERSION" ]] || [[ "$VERSION" = "latest" ]]; then if [[ -z "$VERSION" ]] || [[ "$VERSION" = "latest" ]]; then
if [[ "${TOKEN}" != '' ]]; then
VERSION=$(curl -fsSL -H "Authorization: token ${TOKEN}" https://api.github.com/repos/fluxcd/flux2/releases/latest | grep tag_name | cut -d '"' -f 4) VERSION=$(curl -fsSL -H "Authorization: token ${TOKEN}" https://api.github.com/repos/fluxcd/flux2/releases/latest | grep tag_name | cut -d '"' -f 4)
else
VERSION=$(curl -w "%{url_effective}\n" -IsSL https://github.com/fluxcd/flux2/releases/latest -o /dev/null | sed 's$^.*/$$')
fi
fi fi
if [[ -z "$VERSION" ]]; then if [[ -z "$VERSION" ]]; then
echo "Unable to determine Flux CLI version" echo "Unable to determine Flux CLI version"
@ -60,6 +59,7 @@ runs:
FLUX_EXEC_FILE="${FLUX_EXEC_FILE}.exe" FLUX_EXEC_FILE="${FLUX_EXEC_FILE}.exe"
fi fi
FLUX_TOOL_DIR=${{ inputs.bindir }}
if [[ -z "$FLUX_TOOL_DIR" ]]; then if [[ -z "$FLUX_TOOL_DIR" ]]; then
FLUX_TOOL_DIR="${RUNNER_TOOL_CACHE}/flux2/${VERSION}/${OS}/${ARCH}" FLUX_TOOL_DIR="${RUNNER_TOOL_CACHE}/flux2/${VERSION}/${OS}/${ARCH}"
fi fi

@ -1,57 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"sigs.k8s.io/controller-runtime/pkg/client"
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
)
// swapi.ArtifactGenerator
var artifactGeneratorType = apiType{
kind: swapi.ArtifactGeneratorKind,
humanKind: "artifactgenerator",
groupVersion: swapi.GroupVersion,
}
type artifactGeneratorAdapter struct {
*swapi.ArtifactGenerator
}
func (h artifactGeneratorAdapter) asClientObject() client.Object {
return h.ArtifactGenerator
}
func (h artifactGeneratorAdapter) deepCopyClientObject() client.Object {
return h.ArtifactGenerator.DeepCopy()
}
// swapi.ArtifactGeneratorList
type artifactGeneratorListAdapter struct {
*swapi.ArtifactGeneratorList
}
func (h artifactGeneratorListAdapter) asClientList() client.ObjectList {
return h.ArtifactGeneratorList
}
func (h artifactGeneratorListAdapter) len() int {
return len(h.ArtifactGeneratorList.Items)
}

@ -97,7 +97,7 @@ func init() {
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller,source-watcher'") "list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
"container registry where the Flux controller images are published") "container registry where the Flux controller images are published")

@ -42,7 +42,7 @@ import (
var bootstrapGitLabCmd = &cobra.Command{ var bootstrapGitLabCmd = &cobra.Command{
Use: "gitlab", Use: "gitlab",
Short: "Deploy Flux on a cluster connected to a GitLab repository", Short: "Deploy Flux on a cluster connected to a GitLab repository",
Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exist and Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exists and
commits the Flux manifests to the specified branch. commits the Flux manifests to the specified branch.
Then it configures the target cluster to synchronize with that repository. Then it configures the target cluster to synchronize with that repository.
If the Flux components are present on the cluster, If the Flux components are present on the cluster,

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

@ -94,13 +94,6 @@ var createHelmReleaseCmd = &cobra.Command{
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
--chart=podinfo --chart=podinfo
# Create a HelmRelease with custom storage namespace for hub-and-spoke model
flux create hr podinfo \
--target-namespace=production \
--storage-namespace=fluxcd-system \
--source=HelmRepository/podinfo \
--chart=podinfo
# Create a HelmRelease using a source from a different namespace # Create a HelmRelease using a source from a different namespace
flux create hr podinfo \ flux create hr podinfo \
--namespace=default \ --namespace=default \
@ -134,7 +127,6 @@ type helmReleaseFlags struct {
chartVersion string chartVersion string
chartRef string chartRef string
targetNamespace string targetNamespace string
storageNamespace string
createNamespace bool createNamespace bool
valuesFiles []string valuesFiles []string
valuesFrom []string valuesFrom []string
@ -158,7 +150,6 @@ func init() {
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)")
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.dependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'") createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.dependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.storageNamespace, "storage-namespace", "", "namespace to store the Helm release, defaults to the target namespace")
createHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, "create-target-namespace", false, "create the target namespace if it does not exist") createHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, "create-target-namespace", false, "create the target namespace if it does not exist")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart created by the helm release(accepted values: Revision and ChartRevision)") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart created by the helm release(accepted values: Revision and ChartRevision)")
@ -174,18 +165,10 @@ func init() {
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
if helmReleaseArgs.storageNamespace == "" && helmReleaseArgs.targetNamespace != "" {
helmReleaseArgs.storageNamespace = helmReleaseArgs.targetNamespace
}
if helmReleaseArgs.chart == "" && helmReleaseArgs.chartRef == "" { if helmReleaseArgs.chart == "" && helmReleaseArgs.chartRef == "" {
return fmt.Errorf("chart or chart-ref is required") return fmt.Errorf("chart or chart-ref is required")
} }
if helmReleaseArgs.chart != "" && helmReleaseArgs.chartRef != "" {
return fmt.Errorf("cannot use --chart in combination with --chart-ref")
}
sourceLabels, err := parseLabels() sourceLabels, err := parseLabels()
if err != nil { if err != nil {
return err return err
@ -208,27 +191,15 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}, },
Spec: helmv2.HelmReleaseSpec{ Spec: helmv2.HelmReleaseSpec{
ReleaseName: helmReleaseArgs.name, ReleaseName: helmReleaseArgs.name,
DependsOn: utils.MakeDependsOn(helmReleaseArgs.dependsOn),
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: createArgs.interval, Duration: createArgs.interval,
}, },
TargetNamespace: helmReleaseArgs.targetNamespace, TargetNamespace: helmReleaseArgs.targetNamespace,
StorageNamespace: helmReleaseArgs.storageNamespace,
Suspend: false, Suspend: false,
}, },
} }
if len(helmReleaseArgs.dependsOn) > 0 {
ls := utils.MakeDependsOn(helmReleaseArgs.dependsOn)
hrDependsOn := make([]helmv2.DependencyReference, 0, len(ls))
for _, d := range ls {
hrDependsOn = append(hrDependsOn, helmv2.DependencyReference{
Name: d.Name,
Namespace: d.Namespace,
})
}
helmRelease.Spec.DependsOn = hrDependsOn
}
switch { switch {
case helmReleaseArgs.chart != "": case helmReleaseArgs.chart != "":
helmRelease.Spec.Chart = &helmv2.HelmChartTemplate{ helmRelease.Spec.Chart = &helmv2.HelmChartTemplate{
@ -263,7 +234,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
if helmReleaseArgs.kubeConfigSecretRef != "" { if helmReleaseArgs.kubeConfigSecretRef != "" {
helmRelease.Spec.KubeConfig = &meta.KubeConfigReference{ helmRelease.Spec.KubeConfig = &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{ SecretRef: meta.SecretKeyReference{
Name: helmReleaseArgs.kubeConfigSecretRef, Name: helmReleaseArgs.kubeConfigSecretRef,
}, },
} }

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

@ -29,18 +29,18 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var createImagePolicyCmd = &cobra.Command{ var createImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Create or update an ImagePolicy object", Short: "Create or update an ImagePolicy object",
Long: `The create image policy command generates an ImagePolicy resource. Long: withPreviewNote(`The create image policy command generates an ImagePolicy resource.
An ImagePolicy object calculates a "latest image" given an image An ImagePolicy object calculates a "latest image" given an image
repository and a policy, e.g., semver. repository and a policy, e.g., semver.
The image that sorts highest according to the policy is recorded in The image that sorts highest according to the policy is recorded in
the status of the object.`, the status of the object.`),
Example: ` # Create an ImagePolicy to select the latest stable release Example: ` # Create an ImagePolicy to select the latest stable release
flux create image policy podinfo \ flux create image policy podinfo \
--image-ref=podinfo \ --image-ref=podinfo \
@ -81,6 +81,12 @@ func init() {
createImageCmd.AddCommand(createImagePolicyCmd) createImageCmd.AddCommand(createImagePolicyCmd)
} }
// getObservedGeneration is implemented here, since it's not
// (presently) needed elsewhere.
func (obj imagePolicyAdapter) getObservedGeneration() int64 {
return obj.ImagePolicy.Status.ObservedGeneration
}
func createImagePolicyRun(cmd *cobra.Command, args []string) error { func createImagePolicyRun(cmd *cobra.Command, args []string) error {
objectName := args[0] objectName := args[0]

@ -26,14 +26,14 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var createImageRepositoryCmd = &cobra.Command{ var createImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Create or update an ImageRepository object", Short: "Create or update an ImageRepository object",
Long: `The create image repository command generates an ImageRepository resource. Long: withPreviewNote(`The create image repository command generates an ImageRepository resource.
An ImageRepository object specifies an image repository to scan.`, An ImageRepository object specifies an image repository to scan.`),
Example: ` # Create an ImageRepository object to scan the alpine image repository: Example: ` # Create an ImageRepository object to scan the alpine image repository:
flux create image repository alpine-repo --image alpine --interval 20m flux create image repository alpine-repo --image alpine --interval 20m

@ -22,16 +22,16 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
) )
var createImageUpdateCmd = &cobra.Command{ var createImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Create or update an ImageUpdateAutomation object", Short: "Create or update an ImageUpdateAutomation object",
Long: `The create image update command generates an ImageUpdateAutomation resource. Long: withPreviewNote(`The create image update command generates an ImageUpdateAutomation resource.
An ImageUpdateAutomation object specifies an automated update to images An ImageUpdateAutomation object specifies an automated update to images
mentioned in YAMLs in a git repository.`, mentioned in YAMLs in a git repository.`),
Example: ` # Configure image updates for the main repository created by flux bootstrap Example: ` # Configure image updates for the main repository created by flux bootstrap
flux create image update flux-system \ flux create image update flux-system \
--git-repo-ref=flux-system \ --git-repo-ref=flux-system \

@ -153,6 +153,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
Labels: kslabels, Labels: kslabels,
}, },
Spec: kustomizev1.KustomizationSpec{ Spec: kustomizev1.KustomizationSpec{
DependsOn: utils.MakeDependsOn(kustomizationArgs.dependsOn),
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: createArgs.interval, Duration: createArgs.interval,
}, },
@ -168,21 +169,9 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}, },
} }
if len(kustomizationArgs.dependsOn) > 0 {
ls := utils.MakeDependsOn(kustomizationArgs.dependsOn)
ksDependsOn := make([]kustomizev1.DependencyReference, 0, len(ls))
for _, d := range ls {
ksDependsOn = append(ksDependsOn, kustomizev1.DependencyReference{
Name: d.Name,
Namespace: d.Namespace,
})
}
kustomization.Spec.DependsOn = ksDependsOn
}
if kustomizationArgs.kubeConfigSecretRef != "" { if kustomizationArgs.kubeConfigSecretRef != "" {
kustomization.Spec.KubeConfig = &meta.KubeConfigReference{ kustomization.Spec.KubeConfig = &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{ SecretRef: meta.SecretKeyReference{
Name: kustomizationArgs.kubeConfigSecretRef, Name: kustomizationArgs.kubeConfigSecretRef,
}, },
} }

@ -172,7 +172,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
} }
secret, err := sourcesecret.GenerateGit(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -99,7 +99,7 @@ func createSecretGitHubAppCmdRun(cmd *cobra.Command, args []string) error {
opts.GitHubAppBaseURL = secretGitHubAppArgs.baseURL opts.GitHubAppBaseURL = secretGitHubAppArgs.baseURL
} }
secret, err := sourcesecret.GenerateGitHubApp(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -83,12 +83,10 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
var certFile, keyFile []byte var certFile, keyFile []byte
if secretHelmArgs.tlsCrtFile != "" { if secretHelmArgs.tlsCrtFile != "" && secretHelmArgs.tlsKeyFile != "" {
if certFile, err = os.ReadFile(secretHelmArgs.tlsCrtFile); err != nil { if certFile, err = os.ReadFile(secretHelmArgs.tlsCrtFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err) return fmt.Errorf("failed to read cert file: %w", err)
} }
}
if secretHelmArgs.tlsKeyFile != "" {
if keyFile, err = os.ReadFile(secretHelmArgs.tlsKeyFile); err != nil { if keyFile, err = os.ReadFile(secretHelmArgs.tlsKeyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err) return fmt.Errorf("failed to read key file: %w", err)
} }
@ -104,7 +102,7 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
TLSCrt: certFile, TLSCrt: certFile,
TLSKey: keyFile, TLSKey: keyFile,
} }
secret, err := sourcesecret.GenerateHelm(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -132,7 +132,7 @@ func createSecretNotationCmdRun(cmd *cobra.Command, args []string) error {
VerificationCrts: caCerts, VerificationCrts: caCerts,
TrustPolicy: policy, TrustPolicy: policy,
} }
secret, err := sourcesecret.GenerateNotation(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -92,7 +92,7 @@ func createSecretOCICmdRun(cmd *cobra.Command, args []string) error {
Username: secretOCIArgs.username, Username: secretOCIArgs.username,
} }
secret, err := sourcesecret.GenerateOCI(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -83,7 +83,7 @@ func createSecretProxyCmdRun(cmd *cobra.Command, args []string) error {
Username: secretProxyArgs.username, Username: secretProxyArgs.username,
Password: secretProxyArgs.password, Password: secretProxyArgs.password,
} }
secret, err := sourcesecret.GenerateProxy(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -84,18 +84,16 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
if secretTLSArgs.tlsCrtFile != "" { if secretTLSArgs.tlsCrtFile != "" && secretTLSArgs.tlsKeyFile != "" {
if opts.TLSCrt, err = os.ReadFile(secretTLSArgs.tlsCrtFile); err != nil { if opts.TLSCrt, err = os.ReadFile(secretTLSArgs.tlsCrtFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err) return fmt.Errorf("failed to read cert file: %w", err)
} }
}
if secretTLSArgs.tlsKeyFile != "" {
if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil { if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err) return fmt.Errorf("failed to read key file: %w", err)
} }
} }
secret, err := sourcesecret.GenerateTLS(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {
return err return err
} }

@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -113,6 +114,12 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
tmpDir, err := os.MkdirTemp("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
var ignorePaths *string var ignorePaths *string
if len(sourceBucketArgs.ignorePaths) > 0 { if len(sourceBucketArgs.ignorePaths) > 0 {
ignorePathsStr := strings.Join(sourceBucketArgs.ignorePaths, "\n") ignorePathsStr := strings.Join(sourceBucketArgs.ignorePaths, "\n")

@ -63,7 +63,6 @@ type sourceGitFlags struct {
recurseSubmodules bool recurseSubmodules bool
silent bool silent bool
ignorePaths []string ignorePaths []string
sparseCheckoutPaths []string
} }
var createSourceGitCmd = &cobra.Command{ var createSourceGitCmd = &cobra.Command{
@ -155,7 +154,6 @@ func init() {
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces") "when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation") createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)") createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)")
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.sparseCheckoutPaths, "sparse-checkout-paths", nil, "set paths to sparse checkout in git resource (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceGitCmd) createSourceCmd.AddCommand(createSourceGitCmd)
} }
@ -191,6 +189,12 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("specifying a CA file is not supported for Git over SSH") return fmt.Errorf("specifying a CA file is not supported for Git over SSH")
} }
tmpDir, err := os.MkdirTemp("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
sourceLabels, err := parseLabels() sourceLabels, err := parseLabels()
if err != nil { if err != nil {
return err return err
@ -216,7 +220,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
RecurseSubmodules: sourceGitArgs.recurseSubmodules, RecurseSubmodules: sourceGitArgs.recurseSubmodules,
Reference: &sourcev1.GitRepositoryRef{}, Reference: &sourcev1.GitRepositoryRef{},
Ignore: ignorePaths, Ignore: ignorePaths,
SparseCheckout: sourceGitArgs.sparseCheckoutPaths,
}, },
} }
@ -299,7 +302,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Username = sourceGitArgs.username secretOpts.Username = sourceGitArgs.username
secretOpts.Password = sourceGitArgs.password secretOpts.Password = sourceGitArgs.password
} }
secret, err := sourcesecret.GenerateGit(secretOpts) secret, err := sourcesecret.Generate(secretOpts)
if err != nil { if err != nil {
return err return err
} }

@ -87,7 +87,7 @@ func (r *reconciler) conditionFunc() (bool, error) {
} }
func TestCreateSourceGitExport(t *testing.T) { func TestCreateSourceGitExport(t *testing.T) {
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --sparse-checkout-paths .cosign,non-existent-dir/ --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String() var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String()
cases := []struct { cases := []struct {
name string name string
@ -101,7 +101,7 @@ func TestCreateSourceGitExport(t *testing.T) {
}, },
{ {
name: "no args", name: "no args",
args: "create source git --url=https://github.com/stefanprodan/podinfo", args: "create secret git",
assert: assertError("name is required"), assert: assertError("name is required"),
}, },
{ {
@ -204,13 +204,12 @@ func TestCreateSourceGit(t *testing.T) {
ObservedGeneration: repo.GetGeneration(), ObservedGeneration: repo.GetGeneration(),
} }
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition) apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
repo.Status.Artifact = &meta.Artifact{ repo.Status.Artifact = &sourcev1.Artifact{
Path: "some-path", Path: "some-path",
Revision: "v1", Revision: "v1",
LastUpdateTime: metav1.Time{ LastUpdateTime: metav1.Time{
Time: time.Now(), Time: time.Now(),
}, },
Digest: "sha256:1234567890abcdef",
} }
repo.Status.ObservedGeneration = repo.GetGeneration() repo.Status.ObservedGeneration = repo.GetGeneration()
}, },

@ -114,6 +114,12 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
tmpDir, err := os.MkdirTemp("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
if _, err := url.Parse(sourceHelmArgs.url); err != nil { if _, err := url.Parse(sourceHelmArgs.url); err != nil {
return fmt.Errorf("url parse failed: %w", err) return fmt.Errorf("url parse failed: %w", err)
} }
@ -196,7 +202,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
TLSKey: keyFile, TLSKey: keyFile,
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
} }
secret, err := sourcesecret.GenerateHelm(secretOpts) secret, err := sourcesecret.Generate(secretOpts)
if err != nil { if err != nil {
return err return err
} }

@ -21,6 +21,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
@ -31,8 +32,6 @@ import (
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils"
) )
var createTenantCmd = &cobra.Command{ var createTenantCmd = &cobra.Command{
@ -60,8 +59,6 @@ const (
type tenantFlags struct { type tenantFlags struct {
namespaces []string namespaces []string
clusterRole string clusterRole string
account string
skipNamespace bool
} }
var tenantArgs tenantFlags var tenantArgs tenantFlags
@ -69,8 +66,6 @@ var tenantArgs tenantFlags
func init() { func init() {
createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant") createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant")
createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding") createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createTenantCmd.Flags().StringVar(&tenantArgs.account, "with-service-account", "", "service account belonging to this tenant")
createTenantCmd.Flags().BoolVar(&tenantArgs.skipNamespace, "skip-namespace", false, "skip namespace creation (namespace must exist already)")
createCmd.AddCommand(createTenantCmd) createCmd.AddCommand(createTenantCmd)
} }
@ -112,17 +107,9 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
} }
namespaces = append(namespaces, namespace) namespaces = append(namespaces, namespace)
accountName := tenant
if tenantArgs.account != "" {
accountName = tenantArgs.account
}
if err := validation.IsQualifiedName(accountName); len(err) > 0 {
return fmt.Errorf("invalid service-account name '%s': %v", accountName, err)
}
account := corev1.ServiceAccount{ account := corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: accountName, Name: tenant,
Namespace: ns, Namespace: ns,
Labels: objLabels, Labels: objLabels,
}, },
@ -144,7 +131,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
}, },
{ {
Kind: "ServiceAccount", Kind: "ServiceAccount",
Name: accountName, Name: tenant,
Namespace: ns, Namespace: ns,
}, },
}, },
@ -159,7 +146,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
if createArgs.export { if createArgs.export {
for i := range tenantArgs.namespaces { for i := range tenantArgs.namespaces {
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i], tenantArgs.skipNamespace); err != nil { if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
return err return err
} }
} }
@ -175,12 +162,10 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
} }
for i := range tenantArgs.namespaces { for i := range tenantArgs.namespaces {
if !tenantArgs.skipNamespace {
logger.Actionf("applying namespace %s", namespaces[i].Name) logger.Actionf("applying namespace %s", namespaces[i].Name)
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil { if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
return err return err
} }
}
logger.Actionf("applying service account %s", accounts[i].Name) logger.Actionf("applying service account %s", accounts[i].Name)
if err := upsertServiceAccount(ctx, kubeClient, accounts[i]); err != nil { if err := upsertServiceAccount(ctx, kubeClient, accounts[i]); err != nil {
@ -288,24 +273,19 @@ func upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBindin
return nil return nil
} }
func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, roleBinding rbacv1.RoleBinding, skipNamespace bool) error { func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, roleBinding rbacv1.RoleBinding) error {
var data []byte
var err error
if !skipNamespace {
namespace.TypeMeta = metav1.TypeMeta{ namespace.TypeMeta = metav1.TypeMeta{
APIVersion: "v1", APIVersion: "v1",
Kind: "Namespace", Kind: "Namespace",
} }
data, err = yaml.Marshal(namespace) data, err := yaml.Marshal(namespace)
if err != nil { if err != nil {
return err return err
} }
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
printlnStdout("---") fmt.Println("---")
printlnStdout(resourceToString(data)) data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
} fmt.Println(resourceToString(data))
account.TypeMeta = metav1.TypeMeta{ account.TypeMeta = metav1.TypeMeta{
APIVersion: "v1", APIVersion: "v1",
@ -315,10 +295,10 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
if err != nil { if err != nil {
return err return err
} }
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
printlnStdout("---") fmt.Println("---")
printlnStdout(resourceToString(data)) data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
fmt.Println(resourceToString(data))
roleBinding.TypeMeta = metav1.TypeMeta{ roleBinding.TypeMeta = metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1", APIVersion: "rbac.authorization.k8s.io/v1",
@ -329,8 +309,8 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err return err
} }
printlnStdout("---") fmt.Println("---")
printlnStdout(resourceToString(data)) fmt.Println(resourceToString(data))
return nil return nil
} }

@ -1,73 +0,0 @@
//go:build e2e
// +build e2e
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestCreateTenant(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "no args",
args: "create tenant",
assert: assertError("name is required"),
},
{
name: "no namespace",
args: "create tenant dev-team --cluster-role=cluster-admin",
assert: assertError("with-namespace is required"),
},
{
name: "basic tenant",
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --export",
assert: assertGoldenFile("./testdata/create_tenant/tenant-basic.yaml"),
},
{
name: "tenant with custom serviceaccount",
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --with-service-account=flux-tenant --export",
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-service-account.yaml"),
},
{
name: "tenant with custom cluster role",
args: "create tenant dev-team --with-namespace=apps --cluster-role=custom-role --export",
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-cluster-role.yaml"),
},
{
name: "tenant with skip namespace",
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --skip-namespace --export",
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-skip-namespace.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

@ -40,10 +40,7 @@ WARNING: This command will print sensitive information if Kubernetes Secrets are
flux debug hr podinfo --show-status flux debug hr podinfo --show-status
# Export the final values of a Helm release composed from referred ConfigMaps and Secrets # Export the final values of a Helm release composed from referred ConfigMaps and Secrets
flux debug hr podinfo --show-values > values.yaml flux debug hr podinfo --show-values > values.yaml`,
# Print the reconciliation history of a Helm release
flux debug hr podinfo --show-history`,
RunE: debugHelmReleaseCmdRun, RunE: debugHelmReleaseCmdRun,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
@ -52,7 +49,6 @@ WARNING: This command will print sensitive information if Kubernetes Secrets are
type debugHelmReleaseFlags struct { type debugHelmReleaseFlags struct {
showStatus bool showStatus bool
showValues bool showValues bool
showHistory bool
} }
var debugHelmReleaseArgs debugHelmReleaseFlags var debugHelmReleaseArgs debugHelmReleaseFlags
@ -60,25 +56,15 @@ var debugHelmReleaseArgs debugHelmReleaseFlags
func init() { func init() {
debugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showStatus, "show-status", false, "print the status of the Helm release") debugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showStatus, "show-status", false, "print the status of the Helm release")
debugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showValues, "show-values", false, "print the final values of the Helm release") debugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showValues, "show-values", false, "print the final values of the Helm release")
debugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showHistory, "show-history", false, "print the reconciliation history of the Helm release")
debugCmd.AddCommand(debugHelmReleaseCmd) debugCmd.AddCommand(debugHelmReleaseCmd)
} }
func debugHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { func debugHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
flagsSet := 0 if (!debugHelmReleaseArgs.showStatus && !debugHelmReleaseArgs.showValues) ||
if debugHelmReleaseArgs.showStatus { (debugHelmReleaseArgs.showStatus && debugHelmReleaseArgs.showValues) {
flagsSet++ return fmt.Errorf("either --show-status or --show-values must be set")
}
if debugHelmReleaseArgs.showValues {
flagsSet++
}
if debugHelmReleaseArgs.showHistory {
flagsSet++
}
if flagsSet != 1 {
return fmt.Errorf("exactly one of --show-status, --show-values, or --show-history must be set")
} }
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
@ -123,20 +109,5 @@ func debugHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
rootCmd.Print(string(values)) rootCmd.Print(string(values))
} }
if debugHelmReleaseArgs.showHistory {
if len(hr.Status.History) == 0 {
hr.Status.History = helmv2.Snapshots{}
}
history, err := yaml.Marshal(hr.Status.History)
if err != nil {
return err
}
rootCmd.Println("# History documentation: https://fluxcd.io/flux/components/helm/helmreleases/#history")
rootCmd.Print(string(history))
return nil
}
return nil return nil
} }

@ -56,18 +56,6 @@ func TestDebugHelmRelease(t *testing.T) {
"testdata/debug_helmrelease/values-from.golden.yaml", "testdata/debug_helmrelease/values-from.golden.yaml",
tmpl, tmpl,
}, },
{
"debug history",
"debug helmrelease test-with-history --show-history --show-status=false",
"testdata/debug_helmrelease/history.golden.yaml",
tmpl,
},
{
"debug history empty",
"debug helmrelease test-values-inline --show-history --show-status=false",
"testdata/debug_helmrelease/history-empty.golden.yaml",
tmpl,
},
} }
for _, tt := range cases { for _, tt := range cases {

@ -24,7 +24,6 @@ import (
"strings" "strings"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/kustomize" "github.com/fluxcd/pkg/kustomize"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -45,10 +44,7 @@ WARNING: This command will print sensitive information if Kubernetes Secrets are
flux debug ks podinfo --show-status flux debug ks podinfo --show-status
# Export the final variables used for post-build substitutions composed from referred ConfigMaps and Secrets # Export the final variables used for post-build substitutions composed from referred ConfigMaps and Secrets
flux debug ks podinfo --show-vars > vars.env flux debug ks podinfo --show-vars > vars.env`,
# Print the reconciliation history of a Flux Kustomization
flux debug ks podinfo --show-history`,
RunE: debugKustomizationCmdRun, RunE: debugKustomizationCmdRun,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
@ -57,7 +53,6 @@ WARNING: This command will print sensitive information if Kubernetes Secrets are
type debugKustomizationFlags struct { type debugKustomizationFlags struct {
showStatus bool showStatus bool
showVars bool showVars bool
showHistory bool
} }
var debugKustomizationArgs debugKustomizationFlags var debugKustomizationArgs debugKustomizationFlags
@ -65,25 +60,15 @@ var debugKustomizationArgs debugKustomizationFlags
func init() { func init() {
debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showStatus, "show-status", false, "print the status of the Flux Kustomization") debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showStatus, "show-status", false, "print the status of the Flux Kustomization")
debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showVars, "show-vars", false, "print the final vars of the Flux Kustomization in dot env format") debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showVars, "show-vars", false, "print the final vars of the Flux Kustomization in dot env format")
debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showHistory, "show-history", false, "print the reconciliation history of the Flux Kustomization")
debugCmd.AddCommand(debugKustomizationCmd) debugCmd.AddCommand(debugKustomizationCmd)
} }
func debugKustomizationCmdRun(cmd *cobra.Command, args []string) error { func debugKustomizationCmdRun(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
flagsSet := 0 if (!debugKustomizationArgs.showStatus && !debugKustomizationArgs.showVars) ||
if debugKustomizationArgs.showStatus { (debugKustomizationArgs.showStatus && debugKustomizationArgs.showVars) {
flagsSet++ return fmt.Errorf("either --show-status or --show-vars must be set")
}
if debugKustomizationArgs.showVars {
flagsSet++
}
if debugKustomizationArgs.showHistory {
flagsSet++
}
if flagsSet != 1 {
return fmt.Errorf("exactly one of --show-status, --show-vars, or --show-history must be set")
} }
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
@ -145,20 +130,5 @@ func debugKustomizationCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
if debugKustomizationArgs.showHistory {
if len(ks.Status.History) == 0 {
ks.Status.History = meta.History{}
}
history, err := yaml.Marshal(ks.Status.History)
if err != nil {
return err
}
rootCmd.Println("# History documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#history")
rootCmd.Print(string(history))
return nil
}
return nil return nil
} }

@ -55,17 +55,6 @@ func TestDebugKustomization(t *testing.T) {
"debug ks test-from --show-vars --show-status=false", "debug ks test-from --show-vars --show-status=false",
"testdata/debug_kustomization/vars-from.golden.env", "testdata/debug_kustomization/vars-from.golden.env",
tmpl, tmpl,
}, {
"debug history",
"debug ks test-with-history --show-history --show-status=false",
"testdata/debug_kustomization/history.golden.yaml",
tmpl,
},
{
"debug history empty",
"debug ks test --show-history --show-status=false",
"testdata/debug_kustomization/history-empty.golden.yaml",
tmpl,
}, },
} }

@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var deleteImagePolicyCmd = &cobra.Command{ var deleteImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Delete an ImagePolicy object", Short: "Delete an ImagePolicy object",
Long: `The delete image policy command deletes the given ImagePolicy from the cluster.`, Long: withPreviewNote(`The delete image policy command deletes the given ImagePolicy from the cluster.`),
Example: ` # Delete an image policy Example: ` # Delete an image policy
flux delete image policy alpine3.x`, flux delete image policy alpine3.x`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)), ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),

@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var deleteImageRepositoryCmd = &cobra.Command{ var deleteImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Delete an ImageRepository object", Short: "Delete an ImageRepository object",
Long: "The delete image repository command deletes the given ImageRepository from the cluster.", Long: withPreviewNote("The delete image repository command deletes the given ImageRepository from the cluster."),
Example: ` # Delete an image repository Example: ` # Delete an image repository
flux delete image repository alpine`, flux delete image repository alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),

@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
) )
var deleteImageUpdateCmd = &cobra.Command{ var deleteImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Delete an ImageUpdateAutomation object", Short: "Delete an ImageUpdateAutomation object",
Long: `The delete image update command deletes the given ImageUpdateAutomation from the cluster.`, Long: withPreviewNote(`The delete image update command deletes the given ImageUpdateAutomation from the cluster.`),
Example: ` # Delete an image update automation Example: ` # Delete an image update automation
flux delete image update latest-images`, flux delete image update latest-images`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),

@ -93,7 +93,7 @@ func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider { if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with provider credentials")
opt, _, err := loginWithProvider(ctx, url, diffArtifactArgs.provider.String()) opt, err := loginWithProvider(ctx, url, diffArtifactArgs.provider.String())
if err != nil { if err != nil {
return fmt.Errorf("error during login with provider: %w", err) return fmt.Errorf("error during login with provider: %w", err)
} }

@ -27,7 +27,6 @@ import (
"github.com/fluxcd/flux2/v2/internal/build" "github.com/fluxcd/flux2/v2/internal/build"
"github.com/fluxcd/pkg/ssa" "github.com/fluxcd/pkg/ssa"
"github.com/fluxcd/pkg/ssa/normalize"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
@ -152,7 +151,7 @@ func createObjectFromFile(objectFile string, templateValues map[string]string, t
t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err) t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
} }
if err := normalize.UnstructuredList(clientObjects); err != nil { if err := ssa.SetNativeKindsDefaults(clientObjects); err != nil {
t.Fatalf("Error setting native kinds defaults for '%s': %v", objectFile, err) t.Fatalf("Error setting native kinds defaults for '%s': %v", objectFile, err)
} }

@ -20,6 +20,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -39,13 +40,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2" helmv2 "github.com/fluxcd/helm-controller/api/v2"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/printers" "github.com/fluxcd/flux2/v2/pkg/printers"
@ -112,12 +112,7 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
} }
var diffRefNs bool var diffRefNs bool
// Build the base list options. When --all-namespaces is set we must NOT constrain the clientListOpts := []client.ListOption{client.InNamespace(*kubeconfigArgs.Namespace)}
// query to a single namespace, otherwise we silently return a partial result set.
clientListOpts := []client.ListOption{}
if !eventArgs.allNamespaces {
clientListOpts = append(clientListOpts, client.InNamespace(*kubeconfigArgs.Namespace))
}
var refListOpts [][]client.ListOption var refListOpts [][]client.ListOption
if eventArgs.forSelector != "" { if eventArgs.forSelector != "" {
kind, name := getKindNameFromSelector(eventArgs.forSelector) kind, name := getKindNameFromSelector(eventArgs.forSelector)
@ -251,7 +246,7 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
hdr = getHeaders(showNs) hdr = getHeaders(showNs)
firstIteration = false firstIteration = false
} }
return printers.TablePrinter(hdr).Print(rootCmd.OutOrStdout(), [][]string{rows}) return printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
} }
for _, refOpts := range refListOpts { for _, refOpts := range refListOpts {
@ -455,7 +450,6 @@ var fluxKindMap = refMap{
sourcev1.HelmRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)}, sourcev1.HelmRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)},
autov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)}, autov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)},
imagev1.ImageRepositoryKind: {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)}, imagev1.ImageRepositoryKind: {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)},
swapi.ArtifactGeneratorKind: {gvk: swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)},
} }
func ignoreEvent(e corev1.Event) bool { func ignoreEvent(e corev1.Event) bool {

@ -140,7 +140,7 @@ spec:
address: https://hooks.slack.com/services/mock address: https://hooks.slack.com/services/mock
type: slack type: slack
--- ---
apiVersion: image.toolkit.fluxcd.io/v1 apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy kind: ImagePolicy
metadata: metadata:
name: podinfo name: podinfo

@ -109,13 +109,13 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func printExport(export any) error { func printExport(export interface{}) error {
data, err := yaml.Marshal(export) data, err := yaml.Marshal(export)
if err != nil { if err != nil {
return err return err
} }
printlnStdout("---") rootCmd.Println("---")
printlnStdout(resourceToString(data)) rootCmd.Println(resourceToString(data))
return nil return nil
} }

@ -1,31 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
)
var exportArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Export artifact objects",
Long: `The export artifact sub-commands export artifacts objects in YAML format.`,
}
func init() {
exportCmd.AddCommand(exportArtifactCmd)
}

@ -1,72 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
)
var exportArtifactGeneratorCmd = &cobra.Command{
Use: "generator [name]",
Short: "Export ArtifactGenerator resources in YAML format",
Long: "The export artifact generator command exports one or all ArtifactGenerator resources in YAML format.",
Example: ` # Export all ArtifactGenerator resources
flux export artifact generator --all > artifact-generators.yaml
# Export a specific generator
flux export artifact generator my-generator > my-generator.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),
RunE: exportCommand{
object: artifactGeneratorAdapter{&swapi.ArtifactGenerator{}},
list: artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},
}.run,
}
func init() {
exportArtifactCmd.AddCommand(exportArtifactGeneratorCmd)
}
// Export returns an ArtifactGenerator value which has
// extraneous information stripped out.
func exportArtifactGenerator(item *swapi.ArtifactGenerator) interface{} {
gvk := swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)
export := swapi.ArtifactGenerator{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: item.Name,
Namespace: item.Namespace,
Labels: item.Labels,
Annotations: item.Annotations,
},
Spec: item.Spec,
}
return export
}
func (ex artifactGeneratorAdapter) export() interface{} {
return exportArtifactGenerator(ex.ArtifactGenerator)
}
func (ex artifactGeneratorListAdapter) exportItem(i int) interface{} {
return exportArtifactGenerator(&ex.ArtifactGeneratorList.Items[i])
}

@ -20,13 +20,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var exportImagePolicyCmd = &cobra.Command{ var exportImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Export ImagePolicy resources in YAML format", Short: "Export ImagePolicy resources in YAML format",
Long: "The export image policy command exports one or all ImagePolicy resources in YAML format.", Long: withPreviewNote("The export image policy command exports one or all ImagePolicy resources in YAML format."),
Example: ` # Export all ImagePolicy resources Example: ` # Export all ImagePolicy resources
flux export image policy --all > image-policies.yaml flux export image policy --all > image-policies.yaml

@ -20,13 +20,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var exportImageRepositoryCmd = &cobra.Command{ var exportImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Export ImageRepository resources in YAML format", Short: "Export ImageRepository resources in YAML format",
Long: "The export image repository command exports one or all ImageRepository resources in YAML format.", Long: withPreviewNote("The export image repository command exports one or all ImageRepository resources in YAML format."),
Example: ` # Export all ImageRepository resources Example: ` # Export all ImageRepository resources
flux export image repository --all > image-repositories.yaml flux export image repository --all > image-repositories.yaml

@ -20,13 +20,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
) )
var exportImageUpdateCmd = &cobra.Command{ var exportImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Export ImageUpdateAutomation resources in YAML format", Short: "Export ImageUpdateAutomation resources in YAML format",
Long: "The export image update command exports one or all ImageUpdateAutomation resources in YAML format.", Long: withPreviewNote("The export image update command exports one or all ImageUpdateAutomation resources in YAML format."),
Example: ` # Export all ImageUpdateAutomation resources Example: ` # Export all ImageUpdateAutomation resources
flux export image update --all > updates.yaml flux export image update --all > updates.yaml

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

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

@ -1,32 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
)
var getArtifactCmd = &cobra.Command{
Use: "artifacts",
Aliases: []string{"artifact"},
Short: "Get artifact object status",
Long: `The get artifact sub-commands print the status of artifact objects.`,
}
func init() {
getCmd.AddCommand(getArtifactCmd)
}

@ -1,93 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
)
var getArtifactGeneratorCmd = &cobra.Command{
Use: "generators",
Aliases: []string{"generator"},
Short: "Get artifact generator statuses",
Long: `The get artifact generator command prints the statuses of the resources.`,
Example: ` # List all ArtifactGenerators and their status
flux get artifact generators`,
ValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
apiType: receiverType,
list: artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*swapi.ArtifactGenerator)
if !ok {
return nil, fmt.Errorf("impossible to cast type %#v generator", obj)
}
sink := artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{
Items: []swapi.ArtifactGenerator{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
}
func init() {
getArtifactCmd.AddCommand(getArtifactGeneratorCmd)
}
func (s artifactGeneratorListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
cases.Title(language.English).String(strconv.FormatBool(item.IsDisabled())), status, msg)
}
func (s artifactGeneratorListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Suspended", "Ready", "Message"}
if includeNamespace {
return append(namespaceHeader, headers...)
}
return headers
}
func (s artifactGeneratorListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

@ -19,14 +19,14 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var getImageAllCmd = &cobra.Command{ var getImageAllCmd = &cobra.Command{
Use: "all", Use: "all",
Short: "Get all image statuses", Short: "Get all image statuses",
Long: "The get image sub-commands print the statuses of all image objects.", Long: withPreviewNote("The get image sub-commands print the statuses of all image objects."),
Example: ` # List all image objects in a namespace Example: ` # List all image objects in a namespace
flux get images all --namespace=flux-system flux get images all --namespace=flux-system

@ -22,13 +22,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var getImagePolicyCmd = &cobra.Command{ var getImagePolicyCmd = &cobra.Command{
Use: "policy", Use: "policy",
Short: "Get ImagePolicy status", Short: "Get ImagePolicy status",
Long: "The get image policy command prints the status of ImagePolicy objects.", Long: withPreviewNote("The get image policy command prints the status of ImagePolicy objects."),
Example: ` # List all image policies and their status Example: ` # List all image policies and their status
flux get image policy flux get image policy
@ -74,16 +74,11 @@ func init() {
func (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { func (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i] item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
var image, tag string return append(nameColumns(&item, includeNamespace, includeKind), item.Status.LatestImage, status, msg)
if ref := item.Status.LatestRef; ref != nil {
image = ref.Name
tag = ref.Tag
}
return append(nameColumns(&item, includeNamespace, includeKind), image, tag, status, msg)
} }
func (s imagePolicyListAdapter) headers(includeNamespace bool) []string { func (s imagePolicyListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Image", "Tag", "Ready", "Message"} headers := []string{"Name", "Latest image", "Ready", "Message"}
if includeNamespace { if includeNamespace {
return append(namespaceHeader, headers...) return append(namespaceHeader, headers...)
} }

@ -26,13 +26,13 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var getImageRepositoryCmd = &cobra.Command{ var getImageRepositoryCmd = &cobra.Command{
Use: "repository", Use: "repository",
Short: "Get ImageRepository status", Short: "Get ImageRepository status",
Long: "The get image repository command prints the status of ImageRepository objects.", Long: withPreviewNote("The get image repository command prints the status of ImageRepository objects."),
Example: ` # List all image repositories and their status Example: ` # List all image repositories and their status
flux get image repository flux get image repository

@ -26,13 +26,13 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
) )
var getImageUpdateCmd = &cobra.Command{ var getImageUpdateCmd = &cobra.Command{
Use: "update", Use: "update",
Short: "Get ImageUpdateAutomation status", Short: "Get ImageUpdateAutomation status",
Long: "The get image update command prints the status of ImageUpdateAutomation objects.", Long: withPreviewNote("The get image update command prints the status of ImageUpdateAutomation objects."),
Example: ` # List all image update automation object and their status Example: ` # List all image update automation object and their status
flux get image update flux get image update

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

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

@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
// These are general-purpose adapters for attaching methods to, for // These are general-purpose adapters for attaching methods to, for
@ -79,34 +77,6 @@ func (a imagePolicyAdapter) asClientObject() client.Object {
return a.ImagePolicy return a.ImagePolicy
} }
func (a imagePolicyAdapter) deepCopyClientObject() client.Object {
return a.ImagePolicy.DeepCopy()
}
func (a imagePolicyAdapter) isStatic() bool {
return false
}
func (a imagePolicyAdapter) lastHandledReconcileRequest() string {
return a.Status.GetLastHandledReconcileRequest()
}
func (a imagePolicyAdapter) isSuspended() bool {
return a.Spec.Suspend
}
func (a imagePolicyAdapter) setSuspended() {
a.Spec.Suspend = true
}
func (a imagePolicyAdapter) successMessage() string {
return fmt.Sprintf("selected ref %s", a.Status.LatestRef.String())
}
func (a imagePolicyAdapter) setUnsuspended() {
a.Spec.Suspend = false
}
// imagev1.ImagePolicyList // imagev1.ImagePolicyList
type imagePolicyListAdapter struct { type imagePolicyListAdapter struct {
@ -121,18 +91,6 @@ func (a imagePolicyListAdapter) len() int {
return len(a.ImagePolicyList.Items) return len(a.ImagePolicyList.Items)
} }
func (a imagePolicyListAdapter) resumeItem(i int) resumable {
return &imagePolicyAdapter{&a.ImagePolicyList.Items[i]}
}
func (obj imagePolicyAdapter) getObservedGeneration() int64 {
return obj.ImagePolicy.Status.ObservedGeneration
}
func (a imagePolicyListAdapter) item(i int) suspendable {
return &imagePolicyAdapter{&a.ImagePolicyList.Items[i]}
}
// autov1.ImageUpdateAutomation // autov1.ImageUpdateAutomation
var imageUpdateAutomationType = apiType{ var imageUpdateAutomationType = apiType{

@ -53,18 +53,6 @@ func TestImageScanning(t *testing.T) {
"get image policy podinfo-regex", "get image policy podinfo-regex",
"testdata/image/get_image_policy_regex.golden", "testdata/image/get_image_policy_regex.golden",
}, },
{
"suspend image policy podinfo-semver",
"testdata/image/suspend_image_policy.golden",
},
{
"resume image policy podinfo-semver",
"testdata/image/resume_image_policy.golden",
},
{
"reconcile image policy podinfo-semver",
"testdata/image/reconcile_image_policy.golden",
},
} }
for _, tc := range cases { for _, tc := range cases {

@ -83,7 +83,7 @@ type installFlags struct {
force bool force bool
} }
var installArgs = newInstallFlags() var installArgs = NewInstallFlags()
func init() { func init() {
installCmd.Flags().BoolVar(&installArgs.export, "export", false, installCmd.Flags().BoolVar(&installArgs.export, "export", false,
@ -93,7 +93,7 @@ func init() {
installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components, installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
installCmd.Flags().StringSliceVar(&installArgs.extraComponents, "components-extra", nil, installCmd.Flags().StringSliceVar(&installArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller,source-watcher'") "list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
installCmd.Flags().StringVar(&installArgs.manifestsPath, "manifests", "", "path to the manifest directory") installCmd.Flags().StringVar(&installArgs.manifestsPath, "manifests", "", "path to the manifest directory")
installCmd.Flags().StringVar(&installArgs.registry, "registry", rootArgs.defaults.Registry, installCmd.Flags().StringVar(&installArgs.registry, "registry", rootArgs.defaults.Registry,
"container registry where the toolkit images are published") "container registry where the toolkit images are published")
@ -115,14 +115,9 @@ func init() {
rootCmd.AddCommand(installCmd) rootCmd.AddCommand(installCmd)
} }
func newInstallFlags() installFlags { func NewInstallFlags() installFlags {
return installFlags{ return installFlags{
logLevel: flags.LogLevel(rootArgs.defaults.LogLevel), logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
defaultComponents: rootArgs.defaults.Components,
registry: rootArgs.defaults.Registry,
watchAllNamespaces: rootArgs.defaults.WatchAllNamespaces,
networkPolicy: rootArgs.defaults.NetworkPolicy,
clusterDomain: rootArgs.defaults.ClusterDomain,
} }
} }
@ -200,13 +195,10 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
} }
if installArgs.export { if installArgs.export {
_, err = rootCmd.OutOrStdout().Write([]byte(manifest.Content)) fmt.Print(manifest.Content)
return err return nil
} else if rootArgs.verbose { } else if rootArgs.verbose {
_, err = rootCmd.OutOrStdout().Write([]byte(manifest.Content)) fmt.Print(manifest.Content)
if err != nil {
return err
}
} }
logger.Successf("manifests build completed") logger.Successf("manifests build completed")
@ -246,7 +238,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("install failed: %w", err) return fmt.Errorf("install failed: %w", err)
} }
rootCmd.Println(applyOutput) fmt.Fprintln(os.Stderr, applyOutput)
if opts.ImagePullSecret != "" && opts.RegistryCredential != "" { if opts.ImagePullSecret != "" && opts.RegistryCredential != "" {
logger.Actionf("generating image pull secret %s", opts.ImagePullSecret) logger.Actionf("generating image pull secret %s", opts.ImagePullSecret)
@ -258,7 +250,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
Username: credentials[0], Username: credentials[0],
Password: credentials[1], Password: credentials[1],
} }
imagePullSecret, err := sourcesecret.GenerateOCI(secretOpts) imagePullSecret, err := sourcesecret.Generate(secretOpts)
if err != nil { if err != nil {
return fmt.Errorf("install failed: %w", err) return fmt.Errorf("install failed: %w", err)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2025 The Flux authors Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -16,17 +16,7 @@ limitations under the License.
package main package main
import ( import "testing"
"strings"
"testing"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
ssautil "github.com/fluxcd/pkg/ssa/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install"
)
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
// The pointer to kubeconfigArgs.Namespace is shared across // The pointer to kubeconfigArgs.Namespace is shared across
@ -69,43 +59,3 @@ func TestInstall(t *testing.T) {
}) })
} }
} }
func TestInstall_ComponentsExtra(t *testing.T) {
g := NewWithT(t)
command := "install --export --components-extra=" +
strings.Join(install.MakeDefaultOptions().ComponentsExtra, ",")
output, err := executeCommand(command)
g.Expect(err).NotTo(HaveOccurred())
manifests, err := ssautil.ReadObjects(strings.NewReader(output))
g.Expect(err).NotTo(HaveOccurred())
foundImageAutomation := false
foundImageReflector := false
foundSourceWatcher := false
foundExternalArtifact := false
for _, obj := range manifests {
if obj.GetKind() == "Deployment" && obj.GetName() == "image-automation-controller" {
foundImageAutomation = true
}
if obj.GetKind() == "Deployment" && obj.GetName() == "image-reflector-controller" {
foundImageReflector = true
}
if obj.GetKind() == "Deployment" && obj.GetName() == "source-watcher" {
foundSourceWatcher = true
}
if obj.GetKind() == "Deployment" &&
(obj.GetName() == "kustomize-controller" || obj.GetName() == "helm-controller") {
containers, _, _ := unstructured.NestedSlice(obj.Object, "spec", "template", "spec", "containers")
g.Expect(containers).ToNot(BeEmpty())
args, _, _ := unstructured.NestedSlice(containers[0].(map[string]any), "args")
g.Expect(args).To(ContainElement("--feature-gates=ExternalArtifact=true"))
foundExternalArtifact = true
}
}
g.Expect(foundImageAutomation).To(BeTrue(), "image-automation-controller deployment not found")
g.Expect(foundImageReflector).To(BeTrue(), "image-reflector-controller deployment not found")
g.Expect(foundSourceWatcher).To(BeTrue(), "source-watcher deployment not found")
g.Expect(foundExternalArtifact).To(BeTrue(), "ExternalArtifact feature gate not found")
}

@ -52,7 +52,7 @@ var listArtifactsCmd = &cobra.Command{
Long: `The list command fetches the tags and their metadata from a remote OCI repository. Long: `The list command fetches the tags and their metadata from a remote OCI repository.
The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`, The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,
Example: ` # List the artifacts stored in an OCI repository Example: ` # List the artifacts stored in an OCI repository
flux list artifacts oci://ghcr.io/org/config/app flux list artifact oci://ghcr.io/org/config/app
`, `,
RunE: listArtifactsCmdRun, RunE: listArtifactsCmdRun,
} }
@ -85,7 +85,7 @@ func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider { if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with provider credentials")
ociOpt, _, err := loginWithProvider(ctx, url, listArtifactArgs.provider.String()) ociOpt, err := loginWithProvider(ctx, url, listArtifactArgs.provider.String())
if err != nil { if err != nil {
return fmt.Errorf("error during login with provider: %w", err) return fmt.Errorf("error during login with provider: %w", err)
} }

@ -180,7 +180,7 @@ func main() {
// This is required because controller-runtime expects its consumers to // This is required because controller-runtime expects its consumers to
// set a logger through log.SetLogger within 30 seconds of the program's // set a logger through log.SetLogger within 30 seconds of the program's
// initialization. If not set, the entire debug stack is printed as an // initalization. If not set, the entire debug stack is printed as an
// error, see: https://github.com/kubernetes-sigs/controller-runtime/blob/ed8be90/pkg/log/log.go#L59 // error, see: https://github.com/kubernetes-sigs/controller-runtime/blob/ed8be90/pkg/log/log.go#L59
// Since we have our own logging and don't care about controller-runtime's // Since we have our own logging and don't care about controller-runtime's
// logger, we configure it's logger to do nothing. // logger, we configure it's logger to do nothing.
@ -225,9 +225,7 @@ func configureDefaultNamespace() {
func readPasswordFromStdin(prompt string) (string, error) { func readPasswordFromStdin(prompt string) (string, error) {
var out string var out string
var err error var err error
if _, err := fmt.Fprint(os.Stdout, prompt); err != nil { fmt.Fprint(os.Stdout, prompt)
return "", fmt.Errorf("failed to write prompt: %w", err)
}
stdinFD := int(os.Stdin.Fd()) stdinFD := int(os.Stdin.Fd())
if term.IsTerminal(stdinFD) { if term.IsTerminal(stdinFD) {
var inBytes []byte var inBytes []byte
@ -249,8 +247,3 @@ While we try our best to not introduce breaking changes, they may occur when
we adapt to new features and/or find better ways to facilitate what it does.` we adapt to new features and/or find better ways to facilitate what it does.`
return fmt.Sprintf("%s\n\n%s", strings.TrimSpace(desc), previewNote) return fmt.Sprintf("%s\n\n%s", strings.TrimSpace(desc), previewNote)
} }
// printlnStdout prints the given text to stdout with a newline.
func printlnStdout(txt string) {
_, _ = rootCmd.OutOrStdout().Write([]byte(txt + "\n"))
}

@ -447,7 +447,6 @@ func resetCmdArgs() {
imagePolicyArgs = imagePolicyFlags{} imagePolicyArgs = imagePolicyFlags{}
imageRepoArgs = imageRepoFlags{} imageRepoArgs = imageRepoFlags{}
imageUpdateArgs = imageUpdateFlags{} imageUpdateArgs = imageUpdateFlags{}
installArgs = newInstallFlags()
kustomizationArgs = NewKustomizationFlags() kustomizationArgs = NewKustomizationFlags()
receiverArgs = receiverFlags{} receiverArgs = receiverFlags{}
resumeArgs = ResumeFlags{} resumeArgs = ResumeFlags{}

@ -1,691 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/fluxcd/pkg/ssa"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1"
imageautov1b2 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
imagev1b2 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
swv1b1 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
"github.com/fluxcd/flux2/v2/internal/utils"
)
// APIVersions holds the mapping of GroupKinds to their respective
// latest API versions for a specific Flux version.
type APIVersions struct {
FluxVersion string
LatestVersions map[schema.GroupKind]string
}
// TODO: Update this mapping when new Flux minor versions are released!
// latestAPIVersions contains the latest API versions for each GroupKind
// for each supported Flux version. We maintain the latest two minor versions.
var latestAPIVersions = []APIVersions{
{
FluxVersion: "2.7",
LatestVersions: map[schema.GroupKind]string{
// source-controller
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
// kustomize-controller
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
// helm-controller
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
// notification-controller
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
// image-reflector-controller
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImageRepositoryKind}: imagev1.GroupVersion.Version,
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImagePolicyKind}: imagev1.GroupVersion.Version,
// image-automation-controller
{Group: imageautov1.GroupVersion.Group, Kind: imageautov1.ImageUpdateAutomationKind}: imageautov1.GroupVersion.Version,
// source-watcher
{Group: swv1b1.GroupVersion.Group, Kind: swv1b1.ArtifactGeneratorKind}: swv1b1.GroupVersion.Version,
},
},
{
FluxVersion: "2.6",
LatestVersions: map[schema.GroupKind]string{
// source-controller
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
// kustomize-controller
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
// helm-controller
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
// notification-controller
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
// image-reflector-controller
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImageRepositoryKind}: imagev1b2.GroupVersion.Version,
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImagePolicyKind}: imagev1b2.GroupVersion.Version,
// image-automation-controller
{Group: imageautov1b2.GroupVersion.Group, Kind: imageautov1b2.ImageUpdateAutomationKind}: imageautov1b2.GroupVersion.Version,
},
},
}
var migrateCmd = &cobra.Command{
Use: "migrate",
Args: cobra.NoArgs,
Short: "Migrate the Flux custom resources to their latest API version",
Long: `The migrate command must be run before a Flux minor version upgrade.
The command has two modes of operation:
- Cluster mode (default): migrates all the Flux custom resources stored in Kubernetes etcd to their latest API version.
- File system mode (-f): migrates the Flux custom resources defined in the manifests located in the specified path.
`,
Example: ` # Migrate all the Flux custom resources in the cluster.
# This uses the current kubeconfig context and requires cluster-admin permissions.
flux migrate
# Migrate all the Flux custom resources in a Git repository
# checked out in the current working directory.
flux migrate -f .
# Migrate all Flux custom resources defined in YAML and Helm YAML template files.
flux migrate -f . --extensions=.yml,.yaml,.tpl
# Migrate the Flux custom resources to the latest API versions of Flux 2.6.
flux migrate -f . --version=2.6
# Migrate the Flux custom resources defined in a multi-document YAML manifest file.
flux migrate -f path/to/manifest.yaml
# Simulate the migration without making any changes.
flux migrate -f . --dry-run
# Run the migration skipping confirmation prompts.
flux migrate -f . --yes
`,
RunE: runMigrateCmd,
}
var migrateFlags struct {
yes bool
dryRun bool
path string
version string
extensions []string
}
func init() {
rootCmd.AddCommand(migrateCmd)
migrateCmd.Flags().StringVarP(&migrateFlags.path, "path", "f", "",
"the path to the directory containing the manifests to migrate")
migrateCmd.Flags().StringSliceVarP(&migrateFlags.extensions, "extensions", "e", []string{".yaml", ".yml"},
"the file extensions to consider when migrating manifests, only applicable with --path")
migrateCmd.Flags().StringVarP(&migrateFlags.version, "version", "v", "",
"the target Flux minor version to migrate manifests to, only applicable with --path (defaults to the version of the CLI)")
migrateCmd.Flags().BoolVarP(&migrateFlags.yes, "yes", "y", false,
"skip confirmation prompts when migrating manifests, only applicable with --path")
migrateCmd.Flags().BoolVar(&migrateFlags.dryRun, "dry-run", false,
"simulate the migration of manifests without making any changes, only applicable with --path")
}
func runMigrateCmd(*cobra.Command, []string) error {
if migrateFlags.path == "" {
return migrateCluster()
}
return migrateFileSystem()
}
func migrateCluster() error {
logger.Actionf("starting migration of custom resources")
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
if err != nil {
return fmt.Errorf("the Kubernetes client initialization failed: %w", err)
}
kubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()})
if err != nil {
return err
}
migrator := NewClusterMigrator(kubeClient, client.MatchingLabels{
"app.kubernetes.io/part-of": "flux",
})
if err := migrator.Run(ctx); err != nil {
return err
}
logger.Successf("custom resources migrated successfully")
return nil
}
func migrateFileSystem() error {
pathRoot, err := os.OpenRoot(".")
if err != nil {
return fmt.Errorf("failed to open filesystem at the current working directory: %w", err)
}
defer pathRoot.Close()
fileSystem := &osFS{pathRoot.FS()}
yes := migrateFlags.yes
dryRun := migrateFlags.dryRun
path := migrateFlags.path
extensions := migrateFlags.extensions
var latestVersions map[schema.GroupKind]string
// Determine latest API versions based on the Flux version.
if migrateFlags.version == "" {
latestVersions = latestAPIVersions[0].LatestVersions
} else {
supportedVersions := make([]string, 0, len(latestAPIVersions))
for _, v := range latestAPIVersions {
if v.FluxVersion == migrateFlags.version {
latestVersions = v.LatestVersions
break
}
supportedVersions = append(supportedVersions, v.FluxVersion)
}
if latestVersions == nil {
return fmt.Errorf("version %s is not supported, supported versions are: %s",
migrateFlags.version, strings.Join(supportedVersions, ", "))
}
}
return NewFileSystemMigrator(fileSystem, yes, dryRun, path, extensions, latestVersions).Run()
}
// ClusterMigrator migrates all the CRs in the cluster for the CRDs matching the label selector.
type ClusterMigrator struct {
labelSelector client.MatchingLabels
kubeClient client.Client
}
// NewClusterMigrator creates a new ClusterMigrator instance with the specified label selector.
func NewClusterMigrator(kubeClient client.Client, labelSelector client.MatchingLabels) *ClusterMigrator {
return &ClusterMigrator{
labelSelector: labelSelector,
kubeClient: kubeClient,
}
}
func (c *ClusterMigrator) Run(ctx context.Context) error {
crdList := &apiextensionsv1.CustomResourceDefinitionList{}
if err := c.kubeClient.List(ctx, crdList, c.labelSelector); err != nil {
return fmt.Errorf("failed to list CRDs: %w", err)
}
for _, crd := range crdList.Items {
if err := c.migrateCRD(ctx, crd.Name); err != nil {
return err
}
}
return nil
}
func (c *ClusterMigrator) migrateCRD(ctx context.Context, name string) error {
crd := &apiextensionsv1.CustomResourceDefinition{}
if err := c.kubeClient.Get(ctx, client.ObjectKey{Name: name}, crd); err != nil {
return fmt.Errorf("failed to get CRD %s: %w", name, err)
}
// get the latest storage version for the CRD
storageVersion := c.getStorageVersion(crd)
if storageVersion == "" {
return fmt.Errorf("no storage version found for CRD %s", name)
}
// migrate all the resources for the CRD
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
return c.migrateCR(ctx, crd, storageVersion)
})
if err != nil {
return fmt.Errorf("failed to migrate resources for CRD %s: %w", name, err)
}
// set the CRD status to contain only the latest storage version
if len(crd.Status.StoredVersions) > 1 || crd.Status.StoredVersions[0] != storageVersion {
crd.Status.StoredVersions = []string{storageVersion}
if err := c.kubeClient.Status().Update(ctx, crd); err != nil {
return fmt.Errorf("failed to update CRD %s status: %w", crd.Name, err)
}
logger.Successf("%s migrated to storage version %s", crd.Name, storageVersion)
}
return nil
}
// migrateCR migrates all CRs for the given CRD to the specified version by patching them.
func (c *ClusterMigrator) migrateCR(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition, version string) error {
list := &unstructured.UnstructuredList{}
apiVersion := crd.Spec.Group + "/" + version
listKind := crd.Spec.Names.ListKind
list.SetAPIVersion(apiVersion)
list.SetKind(listKind)
err := c.kubeClient.List(ctx, list, client.InNamespace(""))
if err != nil {
return fmt.Errorf("failed to list resources for CRD %s: %w", crd.Name, err)
}
if len(list.Items) == 0 {
return nil
}
for _, item := range list.Items {
patches, err := ssa.PatchMigrateToVersion(&item, apiVersion)
if err != nil {
return fmt.Errorf("failed to create migration patch for %s/%s/%s: %w",
item.GetKind(), item.GetNamespace(), item.GetName(), err)
}
if len(patches) == 0 {
// patch the resource with an empty patch to update the version
if err := c.kubeClient.Patch(
ctx,
&item,
client.RawPatch(client.Merge.Type(), []byte("{}")),
); err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf(" %s/%s/%s failed to migrate: %w",
item.GetKind(), item.GetNamespace(), item.GetName(), err)
}
} else {
// patch the resource to migrate the managed fields to the latest apiVersion
rawPatch, err := json.Marshal(patches)
if err != nil {
return fmt.Errorf("failed to marshal migration patch for %s/%s/%s: %w",
item.GetKind(), item.GetNamespace(), item.GetName(), err)
}
if err := c.kubeClient.Patch(
ctx,
&item,
client.RawPatch(types.JSONPatchType, rawPatch),
); err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf(" %s/%s/%s failed to migrate managed fields: %w",
item.GetKind(), item.GetNamespace(), item.GetName(), err)
}
}
logger.Successf("%s/%s/%s migrated to version %s",
item.GetKind(), item.GetNamespace(), item.GetName(), version)
}
return nil
}
// getStorageVersion retrieves the storage version of a CustomResourceDefinition.
func (c *ClusterMigrator) getStorageVersion(crd *apiextensionsv1.CustomResourceDefinition) string {
var version string
for _, v := range crd.Spec.Versions {
if v.Storage {
version = v.Name
break
}
}
return version
}
// WritableFS extends fs.FS with a WriteFile method.
type WritableFS interface {
fs.FS
WriteFile(name string, data []byte, perm os.FileMode) error
}
// osFS is a WritableFS implementation that uses the file system of the OS.
type osFS struct {
fs.FS
}
func (o *osFS) WriteFile(name string, data []byte, perm os.FileMode) error {
return os.WriteFile(name, data, perm)
}
// FileSystemMigrator migrates all the CRs found in the manifests located in the specified path.
type FileSystemMigrator struct {
fileSystem WritableFS
yes bool
dryRun bool
path string
extensions []string
latestVersions map[schema.GroupKind]string
}
// FileAPIUpgrades represents the API upgrades detected in a specific manifest file.
type FileAPIUpgrades struct {
File string
Upgrades []APIUpgrade
}
// APIUpgrade represents an upgrade of a specific API version in a manifest file.
type APIUpgrade struct {
Line int
Kind string
OldVersion string
NewVersion string
}
// NewFileSystemMigrator creates a new FileSystemMigrator instance with the specified flags.
func NewFileSystemMigrator(fileSystem WritableFS, yes, dryRun bool, path string,
extensions []string, latestVersions map[schema.GroupKind]string) *FileSystemMigrator {
return &FileSystemMigrator{
fileSystem: fileSystem,
yes: yes,
dryRun: dryRun,
path: filepath.Clean(path), // convert dir/ to dir to avoid error when walking
extensions: extensions,
latestVersions: latestVersions,
}
}
func (f *FileSystemMigrator) Run() error {
logger.Actionf("starting migration of custom resources")
// List and filter files.
files, err := f.listFiles()
if err != nil {
return err
}
// Detect upgrades.
upgrades, err := f.detectUpgrades(files)
if err != nil {
return err
}
if len(upgrades) == 0 {
logger.Successf("no custom resources found that require migration")
return nil
}
if f.dryRun {
logger.Successf("dry-run mode enabled, no changes will be made")
return nil
}
// Confirm upgrades.
if !f.yes {
prompt := promptui.Prompt{
Label: "Are you sure you want to proceed with the above upgrades", // Already prints "? [y/N]"
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return err
}
}
// Migrate files.
for _, fileUpgrades := range upgrades {
if err := f.migrateFile(&fileUpgrades); err != nil {
return err
}
logger.Successf("file %s migrated successfully", fileUpgrades.File)
}
logger.Successf("custom resources migrated successfully")
return nil
}
func (f *FileSystemMigrator) listFiles() ([]string, error) {
fileInfo, err := fs.Stat(f.fileSystem, f.path)
if err != nil {
return nil, fmt.Errorf("failed to stat path %s: %w", f.path, err)
}
if fileInfo.IsDir() {
return f.listDirectoryFiles()
}
if err := f.validateSingleFile(); err != nil {
return nil, err
}
return []string{f.path}, nil
}
func (f *FileSystemMigrator) listDirectoryFiles() ([]string, error) {
var files []string
err := fs.WalkDir(f.fileSystem, f.path, func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if !f.matchesExtensions(path) {
return nil
}
fileInfo, err := dirEntry.Info()
if err != nil {
return err
}
if fileInfo.Mode().IsRegular() {
files = append(files, path)
} else if !fileInfo.IsDir() {
logger.Warningf("skipping irregular file %s", path)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk directory %s: %w", f.path, err)
}
return files, nil
}
func (f *FileSystemMigrator) validateSingleFile() error {
if !f.matchesExtensions(f.path) {
return fmt.Errorf("file %s does not match the specified extensions: %v",
f.path, strings.Join(f.extensions, ", "))
}
// Check if it's irregular by walking the parent directory.
var irregular bool
err := fs.WalkDir(f.fileSystem, filepath.Dir(f.path), func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if path != f.path {
return nil
}
fileInfo, err := dirEntry.Info()
if err != nil {
return err
}
if !fileInfo.Mode().IsRegular() {
irregular = true
}
return nil
})
if err != nil {
return fmt.Errorf("failed to validate file %s: %w", f.path, err)
}
if irregular {
return fmt.Errorf("file %s is irregular", f.path)
}
return nil
}
func (f *FileSystemMigrator) matchesExtensions(file string) bool {
for _, ext := range f.extensions {
if strings.HasSuffix(file, ext) {
return true
}
}
return false
}
func (f *FileSystemMigrator) detectUpgrades(files []string) ([]FileAPIUpgrades, error) {
var upgrades []FileAPIUpgrades
for _, file := range files {
fileUpgrades, err := f.detectFileUpgrades(file)
if err != nil {
return nil, err
}
if len(fileUpgrades) == 0 {
continue
}
fu := FileAPIUpgrades{
File: file,
Upgrades: fileUpgrades,
}
upgrades = append(upgrades, fu)
f.printDetectedUpgrades(&fu)
}
return upgrades, nil
}
func (f *FileSystemMigrator) detectFileUpgrades(file string) ([]APIUpgrade, error) {
b, err := fs.ReadFile(f.fileSystem, file)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %w", file, err)
}
lines := strings.Split(string(b), "\n")
var fileUpgrades []APIUpgrade
for line, apiVersionLine := range lines {
// Parse apiVersion.
const apiVersionPrefix = "apiVersion: "
idx := strings.Index(apiVersionLine, apiVersionPrefix)
if idx == -1 {
continue
}
apiVersionValuePrefix := strings.TrimSpace(apiVersionLine[idx+len(apiVersionPrefix):])
apiVersion := strings.Split(apiVersionValuePrefix, " ")[0]
gv, err := schema.ParseGroupVersion(apiVersion)
if err != nil {
logger.Warningf("%s:%d: %v", file, line+1, err)
continue
}
// Parse kind.
if line+1 >= len(lines) {
continue
}
kindLine := lines[line+1]
const kindPrefix = "kind: "
idx = strings.Index(kindLine, kindPrefix)
if idx == -1 {
continue
}
kindValuePrefix := strings.TrimSpace(kindLine[idx+len(kindPrefix):])
kind := strings.Split(kindValuePrefix, " ")[0]
// Build GroupKind.
gk := schema.GroupKind{
Group: gv.Group,
Kind: kind,
}
// Check if there's a newer version for the GroupKind.
latestVersion, ok := f.latestVersions[gk]
if !ok || latestVersion == gv.Version {
continue
}
// Record the upgrade.
fileUpgrades = append(fileUpgrades, APIUpgrade{
Line: line,
Kind: kind,
OldVersion: gv.Version,
NewVersion: latestVersion,
})
}
return fileUpgrades, nil
}
func (f *FileSystemMigrator) printDetectedUpgrades(fileUpgrades *FileAPIUpgrades) {
for _, upgrade := range fileUpgrades.Upgrades {
logger.Generatef("%s:%d: %s %s -> %s",
fileUpgrades.File,
upgrade.Line+1,
upgrade.Kind,
upgrade.OldVersion,
upgrade.NewVersion)
}
}
func (f *FileSystemMigrator) migrateFile(fileUpgrades *FileAPIUpgrades) error {
// Read file and map lines.
b, err := fs.ReadFile(f.fileSystem, fileUpgrades.File)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", fileUpgrades.File, err)
}
lines := strings.Split(string(b), "\n")
// Apply upgrades to lines.
for _, upgrade := range fileUpgrades.Upgrades {
line := lines[upgrade.Line]
line = strings.Replace(line, upgrade.OldVersion, upgrade.NewVersion, 1)
lines[upgrade.Line] = line
}
// Read file info to preserve permissions.
fileInfo, err := fs.Stat(f.fileSystem, fileUpgrades.File)
if err != nil {
return fmt.Errorf("failed to stat file %s: %w", fileUpgrades.File, err)
}
// Write file with preserved permissions.
b = []byte(strings.Join(lines, "\n"))
if err := f.fileSystem.WriteFile(fileUpgrades.File, b, fileInfo.Mode()); err != nil {
return fmt.Errorf("failed to write file %s: %w", fileUpgrades.File, err)
}
return nil
}

@ -1,161 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"io/fs"
"os"
"testing"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type writeToMemoryFS struct {
fs.FS
writtenFiles map[string][]byte
}
func (m *writeToMemoryFS) WriteFile(name string, data []byte, perm os.FileMode) error {
m.writtenFiles[name] = data
return nil
}
type writtenFile struct {
file string
goldenFile string
}
func TestFileSystemMigrator(t *testing.T) {
for _, tt := range []struct {
name string
path string
outputGolden string
writtenFiles []writtenFile
err string
}{
{
name: "errors out for single file that is a symlink",
path: "testdata/migrate/file-system/single-file-link.yaml",
err: "file testdata/migrate/file-system/single-file-link.yaml is irregular",
},
{
name: "errors out for single file with wrong extension",
path: "testdata/migrate/file-system/single-file-wrong-ext.json",
err: "file testdata/migrate/file-system/single-file-wrong-ext.json does not match the specified extensions: .yaml, .yml",
},
{
name: "migrate single file",
path: "testdata/migrate/file-system/single-file.yaml",
outputGolden: "testdata/migrate/file-system/single-file.yaml.output.golden",
writtenFiles: []writtenFile{
{
file: "testdata/migrate/file-system/single-file.yaml",
goldenFile: "testdata/migrate/file-system/single-file.yaml.golden",
},
},
},
{
name: "migrate files in directory",
path: "testdata/migrate/file-system/dir",
outputGolden: "testdata/migrate/file-system/dir.output.golden",
writtenFiles: []writtenFile{
{
file: "testdata/migrate/file-system/dir/some-dir/another-file.yaml",
goldenFile: "testdata/migrate/file-system/dir.golden/some-dir/another-file.yaml",
},
{
file: "testdata/migrate/file-system/dir/some-dir/another-file.yml",
goldenFile: "testdata/migrate/file-system/dir.golden/some-dir/another-file.yml",
},
{
file: "testdata/migrate/file-system/dir/some-file.yaml",
goldenFile: "testdata/migrate/file-system/dir.golden/some-file.yaml",
},
{
file: "testdata/migrate/file-system/dir/some-file.yml",
goldenFile: "testdata/migrate/file-system/dir.golden/some-file.yml",
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
// Store logger, replace with test logger, and restore at the end of the test.
var testLogger bytes.Buffer
oldLogger := logger
logger = stderrLogger{&testLogger}
t.Cleanup(func() { logger = oldLogger })
// Open current working directory as root and build write-to-memory filesystem.
pathRoot, err := os.OpenRoot(".")
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(func() { pathRoot.Close() })
fileSystem := &writeToMemoryFS{
FS: pathRoot.FS(),
writtenFiles: make(map[string][]byte),
}
// Prepare other inputs.
const yes = true
const dryRun = false
extensions := []string{".yaml", ".yml"}
latestVersions := map[schema.GroupKind]string{
{Group: "image.toolkit.fluxcd.io", Kind: "ImageRepository"}: "v1",
{Group: "image.toolkit.fluxcd.io", Kind: "ImagePolicy"}: "v1",
{Group: "image.toolkit.fluxcd.io", Kind: "ImageUpdateAutomation"}: "v1",
}
// Run migration.
err = NewFileSystemMigrator(fileSystem, yes, dryRun, tt.path, extensions, latestVersions).Run()
if tt.err != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(Equal(tt.err))
return
}
g.Expect(err).ToNot(HaveOccurred())
// Assert logger output.
b, err := os.ReadFile(tt.outputGolden)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(string(b)).To(Equal(testLogger.String()),
"logger output does not match golden file %s", tt.outputGolden)
// Assert which files were written.
writtenFiles := make([]string, 0, len(fileSystem.writtenFiles))
for name := range fileSystem.writtenFiles {
writtenFiles = append(writtenFiles, name)
}
expectedWrittenFiles := make([]string, 0, len(tt.writtenFiles))
for _, wf := range tt.writtenFiles {
expectedWrittenFiles = append(expectedWrittenFiles, wf.file)
}
g.Expect(writtenFiles).To(ConsistOf(expectedWrittenFiles))
// Assert contents of written files.
for _, wf := range tt.writtenFiles {
b, err := os.ReadFile(wf.goldenFile)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(string(fileSystem.writtenFiles[wf.file])).To(Equal(string(b)),
"file %s does not match golden file %s", wf.file, wf.goldenFile)
}
})
}
}

@ -18,25 +18,22 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/crane"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/auth/azure"
authutils "github.com/fluxcd/pkg/auth/utils" authutils "github.com/fluxcd/pkg/auth/utils"
) )
// loginWithProvider gets a crane authentication option for the given provider and URL. // loginWithProvider gets a crane authentication option for the given provider and URL.
func loginWithProvider(ctx context.Context, url, provider string) (crane.Option, authn.Authenticator, error) { func loginWithProvider(ctx context.Context, url, provider string) (crane.Option, error) {
var opts []auth.Option authenticator, err := authutils.GetArtifactRegistryCredentials(ctx, provider, url)
if provider == azure.ProviderName {
opts = append(opts, auth.WithAllowShellOut())
}
authenticator, err := authutils.GetArtifactRegistryCredentials(ctx, provider, url, opts...)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("could not login to provider %s with url %s: %w", provider, url, err) return nil, fmt.Errorf("could not login to provider %s with url %s: %w", provider, url, err)
}
if authenticator == nil {
return nil, errors.New("unsupported provider")
} }
return crane.WithAuth(authenticator), authenticator, nil return crane.WithAuth(authenticator), nil
} }

@ -94,7 +94,7 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
if pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider { if pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with provider credentials")
opt, _, err := loginWithProvider(ctx, url, pullArtifactArgs.provider.String()) opt, err := loginWithProvider(ctx, url, pullArtifactArgs.provider.String())
if err != nil { if err != nil {
return fmt.Errorf("error during login with provider: %w", err) return fmt.Errorf("error during login with provider: %w", err)
} }

@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -33,6 +34,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
authutils "github.com/fluxcd/pkg/auth/utils"
"github.com/fluxcd/pkg/oci" "github.com/fluxcd/pkg/oci"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
@ -223,14 +225,16 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
opts = append(opts, crane.WithAuth(authenticator)) opts = append(opts, crane.WithAuth(authenticator))
} }
if provider := pushArtifactArgs.provider.String(); provider != sourcev1.GenericOCIProvider { if pushArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with provider credentials")
var opt crane.Option authenticator, err = authutils.GetArtifactRegistryCredentials(ctx, pushArtifactArgs.provider.String(), url)
opt, authenticator, err = loginWithProvider(ctx, url, provider)
if err != nil { if err != nil {
return fmt.Errorf("error during login with provider: %w", err) return fmt.Errorf("error during login with provider: %w", err)
} }
opts = append(opts, opt) if authenticator == nil {
return errors.New("unsupported provider")
}
opts = append(opts, crane.WithAuth(authenticator))
} }
if rootArgs.timeout != 0 { if rootArgs.timeout != 0 {
@ -251,13 +255,7 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
} }
transportOpts, err := oci.WithRetryTransport(ctx, transportOpts, err := oci.WithRetryTransport(ctx, ref, authenticator, backoff, []string{ref.Context().Scope(transport.PushScope)})
ref,
authenticator,
backoff,
[]string{ref.Context().Scope(transport.PushScope)},
pushArtifactArgs.insecure,
)
if err != nil { if err != nil {
return fmt.Errorf("error setting up transport: %w", err) return fmt.Errorf("error setting up transport: %w", err)
} }

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
helmv2 "github.com/fluxcd/helm-controller/api/v2" helmv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
@ -66,7 +67,7 @@ func (obj helmReleaseAdapter) reconcileSource() bool {
return rhrArgs.syncHrWithSource return rhrArgs.syncHrWithSource
} }
func (obj helmReleaseAdapter) getSource() (reconcileSource, sourceReference) { func (obj helmReleaseAdapter) getSource() (reconcileSource, types.NamespacedName) {
var ( var (
name string name string
ns string ns string
@ -77,26 +78,21 @@ func (obj helmReleaseAdapter) getSource() (reconcileSource, sourceReference) {
if ns == "" { if ns == "" {
ns = obj.Namespace ns = obj.Namespace
} }
srcRef := sourceReference{ namespacedName := types.NamespacedName{
kind: obj.Spec.ChartRef.Kind, Name: name,
name: name, Namespace: ns,
namespace: ns,
} }
switch obj.Spec.ChartRef.Kind { if obj.Spec.ChartRef.Kind == sourcev1.HelmChartKind {
case sourcev1.HelmChartKind:
return reconcileWithSourceCommand{ return reconcileWithSourceCommand{
apiType: helmChartType, apiType: helmChartType,
object: helmChartAdapter{&sourcev1.HelmChart{}}, object: helmChartAdapter{&sourcev1.HelmChart{}},
force: true, force: true,
}, srcRef }, namespacedName
case sourcev1.OCIRepositoryKind: }
return reconcileCommand{ return reconcileCommand{
apiType: ociRepositoryType, apiType: ociRepositoryType,
object: ociRepositoryAdapter{&sourcev1.OCIRepository{}}, object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
}, srcRef }, namespacedName
default:
return nil, srcRef
}
default: default:
// default case assumes the HelmRelease is using a HelmChartTemplate // default case assumes the HelmRelease is using a HelmChartTemplate
ns = obj.Spec.Chart.Spec.SourceRef.Namespace ns = obj.Spec.Chart.Spec.SourceRef.Namespace
@ -108,10 +104,9 @@ func (obj helmReleaseAdapter) getSource() (reconcileSource, sourceReference) {
apiType: helmChartType, apiType: helmChartType,
object: helmChartAdapter{&sourcev1.HelmChart{}}, object: helmChartAdapter{&sourcev1.HelmChart{}},
force: true, force: true,
}, sourceReference{ }, types.NamespacedName{
kind: sourcev1.HelmChartKind, Name: name,
name: name, Namespace: ns,
namespace: ns,
} }
} }
} }

@ -1,40 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
"github.com/spf13/cobra"
)
var reconcileImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
Short: "Reconcile an ImagePolicy",
Long: `The reconcile image policy command triggers a reconciliation of an ImagePolicy resource and waits for it to finish.`,
Example: `
# Trigger a reconciliation for an existing image policy called 'alpine'
flux reconcile image policy alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
RunE: reconcileCommand{
apiType: imagePolicyType,
object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
}.run,
}
func init() {
reconcileImageCmd.AddCommand(reconcileImagePolicyCmd)
}

@ -21,7 +21,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var reconcileImageRepositoryCmd = &cobra.Command{ var reconcileImageRepositoryCmd = &cobra.Command{

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta" apimeta "k8s.io/apimachinery/pkg/api/meta"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
meta "github.com/fluxcd/pkg/apis/meta" meta "github.com/fluxcd/pkg/apis/meta"
) )

@ -18,6 +18,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
@ -61,8 +62,8 @@ func (obj kustomizationAdapter) reconcileSource() bool {
return rksArgs.syncKsWithSource return rksArgs.syncKsWithSource
} }
func (obj kustomizationAdapter) getSource() (reconcileSource, sourceReference) { func (obj kustomizationAdapter) getSource() (reconcileSource, types.NamespacedName) {
var cmd reconcileSource var cmd reconcileCommand
switch obj.Spec.SourceRef.Kind { switch obj.Spec.SourceRef.Kind {
case sourcev1.OCIRepositoryKind: case sourcev1.OCIRepositoryKind:
cmd = reconcileCommand{ cmd = reconcileCommand{
@ -81,10 +82,9 @@ func (obj kustomizationAdapter) getSource() (reconcileSource, sourceReference) {
} }
} }
return cmd, sourceReference{ return cmd, types.NamespacedName{
kind: obj.Spec.SourceRef.Kind, Name: obj.Spec.SourceRef.Name,
name: obj.Spec.SourceRef.Name, Namespace: obj.Spec.SourceRef.Namespace,
namespace: obj.Spec.SourceRef.Namespace,
} }
} }

@ -18,6 +18,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1"
) )
@ -57,8 +58,8 @@ func (obj helmChartAdapter) reconcileSource() bool {
return rhcArgs.syncHrWithSource return rhcArgs.syncHrWithSource
} }
func (obj helmChartAdapter) getSource() (reconcileSource, sourceReference) { func (obj helmChartAdapter) getSource() (reconcileSource, types.NamespacedName) {
var cmd reconcileSource var cmd reconcileCommand
switch obj.Spec.SourceRef.Kind { switch obj.Spec.SourceRef.Kind {
case sourcev1.HelmRepositoryKind: case sourcev1.HelmRepositoryKind:
cmd = reconcileCommand{ cmd = reconcileCommand{
@ -77,10 +78,9 @@ func (obj helmChartAdapter) getSource() (reconcileSource, sourceReference) {
} }
} }
return cmd, sourceReference{ return cmd, types.NamespacedName{
kind: obj.Spec.SourceRef.Kind, Name: obj.Spec.SourceRef.Name,
name: obj.Spec.SourceRef.Name, Namespace: obj.Namespace,
namespace: obj.Namespace,
} }
} }

@ -15,17 +15,11 @@ import (
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
) )
type sourceReference struct {
kind string
name string
namespace string
}
type reconcileWithSource interface { type reconcileWithSource interface {
adapter adapter
reconcilable reconcilable
reconcileSource() bool reconcileSource() bool
getSource() (reconcileSource, sourceReference) getSource() (reconcileSource, types.NamespacedName)
} }
type reconcileSource interface { type reconcileSource interface {
@ -67,17 +61,14 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
} }
if reconcile.object.reconcileSource() || reconcile.force { if reconcile.object.reconcileSource() || reconcile.force {
reconcileCmd, srcRef := reconcile.object.getSource() reconcileCmd, nsName := reconcile.object.getSource()
if reconcileCmd == nil {
return fmt.Errorf("cannot reconcile source of kind %s", srcRef.kind)
}
nsCopy := *kubeconfigArgs.Namespace nsCopy := *kubeconfigArgs.Namespace
if srcRef.namespace != "" { if nsName.Namespace != "" {
*kubeconfigArgs.Namespace = srcRef.namespace *kubeconfigArgs.Namespace = nsName.Namespace
} }
if err := reconcileCmd.run(nil, []string{srcRef.name}); err != nil { err := reconcileCmd.run(nil, []string{nsName.Name})
if err != nil {
return err return err
} }
*kubeconfigArgs.Namespace = nsCopy *kubeconfigArgs.Namespace = nsCopy

@ -1,40 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
"github.com/spf13/cobra"
)
var resumeImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
Short: "Resume an ImagePolicy",
Long: `The resume image policy command resumes a suspended ImagePolicy resource.`,
Example: `
# Resume a suspended image policy called 'alpine'
flux resume image policy alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
RunE: resumeCommand{
apiType: imagePolicyType,
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
}.run,
}
func init() {
resumeImageCmd.AddCommand(resumeImagePolicyCmd)
}

@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var resumeImageRepositoryCmd = &cobra.Command{ var resumeImageRepositoryCmd = &cobra.Command{

@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
) )
var resumeImageUpdateCmd = &cobra.Command{ var resumeImageUpdateCmd = &cobra.Command{

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

@ -28,8 +28,8 @@ import (
"github.com/fluxcd/cli-utils/pkg/kstatus/status" "github.com/fluxcd/cli-utils/pkg/kstatus/status"
helmv2 "github.com/fluxcd/helm-controller/api/v2" helmv2 "github.com/fluxcd/helm-controller/api/v2"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"

@ -1,37 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
"github.com/spf13/cobra"
)
var suspendImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
Short: "Suspend an ImagePolicy",
Long: `The suspend image policy command suspends the reconciliation of an ImagePolicy resource.`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
RunE: suspendCommand{
apiType: imagePolicyType,
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
}.run,
}
func init() {
suspendImageCmd.AddCommand(suspendImagePolicyCmd)
}

@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
) )
var suspendImageRepositoryCmd = &cobra.Command{ var suspendImageRepositoryCmd = &cobra.Command{

@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
) )
var suspendImageUpdateCmd = &cobra.Command{ var suspendImageUpdateCmd = &cobra.Command{

@ -82,7 +82,7 @@ func tagArtifactCmdRun(cmd *cobra.Command, args []string) error {
if tagArtifactArgs.provider.String() != sourcev1.GenericOCIProvider { if tagArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with provider credentials")
opt, _, err := loginWithProvider(ctx, url, tagArtifactArgs.provider.String()) opt, err := loginWithProvider(ctx, url, tagArtifactArgs.provider.String())
if err != nil { if err != nil {
return fmt.Errorf("error during login with provider: %w", err) return fmt.Errorf("error during login with provider: %w", err)
} }

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

@ -36,5 +36,4 @@ stringData:
lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s
bUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg= bUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg=
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
type: Opaque

@ -35,5 +35,4 @@ stringData:
lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s
bUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg= bUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg=
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
type: Opaque

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

Loading…
Cancel
Save