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"] labels: ["area/ci", "dependencies"]
groups: groups:
# Group all updates together, so that they are all applied in a single PR. # 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 # xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
ci: ci:
patterns: patterns:
- "*" - "*"
schedule: 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 description: Feature request proposals in the RFC format
color: '#D621C3' color: '#D621C3'
aliases: ['area/RFC'] aliases: ['area/RFC']
- name: backport:release/v2.4.x - name: backport:release/v2.0.x
description: To be backported to release/v2.4.x description: To be backported to release/v2.0.x
color: '#ffd700' color: '#ffd700'
- name: backport:release/v2.5.x - name: backport:release/v2.1.x
description: To be backported to release/v2.5.x description: To be backported to release/v2.1.x
color: '#ffd700' color: '#ffd700'
- name: backport:release/v2.6.x - name: backport:release/v2.2.x
description: To be backported to release/v2.6.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' color: '#ffd700'

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

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

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

@ -17,27 +17,27 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: 1.24.x go-version: 1.22.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
with: with:
version: v0.24.0 version: v0.22.0
cluster_name: kind cluster_name: kind
# The versions below should target the newest Kubernetes version # The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes # Keep this up-to-date with https://endoflife.date/kubernetes
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64 node_image: ghcr.io/fluxcd/kindest/node:v1.30.0-amd64
kubectl_version: v1.32.0 kubectl_version: v1.30.0
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main uses: fluxcd/pkg/actions/kustomize@main
- name: Setup yq - name: Setup yq
uses: fluxcd/pkg/actions/yq@7e9c75bbb6a47b08c194edefa11d1c436e5bdd9e # main uses: fluxcd/pkg/actions/yq@main
- name: Build - name: Build
run: make build-dev run: make build-dev
- name: Set outputs - name: Set outputs
@ -107,8 +107,6 @@ jobs:
./bin/flux reconcile image repository podinfo ./bin/flux reconcile image repository podinfo
./bin/flux reconcile image update flux-system ./bin/flux reconcile image update flux-system
./bin/flux get images all ./bin/flux get images all
./bin/flux -n flux-system events --for ImageUpdateAutomation/flux-system
kubectl -n flux-system get -o yaml 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' yq '.status.lastPushCommit | length > 1' | grep 'true'
env: 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]' if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: 1.24.x go-version: 1.22.x
cache-dependency-path: tests/integration/go.sum cache-dependency-path: tests/integration/go.sum
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
- name: Setup Flux CLI - name: Setup Flux CLI
run: make build run: make build
working-directory: ./ working-directory: ./
@ -48,19 +46,19 @@ jobs:
env: env:
SOPS_VER: 3.7.1 SOPS_VER: 3.7.1
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193 # v2.1.10 uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2
id: 'auth' id: 'auth'
with: with:
credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}' credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'
token_format: 'access_token' token_format: 'access_token'
- name: Setup gcloud - name: Setup gcloud
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4 uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0
- name: Setup QEMU - 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 - 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 - 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: with:
registry: us-central1-docker.pkg.dev registry: us-central1-docker.pkg.dev
username: oauth2accesstoken username: oauth2accesstoken

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

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

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

@ -17,14 +17,47 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]' if: github.actor != 'dependabot[bot]'
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Run FOSSA scan and upload build data - 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: with:
# FOSSA Push-Only API Token # FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }} 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: scan-codeql:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@ -32,22 +65,22 @@ jobs:
if: github.actor != 'dependabot[bot]' if: github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
with: with:
languages: go 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://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/ # xref: https://codeql.github.com/codeql-query-help/go/
queries: security-and-quality queries: security-and-quality
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
- name: Perform CodeQL Analysis - 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: permissions:
issues: write issues: write
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3 - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
with: with:
# Configuration file # Configuration file

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

@ -1,6 +1,4 @@
project_name: flux project_name: flux
changelog:
use: github-native
builds: builds:
- <<: &build_defaults - <<: &build_defaults
binary: flux binary: flux
@ -17,7 +15,7 @@ builds:
- arm64 - arm64
- arm - arm
goarm: goarm:
- "7" - 7
- <<: *build_defaults - <<: *build_defaults
id: darwin id: darwin
goos: goos:
@ -75,11 +73,11 @@ signs:
output: true output: true
brews: brews:
- name: flux - name: flux
repository: tap:
owner: fluxcd owner: fluxcd
name: homebrew-tap name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Formula folder: Formula
homepage: "https://fluxcd.io/" homepage: "https://fluxcd.io/"
description: "Flux CLI" description: "Flux CLI"
install: | 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: Prerequisites:
* go >= 1.24 * go >= 1.20
* kubectl >= 1.30 * kubectl >= 1.24
* kustomize >= 5.0 * kustomize >= 5.0
* coreutils (on Mac OS) * 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 RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64 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 \ 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 -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
kubectl version --client=true
RUN kubectl version --client=true FROM alpine:3.19 as flux-cli
FROM alpine:3.21 AS flux-cli
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates

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

@ -2,7 +2,7 @@
[![release](https://img.shields.io/github/release/fluxcd/flux2/all.svg)](https://github.com/fluxcd/flux2/releases) [![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) [![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) [![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) [![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) [![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.Username = bServerArgs.username
} }
secretOpts.Password = bitbucketToken secretOpts.Password = bitbucketToken
secretOpts.CACrt = caBundle secretOpts.CAFile = caBundle
} else { } else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password) keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil { if err != nil {

@ -243,7 +243,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Password = gitArgs.password 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 // 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 // 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 { if bootstrapArgs.tokenAuth {
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = gtToken secretOpts.Password = gtToken
secretOpts.CACrt = caBundle secretOpts.CAFile = caBundle
} else { } else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm) secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits) secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)

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

