Compare commits

..

No commits in common. 'main' and 'v2.3.0' have entirely different histories.
main ... v2.3.0

@ -6,9 +6,11 @@ updates:
labels: ["area/ci", "dependencies"]
groups:
# Group all updates together, so that they are all applied in a single PR.
# Grouped updates are currently in beta and is subject to change.
# xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
ci:
patterns:
- "*"
schedule:
interval: "monthly"
# By default, this will be on a monday.
interval: "weekly"

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

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

@ -4,9 +4,6 @@ on:
pull_request_target:
types: [closed, labeled]
permissions:
contents: read
jobs:
pull-request:
runs-on: ubuntu-latest
@ -16,11 +13,11 @@ jobs:
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
uses: korthout/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
# xref: https://github.com/korthout/backport-action#inputs
with:
# Use token to allow workflows to be triggered for the created PR

@ -9,23 +9,24 @@ permissions:
contents: read
env:
GO_VERSION: 1.24.x
GO_VERSION: 1.22.x
jobs:
conform-kubernetes:
runs-on:
group: "ARM64"
# Hosted on Equinix
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
runs-on: [self-hosted, Linux, ARM64, equinix]
strategy:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml
KUBERNETES_VERSION: [1.31.5, 1.32.1, 1.33.0]
KUBERNETES_VERSION: [ 1.28.9, 1.29.4, 1.30.0 ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@ -39,15 +40,18 @@ jobs:
- name: Build
run: |
make build
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with:
version: v0.27.0
cluster_name: ${{ steps.prep.outputs.CLUSTER }}
node_image: ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64
- name: Setup Kubernetes Kind
run: |
kind create cluster \
--wait 5m \
--name ${{ steps.prep.outputs.CLUSTER }} \
--kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} \
--image=ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64
- name: Run e2e tests
run: TEST_KUBECONFIG=$HOME/.kube/config make e2e
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e
- name: Run multi-tenancy tests
env:
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
run: |
./bin/flux install
./bin/flux create source git flux-system \
@ -64,11 +68,18 @@ jobs:
kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m
- name: Debug failure
if: failure()
env:
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
run: |
kubectl -n flux-system get all
kubectl -n flux-system describe po
kubectl -n flux-system logs deploy/source-controller
kubectl -n flux-system logs deploy/kustomize-controller
- name: Cleanup
if: always()
run: |
kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }}
rm /tmp/${{ steps.prep.outputs.CLUSTER }}
conform-k3s:
runs-on: ubuntu-latest
@ -76,13 +87,13 @@ jobs:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Available versions can be found with "replicated cluster versions"
K3S_VERSION: [ 1.31.8, 1.32.4, 1.33.0 ]
K3S_VERSION: [ 1.28.7, 1.29.2 ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@ -97,7 +108,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/kustomize@main
- name: Build
run: make build-dev
- name: Create repository
@ -107,7 +118,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Create cluster
id: create-cluster
uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/compatibility-actions/create-cluster@v1
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: "k3s"
@ -151,7 +162,7 @@ jobs:
kubectl delete ns flux-system --wait
- name: Delete cluster
if: ${{ always() }}
uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/replicated-actions/remove-cluster@v1
continue-on-error: true
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
@ -169,13 +180,13 @@ jobs:
strategy:
matrix:
# Keep this list up-to-date with https://endoflife.date/red-hat-openshift
OPENSHIFT_VERSION: [ 4.18.0-okd ]
OPENSHIFT_VERSION: [ 4.15.0-okd ]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: |
@ -190,7 +201,7 @@ jobs:
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/kustomize@main
- name: Build
run: make build-dev
- name: Create repository
@ -200,7 +211,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: Create cluster
id: create-cluster
uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/compatibility-actions/create-cluster@v1
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: "openshift"
@ -242,7 +253,7 @@ jobs:
kubectl delete ns flux-system --wait
- name: Delete cluster
if: ${{ always() }}
uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
uses: replicatedhq/replicated-actions/remove-cluster@v1
continue-on-error: true
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}

@ -30,14 +30,12 @@ jobs:
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: CheckoutD
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version: 1.22.x
cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
- name: Setup Flux CLI
run: make build
working-directory: ./
@ -49,7 +47,7 @@ jobs:
env:
SOPS_VER: 3.7.1
- name: Authenticate to Azure
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v1.4.6
uses: Azure/login@6b2456866fc08b011acb422a92a4aa20e2c4de32 # v1.4.6
with:
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

@ -17,27 +17,27 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version: 1.22.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
with:
version: v0.24.0
version: v0.22.0
cluster_name: kind
# The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
kubectl_version: v1.32.0
node_image: ghcr.io/fluxcd/kindest/node:v1.30.0-amd64
kubectl_version: v1.30.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup yq
uses: fluxcd/pkg/actions/yq@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/yq@main
- name: Build
run: make build-dev
- name: Set outputs
@ -107,8 +107,6 @@ jobs:
./bin/flux reconcile image repository podinfo
./bin/flux reconcile image update flux-system
./bin/flux get images all
./bin/flux -n flux-system events --for ImageUpdateAutomation/flux-system
kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system
kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system | \
yq '.status.lastPushCommit | length > 1' | grep 'true'
env:

@ -29,14 +29,12 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version: 1.22.x
cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
- name: Setup Flux CLI
run: make build
working-directory: ./
@ -48,19 +46,19 @@ jobs:
env:
SOPS_VER: 3.7.1
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193 # v2.1.10
uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2
id: 'auth'
with:
credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'
token_format: 'access_token'
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log into us-central1-docker.pkg.dev
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: us-central1-docker.pkg.dev
username: oauth2accesstoken

@ -23,30 +23,30 @@ jobs:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version: 1.22.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
with:
version: v0.24.0
version: v0.22.0
cluster_name: kind
wait: 5s
config: .github/kind/config.yaml # disable KIND-net
# The versions below should target the oldest supported Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.31.5-amd64
kubectl_version: v1.32.0
node_image: ghcr.io/fluxcd/kindest/node:v1.28.9-amd64
kubectl_version: v1.28.9
- name: Setup Calico for network policy
run: |
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/kustomize@main
- name: Run tests
run: make test
- name: Run e2e tests
@ -247,7 +247,7 @@ jobs:
- name: Debug failure
if: failure()
run: |
kubectl version --client
kubectl version --client --short
kubectl -n flux-system get all
kubectl -n flux-system describe pods
kubectl -n flux-system get kustomizations -oyaml

@ -19,21 +19,21 @@ jobs:
actions: read
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Run analysis
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with:
results_file: results.sarif
results_format: sarif
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
with:
sarif_file: results.sarif

@ -2,7 +2,7 @@ name: release
on:
push:
tags: ["v*"]
tags: [ 'v*' ]
permissions:
contents: read
@ -20,33 +20,33 @@ jobs:
packages: write # needed for ghcr access
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Unshallow
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version: 1.22.x
cache: false
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Setup Syft
uses: anchore/sbom-action/download-syft@9f7302141466aa6482940f15371237e9d9f4c34a # v0.19.0
uses: anchore/sbom-action/download-syft@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11
- name: Setup Cosign
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/kustomize@main
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@ -59,19 +59,30 @@ jobs:
run: |
kustomize build manifests/crds > all-crds.yaml
- name: Generate OpenAPI JSON schemas from CRDs
uses: fluxcd/pkg/actions/crdjsonschema@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/crdjsonschema@main
with:
crd: all-crds.yaml
output: schemas
- name: Archive the OpenAPI JSON schemas
run: |
tar -czvf ./output/crd-schemas.tar.gz -C schemas .
- name: Download release notes utility
env:
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
run: cd /tmp && curl -sSL ${GH_REL_URL} | tar xz && sudo mv github-release-notes /usr/local/bin/
- name: Generate release notes
run: |
NOTES="./output/notes.md"
echo '## CLI Changelog' > ${NOTES}
github-release-notes -org fluxcd -repo flux2 -since-latest-release -include-author >> ${NOTES}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
id: run-goreleaser
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0
with:
version: latest
args: release --skip=validate
args: release --release-notes=output/notes.md --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
@ -99,9 +110,9 @@ jobs:
id-token: write
packages: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Flux CLI
uses: ./action/
- name: Prepare
@ -110,13 +121,13 @@ jobs:
VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GITHUB_TOKEN }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@ -144,7 +155,7 @@ jobs:
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
- uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- name: Sign manifests
env:
COSIGN_EXPERIMENTAL: 1
@ -165,7 +176,7 @@ jobs:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
contents: write # for uploading attestations to GitHub releases.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
provenance-name: "provenance.intoto.jsonl"
base64-subjects: "${{ needs.release-flux-cli.outputs.hashes }}"
@ -177,7 +188,7 @@ jobs:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ${{ needs.release-flux-cli.outputs.image_url }}
digest: ${{ needs.release-flux-cli.outputs.image_digest }}
@ -191,10 +202,10 @@ jobs:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ghcr.io/${{ needs.release-flux-cli.outputs.image_url }}
digest: ${{ needs.release-flux-cli.outputs.image_digest }}
registry-username: fluxcdbot
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
registry-password: ${{ secrets.GHCR_TOKEN }}

@ -17,14 +17,47 @@ jobs:
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}
scan-snyk:
runs-on: ubuntu-latest
permissions:
security-events: write
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version-file: 'go.mod'
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Download modules and build manifests
run: |
make tidy
make cmd/flux/.manifests.done
- uses: snyk/actions/setup@b98d498629f1c368650224d6d212bf7dfa89e4bf
- name: Run Snyk to check for vulnerabilities
continue-on-error: true
run: |
snyk test --all-projects --sarif-file-output=snyk.sarif
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Upload result to GitHub Code Scanning
continue-on-error: true
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
with:
sarif_file: snyk.sarif
scan-codeql:
runs-on: ubuntu-latest
permissions:
@ -32,22 +65,22 @@ jobs:
if: github.actor != 'dependabot[bot]'
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
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
uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
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
uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4

@ -17,7 +17,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
with:
# Configuration file

@ -18,11 +18,11 @@ jobs:
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version: 1.22.x
cache-dependency-path: |
**/go.sum
**/go.mod
@ -84,7 +84,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: |