@ -24,7 +24,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/gogit"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -42,7 +41,7 @@ import (
var bootstrapGitLabCmd = &cobra.Command{ var bootstrapGitLabCmd = &cobra.Command{
Use: "gitlab", Use: "gitlab",
Short: "Deploy Flux on a cluster connected to a GitLab repository", Short: "Deploy Flux on a cluster connected to a GitLab repository",
Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exist and Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exists and
commits the Flux manifests to the specified branch. commits the Flux manifests to the specified branch.
Then it configures the target cluster to synchronize with that repository. Then it configures the target cluster to synchronize with that repository.
If the Flux components are present on the cluster, If the Flux components are present on the cluster,
@ -59,14 +58,14 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a repository path # Run bootstrap for a repository path
flux bootstrap gitlab --owner=<group> --repository=<repository name> --path=dev-cluster flux bootstrap gitlab --owner=<group> --repository=<repository name> --path=dev-cluster
# Run bootstrap for a public repository # Run bootstrap for a public repository on a personal account
flux bootstrap gitlab --owner=<group> --repository=<repository name> --visibility=public --token-auth flux bootstrap gitlab --owner=<user> --repository=<repository name> --private=false --personal --token-auth
# Run bootstrap for a private repository hosted on a GitLab server # 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 # 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 # Run bootstrap for a private repository using Deploy Token authentication
flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth
@ -86,7 +85,6 @@ type gitlabFlags struct {
repository string repository string
interval time.Duration interval time.Duration
personal bool personal bool
visibility flags.GitLabVisibility
private bool private bool
hostname string hostname string
path flags.SafeRelativePath path flags.SafeRelativePath
@ -96,13 +94,7 @@ type gitlabFlags struct {
deployTokenAuth bool deployTokenAuth bool
} }
func NewGitlabFlags() gitlabFlags { var gitlabArgs gitlabFlags
return gitlabFlags{
visibility: flags.GitLabVisibility(gitprovider.RepositoryVisibilityPrivate),
}
}
var gitlabArgs = NewGitlabFlags()
func init() { func init() {
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name") 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().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.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().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().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname") 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") 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.") 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 { if err := bootstrapValidate(); err != nil {
return err return err
} }
@ -257,10 +242,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth {
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = glToken secretOpts.Password = glToken
secretOpts.CACrt = caBundle secretOpts.CAFile = caBundle
} else if gitlabArgs.deployTokenAuth { } else if gitlabArgs.deployTokenAuth {
// the actual deploy token will be reconciled later // the actual deploy token will be reconciled later
secretOpts.CACrt = caBundle secretOpts.CAFile = caBundle
} else { } else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password) keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil { if err != nil {
@ -297,7 +282,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// Bootstrap config // Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{ bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal), bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
bootstrap.WithProviderVisibility(gitlabArgs.visibility.String()),
bootstrap.WithBranch(bootstrapArgs.branch), bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"), bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail), bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
@ -317,6 +301,9 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if gitlabArgs.deployTokenAuth { if gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth()) bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())
} }
if !gitlabArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
if gitlabArgs.reconcile { if gitlabArgs.reconcile {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile()) bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
} }

@ -26,15 +26,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/pkg/oci" oci "github.com/fluxcd/pkg/oci/client"
"github.com/fluxcd/pkg/sourceignore" "github.com/fluxcd/pkg/sourceignore"
) )
var buildArtifactCmd = &cobra.Command{ var buildArtifactCmd = &cobra.Command{
Use: "artifact", Use: "artifact",
Short: "Build artifact", Short: "Build artifact",
Long: `The build artifact command creates a tgz file with the manifests Long: withPreviewNote(`The build artifact command creates a tgz file with the manifests
from the given directory or a single manifest file.`, from the given directory or a single manifest file.`),
Example: ` # Build the given manifests directory into an artifact Example: ` # Build the given manifests directory into an artifact
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz 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. # 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 \ flux build kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \ --kustomization-file ./path/to/local/my-app.yaml \
--ignore-paths "/to_ignore/**/*.yaml,ignore.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`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun, RunE: buildKsCmdRun,
} }
@ -69,8 +64,6 @@ type buildKsFlags struct {
ignorePaths []string ignorePaths []string
dryRun bool dryRun bool
strictSubst bool strictSubst bool
recursive bool
localSources map[string]string
} }
var buildKsArgs buildKsFlags var buildKsArgs buildKsFlags
@ -82,8 +75,6 @@ func init() {
buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.") buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.")
buildKsCmd.Flags().BoolVar(&buildKsArgs.strictSubst, "strict-substitute", false, 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.") "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) buildCmd.AddCommand(buildKsCmd)
} }
@ -120,8 +111,6 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
build.WithNamespace(*kubeconfigArgs.Namespace), build.WithNamespace(*kubeconfigArgs.Namespace),
build.WithIgnore(buildKsArgs.ignorePaths), build.WithIgnore(buildKsArgs.ignorePaths),
build.WithStrictSubstitute(buildKsArgs.strictSubst), build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources),
) )
} else { } else {
builder, err = build.NewBuilder(name, buildKsArgs.path, 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.WithKustomizationFile(buildKsArgs.kustomizationFile),
build.WithIgnore(buildKsArgs.ignorePaths), build.WithIgnore(buildKsArgs.ignorePaths),
build.WithStrictSubstitute(buildKsArgs.strictSubst), build.WithStrictSubstitute(buildKsArgs.strictSubst),
build.WithRecursive(buildKsArgs.recursive),
build.WithLocalSources(buildKsArgs.localSources),
) )
} }

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