@ -1,6 +1,4 @@
project_name: flux
changelog:
use: github-native
builds:
- <<: &build_defaults
binary: flux
@ -17,7 +15,7 @@ builds:
- arm64
- arm
goarm:
- "7"
- 7
- <<: *build_defaults
id: darwin
goos:
@ -75,11 +73,11 @@ signs:
output: true
brews:
- name: flux
repository:
tap:
owner: fluxcd
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Formula
folder: Formula
homepage: "https://fluxcd.io/"
description: "Flux CLI"
install: |

@ -1,5 +0,0 @@
annotations:
- checks:
- dangerous-workflow
reasons:
- reason: not-applicable # This workflow does not run untrusted code, the bot will only backport a code if the a PR was approved and merged into main.

@ -67,8 +67,8 @@ for source changes.
Prerequisites:
* go >= 1.24
* kubectl >= 1.30
* go >= 1.20
* kubectl >= 1.24
* kustomize >= 5.0
* coreutils (on Mac OS)

@ -1,16 +1,15 @@
FROM alpine:3.21 AS builder
FROM alpine:3.19 as builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.33.0
ARG KUBECTL_VER=1.30.0
RUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
kubectl version --client=true
RUN kubectl version --client=true
FROM alpine:3.21 AS flux-cli
FROM alpine:3.19 as flux-cli
RUN apk add --no-cache ca-certificates

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

@ -2,7 +2,7 @@
[![release](https://img.shields.io/github/release/fluxcd/flux2/all.svg)](https://github.com/fluxcd/flux2/releases)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4782/badge)](https://bestpractices.coreinfrastructure.org/projects/4782)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2/badge)](https://scorecard.dev/viewer/?uri=github.com/fluxcd/flux2)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2/badge)](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://fluxcd.io/flux/security/slsa-assessment)

@ -226,7 +226,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Username = bServerArgs.username
}
secretOpts.Password = bitbucketToken
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
} else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil {

@ -243,7 +243,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Password = gitArgs.password
}
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
// Remove port of the given host when not syncing over HTTP/S to not assume port for protocol
// This _might_ be overwritten later on by e.g. --ssh-hostname

@ -210,7 +210,7 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth {
secretOpts.Username = "git"
secretOpts.Password = gtToken
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
} else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)

@ -217,7 +217,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth {
secretOpts.Username = "git"
secretOpts.Password = ghToken
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
} else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)

@ -24,7 +24,6 @@ import (
"strings"
"time"
"github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/spf13/cobra"
@ -42,7 +41,7 @@ import (
var bootstrapGitLabCmd = &cobra.Command{
Use: "gitlab",
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.
Then it configures the target cluster to synchronize with that repository.
If the Flux components are present on the cluster,
@ -59,14 +58,14 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a repository path
flux bootstrap gitlab --owner=<group> --repository=<repository name> --path=dev-cluster
# Run bootstrap for a public repository
flux bootstrap gitlab --owner=<group> --repository=<repository name> --visibility=public --token-auth
# Run bootstrap for a public repository on a personal account
flux bootstrap gitlab --owner=<user> --repository=<repository name> --private=false --personal --token-auth
# Run bootstrap for a private repository hosted on a GitLab server
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<gitlab_url> --token-auth
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for an existing repository with a branch named main
flux bootstrap gitlab --owner=<group> --repository=<repository name> --branch=main --token-auth
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth
# Run bootstrap for a private repository using Deploy Token authentication
flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth
@ -86,7 +85,6 @@ type gitlabFlags struct {
repository string
interval time.Duration
personal bool
visibility flags.GitLabVisibility
private bool
hostname string
path flags.SafeRelativePath
@ -96,13 +94,7 @@ type gitlabFlags struct {
deployTokenAuth bool
}
func NewGitlabFlags() gitlabFlags {
return gitlabFlags{
visibility: flags.GitLabVisibility(gitprovider.RepositoryVisibilityPrivate),
}
}
var gitlabArgs = NewGitlabFlags()
var gitlabArgs gitlabFlags
func init() {
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name")
@ -110,8 +102,6 @@ func init() {
bootstrapGitLabCmd.Flags().StringSliceVar(&gitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "if true, the repository is setup or configured as private")
bootstrapGitLabCmd.Flags().MarkDeprecated("private", "use --visibility instead")
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.visibility, "visibility", gitlabArgs.visibility.Description())
bootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname")
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
@ -143,11 +133,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("--token-auth and --deploy-token-auth cannot be set both.")
}
if !gitlabArgs.private {
gitlabArgs.visibility.Set(string(gitprovider.RepositoryVisibilityPublic))
cmd.Println("Using visibility public as --private=false")
}
if err := bootstrapValidate(); err != nil {
return err
}
@ -257,10 +242,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth {
secretOpts.Username = "git"
secretOpts.Password = glToken
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
} else if gitlabArgs.deployTokenAuth {
// the actual deploy token will be reconciled later
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
} else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil {
@ -297,7 +282,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
bootstrap.WithProviderVisibility(gitlabArgs.visibility.String()),
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
@ -317,6 +301,9 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())
}
if !gitlabArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
if gitlabArgs.reconcile {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
}

@ -26,15 +26,15 @@ import (
"github.com/spf13/cobra"
"github.com/fluxcd/pkg/oci"
oci "github.com/fluxcd/pkg/oci/client"
"github.com/fluxcd/pkg/sourceignore"
)
var buildArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Build artifact",
Long: `The build artifact command creates a tgz file with the manifests
from the given directory or a single manifest file.`,
Long: withPreviewNote(`The build artifact command creates a tgz file with the manifests
from the given directory or a single manifest file.`),
Example: ` # Build the given manifests directory into an artifact
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz

@ -53,12 +53,7 @@ flux build kustomization my-app --path ./path/to/local/manifests \
# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.
flux build kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"
# Run recursively on all encountered Kustomizations
flux build kustomization my-app --path ./path/to/local/manifests \
--recursive \
--local-sources GitRepository/flux-system/my-repo=./path/to/local/git`,
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun,
}
@ -69,8 +64,6 @@ type buildKsFlags struct {
ignorePaths []string
dryRun bool
strictSubst bool
recursive bool
localSources map[string]string
}
var buildKsArgs buildKsFlags
@ -82,8 +75,6 @@ func init() {
buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.")
buildKsCmd.Flags().BoolVar(&buildKsArgs.strictSubst, "strict-substitute", false,
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.")
buildKsCmd.Flags().BoolVarP(&buildKsArgs.recursive, "recursive", "r", false, "Recursively build Kustomizations")
buildKsCmd.Flags().StringToStringVar(&buildKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path")
buildCmd.AddCommand(buildKsCmd)
}
@ -120,8 +111,6 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithNamespace(*kubeconfigArgs.Namespace),
build.WithIgnore(buildKsArgs.ignorePaths),
build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources),
)
} else {
builder, err = build.NewBuilder(name, buildKsArgs.path,
@ -130,8 +119,6 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
build.WithIgnore(buildKsArgs.ignorePaths),
build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources),
)
}

@ -22,7 +22,6 @@ package main
import (
"bytes"
"os"
"path/filepath"
"testing"
"text/template"
)
@ -70,12 +69,6 @@ func TestBuildKustomization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive",
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
@ -125,8 +118,6 @@ spec:
cluster_region: "eu-central-1"
`
tmpFile := filepath.Join(t.TempDir(), "podinfo.yaml")
tests := []struct {
name string
args string
@ -141,40 +132,28 @@ spec:
},
{
name: "build podinfo",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/podinfo",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo without service",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/delete-service",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/delete-service",
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build deployment and configmap with var substitution",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/var-substitution",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution",
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build deployment and configmap with var substitution in dry-run mode",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/var-substitution --dry-run",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution --dry-run",
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build with recursive in dry-run mode",
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --dry-run",
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
@ -182,6 +161,8 @@ spec:
}
setup(t, tmpl)
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
temp, err := template.New("podinfo").Parse(podinfo)
if err != nil {
t.Fatal(err)
@ -193,10 +174,11 @@ spec:
t.Fatal(err)
}
err = os.WriteFile(tmpFile, b.Bytes(), 0666)
err = os.WriteFile("./testdata/build-kustomization/podinfo.yaml", b.Bytes(), 0666)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Remove("./testdata/build-kustomization/podinfo.yaml") })
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

@ -60,7 +60,7 @@ type checkFlags struct {
}
var kubernetesConstraints = []string{
">=1.31.0-0",
">=1.28.0-0",
}
var checkArgs checkFlags
@ -217,7 +217,7 @@ func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
}
}
for _, c := range d.Spec.Template.Spec.Containers {
logger.Actionf("%s", c.Image)
logger.Actionf(c.Image)
}
}
}
@ -238,7 +238,7 @@ func crdsCheck(ctx context.Context, kubeClient client.Client) bool {
for _, crd := range list.Items {
versions := crd.Status.StoredVersions
if len(versions) > 0 {
logger.Successf("%s", crd.Name+"/"+versions[len(versions)-1])
logger.Successf(crd.Name + "/" + versions[len(versions)-1])
} else {
ok = false
logger.Failuref("no stored versions for %s", crd.Name)

@ -125,7 +125,7 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
logger.Generatef("generating %s", names.kind)
logger.Actionf("applying %s", names.kind)
namespacedName, err := names.upsert(ctx, kubeClient, object, mutate)
namespacedName, err := imageRepositoryType.upsert(ctx, kubeClient, object, mutate)
if err != nil {
return err
}

@ -37,6 +37,7 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/transform"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
@ -141,7 +142,7 @@ var helmReleaseArgs helmReleaseFlags
var supportedHelmReleaseValuesFromKinds = []string{"Secret", "ConfigMap"}
var supportedHelmReleaseReferenceKinds = []string{sourcev1.OCIRepositoryKind, sourcev1.HelmChartKind}
var supportedHelmReleaseReferenceKinds = []string{sourcev1b2.OCIRepositoryKind, sourcev1.HelmChartKind}
func init() {
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
@ -221,7 +222,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
case helmReleaseArgs.chartRef != "":
kind, name, ns := utils.ParseObjectKindNameNamespace(helmReleaseArgs.chartRef)
if kind != sourcev1.HelmChartKind && kind != sourcev1.OCIRepositoryKind {
if kind != sourcev1.HelmChartKind && kind != sourcev1b2.OCIRepositoryKind {
return fmt.Errorf("chart reference kind '%s' is not supported, must be one of: %s",
kind, strings.Join(supportedHelmReleaseReferenceKinds, ", "))
}

@ -20,7 +20,6 @@ import (
"fmt"
"regexp/syntax"
"strings"
"time"
"unicode"
"unicode/utf8"
@ -35,12 +34,12 @@ import (
var createImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
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
repository and a policy, e.g., semver.
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
flux create image policy podinfo \
--image-ref=podinfo \
@ -61,8 +60,6 @@ type imagePolicyFlags struct {
numeric string
filterRegex string
filterExtract string
reflectDigest string
interval time.Duration
}
var imagePolicyArgs = imagePolicyFlags{}
@ -75,8 +72,6 @@ func init() {
flags.StringVar(&imagePolicyArgs.numeric, "select-numeric", "", "use numeric sorting to select image; either \"asc\" meaning select the last, or \"desc\" meaning select the first")
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", "regular expression pattern used to filter the image tags")
flags.StringVar(&imagePolicyArgs.filterExtract, "filter-extract", "", "replacement pattern (using capture groups from --filter-regex) to use for sorting")
flags.StringVar(&imagePolicyArgs.reflectDigest, "reflect-digest", "", "the digest reflection policy to use when observing latest image tags (one of 'Never', 'IfNotPresent', 'Never')")
flags.DurationVar(&imagePolicyArgs.interval, "interval", 0, "the interval at which to check for new image digests when the policy is set to 'Always'")
createImageCmd.AddCommand(createImagePolicyCmd)
}
@ -158,20 +153,6 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot specify --filter-extract without specifying --filter-regex")
}
if p := imagev1.ReflectionPolicy(imagePolicyArgs.reflectDigest); p != "" {
if p != imagev1.ReflectNever && p != imagev1.ReflectIfNotPresent && p != imagev1.ReflectAlways {
return fmt.Errorf("invalid value for --reflect-digest, must be one of 'Never', 'IfNotPresent', 'Always'")
}
policy.Spec.DigestReflectionPolicy = p
}
if imagePolicyArgs.interval != 0 {
if imagePolicyArgs.reflectDigest != string(imagev1.ReflectAlways) {
return fmt.Errorf("the --interval flag can only be used with the 'Always' digest reflection policy, use --reflect-digest=Always")
}
policy.Spec.Interval = &metav1.Duration{Duration: imagePolicyArgs.interval}
}
if createArgs.export {
return printExport(exportImagePolicy(&policy))
}

@ -32,8 +32,8 @@ import (
var createImageRepositoryCmd = &cobra.Command{
Use: "repository [name]",
Short: "Create or update an ImageRepository object",
Long: `The create image repository command generates an ImageRepository resource.
An ImageRepository object specifies an image repository to scan.`,
Long: withPreviewNote(`The create image repository command generates an ImageRepository resource.
An ImageRepository object specifies an image repository to scan.`),
Example: ` # Create an ImageRepository object to scan the alpine image repository:
flux create image repository alpine-repo --image alpine --interval 20m