@ -60,7 +60,7 @@ type checkFlags struct {
} }
var kubernetesConstraints = []string{ var kubernetesConstraints = []string{
">=1.31.0-0", ">=1.28.0-0",
} }
var checkArgs checkFlags var checkArgs checkFlags
@ -217,7 +217,7 @@ func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
} }
} }
for _, c := range d.Spec.Template.Spec.Containers { 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 { for _, crd := range list.Items {
versions := crd.Status.StoredVersions versions := crd.Status.StoredVersions
if len(versions) > 0 { if len(versions) > 0 {
logger.Successf("%s", crd.Name+"/"+versions[len(versions)-1]) logger.Successf(crd.Name + "/" + versions[len(versions)-1])
} else { } else {
ok = false ok = false
logger.Failuref("no stored versions for %s", crd.Name) 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.Generatef("generating %s", names.kind)
logger.Actionf("applying %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 { if err != nil {
return err return err
} }

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

@ -20,7 +20,6 @@ import (
"fmt" "fmt"
"regexp/syntax" "regexp/syntax"
"strings" "strings"
"time"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@ -35,12 +34,12 @@ import (
var createImagePolicyCmd = &cobra.Command{ var createImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Create or update an ImagePolicy object", Short: "Create or update an ImagePolicy object",
Long: `The create image policy command generates an ImagePolicy resource. Long: withPreviewNote(`The create image policy command generates an ImagePolicy resource.
An ImagePolicy object calculates a "latest image" given an image An ImagePolicy object calculates a "latest image" given an image
repository and a policy, e.g., semver. repository and a policy, e.g., semver.
The image that sorts highest according to the policy is recorded in The image that sorts highest according to the policy is recorded in
the status of the object.`, the status of the object.`),
Example: ` # Create an ImagePolicy to select the latest stable release Example: ` # Create an ImagePolicy to select the latest stable release
flux create image policy podinfo \ flux create image policy podinfo \
--image-ref=podinfo \ --image-ref=podinfo \
@ -61,8 +60,6 @@ type imagePolicyFlags struct {
numeric string numeric string
filterRegex string filterRegex string
filterExtract string filterExtract string
reflectDigest string
interval time.Duration
} }
var imagePolicyArgs = imagePolicyFlags{} 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.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.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.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) 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") 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 { if createArgs.export {
return printExport(exportImagePolicy(&policy)) return printExport(exportImagePolicy(&policy))
} }

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

@ -29,9 +29,9 @@ import (
var createImageUpdateCmd = &cobra.Command{ var createImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Create or update an ImageUpdateAutomation object", Short: "Create or update an ImageUpdateAutomation object",
Long: `The create image update command generates an ImageUpdateAutomation resource. Long: withPreviewNote(`The create image update command generates an ImageUpdateAutomation resource.
An ImageUpdateAutomation object specifies an automated update to images An ImageUpdateAutomation object specifies an automated update to images
mentioned in YAMLs in a git repository.`, mentioned in YAMLs in a git repository.`),
Example: ` # Configure image updates for the main repository created by flux bootstrap Example: ` # Configure image updates for the main repository created by flux bootstrap
flux create image update flux-system \ flux create image update flux-system \
--git-repo-ref=flux-system \ --git-repo-ref=flux-system \

@ -87,6 +87,7 @@ type secretGitFlags struct {
keyAlgorithm flags.PublicKeyAlgorithm keyAlgorithm flags.PublicKeyAlgorithm
rsaBits flags.RSAKeyBits rsaBits flags.RSAKeyBits
ecdsaCurve flags.ECDSACurve ecdsaCurve flags.ECDSACurve
caFile string
caCrtFile string caCrtFile string
privateKeyFile string privateKeyFile string
bearerToken string bearerToken string
@ -101,7 +102,8 @@ func init() {
createSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, "ssh-key-algorithm", secretGitArgs.keyAlgorithm.Description()) 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.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.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.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") 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 { if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err) 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: default:
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)

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

@ -84,7 +84,7 @@ func TestCreateNotationSecret(t *testing.T) {
{ {
name: "invalid trust policy", name: "invalid trust policy",
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", t.TempDir(), invalidTrustPolicy), 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", 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" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -32,8 +33,8 @@ import (
var createSecretTLSCmd = &cobra.Command{ var createSecretTLSCmd = &cobra.Command{
Use: "tls [name]", Use: "tls [name]",
Short: "Create or update a Kubernetes secret with TLS certificates", 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.`, 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 SOPS. Example: ` # Create a TLS secret on disk and encrypt it with Mozilla SOPS.
# Files are expected to be PEM-encoded. # Files are expected to be PEM-encoded.
flux create secret tls certs \ flux create secret tls certs \
--namespace=my-namespace \ --namespace=my-namespace \
@ -48,6 +49,9 @@ var createSecretTLSCmd = &cobra.Command{
} }
type secretTLSFlags struct { type secretTLSFlags struct {
certFile string
keyFile string
caFile string
caCrtFile string caCrtFile string
tlsKeyFile string tlsKeyFile string
tlsCrtFile string tlsCrtFile string
@ -55,10 +59,26 @@ type secretTLSFlags struct {
var secretTLSArgs secretTLSFlags 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() { func init() {
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path") flags := createSecretTLSCmd.Flags()
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path") initSecretDeprecatedTLSFlags(flags, &secretTLSArgs)
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path") 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) createSecretCmd.AddCommand(createSecretTLSCmd)
} }
@ -82,6 +102,11 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err) 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 != "" { 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 { if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err) return fmt.Errorf("failed to read key file: %w", err)
} }
} 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) 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", 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"), 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

@ -32,7 +32,7 @@ import (
"github.com/fluxcd/pkg/apis/meta" "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/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
@ -41,8 +41,8 @@ import (
var createSourceBucketCmd = &cobra.Command{ var createSourceBucketCmd = &cobra.Command{
Use: "bucket [name]", Use: "bucket [name]",
Short: "Create or update a Bucket source", Short: "Create or update a Bucket source",
Long: `The create source bucket command generates a Bucket resource and waits for it to be downloaded. 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.`, For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`),
Example: ` # Create a source for a Bucket using static authentication Example: ` # Create a source for a Bucket using static authentication
flux create source bucket podinfo \ flux create source bucket podinfo \
--bucket-name=podinfo \ --bucket-name=podinfo \
@ -71,7 +71,6 @@ type sourceBucketFlags struct {
region string region string
insecure bool insecure bool
secretRef string secretRef string
proxySecretRef string
ignorePaths []string ignorePaths []string
} }
@ -86,7 +85,6 @@ func init() {
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region") 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().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.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)") 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) createSourceCmd.AddCommand(createSourceBucketCmd)
@ -94,7 +92,7 @@ func init() {
func newSourceBucketFlags() sourceBucketFlags { func newSourceBucketFlags() sourceBucketFlags {
return 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 { if createArgs.export {
return printExport(exportBucket(bucket)) return printExport(exportBucket(bucket))
} }

@ -56,14 +56,11 @@ type sourceGitFlags struct {
keyRSABits flags.RSAKeyBits keyRSABits flags.RSAKeyBits
keyECDSACurve flags.ECDSACurve keyECDSACurve flags.ECDSACurve
secretRef string secretRef string
proxySecretRef string
provider flags.SourceGitProvider
caFile string caFile string
privateKeyFile string privateKeyFile string
recurseSubmodules bool recurseSubmodules bool
silent bool silent bool
ignorePaths []string ignorePaths []string
sparseCheckoutPaths []string
} }
var createSourceGitCmd = &cobra.Command{ 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 \ --url=https://github.com/stefanprodan/podinfo \
--branch=master \ --branch=master \
--username=username \ --username=username \
--password=password --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`,
RunE: createSourceGitCmdRun, RunE: createSourceGitCmdRun,
} }
@ -146,16 +137,13 @@ func init() {
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyAlgorithm, "ssh-key-algorithm", sourceGitArgs.keyAlgorithm.Description()) 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.keyRSABits, "ssh-rsa-bits", sourceGitArgs.keyRSABits.Description())
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, "ssh-ecdsa-curve", sourceGitArgs.keyECDSACurve.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.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials")
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.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates") 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().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, 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") "when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation") createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)") createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)")
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.sparseCheckoutPaths, "sparse-checkout-paths", nil, "set paths to sparse checkout in git resource (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceGitCmd) createSourceCmd.AddCommand(createSourceGitCmd)
} }
@ -222,7 +210,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
RecurseSubmodules: sourceGitArgs.recurseSubmodules, RecurseSubmodules: sourceGitArgs.recurseSubmodules,
Reference: &sourcev1.GitRepositoryRef{}, Reference: &sourcev1.GitRepositoryRef{},
Ignore: ignorePaths, Ignore: ignorePaths,
SparseCheckout: sourceGitArgs.sparseCheckoutPaths,
}, },
} }
@ -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 { if createArgs.export {
return printExport(exportGit(&gitRepository)) return printExport(exportGit(&gitRepository))
} }
@ -296,7 +273,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err) return fmt.Errorf("unable to read TLS CA file: %w", err)
} }
secretOpts.CACrt = caBundle secretOpts.CAFile = caBundle
} }
secretOpts.Username = sourceGitArgs.username secretOpts.Username = sourceGitArgs.username
secretOpts.Password = sourceGitArgs.password secretOpts.Password = sourceGitArgs.password

@ -87,7 +87,7 @@ func (r *reconciler) conditionFunc() (bool, error) {
} }
func TestCreateSourceGitExport(t *testing.T) { func TestCreateSourceGitExport(t *testing.T) {
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --sparse-checkout-paths .cosign,non-existent-dir/ --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String() var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String()
cases := []struct { cases := []struct {
name string name string
@ -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", 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"), 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 { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

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

@ -29,7 +29,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1" 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/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
@ -63,7 +65,6 @@ type sourceOCIRepositoryFlags struct {
semver string semver string
digest string digest string
secretRef string secretRef string
proxySecretRef string
serviceAccount string serviceAccount string
certSecretRef string certSecretRef string
verifyProvider flags.SourceOCIVerifyProvider verifyProvider flags.SourceOCIVerifyProvider
@ -79,7 +80,7 @@ var sourceOCIRepositoryArgs = newSourceOCIFlags()
func newSourceOCIFlags() sourceOCIRepositoryFlags { func newSourceOCIFlags() sourceOCIRepositoryFlags {
return 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.semver, "tag-semver", "", "the OCI artifact tag semver range")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest") 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.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.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().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()) createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description())
@ -125,20 +125,20 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
ignorePaths = &ignorePathsStr ignorePaths = &ignorePathsStr
} }
repository := &sourcev1.OCIRepository{ repository := &sourcev1b2.OCIRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: *kubeconfigArgs.Namespace, Namespace: *kubeconfigArgs.Namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: sourcev1.OCIRepositorySpec{ Spec: sourcev1b2.OCIRepositorySpec{
Provider: sourceOCIRepositoryArgs.provider.String(), Provider: sourceOCIRepositoryArgs.provider.String(),
URL: sourceOCIRepositoryArgs.url, URL: sourceOCIRepositoryArgs.url,
Insecure: sourceOCIRepositoryArgs.insecure, Insecure: sourceOCIRepositoryArgs.insecure,
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: createArgs.interval, Duration: createArgs.interval,
}, },
Reference: &sourcev1.OCIRepositoryRef{}, Reference: &sourcev1b2.OCIRepositoryRef{},
Ignore: ignorePaths, 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 != "" { if secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != "" {
repository.Spec.CertSecretRef = &meta.LocalObjectReference{ repository.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: secretName, Name: secretName,
@ -235,13 +229,13 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
} }
func upsertOCIRepository(ctx context.Context, kubeClient client.Client, func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) { ociRepository *sourcev1b2.OCIRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: ociRepository.GetNamespace(), Namespace: ociRepository.GetNamespace(),
Name: ociRepository.GetName(), Name: ociRepository.GetName(),
} }
var existing sourcev1.OCIRepository var existing sourcev1b2.OCIRepository
err := kubeClient.Get(ctx, namespacedName, &existing) err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {

@ -59,7 +59,6 @@ const (
type tenantFlags struct { type tenantFlags struct {
namespaces []string namespaces []string
clusterRole string clusterRole string
account string
} }
var tenantArgs tenantFlags var tenantArgs tenantFlags
@ -67,7 +66,6 @@ var tenantArgs tenantFlags
func init() { func init() {
createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant") createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant")
createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding") createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createTenantCmd.Flags().StringVar(&tenantArgs.account, "with-service-account", "", "service account belonging to this tenant")
createCmd.AddCommand(createTenantCmd) createCmd.AddCommand(createTenantCmd)
} }
@ -109,17 +107,9 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
} }
namespaces = append(namespaces, namespace) namespaces = append(namespaces, namespace)
accountName := tenant
if tenantArgs.account != "" {
accountName = tenantArgs.account
}
if err := validation.IsQualifiedName(accountName); len(err) > 0 {
return fmt.Errorf("invalid service-account name '%s': %v", accountName, err)
}
account := corev1.ServiceAccount{ account := corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: accountName, Name: tenant,
Namespace: ns, Namespace: ns,
Labels: objLabels, Labels: objLabels,
}, },
@ -141,7 +131,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
}, },
{ {
Kind: "ServiceAccount", Kind: "ServiceAccount",
Name: accountName, Name: tenant,
Namespace: ns, Namespace: ns,
}, },
}, },
@ -293,9 +283,9 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err return err
} }
rootCmd.Println("---") fmt.Println("---")
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1) data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
rootCmd.Println(resourceToString(data)) fmt.Println(resourceToString(data))
account.TypeMeta = metav1.TypeMeta{ account.TypeMeta = metav1.TypeMeta{
APIVersion: "v1", APIVersion: "v1",
@ -306,9 +296,9 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err return err
} }
rootCmd.Println("---") fmt.Println("---")
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1) data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
rootCmd.Println(resourceToString(data)) fmt.Println(resourceToString(data))
roleBinding.TypeMeta = metav1.TypeMeta{ roleBinding.TypeMeta = metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1", APIVersion: "rbac.authorization.k8s.io/v1",
@ -319,8 +309,8 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
return err return err
} }
rootCmd.Println("---") fmt.Println("---")
rootCmd.Println(resourceToString(data)) fmt.Println(resourceToString(data))
return nil 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{ var deleteImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Delete an ImagePolicy object", Short: "Delete an ImagePolicy object",
Long: `The delete image policy command deletes the given ImagePolicy from the cluster.`, Long: withPreviewNote(`The delete image policy command deletes the given ImagePolicy from the cluster.`),
Example: ` # Delete an image policy Example: ` # Delete an image policy
flux delete image policy alpine3.x`, flux delete image policy alpine3.x`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)), ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),

@ -25,7 +25,7 @@ import (
var deleteImageRepositoryCmd = &cobra.Command{ var deleteImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Delete an ImageRepository object", Short: "Delete an ImageRepository object",
Long: "The delete image repository command deletes the given ImageRepository from the cluster.", Long: withPreviewNote("The delete image repository command deletes the given ImageRepository from the cluster."),
Example: ` # Delete an image repository Example: ` # Delete an image repository
flux delete image repository alpine`, flux delete image repository alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),

@ -25,7 +25,7 @@ import (
var deleteImageUpdateCmd = &cobra.Command{ var deleteImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Delete an ImageUpdateAutomation object", Short: "Delete an ImageUpdateAutomation object",
Long: `The delete image update command deletes the given ImageUpdateAutomation from the cluster.`, Long: withPreviewNote(`The delete image update command deletes the given ImageUpdateAutomation from the cluster.`),
Example: ` # Delete an image update automation Example: ` # Delete an image update automation
flux delete image update latest-images`, flux delete image update latest-images`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),

@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
) )
var deleteSourceBucketCmd = &cobra.Command{ var deleteSourceBucketCmd = &cobra.Command{
Use: "bucket [name]", Use: "bucket [name]",
Short: "Delete a Bucket source", 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 Example: ` # Delete a Bucket source
flux delete source bucket podinfo`, flux delete source bucket podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)), ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),

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

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

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

@ -96,7 +96,7 @@ func TestDiffArtifact(t *testing.T) {
tt.url = fmt.Sprintf(tt.url, dockerReg) tt.url = fmt.Sprintf(tt.url, dockerReg)
_, err := executeCommand("push artifact " + tt.url + " --path=" + tt.pushFile + " --source=test --revision=test") _, err := executeCommand("push artifact " + tt.url + " --path=" + tt.pushFile + " --source=test --revision=test")
if err != nil { 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{ 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. # 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 \ flux diff kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \ --kustomization-file ./path/to/local/my-app.yaml \
--ignore-paths "/to_ignore/**/*.yaml,ignore.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`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: diffKsCmdRun, RunE: diffKsCmdRun,
} }
@ -60,8 +55,6 @@ type diffKsFlags struct {
ignorePaths []string ignorePaths []string
progressBar bool progressBar bool
strictSubst bool strictSubst bool
recursive bool
localSources map[string]string
} }
var diffKsArgs diffKsFlags 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().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
diffKsCmd.Flags().BoolVar(&diffKsArgs.strictSubst, "strict-substitute", false, 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.") "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) diffCmd.AddCommand(diffKsCmd)
} }
@ -110,9 +101,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithProgressBar(), build.WithProgressBar(),
build.WithIgnore(diffKsArgs.ignorePaths), build.WithIgnore(diffKsArgs.ignorePaths),
build.WithStrictSubstitute(diffKsArgs.strictSubst), build.WithStrictSubstitute(diffKsArgs.strictSubst),
build.WithRecursive(diffKsArgs.recursive),
build.WithLocalSources(diffKsArgs.localSources),
build.WithSingleKustomization(),
) )
} else { } else {
builder, err = build.NewBuilder(name, diffKsArgs.path, builder, err = build.NewBuilder(name, diffKsArgs.path,
@ -121,9 +109,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
build.WithKustomizationFile(diffKsArgs.kustomizationFile), build.WithKustomizationFile(diffKsArgs.kustomizationFile),
build.WithIgnore(diffKsArgs.ignorePaths), build.WithIgnore(diffKsArgs.ignorePaths),
build.WithStrictSubstitute(diffKsArgs.strictSubst), 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 { select {
case <-sigc: case <-sigc:
if diffKsArgs.progressBar {
err := builder.StopSpinner()
if err != nil {
return err
}
}
fmt.Println("Build cancelled... exiting.") fmt.Println("Build cancelled... exiting.")
return builder.Cancel() return builder.Cancel()
case err := <-errChan: case err := <-errChan:

@ -27,7 +27,6 @@ import (
"github.com/fluxcd/flux2/v2/internal/build" "github.com/fluxcd/flux2/v2/internal/build"
"github.com/fluxcd/pkg/ssa" "github.com/fluxcd/pkg/ssa"
"github.com/fluxcd/pkg/ssa/normalize"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
@ -98,12 +97,6 @@ func TestDiffKustomization(t *testing.T) {
objectFile: "", objectFile: "",
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"), 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{ 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) t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
} }
if err := normalize.UnstructuredList(clientObjects); err != nil { if err := ssa.SetNativeKindsDefaults(clientObjects); err != nil {
t.Fatalf("Error setting native kinds defaults for '%s': %v", objectFile, err) t.Fatalf("Error setting native kinds defaults for '%s': %v", objectFile, err)
} }

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

@ -26,7 +26,7 @@ import (
var exportImagePolicyCmd = &cobra.Command{ var exportImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Export ImagePolicy resources in YAML format", Short: "Export ImagePolicy resources in YAML format",
Long: "The export image policy command exports one or all ImagePolicy resources in YAML format.", Long: withPreviewNote("The export image policy command exports one or all ImagePolicy resources in YAML format."),
Example: ` # Export all ImagePolicy resources Example: ` # Export all ImagePolicy resources
flux export image policy --all > image-policies.yaml flux export image policy --all > image-policies.yaml

@ -26,7 +26,7 @@ import (
var exportImageRepositoryCmd = &cobra.Command{ var exportImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Export ImageRepository resources in YAML format", Short: "Export ImageRepository resources in YAML format",
Long: "The export image repository command exports one or all ImageRepository resources in YAML format.", Long: withPreviewNote("The export image repository command exports one or all ImageRepository resources in YAML format."),
Example: ` # Export all ImageRepository resources Example: ` # Export all ImageRepository resources
flux export image repository --all > image-repositories.yaml flux export image repository --all > image-repositories.yaml

@ -26,7 +26,7 @@ import (
var exportImageUpdateCmd = &cobra.Command{ var exportImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Export ImageUpdateAutomation resources in YAML format", Short: "Export ImageUpdateAutomation resources in YAML format",
Long: "The export image update command exports one or all ImageUpdateAutomation resources in YAML format.", Long: withPreviewNote("The export image update command exports one or all ImageUpdateAutomation resources in YAML format."),
Example: ` # Export all ImageUpdateAutomation resources Example: ` # Export all ImageUpdateAutomation resources
flux export image update --all > updates.yaml flux export image update --all > updates.yaml

@ -21,13 +21,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "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{ var exportSourceBucketCmd = &cobra.Command{
Use: "bucket [name]", Use: "bucket [name]",
Short: "Export Bucket sources in YAML format", 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 Example: ` # Export all Bucket sources
flux export source bucket --all > sources.yaml flux export source bucket --all > sources.yaml

@ -21,7 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "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{ var exportSourceOCIRepositoryCmd = &cobra.Command{

@ -1,22 +1,6 @@
//go:build unit //go:build unit
// +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 package main
import ( import (

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

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

@ -26,7 +26,7 @@ import (
var getImageAllCmd = &cobra.Command{ var getImageAllCmd = &cobra.Command{
Use: "all", Use: "all",
Short: "Get all image statuses", Short: "Get all image statuses",
Long: "The get image sub-commands print the statuses of all image objects.", Long: withPreviewNote("The get image sub-commands print the statuses of all image objects."),
Example: ` # List all image objects in a namespace Example: ` # List all image objects in a namespace
flux get images all --namespace=flux-system flux get images all --namespace=flux-system
@ -55,7 +55,7 @@ var getImageAllCmd = &cobra.Command{
for _, c := range allImageCmd { for _, c := range allImageCmd {
if err := c.run(cmd, args); err != nil { 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{ var getImagePolicyCmd = &cobra.Command{
Use: "policy", Use: "policy",
Short: "Get ImagePolicy status", Short: "Get ImagePolicy status",
Long: "The get image policy command prints the status of ImagePolicy objects.", Long: withPreviewNote("The get image policy command prints the status of ImagePolicy objects."),
Example: ` # List all image policies and their status Example: ` # List all image policies and their status
flux get image policy flux get image policy

@ -32,7 +32,7 @@ import (
var getImageRepositoryCmd = &cobra.Command{ var getImageRepositoryCmd = &cobra.Command{
Use: "repository", Use: "repository",
Short: "Get ImageRepository status", Short: "Get ImageRepository status",
Long: "The get image repository command prints the status of ImageRepository objects.", Long: withPreviewNote("The get image repository command prints the status of ImageRepository objects."),
Example: ` # List all image repositories and their status Example: ` # List all image repositories and their status
flux get image repository flux get image repository

@ -32,7 +32,7 @@ import (
var getImageUpdateCmd = &cobra.Command{ var getImageUpdateCmd = &cobra.Command{
Use: "update", Use: "update",
Short: "Get ImageUpdateAutomation status", Short: "Get ImageUpdateAutomation status",
Long: "The get image update command prints the status of ImageUpdateAutomation objects.", Long: withPreviewNote("The get image update command prints the status of ImageUpdateAutomation objects."),
Example: ` # List all image update automation object and their status Example: ` # List all image update automation object and their status
flux get image update flux get image update

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

@ -25,7 +25,7 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime" "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" "github.com/fluxcd/flux2/v2/internal/utils"
) )
@ -33,7 +33,7 @@ import (
var getSourceBucketCmd = &cobra.Command{ var getSourceBucketCmd = &cobra.Command{
Use: "bucket", Use: "bucket",
Short: "Get Bucket source statuses", 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 Example: ` # List all Buckets and their status
flux get sources bucket flux get sources bucket

@ -25,7 +25,7 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime" "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" "github.com/fluxcd/flux2/v2/internal/utils"
) )

@ -19,10 +19,7 @@ limitations under the License.
package main package main
import ( import "testing"
"fmt"
"testing"
)
func Test_GetCmd(t *testing.T) { func Test_GetCmd(t *testing.T) {
tmpl := map[string]string{ 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", "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", "testdata/image/create_image_policy.golden",
}, },
{ {
@ -46,7 +46,7 @@ func TestImageScanning(t *testing.T) {
"testdata/image/get_image_policy_semver.golden", "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", "testdata/image/create_image_policy.golden",
}, },
{ {

@ -20,11 +20,10 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/pkg/oci" oci "github.com/fluxcd/pkg/oci/client"
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/flags"
"github.com/fluxcd/flux2/v2/pkg/printers" "github.com/fluxcd/flux2/v2/pkg/printers"
@ -35,7 +34,6 @@ type listArtifactFlags struct {
regexFilter string regexFilter string
creds string creds string
provider flags.SourceOCIProvider provider flags.SourceOCIProvider
insecure bool
} }
var listArtifactArgs = newListArtifactFlags() var listArtifactArgs = newListArtifactFlags()
@ -49,8 +47,8 @@ func newListArtifactFlags() listArtifactFlags {
var listArtifactsCmd = &cobra.Command{ var listArtifactsCmd = &cobra.Command{
Use: "artifacts", Use: "artifacts",
Short: "list artifacts", Short: "list artifacts",
Long: `The list command fetches the tags and their metadata from a remote OCI repository. 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.`, The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`),
Example: ` # List the artifacts stored in an OCI repository Example: ` # List the artifacts stored in an OCI repository
flux list artifact oci://ghcr.io/org/config/app 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.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().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().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) listCmd.AddCommand(listArtifactsCmd)
} }
@ -81,27 +78,24 @@ func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
ociOpts := oci.DefaultOptions() ociClient := oci.NewClient(oci.DefaultOptions())
if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider { if listArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && listArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with credentials")
ociOpt, err := loginWithProvider(ctx, url, listArtifactArgs.provider.String()) if err := ociClient.LoginWithCredentials(listArtifactArgs.creds); err != nil {
if err != nil { return fmt.Errorf("could not login with credentials: %w", err)
return fmt.Errorf("error during login with provider: %w", err)
} }
ociOpts = append(ociOpts, ociOpt)
} }
if listArtifactArgs.insecure { if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
ociOpts = append(ociOpts, crane.Insecure) logger.Actionf("logging in to registry with provider credentials")
ociProvider, err := listArtifactArgs.provider.ToOCIProvider()
if err != nil {
return fmt.Errorf("provider not supported: %w", err)
} }
ociClient := oci.NewClient(ociOpts) if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
return fmt.Errorf("error during login with provider: %w", err)
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)
} }
} }

@ -235,7 +235,7 @@ func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error
return errors.Join(<-stdoutErrCh, <-stderrErrCh) 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 // 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. // 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. // This function lets you copy from multiple sources into multiple destinations in parallel.

@ -429,9 +429,7 @@ func resetCmdArgs() {
tail: -1, tail: -1,
fluxNamespace: rootArgs.defaults.Namespace, fluxNamespace: rootArgs.defaults.Namespace,
} }
buildKsArgs = buildKsFlags{ buildKsArgs = buildKsFlags{}
localSources: map[string]string{},
}
checkArgs = checkFlags{} checkArgs = checkFlags{}
createArgs = createFlags{} createArgs = createFlags{}
deleteArgs = deleteFlags{} deleteArgs = deleteFlags{}
@ -453,7 +451,6 @@ func resetCmdArgs() {
rhrArgs = reconcileHelmReleaseFlags{} rhrArgs = reconcileHelmReleaseFlags{}
rksArgs = reconcileKsFlags{} rksArgs = reconcileKsFlags{}
secretGitArgs = NewSecretGitFlags() secretGitArgs = NewSecretGitFlags()
secretProxyArgs = secretProxyFlags{}
secretHelmArgs = secretHelmFlags{} secretHelmArgs = secretHelmFlags{}
secretTLSArgs = secretTLSFlags{} secretTLSArgs = secretTLSFlags{}
sourceBucketArgs = sourceBucketFlags{} sourceBucketArgs = sourceBucketFlags{}
@ -469,8 +466,6 @@ func resetCmdArgs() {
output: "yaml", output: "yaml",
} }
envsubstArgs = envsubstFlags{} envsubstArgs = envsubstFlags{}
debugHelmReleaseArgs = debugHelmReleaseFlags{}
debugKustomizationArgs = debugKustomizationFlags{}
} }
func isChangeError(err error) bool { 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" "fmt"
"os" "os"
"github.com/google/go-containerregistry/pkg/crane" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/pkg/oci"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
oci "github.com/fluxcd/pkg/oci/client"
) )
var pullArtifactCmd = &cobra.Command{ var pullArtifactCmd = &cobra.Command{
Use: "artifact", Use: "artifact",
Short: "Pull artifact", Short: "Pull artifact",
Long: `The pull artifact command downloads and extracts the OCI artifact content to the given path. 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.`, 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 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 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 { type pullArtifactFlags struct {
output string output string
creds string creds string
insecure bool
provider flags.SourceOCIProvider 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().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().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().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) pullCmd.AddCommand(pullArtifactCmd)
} }
@ -86,27 +83,24 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
opts := oci.DefaultOptions() ociClient := oci.NewClient(oci.DefaultOptions())
if pullArtifactArgs.insecure { if pullArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pullArtifactArgs.creds != "" {
opts = append(opts, crane.Insecure) 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 { if pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials") logger.Actionf("logging in to registry with provider credentials")
opt, err := loginWithProvider(ctx, url, pullArtifactArgs.provider.String()) ociProvider, err := pullArtifactArgs.provider.ToOCIProvider()
if err != nil { 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 != "" { if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
logger.Actionf("logging in to registry with credentials") return fmt.Errorf("error during login with provider: %w", err)
if err := ociClient.LoginWithCredentials(pullArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
} }
} }

@ -34,7 +34,9 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/oci" "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" "github.com/fluxcd/flux2/v2/internal/flags"
) )
@ -42,8 +44,8 @@ import (
var pushArtifactCmd = &cobra.Command{ var pushArtifactCmd = &cobra.Command{
Use: "artifact", Use: "artifact",
Short: "Push 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. 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.`, 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 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 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) \ flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \
@ -113,7 +115,6 @@ type pushArtifactFlags struct {
output string output string
debug bool debug bool
reproducible bool reproducible bool
insecure bool
} }
var pushArtifactArgs = newPushArtifactFlags() var pushArtifactArgs = newPushArtifactFlags()
@ -136,7 +137,6 @@ func init() {
"the format in which the artifact digest should be printed, can be 'json' or 'yaml'") "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().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'") pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'")
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pushed without TLS")
pushCmd.AddCommand(pushArtifactCmd) pushCmd.AddCommand(pushArtifactCmd)
} }
@ -159,7 +159,7 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid path %q", pushArtifactArgs.path) return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
} }
url, err := oci.ParseArtifactURL(ociURL) url, err := client.ParseArtifactURL(ociURL)
if err != nil { if err != nil {
return err return err
} }
@ -198,7 +198,7 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
logs.Warn.SetOutput(os.Stderr) logs.Warn.SetOutput(os.Stderr)
} }
meta := oci.Metadata{ meta := client.Metadata{
Source: pushArtifactArgs.source, Source: pushArtifactArgs.source,
Revision: pushArtifactArgs.revision, Revision: pushArtifactArgs.revision,
Annotations: annotations, Annotations: annotations,
@ -212,24 +212,29 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
var authenticator authn.Authenticator var auth authn.Authenticator
opts := oci.DefaultOptions() opts := client.DefaultOptions()
if pushArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pushArtifactArgs.creds != "" { if pushArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pushArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials") logger.Actionf("logging in to registry with credentials")
authenticator, err = oci.GetAuthFromCredentials(pushArtifactArgs.creds) auth, err = client.GetAuthFromCredentials(pushArtifactArgs.creds)
if err != nil { if err != nil {
return fmt.Errorf("could not login with credentials: %w", err) 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") 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 { if err != nil {
return fmt.Errorf("error during login with provider: %w", err) return fmt.Errorf("error during login with provider: %w", err)
} }
opts = append(opts, authOpt) opts = append(opts, crane.WithAuth(auth))
} }
if rootArgs.timeout != 0 { if rootArgs.timeout != 0 {
@ -244,37 +249,27 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
Cap: rootArgs.timeout, Cap: rootArgs.timeout,
} }
if authenticator == nil { if auth == nil {
authenticator, err = authn.DefaultKeychain.Resolve(ref.Context()) auth, err = authn.DefaultKeychain.Resolve(ref.Context())
if err != nil { if err != nil {
return err return err
} }
} }
transportOpts, err := oci.WithRetryTransport(ctx, transportOpts, err := client.WithRetryTransport(ctx, ref, auth, backoff, []string{ref.Context().Scope(transport.PushScope)})
ref,
authenticator,
backoff,
[]string{ref.Context().Scope(transport.PushScope)},
pushArtifactArgs.insecure,
)
if err != nil { if err != nil {
return fmt.Errorf("error setting up transport: %w", err) return fmt.Errorf("error setting up transport: %w", err)
} }
opts = append(opts, transportOpts, oci.WithRetryBackOff(backoff)) opts = append(opts, transportOpts, client.WithRetryBackOff(backoff))
} }
if pushArtifactArgs.output == "" { if pushArtifactArgs.output == "" {
logger.Actionf("pushing artifact to %s", url) logger.Actionf("pushing artifact to %s", url)
} }
if pushArtifactArgs.insecure { ociClient := client.NewClient(opts)
opts = append(opts, crane.Insecure)
}
ociClient := oci.NewClient(opts)
digestURL, err := ociClient.Push(ctx, url, path, digestURL, err := ociClient.Push(ctx, url, path,
oci.WithPushMetadata(meta), client.WithPushMetadata(meta),
oci.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...), client.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),
) )
if err != nil { if err != nil {
return fmt.Errorf("pushing artifact failed: %w", err) return fmt.Errorf("pushing artifact failed: %w", err)
@ -322,3 +317,16 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return nil 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: case kstatus.InProgressStatus:
return false, nil return false, nil
default: 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 { if readyCond.Status != metav1.ConditionTrue {
return fmt.Errorf("%s reconciliation failed: '%s'", reconcile.kind, readyCond.Message) return fmt.Errorf("%s reconciliation failed: '%s'", reconcile.kind, readyCond.Message)
} }
logger.Successf("%s", reconcile.object.successMessage()) logger.Successf(reconcile.object.successMessage())
return nil return nil
} }

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

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

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

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

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

@ -96,6 +96,6 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
if readyCond.Status != metav1.ConditionTrue { if readyCond.Status != metav1.ConditionTrue {
return fmt.Errorf("%s reconciliation failed: %s", reconcile.kind, readyCond.Message) return fmt.Errorf("%s reconciliation failed: %s", reconcile.kind, readyCond.Message)
} }
logger.Successf("%s", reconcile.object.successMessage()) logger.Successf(reconcile.object.successMessage())
return nil 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. // 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) { func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []string) ([]resumable, error) {
if len(args) < 1 { if len(args) < 1 {
objs, err := resume.patch(ctx, args, []client.ListOption{ objs, err := resume.patch(ctx, []client.ListOption{
client.InNamespace(resume.namespace), client.InNamespace(resume.namespace),
}) })
if err != nil { if err != nil {
@ -151,7 +151,7 @@ func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []st
} }
processed[arg] = struct{}{} processed[arg] = struct{}{}
objs, err := resume.patch(ctx, args, []client.ListOption{ objs, err := resume.patch(ctx, []client.ListOption{
client.InNamespace(resume.namespace), client.InNamespace(resume.namespace),
client.MatchingFields{ client.MatchingFields{
"metadata.name": arg, "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. // Patches resumable objects by setting their status to unsuspended.
// Returns a slice of resumables that have been patched and any error encountered during patching. // 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 { if err := resume.client.List(ctx, resume.list.asClientList(), listOpts...); err != nil {
return nil, err return nil, err
} }
if resume.list.len() == 0 { if resume.list.len() == 0 {
if len(args) < 1 {
logger.Failuref("no %s objects found in %s namespace", resume.kind, resume.namespace) 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)
}
return nil, nil return nil, nil
} }
@ -251,9 +247,9 @@ func (resume resumeCommand) printMessage(responses []reconcileResponse) {
continue continue
} }
if r.err != nil { 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 %s reconciliation completed", resume.kind, r.asClientObject().GetName())
logger.Successf("%s", r.successMessage()) logger.Successf(r.successMessage())
} }
} }

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

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

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

Loading…
Cancel
Save