@ -29,9 +29,9 @@ import (
var createImageUpdateCmd = &cobra.Command{
Use: "update [name]",
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
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
flux create image update flux-system \
--git-repo-ref=flux-system \

@ -87,6 +87,7 @@ type secretGitFlags struct {
keyAlgorithm flags.PublicKeyAlgorithm
rsaBits flags.RSAKeyBits
ecdsaCurve flags.ECDSACurve
caFile string
caCrtFile string
privateKeyFile string
bearerToken string
@ -101,7 +102,8 @@ func init() {
createSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, "ssh-key-algorithm", secretGitArgs.keyAlgorithm.Description())
createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, "ca-crt-file", "", "path to TLS CA certificate file used for validating self-signed certificates")
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, "ca-crt-file", "", "path to TLS CA certificate file used for validating self-signed certificates; takes precedence over --ca-file")
createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
createSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, "bearer-token", "", "bearer authentication token")
@ -167,6 +169,11 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
} else if secretGitArgs.caFile != "" {
opts.CAFile, err = os.ReadFile(secretGitArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
default:
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)

@ -1,128 +0,0 @@
/*
Copyright 2024 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"os"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
var createSecretGitHubAppCmd = &cobra.Command{
Use: "githubapp [name]",
Short: "Create or update a github app secret",
Long: withPreviewNote(`The create secret githubapp command generates a Kubernetes secret that can be used for GitRepository authentication with github app`),
Example: ` # Create a githubapp authentication secret on disk and encrypt it with Mozilla SOPS
flux create secret githubapp podinfo-auth \
--app-id="1" \
--app-installation-id="2" \
--app-private-key=./private-key-file.pem \
--export > githubapp-auth.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place githubapp-auth.yaml
`,
RunE: createSecretGitHubAppCmdRun,
}
type secretGitHubAppFlags struct {
appID string
appInstallationID string
privateKeyFile string
baseURL string
}
var secretGitHubAppArgs = secretGitHubAppFlags{}
func init() {
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appID, "app-id", "", "github app ID")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appInstallationID, "app-installation-id", "", "github app installation ID")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.privateKeyFile, "app-private-key", "", "github app private key file path")
createSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.baseURL, "app-base-url", "", "github app base URL")
createSecretCmd.AddCommand(createSecretGitHubAppCmd)
}
func createSecretGitHubAppCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("name is required")
}
secretName := args[0]
if secretGitHubAppArgs.appID == "" {
return fmt.Errorf("--app-id is required")
}
if secretGitHubAppArgs.appInstallationID == "" {
return fmt.Errorf("--app-installation-id is required")
}
if secretGitHubAppArgs.privateKeyFile == "" {
return fmt.Errorf("--app-private-key is required")
}
privateKey, err := os.ReadFile(secretGitHubAppArgs.privateKeyFile)
if err != nil {
return fmt.Errorf("unable to read private key file: %w", err)
}
opts := sourcesecret.Options{
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
GitHubAppID: secretGitHubAppArgs.appID,
GitHubAppInstallationID: secretGitHubAppArgs.appInstallationID,
GitHubAppPrivateKey: string(privateKey),
}
if secretGitHubAppArgs.baseURL != "" {
opts.GitHubAppBaseURL = secretGitHubAppArgs.baseURL
}
secret, err := sourcesecret.Generate(opts)
if err != nil {
return err
}
if createArgs.export {
rootCmd.Println(secret.Content)
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
logger.Actionf("githubapp secret '%s' created in '%s' namespace", secretName, *kubeconfigArgs.Namespace)
return nil
}

@ -1,74 +0,0 @@
/*
Copyright 2022 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 TestCreateSecretGitHubApp(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "create githubapp secret with missing name",
args: "create secret githubapp",
assert: assertError("name is required"),
},
{
name: "create githubapp secret with missing app-id",
args: "create secret githubapp appinfo",
assert: assertError("--app-id is required"),
},
{
name: "create githubapp secret with missing appInstallationID",
args: "create secret githubapp appinfo --app-id 1",
assert: assertError("--app-installation-id is required"),
},
{
name: "create githubapp secret with missing private key file",
args: "create secret githubapp appinfo --app-id 1 --app-installation-id 2",
assert: assertError("--app-private-key is required"),
},
{
name: "create githubapp secret with private key file that does not exist",
args: "create secret githubapp appinfo --app-id 1 --app-installation-id 2 --app-private-key pk.pem",
assert: assertError("unable to read private key file: open pk.pem: no such file or directory"),
},
{
name: "create githubapp secret with app info",
args: "create secret githubapp appinfo --namespace my-namespace --app-id 1 --app-installation-id 2 --app-private-key ./testdata/create_secret/githubapp/test-private-key.pem --export",
assert: assertGoldenFile("testdata/create_secret/githubapp/secret.yaml"),
},
{
name: "create githubapp secret with appinfo and base url",
args: "create secret githubapp appinfo --namespace my-namespace --app-id 1 --app-installation-id 2 --app-private-key ./testdata/create_secret/githubapp/test-private-key.pem --app-base-url www.example.com/api/v3 --export",
assert: assertGoldenFile("testdata/create_secret/githubapp/secret-with-baseurl.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

@ -58,9 +58,12 @@ func init() {
flags := createSecretHelmCmd.Flags()
flags.StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username")
flags.StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password")
flags.StringVar(&secretHelmArgs.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
flags.StringVar(&secretHelmArgs.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
flags.StringVar(&secretHelmArgs.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
initSecretDeprecatedTLSFlags(flags, &secretHelmArgs.secretTLSFlags)
deprecationMsg := "please use the command `flux create secret tls` to generate TLS secrets"
flags.MarkDeprecated("cert-file", deprecationMsg)
flags.MarkDeprecated("key-file", deprecationMsg)
flags.MarkDeprecated("ca-file", deprecationMsg)
createSecretCmd.AddCommand(createSecretHelmCmd)
}
@ -74,20 +77,20 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
}
caBundle := []byte{}
if secretHelmArgs.caCrtFile != "" {
if secretHelmArgs.caFile != "" {
var err error
caBundle, err = os.ReadFile(secretHelmArgs.caCrtFile)
caBundle, err = os.ReadFile(secretHelmArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
var certFile, keyFile []byte
if secretHelmArgs.tlsCrtFile != "" && secretHelmArgs.tlsKeyFile != "" {
if certFile, err = os.ReadFile(secretHelmArgs.tlsCrtFile); err != nil {
if secretHelmArgs.certFile != "" && secretHelmArgs.keyFile != "" {
if certFile, err = os.ReadFile(secretHelmArgs.certFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err)
}
if keyFile, err = os.ReadFile(secretHelmArgs.tlsKeyFile); err != nil {
if keyFile, err = os.ReadFile(secretHelmArgs.keyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
}
@ -98,9 +101,9 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
Labels: labels,
Username: secretHelmArgs.username,
Password: secretHelmArgs.password,
CACrt: caBundle,
TLSCrt: certFile,
TLSKey: keyFile,
CAFile: caBundle,
CertFile: certFile,
KeyFile: keyFile,
}
secret, err := sourcesecret.Generate(opts)
if err != nil {

@ -84,7 +84,7 @@ func TestCreateNotationSecret(t *testing.T) {
{
name: "invalid trust policy",
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", t.TempDir(), invalidTrustPolicy),
assert: assertError("invalid trust policy: trust policy: a trust policy statement is missing a name, every statement requires a name"),
assert: assertError("invalid trust policy: a trust policy statement is missing a name, every statement requires a name"),
},
{
name: "invalid trust policy json",

@ -1,112 +0,0 @@
/*
Copyright 2024 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"
"errors"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
)
var createSecretProxyCmd = &cobra.Command{
Use: "proxy [name]",
Short: "Create or update a Kubernetes secret for proxy authentication",
Long: `The create secret proxy command generates a Kubernetes secret with the
proxy address and the basic authentication credentials.`,
Example: ` # Create a proxy secret on disk and encrypt it with SOPS
flux create secret proxy my-proxy \
--namespace=my-namespace \
--address=https://my-proxy.com \
--username=my-username \
--password=my-password \
--export > proxy.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place proxy.yaml`,
RunE: createSecretProxyCmdRun,
}
type secretProxyFlags struct {
address string
username string
password string
}
var secretProxyArgs secretProxyFlags
func init() {
createSecretProxyCmd.Flags().StringVar(&secretProxyArgs.address, "address", "", "proxy address")
createSecretProxyCmd.Flags().StringVarP(&secretProxyArgs.username, "username", "u", "", "basic authentication username")
createSecretProxyCmd.Flags().StringVarP(&secretProxyArgs.password, "password", "p", "", "basic authentication password")
createSecretCmd.AddCommand(createSecretProxyCmd)
}
func createSecretProxyCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
labels, err := parseLabels()
if err != nil {
return err
}
if secretProxyArgs.address == "" {
return errors.New("address is required")
}
opts := sourcesecret.Options{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Labels: labels,
Address: secretProxyArgs.address,
Username: secretProxyArgs.username,
Password: secretProxyArgs.password,
}
secret, err := sourcesecret.Generate(opts)
if err != nil {
return err
}
if createArgs.export {
rootCmd.Println(secret.Content)
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
logger.Actionf("proxy secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
return nil
}

@ -1,47 +0,0 @@
/*
Copyright 2024 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 TestCreateProxySecret(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
args: "create secret proxy proxy-secret",
assert: assertError("address is required"),
},
{
args: "create secret proxy proxy-secret --address=https://my-proxy.com --username=my-username --password=my-password --namespace=my-namespace --export",
assert: assertGoldenFile("testdata/create_secret/proxy/secret-proxy.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

@ -22,6 +22,7 @@ import (
"os"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
@ -32,8 +33,8 @@ import (
var createSecretTLSCmd = &cobra.Command{
Use: "tls [name]",
Short: "Create or update a Kubernetes secret with TLS certificates",
Long: `The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`,
Example: ` # Create a TLS secret on disk and encrypt it with SOPS.
Long: withPreviewNote(`The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`),
Example: ` # Create a TLS secret on disk and encrypt it with Mozilla SOPS.
# Files are expected to be PEM-encoded.
flux create secret tls certs \
--namespace=my-namespace \
@ -48,6 +49,9 @@ var createSecretTLSCmd = &cobra.Command{
}
type secretTLSFlags struct {
certFile string
keyFile string
caFile string
caCrtFile string
tlsKeyFile string
tlsCrtFile string
@ -55,10 +59,26 @@ type secretTLSFlags struct {
var secretTLSArgs secretTLSFlags
func initSecretDeprecatedTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path")
flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path")
flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path")
}
func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
flags.StringVar(&args.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
flags.StringVar(&args.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
flags.StringVar(&args.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
}
func init() {
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
flags := createSecretTLSCmd.Flags()
initSecretDeprecatedTLSFlags(flags, &secretTLSArgs)
initSecretTLSFlags(flags, &secretTLSArgs)
flags.MarkDeprecated("cert-file", "please use --tls-crt-file instead")
flags.MarkDeprecated("key-file", "please use --tls-key-file instead")
flags.MarkDeprecated("ca-file", "please use --ca-crt-file instead")
createSecretCmd.AddCommand(createSecretTLSCmd)
}
@ -82,6 +102,11 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
} else if secretTLSArgs.caFile != "" {
opts.CAFile, err = os.ReadFile(secretTLSArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
if secretTLSArgs.tlsCrtFile != "" && secretTLSArgs.tlsKeyFile != "" {
@ -91,6 +116,13 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
} else if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" {
if opts.CertFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err)
}
if opts.KeyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
}
secret, err := sourcesecret.Generate(opts)

@ -18,6 +18,10 @@ func TestCreateTlsSecret(t *testing.T) {
args: "create secret tls certs --namespace=my-namespace --tls-crt-file=./testdata/create_secret/tls/test-cert.pem --tls-key-file=./testdata/create_secret/tls/test-key.pem --ca-crt-file=./testdata/create_secret/tls/test-ca.pem --export",
assert: assertGoldenFile("testdata/create_secret/tls/secret-tls.yaml"),
},
{
args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --ca-file=./testdata/create_secret/tls/test-ca.pem --export",
assert: assertGoldenFile("testdata/create_secret/tls/deprecated-secret-tls.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

@ -32,7 +32,7 @@ import (
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
@ -41,8 +41,8 @@ import (
var createSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Create or update a Bucket source",
Long: `The create source bucket command generates a Bucket resource and waits for it to be downloaded.
For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`,
Long: withPreviewNote(`The create source bucket command generates a Bucket resource and waits for it to be downloaded.
For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`),
Example: ` # Create a source for a Bucket using static authentication
flux create source bucket podinfo \
--bucket-name=podinfo \
@ -63,16 +63,15 @@ For Buckets with static authentication, the credentials are stored in a Kubernet
}
type sourceBucketFlags struct {
name string
provider flags.SourceBucketProvider
endpoint string
accessKey string
secretKey string
region string
insecure bool
secretRef string
proxySecretRef string
ignorePaths []string
name string
provider flags.SourceBucketProvider
endpoint string
accessKey string
secretKey string
region string
insecure bool
secretRef string
ignorePaths []string
}
var sourceBucketArgs = newSourceBucketFlags()
@ -86,7 +85,6 @@ func init() {
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region")
createSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.proxySecretRef, "proxy-secret-ref", "", "the name of an existing secret containing the proxy address and credentials")
createSourceBucketCmd.Flags().StringSliceVar(&sourceBucketArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in bucket resource (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceBucketCmd)
@ -94,7 +92,7 @@ func init() {
func newSourceBucketFlags() sourceBucketFlags {
return sourceBucketFlags{
provider: flags.SourceBucketProvider(sourcev1.BucketProviderGeneric),
provider: flags.SourceBucketProvider(sourcev1.GenericBucketProvider),
}
}
@ -155,12 +153,6 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
}
}
if sourceBucketArgs.proxySecretRef != "" {
bucket.Spec.ProxySecretRef = &meta.LocalObjectReference{
Name: sourceBucketArgs.proxySecretRef,
}
}
if createArgs.export {
return printExport(exportBucket(bucket))
}

@ -44,26 +44,23 @@ import (
)
type sourceGitFlags struct {
url string
branch string
tag string
semver string
refName string
commit string
username string
password string
keyAlgorithm flags.PublicKeyAlgorithm
keyRSABits flags.RSAKeyBits
keyECDSACurve flags.ECDSACurve
secretRef string
proxySecretRef string
provider flags.SourceGitProvider
caFile string
privateKeyFile string
recurseSubmodules bool
silent bool
ignorePaths []string
sparseCheckoutPaths []string
url string
branch string
tag string
semver string
refName string
commit string
username string
password string
keyAlgorithm flags.PublicKeyAlgorithm
keyRSABits flags.RSAKeyBits
keyECDSACurve flags.ECDSACurve
secretRef string
caFile string
privateKeyFile string
recurseSubmodules bool
silent bool
ignorePaths []string
}
var createSourceGitCmd = &cobra.Command{
@ -122,13 +119,7 @@ For private Git repositories, the basic authentication credentials are stored in
--url=https://github.com/stefanprodan/podinfo \
--branch=master \
--username=username \
--password=password
# Create a source for a Git repository using azure provider
flux create source git podinfo \
--url=https://dev.azure.com/foo/bar/_git/podinfo \
--branch=master \
--provider=azure`,
--password=password`,
RunE: createSourceGitCmdRun,
}
@ -139,23 +130,20 @@ func init() {
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.branch, "branch", "", "git branch")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.tag, "tag", "", "git tag")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.semver, "tag-semver", "", "git tag semver range")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.refName, "ref-name", "", "git reference name")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.refName, "ref-name", "", " git reference name")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.commit, "commit", "", "git commit")
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.username, "username", "u", "", "basic authentication username")
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.password, "password", "p", "", "basic authentication password")
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyAlgorithm, "ssh-key-algorithm", sourceGitArgs.keyAlgorithm.Description())
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyRSABits, "ssh-rsa-bits", sourceGitArgs.keyRSABits.Description())
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, "ssh-ecdsa-curve", sourceGitArgs.keyECDSACurve.Description())
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials or github app authentication")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.proxySecretRef, "proxy-secret-ref", "", "the name of an existing secret containing the proxy address and credentials")
createSourceGitCmd.Flags().Var(&sourceGitArgs.provider, "provider", sourceGitArgs.provider.Description())
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
"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().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)
}
@ -222,7 +210,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
RecurseSubmodules: sourceGitArgs.recurseSubmodules,
Reference: &sourcev1.GitRepositoryRef{},
Ignore: ignorePaths,
SparseCheckout: sourceGitArgs.sparseCheckoutPaths,
},
}
@ -249,16 +236,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
}
}
if sourceGitArgs.proxySecretRef != "" {
gitRepository.Spec.ProxySecretRef = &meta.LocalObjectReference{
Name: sourceGitArgs.proxySecretRef,
}
}
if provider := sourceGitArgs.provider.String(); provider != "" {
gitRepository.Spec.Provider = provider
}
if createArgs.export {
return printExport(exportGit(&gitRepository))
}
@ -296,7 +273,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
secretOpts.CACrt = caBundle
secretOpts.CAFile = caBundle
}
secretOpts.Username = sourceGitArgs.username
secretOpts.Password = sourceGitArgs.password

@ -87,7 +87,7 @@ func (r *reconciler) conditionFunc() (bool, error) {
}
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 {
name string
@ -134,36 +134,6 @@ func TestCreateSourceGitExport(t *testing.T) {
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --branch=test --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-branch.yaml"),
},
{
name: "source with generic provider",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --provider generic --branch=test --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-provider-generic.yaml"),
},
{
name: "source with azure provider",
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider azure --branch=test --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-provider-azure.yaml"),
},
{
name: "source with github provider",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --provider github --branch=test --interval=1m0s --secret-ref appinfo --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-provider-github.yaml"),
},
{
name: "source with invalid provider",
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider dummy --branch=test --interval=1m0s --export",
assert: assertError("invalid argument \"dummy\" for \"--provider\" flag: source Git provider 'dummy' is not supported, must be one of: generic|azure|github"),
},
{
name: "source with empty provider",
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider \"\" --branch=test --interval=1m0s --export",
assert: assertError("invalid argument \"\" for \"--provider\" flag: no source Git provider given, please specify the Git provider name"),
},
{
name: "source with no provider",
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --branch=test --interval=1m0s --export --provider",
assert: assertError("flag needs an argument: --provider"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {

@ -197,9 +197,9 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
Namespace: *kubeconfigArgs.Namespace,
Username: sourceHelmArgs.username,
Password: sourceHelmArgs.password,
CACrt: caBundle,
TLSCrt: certFile,
TLSKey: keyFile,
CAFile: caBundle,
CertFile: certFile,
KeyFile: keyFile,
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
secret, err := sourcesecret.Generate(secretOpts)

@ -29,7 +29,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
@ -63,7 +65,6 @@ type sourceOCIRepositoryFlags struct {
semver string
digest string
secretRef string
proxySecretRef string
serviceAccount string
certSecretRef string
verifyProvider flags.SourceOCIVerifyProvider
@ -79,7 +80,7 @@ var sourceOCIRepositoryArgs = newSourceOCIFlags()
func newSourceOCIFlags() sourceOCIRepositoryFlags {
return sourceOCIRepositoryFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
provider: flags.SourceOCIProvider(sourcev1b2.GenericOCIProvider),
}
}
@ -90,7 +91,6 @@ func init() {
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, "tag-semver", "", "the OCI artifact tag semver range")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.proxySecretRef, "proxy-secret-ref", "", "the name of an existing secret containing the proxy address and credentials")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description())
@ -125,20 +125,20 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
ignorePaths = &ignorePathsStr
}
repository := &sourcev1.OCIRepository{
repository := &sourcev1b2.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Labels: sourceLabels,
},
Spec: sourcev1.OCIRepositorySpec{
Spec: sourcev1b2.OCIRepositorySpec{
Provider: sourceOCIRepositoryArgs.provider.String(),
URL: sourceOCIRepositoryArgs.url,
Insecure: sourceOCIRepositoryArgs.insecure,
Interval: metav1.Duration{
Duration: createArgs.interval,
},
Reference: &sourcev1.OCIRepositoryRef{},
Reference: &sourcev1b2.OCIRepositoryRef{},
Ignore: ignorePaths,
},
}
@ -167,12 +167,6 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
}
}
if secretName := sourceOCIRepositoryArgs.proxySecretRef; secretName != "" {
repository.Spec.ProxySecretRef = &meta.LocalObjectReference{
Name: secretName,
}
}
if secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != "" {
repository.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: secretName,
@ -235,13 +229,13 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
}
func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {
ociRepository *sourcev1b2.OCIRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: ociRepository.GetNamespace(),
Name: ociRepository.GetName(),
}
var existing sourcev1.OCIRepository
var existing sourcev1b2.OCIRepository
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {

@ -59,7 +59,6 @@ const (
type tenantFlags struct {
namespaces []string
clusterRole string
account string
}
var tenantArgs tenantFlags
@ -67,7 +66,6 @@ var tenantArgs tenantFlags
func init() {
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.account, "with-service-account", "", "service account belonging to this tenant")
createCmd.AddCommand(createTenantCmd)
}
@ -109,17 +107,9 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
}
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{
ObjectMeta: metav1.ObjectMeta{
Name: accountName,
Name: tenant,
Namespace: ns,
Labels: objLabels,
},
@ -141,7 +131,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
},
{
Kind: "ServiceAccount",
Name: accountName,
Name: tenant,
Namespace: ns,
},
},
@ -293,9 +283,9 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err
}
rootCmd.Println("---")
fmt.Println("---")
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
rootCmd.Println(resourceToString(data))
fmt.Println(resourceToString(data))
account.TypeMeta = metav1.TypeMeta{
APIVersion: "v1",
@ -306,9 +296,9 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err
}
rootCmd.Println("---")
fmt.Println("---")
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
rootCmd.Println(resourceToString(data))
fmt.Println(resourceToString(data))
roleBinding.TypeMeta = metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
@ -319,8 +309,8 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err
}
rootCmd.Println("---")
rootCmd.Println(resourceToString(data))
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

@ -1,68 +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"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

@ -1,31 +0,0 @@
/*
Copyright 2024 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 debugCmd = &cobra.Command{
Use: "debug",
Short: "Debug a flux resource",
Long: `The debug command can be used to troubleshoot failing resource reconciliations.`,
}
func init() {
rootCmd.AddCommand(debugCmd)
}

@ -1,113 +0,0 @@
/*
Copyright 2024 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"github.com/fluxcd/pkg/chartutil"
"github.com/go-logr/logr"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils"
)
var debugHelmReleaseCmd = &cobra.Command{
Use: "helmrelease [name]",
Aliases: []string{"hr"},
Short: "Debug a HelmRelease resource",
Long: withPreviewNote(`The debug helmrelease command can be used to troubleshoot failing Helm release reconciliations.
WARNING: This command will print sensitive information if Kubernetes Secrets are referenced in the HelmRelease .spec.valuesFrom field.`),
Example: ` # Print the status of a Helm release
flux debug hr podinfo --show-status
# Export the final values of a Helm release composed from referred ConfigMaps and Secrets
flux debug hr podinfo --show-values > values.yaml`,
RunE: debugHelmReleaseCmdRun,
Args: cobra.ExactArgs(1),
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
}
type debugHelmReleaseFlags struct {
showStatus bool
showValues bool
}
var debugHelmReleaseArgs debugHelmReleaseFlags
func init() {
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")
debugCmd.AddCommand(debugHelmReleaseCmd)
}
func debugHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if (!debugHelmReleaseArgs.showStatus && !debugHelmReleaseArgs.showValues) ||
(debugHelmReleaseArgs.showStatus && debugHelmReleaseArgs.showValues) {
return fmt.Errorf("either --show-status or --show-values must be set")
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
hr := &helmv2.HelmRelease{}
hrName := types.NamespacedName{Namespace: *kubeconfigArgs.Namespace, Name: name}
if err := kubeClient.Get(ctx, hrName, hr); err != nil {
return err
}
if debugHelmReleaseArgs.showStatus {
status, err := yaml.Marshal(hr.Status)
if err != nil {
return err
}
rootCmd.Println("# Status documentation: https://fluxcd.io/flux/components/helm/helmreleases/#helmrelease-status")
rootCmd.Print(string(status))
return nil
}
if debugHelmReleaseArgs.showValues {
finalValues, err := chartutil.ChartValuesFromReferences(ctx,
logr.Discard(),
kubeClient,
hr.GetNamespace(),
hr.GetValues(),
hr.Spec.ValuesFrom...)
if err != nil {
return err
}
values, err := yaml.Marshal(finalValues)
if err != nil {
return err
}
rootCmd.Print(string(values))
}
return nil
}

@ -1,71 +0,0 @@
//go:build unit
// +build unit
/*
Copyright 2024 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 TestDebugHelmRelease(t *testing.T) {
namespace := allocateNamespace("debug")
objectFile := "testdata/debug_helmrelease/objects.yaml"
tmpl := map[string]string{
"fluxns": namespace,
}
testEnv.CreateObjectFile(objectFile, tmpl, t)
cases := []struct {
name string
arg string
goldenFile string
tmpl map[string]string
}{
{
"debug status",
"debug helmrelease test-values-inline --show-status --show-values=false",
"testdata/debug_helmrelease/status.golden.yaml",
tmpl,
},
{
"debug values",
"debug helmrelease test-values-inline --show-values --show-status=false",
"testdata/debug_helmrelease/values-inline.golden.yaml",
tmpl,
},
{
"debug values from",
"debug helmrelease test-values-from --show-values --show-status=false",
"testdata/debug_helmrelease/values-from.golden.yaml",
tmpl,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.arg + " -n=" + namespace,
assert: assertGoldenTemplateFile(tt.goldenFile, tmpl),
}
cmd.runTestCmd(t)
})
}
}

@ -1,134 +0,0 @@
/*
Copyright 2024 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"
"errors"
"fmt"
"sort"
"strings"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/pkg/kustomize"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils"
)
var debugKustomizationCmd = &cobra.Command{
Use: "kustomization [name]",
Aliases: []string{"ks"},
Short: "Debug a Flux Kustomization resource",
Long: withPreviewNote(`The debug kustomization command can be used to troubleshoot failing Flux Kustomization reconciliations.
WARNING: This command will print sensitive information if Kubernetes Secrets are referenced in the Kustomization .spec.postBuild.substituteFrom field.`),
Example: ` # Print the status of a Flux Kustomization
flux debug ks podinfo --show-status
# Export the final variables used for post-build substitutions composed from referred ConfigMaps and Secrets
flux debug ks podinfo --show-vars > vars.env`,
RunE: debugKustomizationCmdRun,
Args: cobra.ExactArgs(1),
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
}
type debugKustomizationFlags struct {
showStatus bool
showVars bool
}
var debugKustomizationArgs debugKustomizationFlags
func init() {
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")
debugCmd.AddCommand(debugKustomizationCmd)
}
func debugKustomizationCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if (!debugKustomizationArgs.showStatus && !debugKustomizationArgs.showVars) ||
(debugKustomizationArgs.showStatus && debugKustomizationArgs.showVars) {
return fmt.Errorf("either --show-status or --show-vars must be set")
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
ks := &kustomizev1.Kustomization{}
ksName := types.NamespacedName{Namespace: *kubeconfigArgs.Namespace, Name: name}
if err := kubeClient.Get(ctx, ksName, ks); err != nil {
return err
}
if debugKustomizationArgs.showStatus {
status, err := yaml.Marshal(ks.Status)
if err != nil {
return err
}
rootCmd.Println("# Status documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#kustomization-status")
rootCmd.Print(string(status))
return nil
}
if debugKustomizationArgs.showVars {
if ks.Spec.PostBuild == nil {
return errors.New("no post build substitutions found")
}
ksObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ks)
if err != nil {
return err
}
finalVars, err := kustomize.LoadVariables(ctx, kubeClient, unstructured.Unstructured{Object: ksObj})
if err != nil {
return err
}
if len(ks.Spec.PostBuild.Substitute) > 0 {
for k, v := range ks.Spec.PostBuild.Substitute {
// Remove new lines from the values as they are not supported.
// Replicates the controller behavior from
// https://github.com/fluxcd/pkg/blob/main/kustomize/kustomize_varsub.go
finalVars[k] = strings.ReplaceAll(v, "\n", "")
}
}
keys := make([]string, 0, len(finalVars))
for k := range finalVars {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
rootCmd.Println(k + "=" + finalVars[k])
}
}
return nil
}

@ -1,71 +0,0 @@
//go:build unit
// +build unit
/*
Copyright 2024 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 TestDebugKustomization(t *testing.T) {
namespace := allocateNamespace("debug")
objectFile := "testdata/debug_kustomization/objects.yaml"
tmpl := map[string]string{
"fluxns": namespace,
}
testEnv.CreateObjectFile(objectFile, tmpl, t)
cases := []struct {
name string
arg string
goldenFile string
tmpl map[string]string
}{
{
"debug status",
"debug ks test --show-status --show-vars=false",
"testdata/debug_kustomization/status.golden.yaml",
tmpl,
},
{
"debug vars",
"debug ks test --show-vars --show-status=false",
"testdata/debug_kustomization/vars.golden.env",
tmpl,
},
{
"debug vars from",
"debug ks test-from --show-vars --show-status=false",
"testdata/debug_kustomization/vars-from.golden.env",
tmpl,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.arg + " -n=" + namespace,
assert: assertGoldenTemplateFile(tt.goldenFile, tmpl),
}
cmd.runTestCmd(t)
})
}
}

@ -25,7 +25,7 @@ import (
var deleteImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
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
flux delete image policy alpine3.x`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),

@ -25,7 +25,7 @@ import (
var deleteImageRepositoryCmd = &cobra.Command{
Use: "repository [name]",
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
flux delete image repository alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),

@ -25,7 +25,7 @@ import (
var deleteImageUpdateCmd = &cobra.Command{
Use: "update [name]",
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
flux delete image update latest-images`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),

@ -19,13 +19,13 @@ package main
import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var deleteSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Delete a Bucket source",
Long: "The delete source bucket command deletes the given Bucket from the cluster.",
Long: withPreviewNote("The delete source bucket command deletes the given Bucket from the cluster."),
Example: ` # Delete a Bucket source
flux delete source bucket podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),

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

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

@ -21,9 +21,8 @@ import (
"fmt"
"os"
"github.com/fluxcd/pkg/oci"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/google/go-containerregistry/pkg/crane"
oci "github.com/fluxcd/pkg/oci/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags"
@ -32,7 +31,7 @@ import (
var diffArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Diff Artifact",
Long: `The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`,
Long: withPreviewNote(`The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`),
Example: `# Check if local files differ from remote
flux diff artifact oci://ghcr.io/stefanprodan/manifests:podinfo:6.2.0 --path=./kustomize`,
RunE: diffArtifactCmdRun,
@ -43,7 +42,6 @@ type diffArtifactFlags struct {
creds string
provider flags.SourceOCIProvider
ignorePaths []string
insecure bool
}
var diffArtifactArgs = newDiffArtifactArgs()
@ -59,7 +57,6 @@ func init() {
diffArtifactCmd.Flags().StringVar(&diffArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
diffArtifactCmd.Flags().Var(&diffArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
diffArtifactCmd.Flags().StringSliceVar(&diffArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
diffArtifactCmd.Flags().BoolVar(&diffArtifactArgs.insecure, "insecure-registry", false, "allows the remote artifact to be pulled without TLS")
diffCmd.AddCommand(diffArtifactCmd)
}
@ -85,27 +82,24 @@ func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
opts := oci.DefaultOptions()
ociClient := oci.NewClient(oci.DefaultOptions())
if diffArtifactArgs.insecure {
opts = append(opts, crane.Insecure)
if diffArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && diffArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(diffArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
opt, err := loginWithProvider(ctx, url, diffArtifactArgs.provider.String())
ociProvider, err := diffArtifactArgs.provider.ToOCIProvider()
if err != nil {
return fmt.Errorf("error during login with provider: %w", err)
return fmt.Errorf("provider not supported: %w", err)
}
opts = append(opts, opt)
}
ociClient := oci.NewClient(opts)
if diffArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && diffArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(diffArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
return fmt.Errorf("error during login with provider: %w", err)
}
}

@ -96,7 +96,7 @@ func TestDiffArtifact(t *testing.T) {
tt.url = fmt.Sprintf(tt.url, dockerReg)
_, err := executeCommand("push artifact " + tt.url + " --path=" + tt.pushFile + " --source=test --revision=test")
if err != nil {
t.Fatal(fmt.Errorf("failed to push image: %w", err).Error())
t.Fatalf(fmt.Errorf("failed to push image: %w", err).Error())
}
cmd := cmdTestCase{

@ -44,12 +44,7 @@ flux diff kustomization my-app --path ./path/to/local/manifests \
# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.
flux diff kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"
# Run recursively on all encountered Kustomizations
flux diff kustomization my-app --path ./path/to/local/manifests \
--recursive \
--local-sources GitRepository/flux-system/my-repo=./path/to/local/git`,
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: diffKsCmdRun,
}
@ -60,8 +55,6 @@ type diffKsFlags struct {
ignorePaths []string
progressBar bool
strictSubst bool
recursive bool
localSources map[string]string
}
var diffKsArgs diffKsFlags
@ -73,8 +66,6 @@ func init() {
diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
diffKsCmd.Flags().BoolVar(&diffKsArgs.strictSubst, "strict-substitute", false,
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.")
diffKsCmd.Flags().BoolVarP(&diffKsArgs.recursive, "recursive", "r", false, "Recursively diff Kustomizations")
diffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path")
diffCmd.AddCommand(diffKsCmd)
}
@ -110,9 +101,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithProgressBar(),
build.WithIgnore(diffKsArgs.ignorePaths),
build.WithStrictSubstitute(diffKsArgs.strictSubst),
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
)
} else {
builder, err = build.NewBuilder(name, diffKsArgs.path,
@ -121,9 +109,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
build.WithIgnore(diffKsArgs.ignorePaths),
build.WithStrictSubstitute(diffKsArgs.strictSubst),
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
)
}
@ -153,12 +138,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
select {
case <-sigc:
if diffKsArgs.progressBar {
err := builder.StopSpinner()
if err != nil {
return err
}
}
fmt.Println("Build cancelled... exiting.")
return builder.Cancel()
case err := <-errChan:

@ -27,7 +27,6 @@ import (
"github.com/fluxcd/flux2/v2/internal/build"
"github.com/fluxcd/pkg/ssa"
"github.com/fluxcd/pkg/ssa/normalize"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -98,12 +97,6 @@ func TestDiffKustomization(t *testing.T) {
objectFile: "",
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
},
{
name: "diff with recursive",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --progress-bar=false --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
objectFile: "./testdata/diff-kustomization/my-app.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-recursive.golden"),
},
}
tmpl := map[string]string{
@ -152,7 +145,7 @@ func createObjectFromFile(objectFile string, templateValues map[string]string, t
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)
}

@ -46,6 +46,7 @@ import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/printers"
@ -445,8 +446,8 @@ var fluxKindMap = refMap{
field: []string{"spec", "sourceRef"},
},
sourcev1.GitRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)},
sourcev1.OCIRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)},
sourcev1.BucketKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)},
sourcev1b2.OCIRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.OCIRepositoryKind)},
sourcev1b2.BucketKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.BucketKind)},
sourcev1.HelmRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)},
autov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)},
imagev1.ImageRepositoryKind: {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)},

@ -26,7 +26,7 @@ import (
var exportImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
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
flux export image policy --all > image-policies.yaml

@ -26,7 +26,7 @@ import (
var exportImageRepositoryCmd = &cobra.Command{
Use: "repository [name]",
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
flux export image repository --all > image-repositories.yaml

@ -26,7 +26,7 @@ import (
var exportImageUpdateCmd = &cobra.Command{
Use: "update [name]",
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
flux export image update --all > updates.yaml

@ -21,13 +21,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var exportSourceBucketCmd = &cobra.Command{
Use: "bucket [name]",
Short: "Export Bucket sources in YAML format",
Long: "The export source git command exports one or all Bucket sources in YAML format.",
Long: withPreviewNote("The export source git command exports one or all Bucket sources in YAML format."),
Example: ` # Export all Bucket sources
flux export source bucket --all > sources.yaml

@ -21,7 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var exportSourceOCIRepositoryCmd = &cobra.Command{

@ -1,22 +1,6 @@
//go:build unit
// +build unit
/*
Copyright 2024 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 (

@ -184,13 +184,13 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
if get.list.len() == 0 {
if len(args) > 0 {
return fmt.Errorf("%s object '%s' not found in %s namespace",
logger.Failuref("%s object '%s' not found in %s namespace",
get.kind,
args[0],
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
)
} else if !getAll {
return fmt.Errorf("no %s objects found in %s namespace",
logger.Failuref("no %s objects found in %s namespace",
get.kind,
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
)

@ -87,7 +87,7 @@ var getAllCmd = &cobra.Command{
func logError(err error) {
if !apimeta.IsNoMatchError(err) {
logger.Failuref("%s", err.Error())
logger.Failuref(err.Error())
}
}

@ -26,7 +26,7 @@ import (
var getImageAllCmd = &cobra.Command{
Use: "all",
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
flux get images all --namespace=flux-system
@ -55,7 +55,7 @@ var getImageAllCmd = &cobra.Command{
for _, c := range allImageCmd {
if err := c.run(cmd, args); err != nil {
logger.Failuref("%s", err.Error())
logger.Failuref(err.Error())
}
}

@ -28,7 +28,7 @@ import (
var getImagePolicyCmd = &cobra.Command{
Use: "policy",
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
flux get image policy

@ -32,7 +32,7 @@ import (
var getImageRepositoryCmd = &cobra.Command{
Use: "repository",
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
flux get image repository

@ -32,7 +32,7 @@ import (
var getImageUpdateCmd = &cobra.Command{
Use: "update",
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
flux get image update

@ -21,6 +21,7 @@ import (
apimeta "k8s.io/apimachinery/pkg/api/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
)
var getSourceAllCmd = &cobra.Command{
@ -41,11 +42,11 @@ var getSourceAllCmd = &cobra.Command{
var allSourceCmd = []getCommand{
{
apiType: ociRepositoryType,
list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
list: &ociRepositoryListAdapter{&sourcev1b2.OCIRepositoryList{}},
},
{
apiType: bucketType,
list: &bucketListAdapter{&sourcev1.BucketList{}},
list: &bucketListAdapter{&sourcev1b2.BucketList{}},
},
{
apiType: gitRepositoryType,
@ -64,7 +65,7 @@ var getSourceAllCmd = &cobra.Command{
for _, c := range allSourceCmd {
if err := c.run(cmd, args); err != nil {
if !apimeta.IsNoMatchError(err) {
logger.Failuref("%s", err.Error())
logger.Failuref(err.Error())
}
}
}

@ -25,7 +25,7 @@ import (
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
)
@ -33,7 +33,7 @@ import (
var getSourceBucketCmd = &cobra.Command{
Use: "bucket",
Short: "Get Bucket source statuses",
Long: "The get sources bucket command prints the status of the Bucket sources.",
Long: withPreviewNote("The get sources bucket command prints the status of the Bucket sources."),
Example: ` # List all Buckets and their status
flux get sources bucket

@ -25,7 +25,7 @@ import (
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
)

@ -19,10 +19,7 @@ limitations under the License.
package main
import (
"fmt"
"testing"
)
import "testing"
func Test_GetCmd(t *testing.T) {
tmpl := map[string]string{
@ -62,76 +59,3 @@ func Test_GetCmd(t *testing.T) {
})
}
}
func Test_GetCmdErrors(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/objects.yaml", tmpl, t)
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "specific object not found",
args: "get kustomization non-existent-resource -n " + tmpl["fluxns"],
assert: assertError(fmt.Sprintf("Kustomization object 'non-existent-resource' not found in \"%s\" namespace", tmpl["fluxns"])),
},
{
name: "no objects found in namespace",
args: "get helmrelease -n " + tmpl["fluxns"],
assert: assertError(fmt.Sprintf("no HelmRelease objects found in \"%s\" namespace", tmpl["fluxns"])),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}
func Test_GetCmdSuccess(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/objects.yaml", tmpl, t)
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "list sources git",
args: "get sources git -n " + tmpl["fluxns"],
assert: assertSuccess(),
},
{
name: "get help",
args: "get --help",
assert: assertSuccess(),
},
{
name: "get with all namespaces flag",
args: "get sources git -A",
assert: assertSuccess(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

@ -38,7 +38,7 @@ func TestImageScanning(t *testing.T) {
"testdata/image/create_image_repository.golden",
},
{
"create image policy podinfo-semver --image-ref=podinfo --interval=10m --reflect-digest=Always --select-semver=5.0.x",
"create image policy podinfo-semver --image-ref=podinfo --interval=10m --select-semver=5.0.x",
"testdata/image/create_image_policy.golden",
},
{
@ -46,7 +46,7 @@ func TestImageScanning(t *testing.T) {
"testdata/image/get_image_policy_semver.golden",
},
{
`create image policy podinfo-regex --image-ref=podinfo --select-semver=">4.0.0" --filter-regex="5\.0\.0"`,
`create image policy podinfo-regex --image-ref=podinfo --interval=10m --select-semver=">4.0.0" --filter-regex="5\.0\.0"`,
"testdata/image/create_image_policy.golden",
},
{

@ -20,11 +20,10 @@ import (
"context"
"fmt"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/spf13/cobra"
"github.com/fluxcd/pkg/oci"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
oci "github.com/fluxcd/pkg/oci/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/pkg/printers"
@ -35,7 +34,6 @@ type listArtifactFlags struct {
regexFilter string
creds string
provider flags.SourceOCIProvider
insecure bool
}
var listArtifactArgs = newListArtifactFlags()
@ -49,8 +47,8 @@ func newListArtifactFlags() listArtifactFlags {
var listArtifactsCmd = &cobra.Command{
Use: "artifacts",
Short: "list artifacts",
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.`,
Long: withPreviewNote(`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.`),
Example: ` # List the artifacts stored in an OCI repository
flux list artifact oci://ghcr.io/org/config/app
`,
@ -62,7 +60,6 @@ func init() {
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.regexFilter, "filter-regex", "", "filter tags returned from the oci repository using regex")
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
listArtifactsCmd.Flags().Var(&listArtifactArgs.provider, "provider", listArtifactArgs.provider.Description())
listArtifactsCmd.Flags().BoolVar(&listArtifactArgs.insecure, "insecure-registry", false, "allows the remote artifacts list to be fetched without TLS")
listCmd.AddCommand(listArtifactsCmd)
}
@ -81,27 +78,24 @@ func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
return err
}
ociOpts := oci.DefaultOptions()
ociClient := oci.NewClient(oci.DefaultOptions())
if listArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && listArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(listArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
ociOpt, err := loginWithProvider(ctx, url, listArtifactArgs.provider.String())
ociProvider, err := listArtifactArgs.provider.ToOCIProvider()
if err != nil {
return fmt.Errorf("error during login with provider: %w", err)
return fmt.Errorf("provider not supported: %w", err)
}
ociOpts = append(ociOpts, ociOpt)
}
if listArtifactArgs.insecure {
ociOpts = append(ociOpts, crane.Insecure)
}
ociClient := oci.NewClient(ociOpts)
if listArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && listArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(listArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
return fmt.Errorf("error during login with provider: %w", err)
}
}

@ -235,7 +235,7 @@ func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error
return errors.Join(<-stdoutErrCh, <-stderrErrCh)
}
// asyncCopy copies all data from dst to src asynchronously and returns a channel for reading an error value.
// asyncCopy copies all data from from dst to src asynchronously and returns a channel for reading an error value.
// This is basically an asynchronous wrapper around `io.Copy`. The returned channel is unbuffered and always is sent
// a value (either nil or the error from `io.Copy`) as soon as `io.Copy` returns.
// This function lets you copy from multiple sources into multiple destinations in parallel.

@ -429,9 +429,7 @@ func resetCmdArgs() {
tail: -1,
fluxNamespace: rootArgs.defaults.Namespace,
}
buildKsArgs = buildKsFlags{
localSources: map[string]string{},
}
buildKsArgs = buildKsFlags{}
checkArgs = checkFlags{}
createArgs = createFlags{}
deleteArgs = deleteFlags{}
@ -453,7 +451,6 @@ func resetCmdArgs() {
rhrArgs = reconcileHelmReleaseFlags{}
rksArgs = reconcileKsFlags{}
secretGitArgs = NewSecretGitFlags()
secretProxyArgs = secretProxyFlags{}
secretHelmArgs = secretHelmFlags{}
secretTLSArgs = secretTLSFlags{}
sourceBucketArgs = sourceBucketFlags{}
@ -469,8 +466,6 @@ func resetCmdArgs() {
output: "yaml",
}
envsubstArgs = envsubstFlags{}
debugHelmReleaseArgs = debugHelmReleaseFlags{}
debugKustomizationArgs = debugKustomizationFlags{}
}
func isChangeError(err error) bool {

@ -1,41 +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"
"fmt"
"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"
)
// loginWithProvider gets a crane authentication option for the given provider and URL.
func loginWithProvider(ctx context.Context, url, provider string) (crane.Option, error) {
var opts []auth.Option
if provider == azure.ProviderName {
opts = append(opts, auth.WithAllowShellOut())
}
authenticator, err := authutils.GetArtifactRegistryCredentials(ctx, provider, url, opts...)
if err != nil {
return nil, fmt.Errorf("could not login to provider %s with url %s: %w", provider, url, err)
}
return crane.WithAuth(authenticator), nil
}

@ -21,20 +21,19 @@ import (
"fmt"
"os"
"github.com/google/go-containerregistry/pkg/crane"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
"github.com/fluxcd/pkg/oci"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/fluxcd/flux2/v2/internal/flags"
oci "github.com/fluxcd/pkg/oci/client"
)
var pullArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Pull artifact",
Long: `The pull artifact command downloads and extracts the OCI artifact content to the given path.
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.`,
Long: withPreviewNote(`The pull artifact command downloads and extracts the OCI artifact content to the given path.
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: ` # Pull an OCI artifact created by flux from GHCR
flux pull artifact oci://ghcr.io/org/manifests/app:v0.0.1 --output ./path/to/local/manifests
`,
@ -44,7 +43,6 @@ The command can read the credentials from '~/.docker/config.json' but they can a
type pullArtifactFlags struct {
output string
creds string
insecure bool
provider flags.SourceOCIProvider
}
@ -60,7 +58,6 @@ func init() {
pullArtifactCmd.Flags().StringVarP(&pullArtifactArgs.output, "output", "o", "", "path where the artifact content should be extracted.")
pullArtifactCmd.Flags().StringVar(&pullArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
pullArtifactCmd.Flags().Var(&pullArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
pullArtifactCmd.Flags().BoolVar(&pullArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pulled without TLS")
pullCmd.AddCommand(pullArtifactCmd)
}
@ -86,27 +83,24 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
opts := oci.DefaultOptions()
ociClient := oci.NewClient(oci.DefaultOptions())
if pullArtifactArgs.insecure {
opts = append(opts, crane.Insecure)
if pullArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pullArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(pullArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
opt, err := loginWithProvider(ctx, url, pullArtifactArgs.provider.String())
ociProvider, err := pullArtifactArgs.provider.ToOCIProvider()
if err != nil {
return fmt.Errorf("error during login with provider: %w", err)
return fmt.Errorf("provider not supported: %w", err)
}
opts = append(opts, opt)
}
ociClient := oci.NewClient(opts)
if pullArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pullArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(pullArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
return fmt.Errorf("error during login with provider: %w", err)
}
}

@ -34,7 +34,9 @@ import (
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/oci"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/fluxcd/pkg/oci/auth/login"
"github.com/fluxcd/pkg/oci/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags"
)
@ -42,8 +44,8 @@ import (
var pushArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Push artifact",
Long: `The push artifact command creates a tarball from the given directory or the single file and uploads the artifact to an 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.`,
Long: withPreviewNote(`The push artifact command creates a tarball from the given directory or the single file and uploads the artifact to an 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.`),
Example: ` # Push manifests to GHCR using the short Git SHA as the OCI artifact tag
echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin
flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \
@ -113,7 +115,6 @@ type pushArtifactFlags struct {
output string
debug bool
reproducible bool
insecure bool
}
var pushArtifactArgs = newPushArtifactFlags()
@ -136,7 +137,6 @@ func init() {
"the format in which the artifact digest should be printed, can be 'json' or 'yaml'")
pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pushed without TLS")
pushCmd.AddCommand(pushArtifactCmd)
}
@ -159,7 +159,7 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
}
url, err := oci.ParseArtifactURL(ociURL)
url, err := client.ParseArtifactURL(ociURL)
if err != nil {
return err
}
@ -198,7 +198,7 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
logs.Warn.SetOutput(os.Stderr)
}
meta := oci.Metadata{
meta := client.Metadata{
Source: pushArtifactArgs.source,
Revision: pushArtifactArgs.revision,
Annotations: annotations,
@ -212,24 +212,29 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
var authenticator authn.Authenticator
opts := oci.DefaultOptions()
var auth authn.Authenticator
opts := client.DefaultOptions()
if pushArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pushArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
authenticator, err = oci.GetAuthFromCredentials(pushArtifactArgs.creds)
auth, err = client.GetAuthFromCredentials(pushArtifactArgs.creds)
if err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
opts = append(opts, crane.WithAuth(authenticator))
opts = append(opts, crane.WithAuth(auth))
}
if provider := pushArtifactArgs.provider.String(); provider != sourcev1.GenericOCIProvider {
if pushArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
authOpt, err := loginWithProvider(ctx, url, provider)
ociProvider, err := pushArtifactArgs.provider.ToOCIProvider()
if err != nil {
return fmt.Errorf("provider not supported: %w", err)
}
auth, err = login.NewManager().Login(ctx, url, ref, getProviderLoginOption(ociProvider))
if err != nil {
return fmt.Errorf("error during login with provider: %w", err)
}
opts = append(opts, authOpt)
opts = append(opts, crane.WithAuth(auth))
}
if rootArgs.timeout != 0 {
@ -244,31 +249,27 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
Cap: rootArgs.timeout,
}
if authenticator == nil {
authenticator, err = authn.DefaultKeychain.Resolve(ref.Context())
if auth == nil {
auth, err = authn.DefaultKeychain.Resolve(ref.Context())
if err != nil {
return err
}
}
transportOpts, err := oci.WithRetryTransport(ctx, ref, authenticator, backoff, []string{ref.Context().Scope(transport.PushScope)})
transportOpts, err := client.WithRetryTransport(ctx, ref, auth, backoff, []string{ref.Context().Scope(transport.PushScope)})
if err != nil {
return fmt.Errorf("error setting up transport: %w", err)
}
opts = append(opts, transportOpts, oci.WithRetryBackOff(backoff))
opts = append(opts, transportOpts, client.WithRetryBackOff(backoff))
}
if pushArtifactArgs.output == "" {
logger.Actionf("pushing artifact to %s", url)
}
if pushArtifactArgs.insecure {
opts = append(opts, crane.Insecure)
}
ociClient := oci.NewClient(opts)
ociClient := client.NewClient(opts)
digestURL, err := ociClient.Push(ctx, url, path,
oci.WithPushMetadata(meta),
oci.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),
client.WithPushMetadata(meta),
client.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),
)
if err != nil {
return fmt.Errorf("pushing artifact failed: %w", err)
@ -316,3 +317,16 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
func getProviderLoginOption(provider oci.Provider) login.ProviderOptions {
var opts login.ProviderOptions
switch provider {
case oci.ProviderAzure:
opts.AzureAutoLogin = true
case oci.ProviderAWS:
opts.AwsAutoLogin = true
case oci.ProviderGCP:
opts.GcpAutoLogin = true
}
return opts
}

@ -108,7 +108,7 @@ func isObjectReady(obj client.Object, statusType objectStatusType) (bool, error)
case kstatus.InProgressStatus:
return false, nil
default:
return false, fmt.Errorf("%s", result.Message)
return false, fmt.Errorf(result.Message)
}
}

@ -132,7 +132,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
if readyCond.Status != metav1.ConditionTrue {
return fmt.Errorf("%s reconciliation failed: '%s'", reconcile.kind, readyCond.Message)
}
logger.Successf("%s", reconcile.object.successMessage())
logger.Successf(reconcile.object.successMessage())
return nil
}

@ -24,6 +24,7 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
)
var reconcileHrCmd = &cobra.Command{
@ -31,7 +32,7 @@ var reconcileHrCmd = &cobra.Command{
Aliases: []string{"hr"},
Short: "Reconcile a HelmRelease resource",
Long: `
The reconcile helmrelease command triggers a reconciliation of a HelmRelease resource and waits for it to finish.`,
The reconcile kustomization command triggers a reconciliation of a HelmRelease resource and waits for it to finish.`,
Example: ` # Trigger a HelmRelease apply outside of the reconciliation interval
flux reconcile hr podinfo
@ -91,7 +92,7 @@ func (obj helmReleaseAdapter) getSource() (reconcileSource, types.NamespacedName
}
return reconcileCommand{
apiType: ociRepositoryType,
object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
object: ociRepositoryAdapter{&sourcev1b2.OCIRepository{}},
}, namespacedName
default:
// default case assumes the HelmRelease is using a HelmChartTemplate

@ -22,6 +22,7 @@ import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
)
var reconcileKsCmd = &cobra.Command{
@ -65,20 +66,20 @@ func (obj kustomizationAdapter) reconcileSource() bool {
func (obj kustomizationAdapter) getSource() (reconcileSource, types.NamespacedName) {
var cmd reconcileCommand
switch obj.Spec.SourceRef.Kind {
case sourcev1.OCIRepositoryKind:
case sourcev1b2.OCIRepositoryKind:
cmd = reconcileCommand{
apiType: ociRepositoryType,
object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
object: ociRepositoryAdapter{&sourcev1b2.OCIRepository{}},
}
case sourcev1.GitRepositoryKind:
cmd = reconcileCommand{
apiType: gitRepositoryType,
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
}
case sourcev1.BucketKind:
case sourcev1b2.BucketKind:
cmd = reconcileCommand{
apiType: bucketType,
object: bucketAdapter{&sourcev1.Bucket{}},
object: bucketAdapter{&sourcev1b2.Bucket{}},
}
}

@ -21,7 +21,7 @@ import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var reconcileSourceBucketCmd = &cobra.Command{

@ -21,6 +21,7 @@ import (
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
)
var reconcileSourceHelmChartCmd = &cobra.Command{
@ -71,10 +72,10 @@ func (obj helmChartAdapter) getSource() (reconcileSource, types.NamespacedName)
apiType: gitRepositoryType,
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
}
case sourcev1.BucketKind:
case sourcev1b2.BucketKind:
cmd = reconcileCommand{
apiType: bucketType,
object: bucketAdapter{&sourcev1.Bucket{}},
object: bucketAdapter{&sourcev1b2.Bucket{}},
}
}

@ -21,7 +21,7 @@ import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var reconcileSourceOCIRepositoryCmd = &cobra.Command{

@ -96,6 +96,6 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
if readyCond.Status != metav1.ConditionTrue {
return fmt.Errorf("%s reconciliation failed: %s", reconcile.kind, readyCond.Message)
}
logger.Successf("%s", reconcile.object.successMessage())
logger.Successf(reconcile.object.successMessage())
return nil
}

@ -133,7 +133,7 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
// If the args slice is empty, it patches all resumable objects in the given namespace.
func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []string) ([]resumable, error) {
if len(args) < 1 {
objs, err := resume.patch(ctx, args, []client.ListOption{
objs, err := resume.patch(ctx, []client.ListOption{
client.InNamespace(resume.namespace),
})
if err != nil {
@ -151,7 +151,7 @@ func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []st
}
processed[arg] = struct{}{}
objs, err := resume.patch(ctx, args, []client.ListOption{
objs, err := resume.patch(ctx, []client.ListOption{
client.InNamespace(resume.namespace),
client.MatchingFields{
"metadata.name": arg,
@ -169,17 +169,13 @@ func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []st
// Patches resumable objects by setting their status to unsuspended.
// Returns a slice of resumables that have been patched and any error encountered during patching.
func (resume resumeCommand) patch(ctx context.Context, args []string, listOpts []client.ListOption) ([]resumable, error) {
func (resume resumeCommand) patch(ctx context.Context, listOpts []client.ListOption) ([]resumable, error) {
if err := resume.client.List(ctx, resume.list.asClientList(), listOpts...); err != nil {
return nil, err
}
if resume.list.len() == 0 {
if len(args) < 1 {
logger.Failuref("no %s objects found in %s namespace", resume.kind, resume.namespace)
} else {
logger.Failuref("%s object '%s' not found in %s namespace", resume.kind, args[0], resume.namespace)
}
logger.Failuref("no %s objects found in %s namespace", resume.kind, resume.namespace)
return nil, nil
}
@ -251,9 +247,9 @@ func (resume resumeCommand) printMessage(responses []reconcileResponse) {
continue
}
if r.err != nil {
logger.Failuref("%s", r.err.Error())
logger.Failuref(r.err.Error())
}
logger.Successf("%s %s reconciliation completed", resume.kind, r.asClientObject().GetName())
logger.Successf("%s", r.successMessage())
logger.Successf(r.successMessage())
}
}

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

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

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

Loading…
Cancel
Save