Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ecbad956d |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -6,9 +6,11 @@ updates:
|
||||
labels: ["area/ci", "dependencies"]
|
||||
groups:
|
||||
# Group all updates together, so that they are all applied in a single PR.
|
||||
# Grouped updates are currently in beta and is subject to change.
|
||||
# xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
|
||||
ci:
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
# By default, this will be on a monday.
|
||||
interval: "weekly"
|
||||
|
||||
4
.github/kind/config.yaml
vendored
4
.github/kind/config.yaml
vendored
@@ -1,9 +1,5 @@
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
- role: worker
|
||||
- role: worker
|
||||
networking:
|
||||
disableDefaultCNI: true # disable kindnet
|
||||
podSubnet: 192.168.0.0/16 # set to Calico's default subnet
|
||||
|
||||
12
.github/labels.yaml
vendored
12
.github/labels.yaml
vendored
@@ -44,12 +44,12 @@
|
||||
description: Feature request proposals in the RFC format
|
||||
color: '#D621C3'
|
||||
aliases: ['area/RFC']
|
||||
- name: backport:release/v2.6.x
|
||||
description: To be backported to release/v2.6.x
|
||||
- name: backport:release/v2.0.x
|
||||
description: To be backported to release/v2.0.x
|
||||
color: '#ffd700'
|
||||
- name: backport:release/v2.7.x
|
||||
description: To be backported to release/v2.7.x
|
||||
- name: backport:release/v2.1.x
|
||||
description: To be backported to release/v2.1.x
|
||||
color: '#ffd700'
|
||||
- name: backport:release/v2.8.x
|
||||
description: To be backported to release/v2.8.x
|
||||
- name: backport:release/v2.2.x
|
||||
description: To be backported to release/v2.2.x
|
||||
color: '#ffd700'
|
||||
|
||||
2
.github/workflows/action.yaml
vendored
2
.github/workflows/action.yaml
vendored
@@ -24,6 +24,6 @@ jobs:
|
||||
name: action on ${{ matrix.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup flux
|
||||
uses: ./action
|
||||
|
||||
32
.github/workflows/backport.yaml
vendored
32
.github/workflows/backport.yaml
vendored
@@ -1,13 +1,31 @@
|
||||
name: backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # for reading and creating branches.
|
||||
pull-requests: write # for creating pull requests against release branches.
|
||||
uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.9.0
|
||||
secrets:
|
||||
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@e8161d6a0dbfa2651b7daa76cbb75bc7c925bbf3 # v2.4.1
|
||||
# xref: https://github.com/korthout/backport-action#inputs
|
||||
with:
|
||||
# Use token to allow workflows to be triggered for the created PR
|
||||
github_token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
# Match labels with a pattern `backport:<target-branch>`
|
||||
label_pattern: '^backport:([^ ]+)$'
|
||||
# A bit shorter pull-request title than the default
|
||||
pull_title: '[${target_branch}] ${pull_title}'
|
||||
# Simpler PR description than default
|
||||
pull_description: |-
|
||||
Automated backport to `${target_branch}`, triggered by a label in #${pull_number}.
|
||||
|
||||
254
.github/workflows/conformance.yaml
vendored
254
.github/workflows/conformance.yaml
vendored
@@ -1,254 +0,0 @@
|
||||
name: conformance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ 'main', 'update-components', 'release/**', 'conform*' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.26.x
|
||||
|
||||
jobs:
|
||||
conform-kubernetes:
|
||||
runs-on:
|
||||
group: "ARM64"
|
||||
strategy:
|
||||
matrix:
|
||||
# Keep this list up-to-date with https://endoflife.date/kubernetes
|
||||
# Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml
|
||||
KUBERNETES_VERSION: [1.33.0, 1.34.1, 1.35.0]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s)
|
||||
echo "CLUSTER=arm64-${ID}" >> $GITHUB_OUTPUT
|
||||
- name: Build
|
||||
run: |
|
||||
make build
|
||||
- name: Setup Kubernetes
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
|
||||
with:
|
||||
version: v0.30.0
|
||||
cluster_name: ${{ steps.prep.outputs.CLUSTER }}
|
||||
node_image: ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64
|
||||
- name: Run e2e tests
|
||||
run: TEST_KUBECONFIG=$HOME/.kube/config make e2e
|
||||
- name: Run multi-tenancy tests
|
||||
run: |
|
||||
./bin/flux install
|
||||
./bin/flux create source git flux-system \
|
||||
--interval=15m \
|
||||
--url=https://github.com/fluxcd/flux2-multi-tenancy \
|
||||
--branch=main \
|
||||
--ignore-paths="./clusters/**/flux-system/"
|
||||
./bin/flux create kustomization flux-system \
|
||||
--interval=15m \
|
||||
--source=flux-system \
|
||||
--path=./clusters/staging
|
||||
kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m
|
||||
kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m
|
||||
kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m
|
||||
- name: Debug failure
|
||||
if: failure()
|
||||
run: |
|
||||
kubectl -n flux-system get all
|
||||
kubectl -n flux-system describe po
|
||||
kubectl -n flux-system logs deploy/source-controller
|
||||
kubectl -n flux-system logs deploy/kustomize-controller
|
||||
|
||||
conform-k3s:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# Keep this list up-to-date with https://endoflife.date/kubernetes
|
||||
# Available versions can be found with "replicated cluster versions"
|
||||
K3S_VERSION: [ 1.33.7, 1.34.3, 1.35.0 ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
ID=${GITHUB_SHA:0:7}-${{ matrix.K3S_VERSION }}-$(date +%s)
|
||||
PSEUDO_RAND_SUFFIX=$(echo "${ID}" | shasum | awk '{print $1}')
|
||||
echo "cluster=flux2-k3s-${PSEUDO_RAND_SUFFIX}" >> $GITHUB_OUTPUT
|
||||
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
|
||||
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
- name: Build
|
||||
run: make build-dev
|
||||
- name: Create repository
|
||||
run: |
|
||||
gh repo create --private --add-readme fluxcd-testing/${{ steps.prep.outputs.cluster }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: Create cluster
|
||||
id: create-cluster
|
||||
uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
|
||||
with:
|
||||
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
|
||||
kubernetes-distribution: "k3s"
|
||||
kubernetes-version: ${{ matrix.K3S_VERSION }}
|
||||
ttl: 20m
|
||||
cluster-name: "${{ steps.prep.outputs.cluster }}"
|
||||
kubeconfig-path: ${{ steps.prep.outputs.kubeconfig-path }}
|
||||
export-kubeconfig: true
|
||||
- name: Run e2e tests
|
||||
run: TEST_KUBECONFIG=${{ steps.prep.outputs.kubeconfig-path }} make e2e
|
||||
- name: Run flux bootstrap
|
||||
run: |
|
||||
./bin/flux bootstrap git --manifests ./manifests/test/ \
|
||||
--url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \
|
||||
--branch=main \
|
||||
--path=clusters/k3s \
|
||||
--token-auth
|
||||
env:
|
||||
GIT_PASSWORD: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: Run flux check
|
||||
run: |
|
||||
./bin/flux check
|
||||
- name: Run flux reconcile
|
||||
run: |
|
||||
./bin/flux reconcile ks flux-system --with-source
|
||||
./bin/flux get all
|
||||
./bin/flux events
|
||||
- name: Collect reconcile logs
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
kubectl -n flux-system get all
|
||||
kubectl -n flux-system describe pods
|
||||
kubectl -n flux-system logs deploy/source-controller
|
||||
kubectl -n flux-system logs deploy/kustomize-controller
|
||||
kubectl -n flux-system logs deploy/notification-controller
|
||||
- name: Delete flux
|
||||
run: |
|
||||
./bin/flux uninstall -s --keep-namespace
|
||||
kubectl delete ns flux-system --wait
|
||||
- name: Delete cluster
|
||||
if: ${{ always() }}
|
||||
uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
|
||||
cluster-id: ${{ steps.create-cluster.outputs.cluster-id }}
|
||||
- name: Delete repository
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
gh repo delete fluxcd-testing/${{ steps.prep.outputs.cluster }} --yes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
|
||||
conform-openshift:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# Keep this list up-to-date with https://endoflife.date/red-hat-openshift
|
||||
OPENSHIFT_VERSION: [ 4.20.0-okd ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
ID=${GITHUB_SHA:0:7}-${{ matrix.OPENSHIFT_VERSION }}-$(date +%s)
|
||||
PSEUDO_RAND_SUFFIX=$(echo "${ID}" | shasum | awk '{print $1}')
|
||||
echo "cluster=flux2-openshift-${PSEUDO_RAND_SUFFIX}" >> $GITHUB_OUTPUT
|
||||
KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml"
|
||||
echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
- name: Build
|
||||
run: make build-dev
|
||||
- name: Create repository
|
||||
run: |
|
||||
gh repo create --private --add-readme fluxcd-testing/${{ steps.prep.outputs.cluster }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: Create cluster
|
||||
id: create-cluster
|
||||
uses: replicatedhq/replicated-actions/create-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
|
||||
with:
|
||||
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
|
||||
kubernetes-distribution: "openshift"
|
||||
kubernetes-version: ${{ matrix.OPENSHIFT_VERSION }}
|
||||
ttl: 20m
|
||||
cluster-name: "${{ steps.prep.outputs.cluster }}"
|
||||
kubeconfig-path: ${{ steps.prep.outputs.kubeconfig-path }}
|
||||
export-kubeconfig: true
|
||||
- name: Run flux bootstrap
|
||||
run: |
|
||||
./bin/flux bootstrap git --manifests ./manifests/openshift/ \
|
||||
--url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \
|
||||
--branch=main \
|
||||
--path=clusters/openshift \
|
||||
--token-auth
|
||||
env:
|
||||
GIT_PASSWORD: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: Run flux check
|
||||
run: |
|
||||
./bin/flux check
|
||||
- name: Run flux reconcile
|
||||
run: |
|
||||
./bin/flux reconcile ks flux-system --with-source
|
||||
./bin/flux get all
|
||||
./bin/flux events
|
||||
- name: Collect reconcile logs
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
kubectl -n flux-system get all
|
||||
kubectl -n flux-system describe pods
|
||||
kubectl -n flux-system logs deploy/source-controller
|
||||
kubectl -n flux-system logs deploy/kustomize-controller
|
||||
kubectl -n flux-system logs deploy/notification-controller
|
||||
- name: Delete flux
|
||||
run: |
|
||||
./bin/flux uninstall -s --keep-namespace
|
||||
kubectl delete ns flux-system --wait
|
||||
- name: Delete cluster
|
||||
if: ${{ always() }}
|
||||
uses: replicatedhq/replicated-actions/remove-cluster@49b440dabd7e0e868cbbabda5cfc0d8332a279fa # v1.19.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
|
||||
cluster-id: ${{ steps.create-cluster.outputs.cluster-id }}
|
||||
- name: Delete repository
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
gh repo delete fluxcd-testing/${{ steps.prep.outputs.cluster }} --yes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
79
.github/workflows/e2e-arm64.yaml
vendored
Normal file
79
.github/workflows/e2e-arm64.yaml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: e2e-arm64
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ 'main', 'update-components', 'e2e-*', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
e2e-arm64-kubernetes:
|
||||
# Hosted on Equinix
|
||||
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
|
||||
runs-on: [self-hosted, Linux, ARM64, equinix]
|
||||
strategy:
|
||||
matrix:
|
||||
# Keep this list up-to-date with https://endoflife.date/kubernetes
|
||||
# Check which versions are available on DockerHub with 'crane ls kindest/node'
|
||||
KUBERNETES_VERSION: [ 1.26.6, 1.27.3, 1.28.0, 1.29.0 ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s)
|
||||
echo "CLUSTER=arm64-${ID}" >> $GITHUB_OUTPUT
|
||||
- name: Build
|
||||
run: |
|
||||
make build
|
||||
- name: Setup Kubernetes Kind
|
||||
run: |
|
||||
kind create cluster \
|
||||
--wait 5m \
|
||||
--name ${{ steps.prep.outputs.CLUSTER }} \
|
||||
--kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} \
|
||||
--image=kindest/node:v${{ matrix.KUBERNETES_VERSION }}
|
||||
- name: Run e2e tests
|
||||
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e
|
||||
- name: Run multi-tenancy tests
|
||||
env:
|
||||
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||
run: |
|
||||
./bin/flux install
|
||||
./bin/flux create source git flux-system \
|
||||
--interval=15m \
|
||||
--url=https://github.com/fluxcd/flux2-multi-tenancy \
|
||||
--branch=main \
|
||||
--ignore-paths="./clusters/**/flux-system/"
|
||||
./bin/flux create kustomization flux-system \
|
||||
--interval=15m \
|
||||
--source=flux-system \
|
||||
--path=./clusters/staging
|
||||
kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m
|
||||
kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m
|
||||
kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m
|
||||
- name: Debug failure
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||
run: |
|
||||
kubectl -n flux-system get all
|
||||
kubectl -n flux-system describe po
|
||||
kubectl -n flux-system logs deploy/source-controller
|
||||
kubectl -n flux-system logs deploy/kustomize-controller
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }}
|
||||
rm /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||
98
.github/workflows/e2e-azure.yaml
vendored
98
.github/workflows/e2e-azure.yaml
vendored
@@ -21,22 +21,66 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
e2e-aks:
|
||||
runs-on: ubuntu-latest
|
||||
e2e-amd64-aks:
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./tests/azure
|
||||
# This job is currently disabled. Remove the false check when Azure subscription is enabled.
|
||||
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: tests/azure/go.sum
|
||||
- name: Setup Flux CLI
|
||||
run: |
|
||||
make build
|
||||
mkdir -p $HOME/.local/bin
|
||||
mv ./bin/flux $HOME/.local/bin
|
||||
working-directory: ./
|
||||
- name: Setup SOPS
|
||||
run: |
|
||||
mkdir -p $HOME/.local/bin
|
||||
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux -O $HOME/.local/bin/sops
|
||||
chmod +x $HOME/.local/bin/sops
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v2
|
||||
with:
|
||||
terraform_version: 1.2.8
|
||||
terraform_wrapper: false
|
||||
- name: Setup Azure CLI
|
||||
run: |
|
||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||
- name: Run Azure e2e tests
|
||||
env:
|
||||
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
|
||||
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||
run: |
|
||||
ls $HOME/.local/bin
|
||||
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
|
||||
go test -v -coverprofile cover.out -timeout 60m .
|
||||
|
||||
refactored-e2e-amd64-aks:
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./tests/integration
|
||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||
# This job is currently disabled. Remove the false check when Azure subscription is enabled.
|
||||
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: CheckoutD
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: tests/integration/go.sum
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
|
||||
- name: Setup Flux CLI
|
||||
run: make build
|
||||
working-directory: ./
|
||||
@@ -48,9 +92,9 @@ jobs:
|
||||
env:
|
||||
SOPS_VER: 3.7.1
|
||||
- name: Authenticate to Azure
|
||||
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v1.4.6
|
||||
uses: Azure/login@cb79c773a3cfa27f31f25eb3f677781210c9ce3d # v1.4.6
|
||||
with:
|
||||
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
|
||||
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
|
||||
run: |
|
||||
cat > .env <<EOF
|
||||
@@ -60,35 +104,33 @@ jobs:
|
||||
run: cat .env
|
||||
- name: Run Azure e2e tests
|
||||
env:
|
||||
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
|
||||
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||
ARM_CLIENT_ID: ${{ secrets.AZ_ARM_CLIENT_ID }}
|
||||
ARM_CLIENT_SECRET: ${{ secrets.AZ_ARM_CLIENT_SECRET }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.AZ_ARM_TENANT_ID }}
|
||||
TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}
|
||||
TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}
|
||||
TF_VAR_azure_location: ${{ vars.TF_VAR_azure_location }}
|
||||
GITREPO_SSH_CONTENTS: ${{ secrets.GIT_SSH_IDENTITY }}
|
||||
GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GIT_SSH_IDENTITY_PUB }}
|
||||
TF_VAR_location: ${{ vars.TF_VAR_azure_location }}
|
||||
GITREPO_SSH_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_CONTENTS }}
|
||||
GITREPO_SSH_PUB_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_PUB_CONTENTS }}
|
||||
run: |
|
||||
source .env
|
||||
mkdir -p ./build/ssh
|
||||
cat <<EOF > build/ssh/key
|
||||
$GITREPO_SSH_CONTENTS
|
||||
EOF
|
||||
touch ./build/ssh/key
|
||||
echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key
|
||||
export GITREPO_SSH_PATH=build/ssh/key
|
||||
cat <<EOF > build/ssh/key.pub
|
||||
$GITREPO_SSH_PUB_CONTENTS
|
||||
EOF
|
||||
touch ./build/ssh/key.pub
|
||||
echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub
|
||||
export GITREPO_SSH_PUB_PATH=build/ssh/key.pub
|
||||
make test-azure
|
||||
- name: Ensure resource cleanup
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
|
||||
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||
ARM_CLIENT_ID: ${{ secrets.AZ_ARM_CLIENT_ID }}
|
||||
ARM_CLIENT_SECRET: ${{ secrets.AZ_ARM_CLIENT_SECRET }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.AZ_ARM_TENANT_ID }}
|
||||
TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}
|
||||
TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}
|
||||
TF_VAR_azure_location: ${{ vars.TF_VAR_azure_location }}
|
||||
TF_VAR_location: ${{ vars.TF_VAR_azure_location }}
|
||||
run: source .env && make destroy-azure
|
||||
|
||||
70
.github/workflows/e2e-bootstrap.yaml
vendored
70
.github/workflows/e2e-bootstrap.yaml
vendored
@@ -17,29 +17,29 @@ jobs:
|
||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Setup Kubernetes
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
|
||||
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
||||
with:
|
||||
version: v0.30.0
|
||||
version: v0.20.0
|
||||
cluster_name: kind
|
||||
# The versions below should target the newest Kubernetes version
|
||||
# Keep this up-to-date with https://endoflife.date/kubernetes
|
||||
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
|
||||
kubectl_version: v1.33.0
|
||||
node_image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
|
||||
kubectl_version: v1.28.0
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
- name: Setup yq
|
||||
uses: fluxcd/pkg/actions/yq@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Build
|
||||
run: make build-dev
|
||||
run: |
|
||||
make cmd/flux/.manifests.done
|
||||
go build -o /tmp/flux ./cmd/flux
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
run: |
|
||||
@@ -51,24 +51,18 @@ jobs:
|
||||
echo "test_repo_name=$TEST_REPO_NAME" >> $GITHUB_OUTPUT
|
||||
- name: bootstrap init
|
||||
run: |
|
||||
./bin/flux bootstrap github --manifests ./manifests/test/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--image-pull-secret=ghcr-auth \
|
||||
--registry-creds=fluxcd:$GITHUB_TOKEN \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
--path=test-cluster \
|
||||
--team=team-z
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: verify image pull secret
|
||||
run: |
|
||||
kubectl -n flux-system get secret ghcr-auth | grep dockerconfigjson
|
||||
- name: bootstrap no-op
|
||||
run: |
|
||||
./bin/flux bootstrap github --manifests ./manifests/test/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--image-pull-secret=ghcr-auth \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
--path=test-cluster \
|
||||
@@ -78,7 +72,7 @@ jobs:
|
||||
- name: bootstrap customize
|
||||
run: |
|
||||
make setup-bootstrap-patch
|
||||
./bin/flux bootstrap github --manifests ./manifests/test/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
@@ -93,34 +87,46 @@ jobs:
|
||||
GITHUB_ORG_NAME: fluxcd-testing
|
||||
- name: uninstall
|
||||
run: |
|
||||
./bin/flux uninstall -s --keep-namespace
|
||||
/tmp/flux uninstall -s --keep-namespace
|
||||
kubectl delete ns flux-system --timeout=10m --wait=true
|
||||
- name: test image automation
|
||||
run: |
|
||||
make setup-image-automation
|
||||
./bin/flux bootstrap github --manifests ./manifests/test/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
--path=test-cluster \
|
||||
--read-write-key
|
||||
./bin/flux reconcile image repository podinfo
|
||||
./bin/flux reconcile image policy podinfo
|
||||
./bin/flux reconcile image update flux-system
|
||||
./bin/flux get images all
|
||||
./bin/flux -n flux-system events --for ImageUpdateAutomation/flux-system
|
||||
kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system
|
||||
kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system | \
|
||||
yq '.status.lastPushCommit | length > 1' | grep 'true'
|
||||
/tmp/flux reconcile image repository podinfo
|
||||
/tmp/flux get images all
|
||||
|
||||
retries=10
|
||||
count=0
|
||||
ok=false
|
||||
until ${ok}; do
|
||||
/tmp/flux get image update flux-system | grep 'commit' && ok=true || ok=false
|
||||
count=$(($count + 1))
|
||||
if [[ ${count} -eq ${retries} ]]; then
|
||||
echo "No more retries left"
|
||||
exit 1
|
||||
fi
|
||||
sleep 6
|
||||
/tmp/flux reconcile image update flux-system
|
||||
done
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}
|
||||
GITHUB_ORG_NAME: fluxcd-testing
|
||||
- name: delete repository
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
gh repo delete fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} --yes
|
||||
curl \
|
||||
-X DELETE \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
--fail --silent \
|
||||
https://api.github.com/repos/fluxcd-testing/${{ steps.vars.outputs.test_repo_name }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: Debug failure
|
||||
|
||||
20
.github/workflows/e2e-gcp.yaml
vendored
20
.github/workflows/e2e-gcp.yaml
vendored
@@ -22,21 +22,19 @@ permissions:
|
||||
|
||||
jobs:
|
||||
e2e-gcp:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./tests/integration
|
||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: tests/integration/go.sum
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
|
||||
- name: Setup Flux CLI
|
||||
run: make build
|
||||
working-directory: ./
|
||||
@@ -48,19 +46,19 @@ jobs:
|
||||
env:
|
||||
SOPS_VER: 3.7.1
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
|
||||
uses: google-github-actions/auth@5a50e581162a13f4baa8916d01180d2acbc04363 # v2.1.0
|
||||
id: 'auth'
|
||||
with:
|
||||
credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'
|
||||
token_format: 'access_token'
|
||||
- name: Setup gcloud
|
||||
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
|
||||
uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
- name: Log into us-central1-docker.pkg.dev
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
registry: us-central1-docker.pkg.dev
|
||||
username: oauth2accesstoken
|
||||
|
||||
146
.github/workflows/e2e.yaml
vendored
146
.github/workflows/e2e.yaml
vendored
@@ -13,9 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
e2e-amd64-kubernetes:
|
||||
runs-on:
|
||||
group: "Default Larger Runners"
|
||||
labels: ubuntu-latest-16-cores
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
@@ -23,30 +21,30 @@ jobs:
|
||||
- 5000:5000
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Setup Kubernetes
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
|
||||
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
||||
with:
|
||||
version: v0.30.0
|
||||
version: v0.20.0
|
||||
cluster_name: kind
|
||||
wait: 5s
|
||||
config: .github/kind/config.yaml # disable KIND-net
|
||||
# The versions below should target the oldest supported Kubernetes version
|
||||
# The versions below should target the newest Kubernetes version
|
||||
# Keep this up-to-date with https://endoflife.date/kubernetes
|
||||
node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64
|
||||
kubectl_version: v1.33.0
|
||||
node_image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
|
||||
kubectl_version: v1.28.0
|
||||
- name: Setup Calico for network policy
|
||||
run: |
|
||||
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
|
||||
kubectl apply -f https://docs.projectcalico.org/v3.25/manifests/calico.yaml
|
||||
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Run tests
|
||||
run: make test
|
||||
- name: Run e2e tests
|
||||
@@ -59,43 +57,44 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
- name: Build
|
||||
run: make build-dev
|
||||
run: |
|
||||
go build -o /tmp/flux ./cmd/flux
|
||||
- name: flux check --pre
|
||||
run: |
|
||||
./bin/flux check --pre
|
||||
/tmp/flux check --pre
|
||||
- name: flux install --manifests
|
||||
run: |
|
||||
./bin/flux install --manifests ./manifests/test/
|
||||
/tmp/flux install --manifests ./manifests/install/
|
||||
- name: flux create secret
|
||||
run: |
|
||||
./bin/flux create secret git git-ssh-test \
|
||||
/tmp/flux create secret git git-ssh-test \
|
||||
--url ssh://git@github.com/stefanprodan/podinfo
|
||||
./bin/flux create secret git git-https-test \
|
||||
/tmp/flux create secret git git-https-test \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--username=test --password=test
|
||||
./bin/flux create secret helm helm-test \
|
||||
/tmp/flux create secret helm helm-test \
|
||||
--username=test --password=test
|
||||
- name: flux create source git
|
||||
run: |
|
||||
./bin/flux create source git podinfo \
|
||||
/tmp/flux create source git podinfo \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--tag-semver=">=6.3.5"
|
||||
- name: flux create source git export apply
|
||||
run: |
|
||||
./bin/flux create source git podinfo-export \
|
||||
/tmp/flux create source git podinfo-export \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--tag-semver=">=6.3.5" \
|
||||
--export | kubectl apply -f -
|
||||
./bin/flux delete source git podinfo-export --silent
|
||||
/tmp/flux delete source git podinfo-export --silent
|
||||
- name: flux get sources git
|
||||
run: |
|
||||
./bin/flux get sources git
|
||||
/tmp/flux get sources git
|
||||
- name: flux get sources git --all-namespaces
|
||||
run: |
|
||||
./bin/flux get sources git --all-namespaces
|
||||
/tmp/flux get sources git --all-namespaces
|
||||
- name: flux create kustomization
|
||||
run: |
|
||||
./bin/flux create kustomization podinfo \
|
||||
/tmp/flux create kustomization podinfo \
|
||||
--source=podinfo \
|
||||
--path="./deploy/overlays/dev" \
|
||||
--prune=true \
|
||||
@@ -105,89 +104,89 @@ jobs:
|
||||
--health-check-timeout=3m
|
||||
- name: flux trace
|
||||
run: |
|
||||
./bin/flux trace frontend \
|
||||
/tmp/flux trace frontend \
|
||||
--kind=deployment \
|
||||
--api-version=apps/v1 \
|
||||
--namespace=dev
|
||||
- name: flux reconcile kustomization --with-source
|
||||
run: |
|
||||
./bin/flux reconcile kustomization podinfo --with-source
|
||||
/tmp/flux reconcile kustomization podinfo --with-source
|
||||
- name: flux get kustomizations
|
||||
run: |
|
||||
./bin/flux get kustomizations
|
||||
/tmp/flux get kustomizations
|
||||
- name: flux get kustomizations --all-namespaces
|
||||
run: |
|
||||
./bin/flux get kustomizations --all-namespaces
|
||||
/tmp/flux get kustomizations --all-namespaces
|
||||
- name: flux suspend kustomization
|
||||
run: |
|
||||
./bin/flux suspend kustomization podinfo
|
||||
/tmp/flux suspend kustomization podinfo
|
||||
- name: flux resume kustomization
|
||||
run: |
|
||||
./bin/flux resume kustomization podinfo
|
||||
/tmp/flux resume kustomization podinfo
|
||||
- name: flux export
|
||||
run: |
|
||||
./bin/flux export source git --all
|
||||
./bin/flux export kustomization --all
|
||||
/tmp/flux export source git --all
|
||||
/tmp/flux export kustomization --all
|
||||
- name: flux delete kustomization
|
||||
run: |
|
||||
./bin/flux delete kustomization podinfo --silent
|
||||
/tmp/flux delete kustomization podinfo --silent
|
||||
- name: flux create source helm
|
||||
run: |
|
||||
./bin/flux create source helm podinfo \
|
||||
/tmp/flux create source helm podinfo \
|
||||
--url https://stefanprodan.github.io/podinfo
|
||||
- name: flux create helmrelease --source=HelmRepository/podinfo
|
||||
run: |
|
||||
./bin/flux create hr podinfo-helm \
|
||||
/tmp/flux create hr podinfo-helm \
|
||||
--target-namespace=default \
|
||||
--source=HelmRepository/podinfo.flux-system \
|
||||
--chart=podinfo \
|
||||
--chart-version=">6.0.0 <7.0.0"
|
||||
- name: flux create helmrelease --source=GitRepository/podinfo
|
||||
run: |
|
||||
./bin/flux create hr podinfo-git \
|
||||
/tmp/flux create hr podinfo-git \
|
||||
--target-namespace=default \
|
||||
--source=GitRepository/podinfo \
|
||||
--chart=./charts/podinfo
|
||||
- name: flux reconcile helmrelease --with-source
|
||||
run: |
|
||||
./bin/flux reconcile helmrelease podinfo-git --with-source
|
||||
/tmp/flux reconcile helmrelease podinfo-git --with-source
|
||||
- name: flux get helmreleases
|
||||
run: |
|
||||
./bin/flux get helmreleases
|
||||
/tmp/flux get helmreleases
|
||||
- name: flux get helmreleases --all-namespaces
|
||||
run: |
|
||||
./bin/flux get helmreleases --all-namespaces
|
||||
/tmp/flux get helmreleases --all-namespaces
|
||||
- name: flux export helmrelease
|
||||
run: |
|
||||
./bin/flux export hr --all
|
||||
/tmp/flux export hr --all
|
||||
- name: flux delete helmrelease podinfo-helm
|
||||
run: |
|
||||
./bin/flux delete hr podinfo-helm --silent
|
||||
/tmp/flux delete hr podinfo-helm --silent
|
||||
- name: flux delete helmrelease podinfo-git
|
||||
run: |
|
||||
./bin/flux delete hr podinfo-git --silent
|
||||
/tmp/flux delete hr podinfo-git --silent
|
||||
- name: flux delete source helm
|
||||
run: |
|
||||
./bin/flux delete source helm podinfo --silent
|
||||
/tmp/flux delete source helm podinfo --silent
|
||||
- name: flux delete source git
|
||||
run: |
|
||||
./bin/flux delete source git podinfo --silent
|
||||
/tmp/flux delete source git podinfo --silent
|
||||
- name: flux oci artifacts
|
||||
run: |
|
||||
./bin/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
/tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
--path="./manifests" \
|
||||
--source="${{ github.repositoryUrl }}" \
|
||||
--revision="${{ github.ref }}@sha1:${{ github.sha }}"
|
||||
./bin/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
/tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
--tag latest
|
||||
./bin/flux list artifacts oci://localhost:5000/fluxcd/flux
|
||||
/tmp/flux list artifacts oci://localhost:5000/fluxcd/flux
|
||||
- name: flux oci repositories
|
||||
run: |
|
||||
./bin/flux create source oci podinfo-oci \
|
||||
/tmp/flux create source oci podinfo-oci \
|
||||
--url oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--tag-semver 6.3.x \
|
||||
--interval 10m
|
||||
./bin/flux create kustomization podinfo-oci \
|
||||
/tmp/flux create kustomization podinfo-oci \
|
||||
--source=OCIRepository/podinfo-oci \
|
||||
--path="./" \
|
||||
--prune=true \
|
||||
@@ -195,31 +194,31 @@ jobs:
|
||||
--target-namespace=default \
|
||||
--wait=true \
|
||||
--health-check-timeout=3m
|
||||
./bin/flux reconcile source oci podinfo-oci
|
||||
./bin/flux suspend source oci podinfo-oci
|
||||
./bin/flux get sources oci
|
||||
./bin/flux resume source oci podinfo-oci
|
||||
./bin/flux export source oci podinfo-oci
|
||||
./bin/flux delete ks podinfo-oci --silent
|
||||
./bin/flux delete source oci podinfo-oci --silent
|
||||
/tmp/flux reconcile source oci podinfo-oci
|
||||
/tmp/flux suspend source oci podinfo-oci
|
||||
/tmp/flux get sources oci
|
||||
/tmp/flux resume source oci podinfo-oci
|
||||
/tmp/flux export source oci podinfo-oci
|
||||
/tmp/flux delete ks podinfo-oci --silent
|
||||
/tmp/flux delete source oci podinfo-oci --silent
|
||||
- name: flux create tenant
|
||||
run: |
|
||||
./bin/flux create tenant dev-team --with-namespace=apps
|
||||
./bin/flux -n apps create source helm podinfo \
|
||||
/tmp/flux create tenant dev-team --with-namespace=apps
|
||||
/tmp/flux -n apps create source helm podinfo \
|
||||
--url https://stefanprodan.github.io/podinfo
|
||||
./bin/flux -n apps create hr podinfo-helm \
|
||||
/tmp/flux -n apps create hr podinfo-helm \
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo \
|
||||
--chart-version="6.3.x" \
|
||||
--service-account=dev-team
|
||||
- name: flux2-kustomize-helm-example
|
||||
run: |
|
||||
./bin/flux create source git flux-system \
|
||||
/tmp/flux create source git flux-system \
|
||||
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \
|
||||
--branch=main \
|
||||
--ignore-paths="./clusters/**/flux-system/" \
|
||||
--recurse-submodules
|
||||
./bin/flux create kustomization flux-system \
|
||||
/tmp/flux create kustomization flux-system \
|
||||
--source=flux-system \
|
||||
--path=./clusters/staging
|
||||
kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m
|
||||
@@ -227,30 +226,17 @@ jobs:
|
||||
kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m
|
||||
- name: flux tree
|
||||
run: |
|
||||
./bin/flux tree kustomization flux-system | grep Service/podinfo
|
||||
- name: flux events
|
||||
run: |
|
||||
./bin/flux -n flux-system events --for Kustomization/apps | grep 'HelmRelease/podinfo'
|
||||
./bin/flux -n podinfo events --for HelmRelease/podinfo | grep 'podinfo.v1'
|
||||
- name: flux stats
|
||||
run: |
|
||||
./bin/flux stats -A
|
||||
/tmp/flux tree kustomization flux-system | grep Service/podinfo
|
||||
- name: flux check
|
||||
run: |
|
||||
./bin/flux check
|
||||
- name: flux migrate
|
||||
run: |
|
||||
./bin/flux migrate
|
||||
- name: flux version
|
||||
run: |
|
||||
./bin/flux version
|
||||
/tmp/flux check
|
||||
- name: flux uninstall
|
||||
run: |
|
||||
./bin/flux uninstall --silent
|
||||
/tmp/flux uninstall --silent
|
||||
- name: Debug failure
|
||||
if: failure()
|
||||
run: |
|
||||
kubectl version --client
|
||||
kubectl version --client --short
|
||||
kubectl -n flux-system get all
|
||||
kubectl -n flux-system describe pods
|
||||
kubectl -n flux-system get kustomizations -oyaml
|
||||
|
||||
8
.github/workflows/ossf.yaml
vendored
8
.github/workflows/ossf.yaml
vendored
@@ -19,21 +19,21 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_results: true
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
- name: Upload SARIF results
|
||||
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
81
.github/workflows/release.yaml
vendored
81
.github/workflows/release.yaml
vendored
@@ -2,7 +2,7 @@ name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
tags: [ 'v*' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -13,44 +13,40 @@ jobs:
|
||||
hashes: ${{ steps.slsa.outputs.hashes }}
|
||||
image_url: ${{ steps.slsa.outputs.image_url }}
|
||||
image_digest: ${{ steps.slsa.outputs.image_digest }}
|
||||
runs-on:
|
||||
group: "Default Larger Runners"
|
||||
labels: ubuntu-latest-16-cores
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # needed to write releases
|
||||
id-token: write # needed for keyless signing
|
||||
packages: write # needed for ghcr access
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
go-version: 1.20.x
|
||||
cache: false
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
- name: Setup Syft
|
||||
uses: anchore/sbom-action/download-syft@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
|
||||
uses: anchore/sbom-action/download-syft@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8
|
||||
- name: Setup Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
with:
|
||||
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
|
||||
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
@@ -63,19 +59,30 @@ jobs:
|
||||
run: |
|
||||
kustomize build manifests/crds > all-crds.yaml
|
||||
- name: Generate OpenAPI JSON schemas from CRDs
|
||||
uses: fluxcd/pkg/actions/crdjsonschema@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
uses: fluxcd/pkg/actions/crdjsonschema@main
|
||||
with:
|
||||
crd: all-crds.yaml
|
||||
output: schemas
|
||||
- name: Archive the OpenAPI JSON schemas
|
||||
run: |
|
||||
tar -czvf ./output/crd-schemas.tar.gz -C schemas .
|
||||
- name: Download release notes utility
|
||||
env:
|
||||
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
|
||||
run: cd /tmp && curl -sSL ${GH_REL_URL} | tar xz && sudo mv github-release-notes /usr/local/bin/
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
NOTES="./output/notes.md"
|
||||
echo '## CLI Changelog' > ${NOTES}
|
||||
github-release-notes -org fluxcd -repo flux2 -since-latest-release -include-author >> ${NOTES}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run GoReleaser
|
||||
id: run-goreleaser
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
||||
with:
|
||||
version: latest
|
||||
args: release --skip=validate
|
||||
args: release --release-notes=output/notes.md --skip-validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
@@ -86,13 +93,13 @@ jobs:
|
||||
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
hashes=$(echo -E $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
|
||||
echo "hashes=$hashes" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
image_url=fluxcd/flux-cli:$GITHUB_REF_NAME
|
||||
echo "image_url=$image_url" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
image_digest=$(docker buildx imagetools inspect ${image_url} --format '{{json .}}' | jq -r .manifest.digest)
|
||||
echo "image_digest=$image_digest" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -103,26 +110,24 @@ jobs:
|
||||
id-token: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Setup Flux CLI
|
||||
uses: ./action/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
VERSION=$(flux version --client | awk '{ print $NF }')
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
@@ -132,7 +137,7 @@ jobs:
|
||||
flux install --registry=ghcr.io/fluxcd \
|
||||
--components-extra=image-reflector-controller,image-automation-controller \
|
||||
--export > ./ghcr.io/flux-system/gotk-components.yaml
|
||||
|
||||
|
||||
cd ./ghcr.io && flux push artifact \
|
||||
oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
|
||||
--path="./flux-system" \
|
||||
@@ -144,15 +149,13 @@ jobs:
|
||||
flux install --registry=docker.io/fluxcd \
|
||||
--components-extra=image-reflector-controller,image-automation-controller \
|
||||
--export > ./docker.io/flux-system/gotk-components.yaml
|
||||
|
||||
|
||||
cd ./docker.io && flux push artifact \
|
||||
oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
|
||||
--path="./flux-system" \
|
||||
--source=${{ github.repositoryUrl }} \
|
||||
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
|
||||
- uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
with:
|
||||
cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3
|
||||
- uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
|
||||
- name: Sign manifests
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: 1
|
||||
@@ -173,7 +176,7 @@ jobs:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
contents: write # for uploading attestations to GitHub releases.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
||||
with:
|
||||
provenance-name: "provenance.intoto.jsonl"
|
||||
base64-subjects: "${{ needs.release-flux-cli.outputs.hashes }}"
|
||||
@@ -185,7 +188,7 @@ jobs:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||
with:
|
||||
image: ${{ needs.release-flux-cli.outputs.image_url }}
|
||||
digest: ${{ needs.release-flux-cli.outputs.image_digest }}
|
||||
@@ -199,10 +202,10 @@ jobs:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||
with:
|
||||
image: ghcr.io/${{ needs.release-flux-cli.outputs.image_url }}
|
||||
digest: ${{ needs.release-flux-cli.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
registry-password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
82
.github/workflows/scan.yaml
vendored
82
.github/workflows/scan.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: scan
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -7,13 +8,78 @@ on:
|
||||
branches: [ 'main', 'release/**' ]
|
||||
schedule:
|
||||
- cron: '18 10 * * 3'
|
||||
permissions: read-all
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
scan-fossa:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Run FOSSA scan and upload build data
|
||||
uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0
|
||||
with:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
scan-snyk:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # for reading the repository code.
|
||||
security-events: write # for uploading the CodeQL analysis results.
|
||||
uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.9.0
|
||||
secrets:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fossa-token: ${{ secrets.FOSSA_TOKEN }}
|
||||
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
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 --sarif-file-output=snyk.sarif
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
- name: Upload result to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
||||
|
||||
scan-codeql:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
with:
|
||||
languages: go
|
||||
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# xref: https://codeql.github.com/codeql-query-help/go/
|
||||
queries: security-and-quality
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
|
||||
25
.github/workflows/sync-labels.yaml
vendored
25
.github/workflows/sync-labels.yaml
vendored
@@ -6,12 +6,23 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- .github/labels.yaml
|
||||
permissions: read-all
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync-labels:
|
||||
labels:
|
||||
name: Run sync
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # for reading the labels file.
|
||||
issues: write # for creating and updating labels.
|
||||
uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.9.0
|
||||
secrets:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
|
||||
with:
|
||||
# Configuration file
|
||||
config-file: |
|
||||
https://raw.githubusercontent.com/fluxcd/community/main/.github/standard-labels.yaml
|
||||
.github/labels.yaml
|
||||
# Strictly declarative
|
||||
delete-other-labels: true
|
||||
|
||||
30
.github/workflows/update.yaml
vendored
30
.github/workflows/update.yaml
vendored
@@ -2,6 +2,8 @@ name: update
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 * * * *"
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
@@ -16,37 +18,24 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
go-version: 1.20.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Update component versions
|
||||
id: update
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR_BODY=$(mktemp)
|
||||
|
||||
bump_version() {
|
||||
local LATEST_VERSION=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
|
||||
|
||||
if [[ "$LATEST_VERSION" == *"-rc"* ]]; then
|
||||
echo "Skipping release candidate version for $1: $LATEST_VERSION"
|
||||
return
|
||||
fi
|
||||
|
||||
local LATEST_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
|
||||
local CTRL_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p;n" manifests/bases/$1/kustomization.yaml)
|
||||
local CRD_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p" manifests/crds/kustomization.yaml)
|
||||
|
||||
local API_PKG="github.com/fluxcd/$1/api"
|
||||
if [[ "$1" == "source-watcher" ]]; then
|
||||
API_PKG="github.com/fluxcd/$1/api/v2"
|
||||
fi
|
||||
local MOD_VERSION=$(go list -m -f '{{ .Version }}' "$API_PKG")
|
||||
local MOD_VERSION=$(go list -m -f '{{ .Version }}' "github.com/fluxcd/$1/api")
|
||||
|
||||
local changed=false
|
||||
|
||||
@@ -61,7 +50,7 @@ jobs:
|
||||
fi
|
||||
|
||||
if [[ "${MOD_VERSION}" != "${LATEST_VERSION}" ]]; then
|
||||
go mod edit -require="$API_PKG@${LATEST_VERSION}"
|
||||
go mod edit -require="github.com/fluxcd/$1/api@${LATEST_VERSION}"
|
||||
make tidy
|
||||
changed=true
|
||||
fi
|
||||
@@ -80,7 +69,6 @@ jobs:
|
||||
bump_version notification-controller
|
||||
bump_version image-reflector-controller
|
||||
bump_version image-automation-controller
|
||||
bump_version source-watcher
|
||||
|
||||
# diff change
|
||||
git diff
|
||||
@@ -96,7 +84,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 # v6.0.0
|
||||
with:
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
commit-message: |
|
||||
|
||||
10
.github/workflows/upgrade-fluxcd-pkg.yaml
vendored
10
.github/workflows/upgrade-fluxcd-pkg.yaml
vendored
@@ -1,10 +0,0 @@
|
||||
name: upgrade-fluxcd-pkg
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upgrade-fluxcd-pkg:
|
||||
uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.9.0
|
||||
secrets:
|
||||
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
@@ -1,6 +1,4 @@
|
||||
project_name: flux
|
||||
changelog:
|
||||
use: github-native
|
||||
builds:
|
||||
- <<: &build_defaults
|
||||
binary: flux
|
||||
@@ -17,7 +15,7 @@ builds:
|
||||
- arm64
|
||||
- arm
|
||||
goarm:
|
||||
- "7"
|
||||
- 7
|
||||
- <<: *build_defaults
|
||||
id: darwin
|
||||
goos:
|
||||
@@ -75,11 +73,11 @@ signs:
|
||||
output: true
|
||||
brews:
|
||||
- name: flux
|
||||
repository:
|
||||
tap:
|
||||
owner: fluxcd
|
||||
name: homebrew-tap
|
||||
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
|
||||
directory: Formula
|
||||
folder: Formula
|
||||
homepage: "https://fluxcd.io/"
|
||||
description: "Flux CLI"
|
||||
install: |
|
||||
@@ -88,6 +86,22 @@ brews:
|
||||
generate_completions_from_executable(bin/"flux", "completion")
|
||||
test: |
|
||||
system "#{bin}/flux --version"
|
||||
publishers:
|
||||
- name: aur-pkg-bin
|
||||
env:
|
||||
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
|
||||
cmd: |
|
||||
.github/aur/flux-bin/publish.sh {{ .Version }}
|
||||
- name: aur-pkg-scm
|
||||
env:
|
||||
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
|
||||
cmd: |
|
||||
.github/aur/flux-scm/publish.sh {{ .Version }}
|
||||
- name: aur-pkg-go
|
||||
env:
|
||||
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
|
||||
cmd: |
|
||||
.github/aur/flux-go/publish.sh {{ .Version }}
|
||||
dockers:
|
||||
- image_templates:
|
||||
- 'fluxcd/flux-cli:{{ .Tag }}-amd64'
|
||||
|
||||
@@ -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.
|
||||
@@ -49,8 +49,7 @@ you might want to take a look at the [introductory talk and demo](https://www.yo
|
||||
This project is composed of:
|
||||
|
||||
- [flux2](https://github.com/fluxcd/flux2): The Flux CLI
|
||||
- [source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets)
|
||||
- [source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns
|
||||
- [source-manager](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git and Helm repositories, S3-compatible Buckets)
|
||||
- [kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize
|
||||
- [helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm
|
||||
- [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events
|
||||
@@ -68,9 +67,10 @@ for source changes.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
* go >= 1.26
|
||||
* kubectl >= 1.33
|
||||
* go >= 1.20
|
||||
* kubectl >= 1.24
|
||||
* kustomize >= 5.0
|
||||
* coreutils (on Mac OS)
|
||||
|
||||
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
|
||||
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,16 +1,15 @@
|
||||
FROM alpine:3.23 AS builder
|
||||
FROM alpine:3.19 as builder
|
||||
|
||||
RUN apk add --no-cache ca-certificates curl
|
||||
|
||||
ARG ARCH=linux/amd64
|
||||
ARG KUBECTL_VER=1.35.0
|
||||
ARG KUBECTL_VER=1.28.6
|
||||
|
||||
RUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
|
||||
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
|
||||
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
|
||||
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
|
||||
kubectl version --client=true
|
||||
|
||||
RUN kubectl version --client=true
|
||||
|
||||
FROM alpine:3.23 AS flux-cli
|
||||
FROM alpine:3.19 as flux-cli
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
|
||||
5
Makefile
5
Makefile
@@ -17,8 +17,9 @@ rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2
|
||||
all: test build
|
||||
|
||||
tidy:
|
||||
go mod tidy -compat=1.26
|
||||
cd tests/integration && go mod tidy -compat=1.26
|
||||
go mod tidy -compat=1.20
|
||||
cd tests/azure && go mod tidy -compat=1.20
|
||||
cd tests/integration && go mod tidy -compat=1.20
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
10
README.md
10
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/fluxcd/flux2/releases)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/4782)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/fluxcd/flux2)
|
||||
[](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
|
||||
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
|
||||
[](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
|
||||
[](https://fluxcd.io/flux/security/slsa-assessment)
|
||||
@@ -21,7 +21,7 @@ Flux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a
|
||||
set of composable APIs and specialized tools for building Continuous
|
||||
Delivery on top of Kubernetes.
|
||||
|
||||
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) graduated project, used in
|
||||
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project, used in
|
||||
production by various [organisations](https://fluxcd.io/adopters) and [cloud providers](https://fluxcd.io/ecosystem).
|
||||
|
||||
## Quickstart and documentation
|
||||
@@ -44,7 +44,7 @@ runtime for Flux v2. The APIs comprise Kubernetes custom resources,
|
||||
which can be created and updated by a cluster user, or by other
|
||||
automation tooling.
|
||||
|
||||

|
||||

|
||||
|
||||
You can use the toolkit to extend Flux, or to build your own systems
|
||||
for continuous delivery -- see [the developer
|
||||
@@ -52,14 +52,12 @@ guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
|
||||
|
||||
### Components
|
||||
|
||||
- [Source Controllers](https://fluxcd.io/flux/components/source/)
|
||||
- [Source Controller](https://fluxcd.io/flux/components/source/)
|
||||
- [GitRepository CRD](https://fluxcd.io/flux/components/source/gitrepositories/)
|
||||
- [OCIRepository CRD](https://fluxcd.io/flux/components/source/ocirepositories/)
|
||||
- [HelmRepository CRD](https://fluxcd.io/flux/components/source/helmrepositories/)
|
||||
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
|
||||
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
|
||||
- [ExternalArtifact CRD](https://fluxcd.io/flux/components/source/externalartifacts/)
|
||||
- [ArtifactGenerator CRD](https://fluxcd.io/flux/components/source/artifactgenerators/)
|
||||
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
|
||||
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)
|
||||
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
|
||||
|
||||
@@ -16,24 +16,23 @@ inputs:
|
||||
description: "Alternative location for the Flux binary, defaults to path relative to $RUNNER_TOOL_CACHE."
|
||||
required: false
|
||||
token:
|
||||
description: "Token used to authenticate against the GitHub.com API."
|
||||
description: "Token used to authentication against the GitHub.com API. Defaults to the token from the GitHub context of the workflow."
|
||||
required: false
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: "Download the binary to the runner's cache dir"
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: "${{ inputs.version }}"
|
||||
FLUX_TOOL_DIR: "${{ inputs.bindir }}"
|
||||
TOKEN: "${{ inputs.token }}"
|
||||
run: |
|
||||
VERSION=${{ inputs.version }}
|
||||
|
||||
TOKEN=${{ inputs.token }}
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
TOKEN=${{ github.token }}
|
||||
fi
|
||||
|
||||
if [[ -z "$VERSION" ]] || [[ "$VERSION" = "latest" ]]; then
|
||||
if [[ "${TOKEN}" != '' ]]; then
|
||||
VERSION=$(curl -fsSL -H "Authorization: token ${TOKEN}" https://api.github.com/repos/fluxcd/flux2/releases/latest | grep tag_name | cut -d '"' -f 4)
|
||||
else
|
||||
VERSION=$(curl -w "%{url_effective}\n" -IsSL https://github.com/fluxcd/flux2/releases/latest -o /dev/null | sed 's$^.*/$$')
|
||||
fi
|
||||
VERSION=$(curl -fsSL -H "Authorization: token ${TOKEN}" https://api.github.com/repos/fluxcd/flux2/releases/latest | grep tag_name | cut -d '"' -f 4)
|
||||
fi
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "Unable to determine Flux CLI version"
|
||||
@@ -60,6 +59,7 @@ runs:
|
||||
FLUX_EXEC_FILE="${FLUX_EXEC_FILE}.exe"
|
||||
fi
|
||||
|
||||
FLUX_TOOL_DIR=${{ inputs.bindir }}
|
||||
if [[ -z "$FLUX_TOOL_DIR" ]]; then
|
||||
FLUX_TOOL_DIR="${RUNNER_TOOL_CACHE}/flux2/${VERSION}/${OS}/${ARCH}"
|
||||
fi
|
||||
@@ -77,37 +77,9 @@ runs:
|
||||
|
||||
FLUX_DOWNLOAD_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/"
|
||||
|
||||
MAX_RETRIES=5
|
||||
RETRY_DELAY=5
|
||||
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
echo "Downloading flux binary (attempt $i/$MAX_RETRIES)"
|
||||
if curl -fsSL -o "$DL_DIR/$FLUX_TARGET_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_TARGET_FILE"; then
|
||||
break
|
||||
fi
|
||||
if [ $i -lt $MAX_RETRIES ]; then
|
||||
echo "Download failed, retrying in ${RETRY_DELAY} seconds..."
|
||||
sleep $RETRY_DELAY
|
||||
else
|
||||
echo "Failed to download flux binary after $MAX_RETRIES attempts"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
echo "Downloading checksums file (attempt $i/$MAX_RETRIES)"
|
||||
if curl -fsSL -o "$DL_DIR/$FLUX_CHECKSUMS_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_CHECKSUMS_FILE"; then
|
||||
break
|
||||
fi
|
||||
if [ $i -lt $MAX_RETRIES ]; then
|
||||
echo "Download failed, retrying in ${RETRY_DELAY} seconds..."
|
||||
sleep $RETRY_DELAY
|
||||
else
|
||||
echo "Failed to download checksums file after $MAX_RETRIES attempts"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
curl -fsSL -o "$DL_DIR/$FLUX_TARGET_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_TARGET_FILE"
|
||||
curl -fsSL -o "$DL_DIR/$FLUX_CHECKSUMS_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_CHECKSUMS_FILE"
|
||||
|
||||
echo "Verifying checksum"
|
||||
sum=""
|
||||
if command -v openssl > /dev/null; then
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
)
|
||||
|
||||
// swapi.ArtifactGenerator
|
||||
|
||||
var artifactGeneratorType = apiType{
|
||||
kind: swapi.ArtifactGeneratorKind,
|
||||
humanKind: "artifactgenerator",
|
||||
groupVersion: swapi.GroupVersion,
|
||||
}
|
||||
|
||||
type artifactGeneratorAdapter struct {
|
||||
*swapi.ArtifactGenerator
|
||||
}
|
||||
|
||||
func (h artifactGeneratorAdapter) asClientObject() client.Object {
|
||||
return h.ArtifactGenerator
|
||||
}
|
||||
|
||||
func (h artifactGeneratorAdapter) deepCopyClientObject() client.Object {
|
||||
return h.ArtifactGenerator.DeepCopy()
|
||||
}
|
||||
|
||||
// swapi.ArtifactGeneratorList
|
||||
|
||||
type artifactGeneratorListAdapter struct {
|
||||
*swapi.ArtifactGeneratorList
|
||||
}
|
||||
|
||||
func (h artifactGeneratorListAdapter) asClientList() client.ObjectList {
|
||||
return h.ArtifactGeneratorList
|
||||
}
|
||||
|
||||
func (h artifactGeneratorListAdapter) len() int {
|
||||
return len(h.ArtifactGeneratorList.Items)
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -53,19 +52,17 @@ type bootstrapFlags struct {
|
||||
extraComponents []string
|
||||
requiredComponents []string
|
||||
|
||||
registry string
|
||||
registryCredential string
|
||||
imagePullSecret string
|
||||
registry string
|
||||
imagePullSecret string
|
||||
|
||||
secretName string
|
||||
tokenAuth bool
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
keyRSABits flags.RSAKeyBits
|
||||
keyECDSACurve flags.ECDSACurve
|
||||
sshHostname string
|
||||
caFile string
|
||||
privateKeyFile string
|
||||
sshHostKeyAlgorithms []string
|
||||
secretName string
|
||||
tokenAuth bool
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
keyRSABits flags.RSAKeyBits
|
||||
keyECDSACurve flags.ECDSACurve
|
||||
sshHostname string
|
||||
caFile string
|
||||
privateKeyFile string
|
||||
|
||||
watchAllNamespaces bool
|
||||
networkPolicy bool
|
||||
@@ -97,12 +94,10 @@ func init() {
|
||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
|
||||
"list of components, accepts comma-separated values")
|
||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
|
||||
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller,source-watcher'")
|
||||
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
|
||||
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
|
||||
"container registry where the Flux controller images are published")
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registryCredential, "registry-creds", "",
|
||||
"container registry credentials in the format 'user:password', requires --image-pull-secret to be set")
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "",
|
||||
"Kubernetes secret name used for pulling the controller images from a private registry")
|
||||
|
||||
@@ -126,7 +121,6 @@ func init() {
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.secretName, "secret-name", rootArgs.defaults.Namespace, "name of the secret the sync credentials can be found in or stored to")
|
||||
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyAlgorithm, "ssh-key-algorithm", bootstrapArgs.keyAlgorithm.Description())
|
||||
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyRSABits, "ssh-rsa-bits", bootstrapArgs.keyRSABits.Description())
|
||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.sshHostKeyAlgorithms, "ssh-hostkey-algos", nil, "list of host key algorithms to be used by the CLI for SSH connections")
|
||||
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyECDSACurve, "ssh-ecdsa-curve", bootstrapArgs.keyECDSACurve.Description())
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshHostname, "ssh-hostname", "", "SSH hostname, to be used when the SSH host differs from the HTTPS one")
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
|
||||
@@ -187,18 +181,6 @@ func bootstrapValidate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if bootstrapArgs.registryCredential != "" && bootstrapArgs.imagePullSecret == "" {
|
||||
return fmt.Errorf("--registry-creds requires --image-pull-secret to be set")
|
||||
}
|
||||
|
||||
if bootstrapArgs.registryCredential != "" && len(strings.Split(bootstrapArgs.registryCredential, ":")) != 2 {
|
||||
return fmt.Errorf("invalid --registry-creds format, expected 'user:password'")
|
||||
}
|
||||
|
||||
if len(bootstrapArgs.sshHostKeyAlgorithms) > 0 {
|
||||
git.HostKeyAlgos = bootstrapArgs.sshHostKeyAlgorithms
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,6 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Components: bootstrapComponents(),
|
||||
Registry: bootstrapArgs.registry,
|
||||
RegistryCredential: bootstrapArgs.registryCredential,
|
||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||
@@ -226,7 +225,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
||||
secretOpts.Username = bServerArgs.username
|
||||
}
|
||||
secretOpts.Password = bitbucketToken
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.CAFile = caBundle
|
||||
} else {
|
||||
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
|
||||
if err != nil {
|
||||
|
||||
@@ -28,9 +28,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
"github.com/fluxcd/flux2/v2/pkg/bootstrap"
|
||||
@@ -38,6 +35,8 @@ import (
|
||||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install"
|
||||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
|
||||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
)
|
||||
|
||||
var bootstrapGitCmd = &cobra.Command{
|
||||
@@ -66,10 +65,7 @@ command will perform an upgrade if needed.`,
|
||||
flux bootstrap git --url=ssh://<SSH-Key-ID>@git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --private-key-file=<path/to/private.key> --password=<SSH-passphrase> --path=clusters/my-cluster
|
||||
|
||||
# Run bootstrap for a Git repository on Azure Devops
|
||||
flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --private-key-file=<path/to/rsa-sha2-private.key> --ssh-hostkey-algos=rsa-sha2-512,rsa-sha2-256 --path=clusters/my-cluster
|
||||
|
||||
# Run bootstrap for a Git repository on Oracle VBS
|
||||
flux bootstrap git --url=https://repository_url.git --with-bearer-token=true --password=<PAT> --path=clusters/my-cluster
|
||||
flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --ssh-key-algorithm=rsa --ssh-rsa-bits=4096 --path=clusters/my-cluster
|
||||
`,
|
||||
RunE: bootstrapGitCmdRun,
|
||||
}
|
||||
@@ -82,7 +78,6 @@ type gitFlags struct {
|
||||
password string
|
||||
silent bool
|
||||
insecureHttpAllowed bool
|
||||
withBearerToken bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -99,16 +94,11 @@ func init() {
|
||||
bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password")
|
||||
bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
|
||||
bootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, "allow-insecure-http", false, "allows insecure HTTP connections")
|
||||
bootstrapGitCmd.Flags().BoolVar(&gitArgs.withBearerToken, "with-bearer-token", false, "use password as bearer token for Authorization header")
|
||||
|
||||
bootstrapCmd.AddCommand(bootstrapGitCmd)
|
||||
}
|
||||
|
||||
func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if gitArgs.withBearerToken {
|
||||
bootstrapArgs.tokenAuth = true
|
||||
}
|
||||
|
||||
gitPassword := os.Getenv(gitPasswordEnvVar)
|
||||
if gitPassword != "" && gitArgs.password == "" {
|
||||
gitArgs.password = gitPassword
|
||||
@@ -211,7 +201,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Components: bootstrapComponents(),
|
||||
Registry: bootstrapArgs.registry,
|
||||
RegistryCredential: bootstrapArgs.registryCredential,
|
||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||
@@ -234,16 +223,10 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
TargetPath: gitArgs.path.String(),
|
||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||
}
|
||||
|
||||
if bootstrapArgs.tokenAuth {
|
||||
if gitArgs.withBearerToken {
|
||||
secretOpts.BearerToken = gitArgs.password
|
||||
} else {
|
||||
secretOpts.Username = gitArgs.username
|
||||
secretOpts.Password = gitArgs.password
|
||||
}
|
||||
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.Username = gitArgs.username
|
||||
secretOpts.Password = gitArgs.password
|
||||
secretOpts.CAFile = caBundle
|
||||
|
||||
// Remove port of the given host when not syncing over HTTP/S to not assume port for protocol
|
||||
// This _might_ be overwritten later on by e.g. --ssh-hostname
|
||||
@@ -335,28 +318,18 @@ func getAuthOpts(u *url.URL, caBundle []byte) (*git.AuthOptions, error) {
|
||||
if !gitArgs.insecureHttpAllowed {
|
||||
return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it")
|
||||
}
|
||||
httpAuth := git.AuthOptions{
|
||||
return &git.AuthOptions{
|
||||
Transport: git.HTTP,
|
||||
}
|
||||
if gitArgs.withBearerToken {
|
||||
httpAuth.BearerToken = gitArgs.password
|
||||
} else {
|
||||
httpAuth.Username = gitArgs.username
|
||||
httpAuth.Password = gitArgs.password
|
||||
}
|
||||
return &httpAuth, nil
|
||||
Username: gitArgs.username,
|
||||
Password: gitArgs.password,
|
||||
}, nil
|
||||
case "https":
|
||||
httpsAuth := git.AuthOptions{
|
||||
return &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Username: gitArgs.username,
|
||||
Password: gitArgs.password,
|
||||
CAFile: caBundle,
|
||||
}
|
||||
if gitArgs.withBearerToken {
|
||||
httpsAuth.BearerToken = gitArgs.password
|
||||
} else {
|
||||
httpsAuth.Username = gitArgs.username
|
||||
httpsAuth.Password = gitArgs.password
|
||||
}
|
||||
return &httpsAuth, nil
|
||||
}, nil
|
||||
case "ssh":
|
||||
authOpts := &git.AuthOptions{
|
||||
Transport: git.SSH,
|
||||
|
||||
@@ -184,7 +184,6 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Components: bootstrapComponents(),
|
||||
Registry: bootstrapArgs.registry,
|
||||
RegistryCredential: bootstrapArgs.registryCredential,
|
||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||
@@ -210,7 +209,7 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if bootstrapArgs.tokenAuth {
|
||||
secretOpts.Username = "git"
|
||||
secretOpts.Password = gtToken
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.CAFile = caBundle
|
||||
} else {
|
||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||
|
||||
@@ -191,7 +191,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Components: bootstrapComponents(),
|
||||
Registry: bootstrapArgs.registry,
|
||||
RegistryCredential: bootstrapArgs.registryCredential,
|
||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||
@@ -217,7 +216,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if bootstrapArgs.tokenAuth {
|
||||
secretOpts.Username = "git"
|
||||
secretOpts.Password = ghToken
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.CAFile = caBundle
|
||||
} else {
|
||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fluxcd/go-git-providers/gitprovider"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -42,7 +41,7 @@ import (
|
||||
var bootstrapGitLabCmd = &cobra.Command{
|
||||
Use: "gitlab",
|
||||
Short: "Deploy Flux on a cluster connected to a GitLab repository",
|
||||
Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exist and
|
||||
Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exists and
|
||||
commits the Flux manifests to the specified branch.
|
||||
Then it configures the target cluster to synchronize with that repository.
|
||||
If the Flux components are present on the cluster,
|
||||
@@ -59,14 +58,14 @@ the bootstrap command will perform an upgrade if needed.`,
|
||||
# Run bootstrap for a repository path
|
||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --path=dev-cluster
|
||||
|
||||
# Run bootstrap for a public repository
|
||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --visibility=public --token-auth
|
||||
# Run bootstrap for a public repository on a personal account
|
||||
flux bootstrap gitlab --owner=<user> --repository=<repository name> --private=false --personal --token-auth
|
||||
|
||||
# Run bootstrap for a private repository hosted on a GitLab server
|
||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<gitlab_url> --token-auth
|
||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
|
||||
|
||||
# Run bootstrap for an existing repository with a branch named main
|
||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --branch=main --token-auth
|
||||
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth
|
||||
|
||||
# Run bootstrap for a private repository using Deploy Token authentication
|
||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth
|
||||
@@ -86,7 +85,6 @@ type gitlabFlags struct {
|
||||
repository string
|
||||
interval time.Duration
|
||||
personal bool
|
||||
visibility flags.GitLabVisibility
|
||||
private bool
|
||||
hostname string
|
||||
path flags.SafeRelativePath
|
||||
@@ -96,13 +94,7 @@ type gitlabFlags struct {
|
||||
deployTokenAuth bool
|
||||
}
|
||||
|
||||
func NewGitlabFlags() gitlabFlags {
|
||||
return gitlabFlags{
|
||||
visibility: flags.GitLabVisibility(gitprovider.RepositoryVisibilityPrivate),
|
||||
}
|
||||
}
|
||||
|
||||
var gitlabArgs = NewGitlabFlags()
|
||||
var gitlabArgs gitlabFlags
|
||||
|
||||
func init() {
|
||||
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name")
|
||||
@@ -110,8 +102,6 @@ func init() {
|
||||
bootstrapGitLabCmd.Flags().StringSliceVar(&gitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)")
|
||||
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group")
|
||||
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "if true, the repository is setup or configured as private")
|
||||
bootstrapGitLabCmd.Flags().MarkDeprecated("private", "use --visibility instead")
|
||||
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.visibility, "visibility", gitlabArgs.visibility.Description())
|
||||
bootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval")
|
||||
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname")
|
||||
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
|
||||
@@ -143,11 +133,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("--token-auth and --deploy-token-auth cannot be set both.")
|
||||
}
|
||||
|
||||
if !gitlabArgs.private {
|
||||
gitlabArgs.visibility.Set(string(gitprovider.RepositoryVisibilityPublic))
|
||||
cmd.Println("Using visibility public as --private=false")
|
||||
}
|
||||
|
||||
if err := bootstrapValidate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -231,7 +216,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Components: bootstrapComponents(),
|
||||
Registry: bootstrapArgs.registry,
|
||||
RegistryCredential: bootstrapArgs.registryCredential,
|
||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||
@@ -257,10 +241,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if bootstrapArgs.tokenAuth {
|
||||
secretOpts.Username = "git"
|
||||
secretOpts.Password = glToken
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.CAFile = caBundle
|
||||
} else if gitlabArgs.deployTokenAuth {
|
||||
// the actual deploy token will be reconciled later
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.CAFile = caBundle
|
||||
} else {
|
||||
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
|
||||
if err != nil {
|
||||
@@ -297,7 +281,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
// Bootstrap config
|
||||
bootstrapOpts := []bootstrap.GitProviderOption{
|
||||
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
|
||||
bootstrap.WithProviderVisibility(gitlabArgs.visibility.String()),
|
||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||
bootstrap.WithBootstrapTransportType("https"),
|
||||
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
@@ -317,6 +300,9 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if gitlabArgs.deployTokenAuth {
|
||||
bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())
|
||||
}
|
||||
if !gitlabArgs.private {
|
||||
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
|
||||
}
|
||||
if gitlabArgs.reconcile {
|
||||
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fluxcd/pkg/oci"
|
||||
oci "github.com/fluxcd/pkg/oci/client"
|
||||
"github.com/fluxcd/pkg/sourceignore"
|
||||
)
|
||||
|
||||
var buildArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Build artifact",
|
||||
Long: `The build artifact command creates a tgz file with the manifests
|
||||
from the given directory or a single manifest file.`,
|
||||
Long: withPreviewNote(`The build artifact command creates a tgz file with the manifests
|
||||
from the given directory or a single manifest file.`),
|
||||
Example: ` # Build the given manifests directory into an artifact
|
||||
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -54,12 +53,7 @@ flux build kustomization my-app --path ./path/to/local/manifests \
|
||||
# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.
|
||||
flux build kustomization my-app --path ./path/to/local/manifests \
|
||||
--kustomization-file ./path/to/local/my-app.yaml \
|
||||
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"
|
||||
|
||||
# Run recursively on all encountered Kustomizations
|
||||
flux build kustomization my-app --path ./path/to/local/manifests \
|
||||
--recursive \
|
||||
--local-sources GitRepository/flux-system/my-repo=./path/to/local/git`,
|
||||
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: buildKsCmdRun,
|
||||
}
|
||||
@@ -69,9 +63,6 @@ type buildKsFlags struct {
|
||||
path string
|
||||
ignorePaths []string
|
||||
dryRun bool
|
||||
strictSubst bool
|
||||
recursive bool
|
||||
localSources map[string]string
|
||||
}
|
||||
|
||||
var buildKsArgs buildKsFlags
|
||||
@@ -81,10 +72,6 @@ func init() {
|
||||
buildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
|
||||
buildKsCmd.Flags().StringSliceVar(&buildKsArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in .gitignore format")
|
||||
buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.")
|
||||
buildKsCmd.Flags().BoolVar(&buildKsArgs.strictSubst, "strict-substitute", false,
|
||||
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.")
|
||||
buildKsCmd.Flags().BoolVarP(&buildKsArgs.recursive, "recursive", "r", false, "Recursively build Kustomizations")
|
||||
buildKsCmd.Flags().StringToStringVar(&buildKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path")
|
||||
buildCmd.AddCommand(buildKsCmd)
|
||||
}
|
||||
|
||||
@@ -98,13 +85,6 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
|
||||
}
|
||||
|
||||
// Normalize the path to handle Windows absolute and relative paths correctly
|
||||
buildKsArgs.path, err = filepath.Abs(buildKsArgs.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve absolute path: %w", err)
|
||||
}
|
||||
buildKsArgs.path = filepath.Clean(buildKsArgs.path)
|
||||
|
||||
if fs, err := os.Stat(buildKsArgs.path); err != nil || !fs.IsDir() {
|
||||
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
|
||||
}
|
||||
@@ -127,9 +107,6 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
build.WithDryRun(buildKsArgs.dryRun),
|
||||
build.WithNamespace(*kubeconfigArgs.Namespace),
|
||||
build.WithIgnore(buildKsArgs.ignorePaths),
|
||||
build.WithStrictSubstitute(buildKsArgs.strictSubst),
|
||||
build.WithRecursive(buildKsArgs.recursive),
|
||||
build.WithLocalSources(buildKsArgs.localSources),
|
||||
)
|
||||
} else {
|
||||
builder, err = build.NewBuilder(name, buildKsArgs.path,
|
||||
@@ -137,9 +114,6 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
build.WithTimeout(rootArgs.timeout),
|
||||
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
|
||||
build.WithIgnore(buildKsArgs.ignorePaths),
|
||||
build.WithStrictSubstitute(buildKsArgs.strictSubst),
|
||||
build.WithRecursive(buildKsArgs.recursive),
|
||||
build.WithLocalSources(buildKsArgs.localSources),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
@@ -70,12 +69,6 @@ func TestBuildKustomization(t *testing.T) {
|
||||
resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build with recursive",
|
||||
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := map[string]string{
|
||||
@@ -125,8 +118,6 @@ spec:
|
||||
cluster_region: "eu-central-1"
|
||||
`
|
||||
|
||||
tmpFile := filepath.Join(t.TempDir(), "podinfo.yaml")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
@@ -141,40 +132,28 @@ spec:
|
||||
},
|
||||
{
|
||||
name: "build podinfo",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo",
|
||||
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/podinfo",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build podinfo without service",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/delete-service",
|
||||
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/delete-service",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build deployment and configmap with var substitution",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/var-substitution",
|
||||
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build deployment and configmap with var substitution in dry-run mode",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/var-substitution --dry-run",
|
||||
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution --dry-run",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build with recursive",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build with recursive in dry-run mode",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --dry-run",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := map[string]string{
|
||||
@@ -182,6 +161,8 @@ spec:
|
||||
}
|
||||
setup(t, tmpl)
|
||||
|
||||
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
|
||||
|
||||
temp, err := template.New("podinfo").Parse(podinfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -193,72 +174,11 @@ spec:
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(tmpFile, b.Bytes(), 0666)
|
||||
err = os.WriteFile("./testdata/build-kustomization/podinfo.yaml", b.Bytes(), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var assert assertFunc
|
||||
|
||||
switch tt.assertFunc {
|
||||
case "assertGoldenTemplateFile":
|
||||
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
|
||||
case "assertError":
|
||||
assert = assertError(tt.resultFile)
|
||||
}
|
||||
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args + " -n " + tmpl["fluxns"],
|
||||
assert: assert,
|
||||
}
|
||||
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildKustomizationPathNormalization verifies that absolute and complex
|
||||
// paths are normalized to prevent path concatenation bugs (issue #5673).
|
||||
// Without normalization, paths could be duplicated like: /path/test/path/test/file
|
||||
func TestBuildKustomizationPathNormalization(t *testing.T) {
|
||||
// Get absolute path to testdata to test absolute path handling
|
||||
absTestDataPath, err := filepath.Abs("testdata/build-kustomization/podinfo")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get absolute path: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
resultFile string
|
||||
assertFunc string
|
||||
}{
|
||||
{
|
||||
name: "build with absolute path",
|
||||
args: "build kustomization podinfo --path " + absTestDataPath,
|
||||
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build with complex relative path (parent dir)",
|
||||
args: "build kustomization podinfo --path ./testdata/build-kustomization/../build-kustomization/podinfo",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build with path containing redundant separators",
|
||||
args: "build kustomization podinfo --path ./testdata//build-kustomization//podinfo",
|
||||
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := map[string]string{
|
||||
"fluxns": allocateNamespace("flux-system"),
|
||||
}
|
||||
setup(t, tmpl)
|
||||
t.Cleanup(func() { _ = os.Remove("./testdata/build-kustomization/podinfo.yaml") })
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -60,7 +60,7 @@ type checkFlags struct {
|
||||
}
|
||||
|
||||
var kubernetesConstraints = []string{
|
||||
">=1.33.0-0",
|
||||
">=1.26.0-0",
|
||||
}
|
||||
|
||||
var checkArgs checkFlags
|
||||
@@ -217,7 +217,7 @@ func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
|
||||
}
|
||||
}
|
||||
for _, c := range d.Spec.Template.Spec.Containers {
|
||||
logger.Actionf("%s", c.Image)
|
||||
logger.Actionf(c.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func crdsCheck(ctx context.Context, kubeClient client.Client) bool {
|
||||
for _, crd := range list.Items {
|
||||
versions := crd.Status.StoredVersions
|
||||
if len(versions) > 0 {
|
||||
logger.Successf("%s", crd.Name+"/"+versions[len(versions)-1])
|
||||
logger.Successf(crd.Name + "/" + versions[len(versions)-1])
|
||||
} else {
|
||||
ok = false
|
||||
logger.Failuref("no stored versions for %s", crd.Name)
|
||||
|
||||
@@ -125,7 +125,7 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
|
||||
logger.Generatef("generating %s", names.kind)
|
||||
logger.Actionf("applying %s", names.kind)
|
||||
|
||||
namespacedName, err := names.upsert(ctx, kubeClient, object, mutate)
|
||||
namespacedName, err := imageRepositoryType.upsert(ctx, kubeClient, object, mutate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
Copyright 2020 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.
|
||||
@@ -24,6 +24,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/transform"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -33,20 +39,14 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/transform"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta2"
|
||||
)
|
||||
|
||||
var createHelmReleaseCmd = &cobra.Command{
|
||||
Use: "helmrelease [name]",
|
||||
Aliases: []string{"hr"},
|
||||
Short: "Create or update a HelmRelease resource",
|
||||
Long: `The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.`,
|
||||
Long: withPreviewNote(`The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.`),
|
||||
Example: ` # Create a HelmRelease with a chart from a HelmRepository source
|
||||
flux create hr podinfo \
|
||||
--interval=10m \
|
||||
@@ -94,13 +94,6 @@ var createHelmReleaseCmd = &cobra.Command{
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo
|
||||
|
||||
# Create a HelmRelease with custom storage namespace for hub-and-spoke model
|
||||
flux create hr podinfo \
|
||||
--target-namespace=production \
|
||||
--storage-namespace=fluxcd-system \
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo
|
||||
|
||||
# Create a HelmRelease using a source from a different namespace
|
||||
flux create hr podinfo \
|
||||
--namespace=default \
|
||||
@@ -112,17 +105,7 @@ var createHelmReleaseCmd = &cobra.Command{
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo \
|
||||
--values=./values.yaml \
|
||||
--export > podinfo-release.yaml
|
||||
|
||||
# Create a HelmRelease using a chart from a HelmChart resource
|
||||
flux create hr podinfo \
|
||||
--namespace=default \
|
||||
--chart-ref=HelmChart/podinfo.flux-system \
|
||||
|
||||
# Create a HelmRelease using a chart from an OCIRepository resource
|
||||
flux create hr podinfo \
|
||||
--namespace=default \
|
||||
--chart-ref=OCIRepository/podinfo.flux-system`,
|
||||
--export > podinfo-release.yaml`,
|
||||
RunE: createHelmReleaseCmdRun,
|
||||
}
|
||||
|
||||
@@ -132,9 +115,7 @@ type helmReleaseFlags struct {
|
||||
dependsOn []string
|
||||
chart string
|
||||
chartVersion string
|
||||
chartRef string
|
||||
targetNamespace string
|
||||
storageNamespace string
|
||||
createNamespace bool
|
||||
valuesFiles []string
|
||||
valuesFrom []string
|
||||
@@ -149,8 +130,6 @@ var helmReleaseArgs helmReleaseFlags
|
||||
|
||||
var supportedHelmReleaseValuesFromKinds = []string{"Secret", "ConfigMap"}
|
||||
|
||||
var supportedHelmReleaseReferenceKinds = []string{sourcev1.OCIRepositoryKind, sourcev1.HelmChartKind}
|
||||
|
||||
func init() {
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
|
||||
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description())
|
||||
@@ -158,7 +137,6 @@ func init() {
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)")
|
||||
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.dependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'")
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.storageNamespace, "storage-namespace", "", "namespace to store the Helm release, defaults to the target namespace")
|
||||
createHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, "create-target-namespace", false, "create the target namespace if it does not exist")
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart created by the helm release(accepted values: Revision and ChartRevision)")
|
||||
@@ -167,23 +145,14 @@ func init() {
|
||||
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFrom, "values-from", nil, "a Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret,ConfigMap)")
|
||||
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, "crds", helmReleaseArgs.crds.Description())
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
|
||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartRef, "chart-ref", "", "the name of the HelmChart resource to use as source for the HelmRelease, in the format '<kind>/<name>.<namespace>', where kind must be one of: (OCIRepository,HelmChart)")
|
||||
createCmd.AddCommand(createHelmReleaseCmd)
|
||||
}
|
||||
|
||||
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
if helmReleaseArgs.storageNamespace == "" && helmReleaseArgs.targetNamespace != "" {
|
||||
helmReleaseArgs.storageNamespace = helmReleaseArgs.targetNamespace
|
||||
}
|
||||
|
||||
if helmReleaseArgs.chart == "" && helmReleaseArgs.chartRef == "" {
|
||||
return fmt.Errorf("chart or chart-ref is required")
|
||||
}
|
||||
|
||||
if helmReleaseArgs.chart != "" && helmReleaseArgs.chartRef != "" {
|
||||
return fmt.Errorf("cannot use --chart in combination with --chart-ref")
|
||||
if helmReleaseArgs.chart == "" {
|
||||
return fmt.Errorf("chart name or path is required")
|
||||
}
|
||||
|
||||
sourceLabels, err := parseLabels()
|
||||
@@ -208,67 +177,42 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
Spec: helmv2.HelmReleaseSpec{
|
||||
ReleaseName: helmReleaseArgs.name,
|
||||
DependsOn: utils.MakeDependsOn(helmReleaseArgs.dependsOn),
|
||||
Interval: metav1.Duration{
|
||||
Duration: createArgs.interval,
|
||||
},
|
||||
TargetNamespace: helmReleaseArgs.targetNamespace,
|
||||
StorageNamespace: helmReleaseArgs.storageNamespace,
|
||||
Suspend: false,
|
||||
},
|
||||
}
|
||||
TargetNamespace: helmReleaseArgs.targetNamespace,
|
||||
|
||||
if len(helmReleaseArgs.dependsOn) > 0 {
|
||||
ls := utils.MakeDependsOn(helmReleaseArgs.dependsOn)
|
||||
hrDependsOn := make([]helmv2.DependencyReference, 0, len(ls))
|
||||
for _, d := range ls {
|
||||
hrDependsOn = append(hrDependsOn, helmv2.DependencyReference{
|
||||
Name: d.Name,
|
||||
Namespace: d.Namespace,
|
||||
})
|
||||
}
|
||||
helmRelease.Spec.DependsOn = hrDependsOn
|
||||
}
|
||||
|
||||
switch {
|
||||
case helmReleaseArgs.chart != "":
|
||||
helmRelease.Spec.Chart = &helmv2.HelmChartTemplate{
|
||||
Spec: helmv2.HelmChartTemplateSpec{
|
||||
Chart: helmReleaseArgs.chart,
|
||||
Version: helmReleaseArgs.chartVersion,
|
||||
SourceRef: helmv2.CrossNamespaceObjectReference{
|
||||
Kind: helmReleaseArgs.source.Kind,
|
||||
Name: helmReleaseArgs.source.Name,
|
||||
Namespace: helmReleaseArgs.source.Namespace,
|
||||
Chart: helmv2.HelmChartTemplate{
|
||||
Spec: helmv2.HelmChartTemplateSpec{
|
||||
Chart: helmReleaseArgs.chart,
|
||||
Version: helmReleaseArgs.chartVersion,
|
||||
SourceRef: helmv2.CrossNamespaceObjectReference{
|
||||
Kind: helmReleaseArgs.source.Kind,
|
||||
Name: helmReleaseArgs.source.Name,
|
||||
Namespace: helmReleaseArgs.source.Namespace,
|
||||
},
|
||||
ReconcileStrategy: helmReleaseArgs.reconcileStrategy,
|
||||
},
|
||||
ReconcileStrategy: helmReleaseArgs.reconcileStrategy,
|
||||
},
|
||||
}
|
||||
if helmReleaseArgs.chartInterval != 0 {
|
||||
helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{
|
||||
Duration: helmReleaseArgs.chartInterval,
|
||||
}
|
||||
}
|
||||
case helmReleaseArgs.chartRef != "":
|
||||
kind, name, ns := utils.ParseObjectKindNameNamespace(helmReleaseArgs.chartRef)
|
||||
if kind != sourcev1.HelmChartKind && kind != sourcev1.OCIRepositoryKind {
|
||||
return fmt.Errorf("chart reference kind '%s' is not supported, must be one of: %s",
|
||||
kind, strings.Join(supportedHelmReleaseReferenceKinds, ", "))
|
||||
}
|
||||
helmRelease.Spec.ChartRef = &helmv2.CrossNamespaceSourceReference{
|
||||
Kind: kind,
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}
|
||||
Suspend: false,
|
||||
},
|
||||
}
|
||||
|
||||
if helmReleaseArgs.kubeConfigSecretRef != "" {
|
||||
helmRelease.Spec.KubeConfig = &meta.KubeConfigReference{
|
||||
SecretRef: &meta.SecretKeyReference{
|
||||
SecretRef: meta.SecretKeyReference{
|
||||
Name: helmReleaseArgs.kubeConfigSecretRef,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if helmReleaseArgs.chartInterval != 0 {
|
||||
helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{
|
||||
Duration: helmReleaseArgs.chartInterval,
|
||||
}
|
||||
}
|
||||
|
||||
if helmReleaseArgs.createNamespace {
|
||||
if helmRelease.Spec.Install == nil {
|
||||
helmRelease.Spec.Install = &helmv2.Install{}
|
||||
@@ -365,7 +309,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
logger.Successf("HelmRelease %s is ready", name)
|
||||
|
||||
logger.Successf("applied revision %s", getHelmReleaseRevision(helmRelease))
|
||||
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,91 +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 TestCreateHelmRelease(t *testing.T) {
|
||||
tmpl := map[string]string{
|
||||
"fluxns": allocateNamespace("flux-system"),
|
||||
}
|
||||
setupHRSource(t, tmpl)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
name: "missing name",
|
||||
args: "create helmrelease --export",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
name: "missing chart template and chartRef",
|
||||
args: "create helmrelease podinfo --export",
|
||||
assert: assertError("chart or chart-ref is required"),
|
||||
},
|
||||
{
|
||||
name: "chart and chartRef used in combination",
|
||||
args: "create helmrelease podinfo --chart podinfo --chart-ref foobar/podinfo --export",
|
||||
assert: assertError("cannot use --chart in combination with --chart-ref"),
|
||||
},
|
||||
{
|
||||
name: "unknown source kind",
|
||||
args: "create helmrelease podinfo --source foobar/podinfo --chart podinfo --export",
|
||||
assert: assertError(`invalid argument "foobar/podinfo" for "--source" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`),
|
||||
},
|
||||
{
|
||||
name: "unknown chart reference kind",
|
||||
args: "create helmrelease podinfo --chart-ref foobar/podinfo --export",
|
||||
assert: assertError(`chart reference kind 'foobar' is not supported, must be one of: OCIRepository, HelmChart`),
|
||||
},
|
||||
{
|
||||
name: "basic helmrelease",
|
||||
args: "create helmrelease podinfo --source Helmrepository/podinfo --chart podinfo --interval=1m0s --export",
|
||||
assert: assertGoldenTemplateFile("testdata/create_hr/basic.yaml", tmpl),
|
||||
},
|
||||
{
|
||||
name: "chart with OCIRepository source",
|
||||
args: "create helmrelease podinfo --chart-ref OCIRepository/podinfo --interval=1m0s --export",
|
||||
assert: assertGoldenTemplateFile("testdata/create_hr/or_basic.yaml", tmpl),
|
||||
},
|
||||
{
|
||||
name: "chart with HelmChart source",
|
||||
args: "create helmrelease podinfo --chart-ref HelmChart/podinfo --interval=1m0s --export",
|
||||
assert: assertGoldenTemplateFile("testdata/create_hr/hc_basic.yaml", tmpl),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args + " -n " + tmpl["fluxns"],
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupHRSource(t *testing.T, tmpl map[string]string) {
|
||||
t.Helper()
|
||||
testEnv.CreateObjectFile("./testdata/create_hr/setup-source.yaml", tmpl, t)
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"regexp/syntax"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -29,18 +28,18 @@ import (
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var createImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy [name]",
|
||||
Short: "Create or update an ImagePolicy object",
|
||||
Long: `The create image policy command generates an ImagePolicy resource.
|
||||
Long: withPreviewNote(`The create image policy command generates an ImagePolicy resource.
|
||||
An ImagePolicy object calculates a "latest image" given an image
|
||||
repository and a policy, e.g., semver.
|
||||
|
||||
The image that sorts highest according to the policy is recorded in
|
||||
the status of the object.`,
|
||||
the status of the object.`),
|
||||
Example: ` # Create an ImagePolicy to select the latest stable release
|
||||
flux create image policy podinfo \
|
||||
--image-ref=podinfo \
|
||||
@@ -61,8 +60,6 @@ type imagePolicyFlags struct {
|
||||
numeric string
|
||||
filterRegex string
|
||||
filterExtract string
|
||||
reflectDigest string
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
var imagePolicyArgs = imagePolicyFlags{}
|
||||
@@ -75,12 +72,16 @@ func init() {
|
||||
flags.StringVar(&imagePolicyArgs.numeric, "select-numeric", "", "use numeric sorting to select image; either \"asc\" meaning select the last, or \"desc\" meaning select the first")
|
||||
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", "regular expression pattern used to filter the image tags")
|
||||
flags.StringVar(&imagePolicyArgs.filterExtract, "filter-extract", "", "replacement pattern (using capture groups from --filter-regex) to use for sorting")
|
||||
flags.StringVar(&imagePolicyArgs.reflectDigest, "reflect-digest", "", "the digest reflection policy to use when observing latest image tags (one of 'Never', 'IfNotPresent', 'Never')")
|
||||
flags.DurationVar(&imagePolicyArgs.interval, "interval", 0, "the interval at which to check for new image digests when the policy is set to 'Always'")
|
||||
|
||||
createImageCmd.AddCommand(createImagePolicyCmd)
|
||||
}
|
||||
|
||||
// getObservedGeneration is implemented here, since it's not
|
||||
// (presently) needed elsewhere.
|
||||
func (obj imagePolicyAdapter) getObservedGeneration() int64 {
|
||||
return obj.ImagePolicy.Status.ObservedGeneration
|
||||
}
|
||||
|
||||
func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
||||
objectName := args[0]
|
||||
|
||||
@@ -152,20 +153,6 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("cannot specify --filter-extract without specifying --filter-regex")
|
||||
}
|
||||
|
||||
if p := imagev1.ReflectionPolicy(imagePolicyArgs.reflectDigest); p != "" {
|
||||
if p != imagev1.ReflectNever && p != imagev1.ReflectIfNotPresent && p != imagev1.ReflectAlways {
|
||||
return fmt.Errorf("invalid value for --reflect-digest, must be one of 'Never', 'IfNotPresent', 'Always'")
|
||||
}
|
||||
policy.Spec.DigestReflectionPolicy = p
|
||||
}
|
||||
|
||||
if imagePolicyArgs.interval != 0 {
|
||||
if imagePolicyArgs.reflectDigest != string(imagev1.ReflectAlways) {
|
||||
return fmt.Errorf("the --interval flag can only be used with the 'Always' digest reflection policy, use --reflect-digest=Always")
|
||||
}
|
||||
policy.Spec.Interval = &metav1.Duration{Duration: imagePolicyArgs.interval}
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return printExport(exportImagePolicy(&policy))
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@ import (
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var createImageRepositoryCmd = &cobra.Command{
|
||||
Use: "repository [name]",
|
||||
Short: "Create or update an ImageRepository object",
|
||||
Long: `The create image repository command generates an ImageRepository resource.
|
||||
An ImageRepository object specifies an image repository to scan.`,
|
||||
Long: withPreviewNote(`The create image repository command generates an ImageRepository resource.
|
||||
An ImageRepository object specifies an image repository to scan.`),
|
||||
Example: ` # Create an ImageRepository object to scan the alpine image repository:
|
||||
flux create image repository alpine-repo --image alpine --interval 20m
|
||||
|
||||
|
||||
@@ -22,16 +22,16 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1"
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
var createImageUpdateCmd = &cobra.Command{
|
||||
Use: "update [name]",
|
||||
Short: "Create or update an ImageUpdateAutomation object",
|
||||
Long: `The create image update command generates an ImageUpdateAutomation resource.
|
||||
Long: withPreviewNote(`The create image update command generates an ImageUpdateAutomation resource.
|
||||
An ImageUpdateAutomation object specifies an automated update to images
|
||||
mentioned in YAMLs in a git repository.`,
|
||||
mentioned in YAMLs in a git repository.`),
|
||||
Example: ` # Configure image updates for the main repository created by flux bootstrap
|
||||
flux create image update flux-system \
|
||||
--git-repo-ref=flux-system \
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta2"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
|
||||
@@ -153,6 +153,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Labels: kslabels,
|
||||
},
|
||||
Spec: kustomizev1.KustomizationSpec{
|
||||
DependsOn: utils.MakeDependsOn(kustomizationArgs.dependsOn),
|
||||
Interval: metav1.Duration{
|
||||
Duration: createArgs.interval,
|
||||
},
|
||||
@@ -168,21 +169,9 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
}
|
||||
|
||||
if len(kustomizationArgs.dependsOn) > 0 {
|
||||
ls := utils.MakeDependsOn(kustomizationArgs.dependsOn)
|
||||
ksDependsOn := make([]kustomizev1.DependencyReference, 0, len(ls))
|
||||
for _, d := range ls {
|
||||
ksDependsOn = append(ksDependsOn, kustomizev1.DependencyReference{
|
||||
Name: d.Name,
|
||||
Namespace: d.Namespace,
|
||||
})
|
||||
}
|
||||
kustomization.Spec.DependsOn = ksDependsOn
|
||||
}
|
||||
|
||||
if kustomizationArgs.kubeConfigSecretRef != "" {
|
||||
kustomization.Spec.KubeConfig = &meta.KubeConfigReference{
|
||||
SecretRef: &meta.SecretKeyReference{
|
||||
SecretRef: meta.SecretKeyReference{
|
||||
Name: kustomizationArgs.kubeConfigSecretRef,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ type secretGitFlags struct {
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
rsaBits flags.RSAKeyBits
|
||||
ecdsaCurve flags.ECDSACurve
|
||||
caFile string
|
||||
caCrtFile string
|
||||
privateKeyFile string
|
||||
bearerToken string
|
||||
@@ -101,7 +102,8 @@ func init() {
|
||||
createSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, "ssh-key-algorithm", secretGitArgs.keyAlgorithm.Description())
|
||||
createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
|
||||
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
|
||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, "ca-crt-file", "", "path to TLS CA certificate file used for validating self-signed certificates")
|
||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
|
||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, "ca-crt-file", "", "path to TLS CA certificate file used for validating self-signed certificates; takes precedence over --ca-file")
|
||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
|
||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, "bearer-token", "", "bearer authentication token")
|
||||
|
||||
@@ -167,12 +169,17 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
} else if secretGitArgs.caFile != "" {
|
||||
opts.CAFile, err = os.ReadFile(secretGitArgs.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.GenerateGit(opts)
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,116 +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
|
||||
appInstallationOwner 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.appInstallationOwner, "app-installation-owner", "", "github app installation owner (user or organization)")
|
||||
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]
|
||||
|
||||
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,
|
||||
GitHubAppInstallationOwner: secretGitHubAppArgs.appInstallationOwner,
|
||||
GitHubAppInstallationID: secretGitHubAppArgs.appInstallationID,
|
||||
GitHubAppPrivateKey: string(privateKey),
|
||||
GitHubAppBaseURL: secretGitHubAppArgs.baseURL,
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.GenerateGitHubApp(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,59 +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 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-owner my-org --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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
var createSecretHelmCmd = &cobra.Command{
|
||||
Use: "helm [name]",
|
||||
Short: "Create or update a Kubernetes secret for Helm repository authentication",
|
||||
Long: `The create secret helm command generates a Kubernetes secret with basic authentication credentials.`,
|
||||
Long: withPreviewNote(`The create secret helm command generates a Kubernetes secret with basic authentication credentials.`),
|
||||
Example: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS
|
||||
flux create secret helm repo-auth \
|
||||
--namespace=my-namespace \
|
||||
@@ -58,9 +58,12 @@ func init() {
|
||||
flags := createSecretHelmCmd.Flags()
|
||||
flags.StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username")
|
||||
flags.StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password")
|
||||
flags.StringVar(&secretHelmArgs.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
|
||||
flags.StringVar(&secretHelmArgs.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
|
||||
flags.StringVar(&secretHelmArgs.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
|
||||
|
||||
initSecretDeprecatedTLSFlags(flags, &secretHelmArgs.secretTLSFlags)
|
||||
deprecationMsg := "please use the command `flux create secret tls` to generate TLS secrets"
|
||||
flags.MarkDeprecated("cert-file", deprecationMsg)
|
||||
flags.MarkDeprecated("key-file", deprecationMsg)
|
||||
flags.MarkDeprecated("ca-file", deprecationMsg)
|
||||
|
||||
createSecretCmd.AddCommand(createSecretHelmCmd)
|
||||
}
|
||||
@@ -74,22 +77,20 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
caBundle := []byte{}
|
||||
if secretHelmArgs.caCrtFile != "" {
|
||||
if secretHelmArgs.caFile != "" {
|
||||
var err error
|
||||
caBundle, err = os.ReadFile(secretHelmArgs.caCrtFile)
|
||||
caBundle, err = os.ReadFile(secretHelmArgs.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var certFile, keyFile []byte
|
||||
if secretHelmArgs.tlsCrtFile != "" {
|
||||
if certFile, err = os.ReadFile(secretHelmArgs.tlsCrtFile); err != nil {
|
||||
if secretHelmArgs.certFile != "" && secretHelmArgs.keyFile != "" {
|
||||
if certFile, err = os.ReadFile(secretHelmArgs.certFile); err != nil {
|
||||
return fmt.Errorf("failed to read cert file: %w", err)
|
||||
}
|
||||
}
|
||||
if secretHelmArgs.tlsKeyFile != "" {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -100,11 +101,11 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Labels: labels,
|
||||
Username: secretHelmArgs.username,
|
||||
Password: secretHelmArgs.password,
|
||||
CACrt: caBundle,
|
||||
TLSCrt: certFile,
|
||||
TLSKey: keyFile,
|
||||
CAFile: caBundle,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
}
|
||||
secret, err := sourcesecret.GenerateHelm(opts)
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,161 +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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
|
||||
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var createSecretNotationCmd = &cobra.Command{
|
||||
Use: "notation [name]",
|
||||
Short: "Create or update a Kubernetes secret for verifications of artifacts signed by Notation",
|
||||
Long: withPreviewNote(`The create secret notation command generates a Kubernetes secret with root ca certificates and trust policy.`),
|
||||
Example: ` # Create a Notation configuration secret on disk and encrypt it with Mozilla SOPS
|
||||
flux create secret notation my-notation-cert \
|
||||
--namespace=my-namespace \
|
||||
--trust-policy-file=./my-trust-policy.json \
|
||||
--ca-cert-file=./my-cert.crt \
|
||||
--export > my-notation-cert.yaml
|
||||
|
||||
sops --encrypt --encrypted-regex '^(data|stringData)$' \
|
||||
--in-place my-notation-cert.yaml`,
|
||||
|
||||
RunE: createSecretNotationCmdRun,
|
||||
}
|
||||
|
||||
type secretNotationFlags struct {
|
||||
trustPolicyFile string
|
||||
caCrtFile []string
|
||||
}
|
||||
|
||||
var secretNotationArgs secretNotationFlags
|
||||
|
||||
func init() {
|
||||
createSecretNotationCmd.Flags().StringVar(&secretNotationArgs.trustPolicyFile, "trust-policy-file", "", "notation trust policy file path")
|
||||
createSecretNotationCmd.Flags().StringSliceVar(&secretNotationArgs.caCrtFile, "ca-cert-file", []string{}, "root ca cert file path")
|
||||
|
||||
createSecretCmd.AddCommand(createSecretNotationCmd)
|
||||
}
|
||||
|
||||
func createSecretNotationCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("name is required")
|
||||
}
|
||||
|
||||
if secretNotationArgs.caCrtFile == nil || len(secretNotationArgs.caCrtFile) == 0 {
|
||||
return fmt.Errorf("--ca-cert-file is required")
|
||||
}
|
||||
|
||||
if secretNotationArgs.trustPolicyFile == "" {
|
||||
return fmt.Errorf("--trust-policy-file is required")
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
labels, err := parseLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policy, err := os.ReadFile(secretNotationArgs.trustPolicyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read trust policy file: %w", err)
|
||||
}
|
||||
|
||||
var doc trustpolicy.Document
|
||||
|
||||
if err := json.Unmarshal(policy, &doc); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal trust policy %s: %w", secretNotationArgs.trustPolicyFile, err)
|
||||
}
|
||||
|
||||
if err := doc.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid trust policy: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
caCerts []sourcesecret.VerificationCrt
|
||||
fileErr error
|
||||
)
|
||||
for _, caCrtFile := range secretNotationArgs.caCrtFile {
|
||||
fileName := filepath.Base(caCrtFile)
|
||||
if !strings.HasSuffix(fileName, ".crt") && !strings.HasSuffix(fileName, ".pem") {
|
||||
fileErr = errors.Join(fileErr, fmt.Errorf("%s must end with either .crt or .pem", fileName))
|
||||
continue
|
||||
}
|
||||
caBundle, err := os.ReadFile(caCrtFile)
|
||||
if err != nil {
|
||||
fileErr = errors.Join(fileErr, fmt.Errorf("unable to read TLS CA file: %w", err))
|
||||
continue
|
||||
}
|
||||
caCerts = append(caCerts, sourcesecret.VerificationCrt{Name: fileName, CACrt: caBundle})
|
||||
}
|
||||
|
||||
if fileErr != nil {
|
||||
return fileErr
|
||||
}
|
||||
|
||||
if len(caCerts) == 0 {
|
||||
return fmt.Errorf("no CA certs found")
|
||||
}
|
||||
|
||||
opts := sourcesecret.Options{
|
||||
Name: name,
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Labels: labels,
|
||||
VerificationCrts: caCerts,
|
||||
TrustPolicy: policy,
|
||||
}
|
||||
secret, err := sourcesecret.GenerateNotation(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("notation configuration secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
|
||||
return nil
|
||||
}
|
||||
@@ -1,124 +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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
trustPolicy = "./testdata/create_secret/notation/test-trust-policy.json"
|
||||
invalidTrustPolicy = "./testdata/create_secret/notation/invalid-trust-policy.json"
|
||||
invalidJson = "./testdata/create_secret/notation/invalid.json"
|
||||
testCertFolder = "./testdata/create_secret/notation"
|
||||
)
|
||||
|
||||
func TestCreateNotationSecret(t *testing.T) {
|
||||
crt, err := os.Create(filepath.Join(t.TempDir(), "ca.crt"))
|
||||
if err != nil {
|
||||
t.Fatal("could not create ca.crt file")
|
||||
}
|
||||
|
||||
pem, err := os.Create(filepath.Join(t.TempDir(), "ca.pem"))
|
||||
if err != nil {
|
||||
t.Fatal("could not create ca.pem file")
|
||||
}
|
||||
|
||||
invalidCert, err := os.Create(filepath.Join(t.TempDir(), "ca.p12"))
|
||||
if err != nil {
|
||||
t.Fatal("could not create ca.p12 file")
|
||||
}
|
||||
|
||||
_, err = crt.Write([]byte("ca-data-crt"))
|
||||
if err != nil {
|
||||
t.Fatal("could not write to crt certificate file")
|
||||
}
|
||||
|
||||
_, err = pem.Write([]byte("ca-data-pem"))
|
||||
if err != nil {
|
||||
t.Fatal("could not write to pem certificate file")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
name: "no args",
|
||||
args: "create secret notation",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
name: "no trust policy",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s", testCertFolder),
|
||||
assert: assertError("--trust-policy-file is required"),
|
||||
},
|
||||
{
|
||||
name: "no cert",
|
||||
args: fmt.Sprintf("create secret notation notation-config --trust-policy-file=%s", trustPolicy),
|
||||
assert: assertError("--ca-cert-file is required"),
|
||||
},
|
||||
{
|
||||
name: "non pem and crt cert",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", invalidCert.Name(), trustPolicy),
|
||||
assert: assertError("ca.p12 must end with either .crt or .pem"),
|
||||
},
|
||||
{
|
||||
name: "invalid trust policy",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", t.TempDir(), invalidTrustPolicy),
|
||||
assert: assertError("invalid trust policy: trust policy: a trust policy statement is missing a name, every statement requires a name"),
|
||||
},
|
||||
{
|
||||
name: "invalid trust policy json",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", t.TempDir(), invalidJson),
|
||||
assert: assertError(fmt.Sprintf("failed to unmarshal trust policy %s: json: cannot unmarshal string into Go value of type trustpolicy.Document", invalidJson)),
|
||||
},
|
||||
{
|
||||
name: "crt secret",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export", crt.Name(), trustPolicy),
|
||||
assert: assertGoldenFile("./testdata/create_secret/notation/secret-ca-crt.yaml"),
|
||||
},
|
||||
{
|
||||
name: "pem secret",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export", pem.Name(), trustPolicy),
|
||||
assert: assertGoldenFile("./testdata/create_secret/notation/secret-ca-pem.yaml"),
|
||||
},
|
||||
{
|
||||
name: "multi secret",
|
||||
args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export", crt.Name(), pem.Name(), trustPolicy),
|
||||
assert: assertGoldenFile("./testdata/create_secret/notation/secret-ca-multi.yaml"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
secretNotationArgs = secretNotationFlags{}
|
||||
}()
|
||||
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args,
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func createSecretOCICmdRun(cmd *cobra.Command, args []string) error {
|
||||
Username: secretOCIArgs.username,
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.GenerateOCI(opts)
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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.GenerateProxy(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
rootCmd.Println(secret.Content)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Actionf("proxy secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
|
||||
return nil
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateProxySecret(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
args: "create secret proxy proxy-secret",
|
||||
assert: assertError("address is required"),
|
||||
},
|
||||
{
|
||||
args: "create secret proxy proxy-secret --address=https://my-proxy.com --username=my-username --password=my-password --namespace=my-namespace --export",
|
||||
assert: assertGoldenFile("testdata/create_secret/proxy/secret-proxy.yaml"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args,
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@@ -32,8 +33,8 @@ import (
|
||||
var createSecretTLSCmd = &cobra.Command{
|
||||
Use: "tls [name]",
|
||||
Short: "Create or update a Kubernetes secret with TLS certificates",
|
||||
Long: `The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`,
|
||||
Example: ` # Create a TLS secret on disk and encrypt it with SOPS.
|
||||
Long: withPreviewNote(`The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`),
|
||||
Example: ` # Create a TLS secret on disk and encrypt it with Mozilla SOPS.
|
||||
# Files are expected to be PEM-encoded.
|
||||
flux create secret tls certs \
|
||||
--namespace=my-namespace \
|
||||
@@ -48,6 +49,9 @@ var createSecretTLSCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
type secretTLSFlags struct {
|
||||
certFile string
|
||||
keyFile string
|
||||
caFile string
|
||||
caCrtFile string
|
||||
tlsKeyFile string
|
||||
tlsCrtFile string
|
||||
@@ -55,10 +59,26 @@ type secretTLSFlags struct {
|
||||
|
||||
var secretTLSArgs secretTLSFlags
|
||||
|
||||
func initSecretDeprecatedTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
|
||||
flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path")
|
||||
flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path")
|
||||
flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path")
|
||||
}
|
||||
|
||||
func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
|
||||
flags.StringVar(&args.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
|
||||
flags.StringVar(&args.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
|
||||
flags.StringVar(&args.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
|
||||
}
|
||||
|
||||
func init() {
|
||||
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
|
||||
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
|
||||
createSecretTLSCmd.Flags().StringVar(&secretTLSArgs.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
|
||||
flags := createSecretTLSCmd.Flags()
|
||||
initSecretDeprecatedTLSFlags(flags, &secretTLSArgs)
|
||||
initSecretTLSFlags(flags, &secretTLSArgs)
|
||||
|
||||
flags.MarkDeprecated("cert-file", "please use --tls-crt-file instead")
|
||||
flags.MarkDeprecated("key-file", "please use --tls-key-file instead")
|
||||
flags.MarkDeprecated("ca-file", "please use --ca-crt-file instead")
|
||||
|
||||
createSecretCmd.AddCommand(createSecretTLSCmd)
|
||||
}
|
||||
@@ -82,20 +102,30 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
} else if secretTLSArgs.caFile != "" {
|
||||
opts.CAFile, err = os.ReadFile(secretTLSArgs.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if secretTLSArgs.tlsCrtFile != "" {
|
||||
if secretTLSArgs.tlsCrtFile != "" && secretTLSArgs.tlsKeyFile != "" {
|
||||
if opts.TLSCrt, err = os.ReadFile(secretTLSArgs.tlsCrtFile); err != nil {
|
||||
return fmt.Errorf("failed to read cert file: %w", err)
|
||||
}
|
||||
}
|
||||
if secretTLSArgs.tlsKeyFile != "" {
|
||||
if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil {
|
||||
return fmt.Errorf("failed to read key file: %w", err)
|
||||
}
|
||||
} else if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" {
|
||||
if opts.CertFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil {
|
||||
return fmt.Errorf("failed to read cert file: %w", err)
|
||||
}
|
||||
if opts.KeyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil {
|
||||
return fmt.Errorf("failed to read key file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.GenerateTLS(opts)
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ func TestCreateTlsSecret(t *testing.T) {
|
||||
args: "create secret tls certs --namespace=my-namespace --tls-crt-file=./testdata/create_secret/tls/test-cert.pem --tls-key-file=./testdata/create_secret/tls/test-key.pem --ca-crt-file=./testdata/create_secret/tls/test-ca.pem --export",
|
||||
assert: assertGoldenFile("testdata/create_secret/tls/secret-tls.yaml"),
|
||||
},
|
||||
{
|
||||
args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --ca-file=./testdata/create_secret/tls/test-ca.pem --export",
|
||||
assert: assertGoldenFile("testdata/create_secret/tls/deprecated-secret-tls.yaml"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -31,7 +32,7 @@ import (
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
@@ -40,8 +41,8 @@ import (
|
||||
var createSourceBucketCmd = &cobra.Command{
|
||||
Use: "bucket [name]",
|
||||
Short: "Create or update a Bucket source",
|
||||
Long: `The create source bucket command generates a Bucket resource and waits for it to be downloaded.
|
||||
For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`,
|
||||
Long: withPreviewNote(`The create source bucket command generates a Bucket resource and waits for it to be downloaded.
|
||||
For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`),
|
||||
Example: ` # Create a source for a Bucket using static authentication
|
||||
flux create source bucket podinfo \
|
||||
--bucket-name=podinfo \
|
||||
@@ -62,16 +63,15 @@ For Buckets with static authentication, the credentials are stored in a Kubernet
|
||||
}
|
||||
|
||||
type sourceBucketFlags struct {
|
||||
name string
|
||||
provider flags.SourceBucketProvider
|
||||
endpoint string
|
||||
accessKey string
|
||||
secretKey string
|
||||
region string
|
||||
insecure bool
|
||||
secretRef string
|
||||
proxySecretRef string
|
||||
ignorePaths []string
|
||||
name string
|
||||
provider flags.SourceBucketProvider
|
||||
endpoint string
|
||||
accessKey string
|
||||
secretKey string
|
||||
region string
|
||||
insecure bool
|
||||
secretRef string
|
||||
ignorePaths []string
|
||||
}
|
||||
|
||||
var sourceBucketArgs = newSourceBucketFlags()
|
||||
@@ -85,7 +85,6 @@ func init() {
|
||||
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region")
|
||||
createSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
|
||||
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
|
||||
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.proxySecretRef, "proxy-secret-ref", "", "the name of an existing secret containing the proxy address and credentials")
|
||||
createSourceBucketCmd.Flags().StringSliceVar(&sourceBucketArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in bucket resource (can specify multiple paths with commas: path1,path2)")
|
||||
|
||||
createSourceCmd.AddCommand(createSourceBucketCmd)
|
||||
@@ -93,7 +92,7 @@ func init() {
|
||||
|
||||
func newSourceBucketFlags() sourceBucketFlags {
|
||||
return sourceBucketFlags{
|
||||
provider: flags.SourceBucketProvider(sourcev1.BucketProviderGeneric),
|
||||
provider: flags.SourceBucketProvider(sourcev1.GenericBucketProvider),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +112,12 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
var ignorePaths *string
|
||||
if len(sourceBucketArgs.ignorePaths) > 0 {
|
||||
ignorePathsStr := strings.Join(sourceBucketArgs.ignorePaths, "\n")
|
||||
@@ -148,12 +153,6 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if sourceBucketArgs.proxySecretRef != "" {
|
||||
bucket.Spec.ProxySecretRef = &meta.LocalObjectReference{
|
||||
Name: sourceBucketArgs.proxySecretRef,
|
||||
}
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return printExport(exportBucket(bucket))
|
||||
}
|
||||
|
||||
@@ -1,217 +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"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
)
|
||||
|
||||
var createSourceChartCmd = &cobra.Command{
|
||||
Use: "chart [name]",
|
||||
Short: "Create or update a HelmChart source",
|
||||
Long: `The create source chart command generates a HelmChart resource and waits for the chart to be available.`,
|
||||
Example: ` # Create a source for a chart residing in a HelmRepository
|
||||
flux create source chart podinfo \
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo \
|
||||
--chart-version=6.x
|
||||
|
||||
# Create a source for a chart residing in a Git repository
|
||||
flux create source chart podinfo \
|
||||
--source=GitRepository/podinfo \
|
||||
--chart=./charts/podinfo
|
||||
|
||||
# Create a source for a chart residing in a S3 Bucket
|
||||
flux create source chart podinfo \
|
||||
--source=Bucket/podinfo \
|
||||
--chart=./charts/podinfo
|
||||
|
||||
# Create a source for a chart from OCI and verify its signature
|
||||
flux create source chart podinfo \
|
||||
--source HelmRepository/podinfo \
|
||||
--chart podinfo \
|
||||
--chart-version=6.6.2 \
|
||||
--verify-provider=cosign \
|
||||
--verify-issuer=https://token.actions.githubusercontent.com \
|
||||
--verify-subject=https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2`,
|
||||
RunE: createSourceChartCmdRun,
|
||||
}
|
||||
|
||||
type sourceChartFlags struct {
|
||||
chart string
|
||||
chartVersion string
|
||||
source flags.LocalHelmChartSource
|
||||
reconcileStrategy string
|
||||
verifyProvider flags.SourceOCIVerifyProvider
|
||||
verifySecretRef string
|
||||
verifyOIDCIssuer string
|
||||
verifySubject string
|
||||
}
|
||||
|
||||
var sourceChartArgs sourceChartFlags
|
||||
|
||||
func init() {
|
||||
createSourceChartCmd.Flags().StringVar(&sourceChartArgs.chart, "chart", "", "Helm chart name or path")
|
||||
createSourceChartCmd.Flags().StringVar(&sourceChartArgs.chartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)")
|
||||
createSourceChartCmd.Flags().Var(&sourceChartArgs.source, "source", sourceChartArgs.source.Description())
|
||||
createSourceChartCmd.Flags().StringVar(&sourceChartArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart (accepted values: Revision and ChartRevision)")
|
||||
createSourceChartCmd.Flags().Var(&sourceChartArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description())
|
||||
createSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifySecretRef, "verify-secret-ref", "", "the name of a secret to use for signature verification")
|
||||
createSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifySubject, "verify-subject", "", "regular expression to use for the OIDC subject during signature verification")
|
||||
createSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifyOIDCIssuer, "verify-issuer", "", "regular expression to use for the OIDC issuer during signature verification")
|
||||
|
||||
createSourceCmd.AddCommand(createSourceChartCmd)
|
||||
}
|
||||
|
||||
func createSourceChartCmdRun(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
if sourceChartArgs.source.Kind == "" || sourceChartArgs.source.Name == "" {
|
||||
return fmt.Errorf("chart source is required")
|
||||
}
|
||||
|
||||
if sourceChartArgs.chart == "" {
|
||||
return fmt.Errorf("chart name or path is required")
|
||||
}
|
||||
|
||||
logger.Generatef("generating HelmChart source")
|
||||
|
||||
sourceLabels, err := parseLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
helmChart := &sourcev1.HelmChart{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Labels: sourceLabels,
|
||||
},
|
||||
Spec: sourcev1.HelmChartSpec{
|
||||
Chart: sourceChartArgs.chart,
|
||||
Version: sourceChartArgs.chartVersion,
|
||||
Interval: metav1.Duration{
|
||||
Duration: createArgs.interval,
|
||||
},
|
||||
ReconcileStrategy: sourceChartArgs.reconcileStrategy,
|
||||
SourceRef: sourcev1.LocalHelmChartSourceReference{
|
||||
Kind: sourceChartArgs.source.Kind,
|
||||
Name: sourceChartArgs.source.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if provider := sourceChartArgs.verifyProvider.String(); provider != "" {
|
||||
helmChart.Spec.Verify = &sourcev1.OCIRepositoryVerification{
|
||||
Provider: provider,
|
||||
}
|
||||
if secretName := sourceChartArgs.verifySecretRef; secretName != "" {
|
||||
helmChart.Spec.Verify.SecretRef = &meta.LocalObjectReference{
|
||||
Name: secretName,
|
||||
}
|
||||
}
|
||||
verifyIssuer := sourceChartArgs.verifyOIDCIssuer
|
||||
verifySubject := sourceChartArgs.verifySubject
|
||||
if verifyIssuer != "" || verifySubject != "" {
|
||||
helmChart.Spec.Verify.MatchOIDCIdentity = []sourcev1.OIDCIdentityMatch{{
|
||||
Issuer: verifyIssuer,
|
||||
Subject: verifySubject,
|
||||
}}
|
||||
}
|
||||
} else if sourceChartArgs.verifySecretRef != "" {
|
||||
return fmt.Errorf("a verification provider must be specified when a secret is specified")
|
||||
} else if sourceChartArgs.verifyOIDCIssuer != "" || sourceOCIRepositoryArgs.verifySubject != "" {
|
||||
return fmt.Errorf("a verification provider must be specified when OIDC issuer/subject is specified")
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return printExport(exportHelmChart(helmChart))
|
||||
}
|
||||
|
||||
logger.Actionf("applying HelmChart source")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespacedName, err := upsertHelmChart(ctx, kubeClient, helmChart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Waitingf("waiting for HelmChart source reconciliation")
|
||||
readyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmChart)
|
||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Successf("HelmChart source reconciliation completed")
|
||||
|
||||
if helmChart.Status.Artifact == nil {
|
||||
return fmt.Errorf("HelmChart source reconciliation completed but no artifact was found")
|
||||
}
|
||||
logger.Successf("fetched revision: %s", helmChart.Status.Artifact.Revision)
|
||||
return nil
|
||||
}
|
||||
|
||||
func upsertHelmChart(ctx context.Context, kubeClient client.Client,
|
||||
helmChart *sourcev1.HelmChart) (types.NamespacedName, error) {
|
||||
namespacedName := types.NamespacedName{
|
||||
Namespace: helmChart.GetNamespace(),
|
||||
Name: helmChart.GetName(),
|
||||
}
|
||||
|
||||
var existing sourcev1.HelmChart
|
||||
err := kubeClient.Get(ctx, namespacedName, &existing)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
if err := kubeClient.Create(ctx, helmChart); err != nil {
|
||||
return namespacedName, err
|
||||
} else {
|
||||
logger.Successf("source created")
|
||||
return namespacedName, nil
|
||||
}
|
||||
}
|
||||
return namespacedName, err
|
||||
}
|
||||
|
||||
existing.Labels = helmChart.Labels
|
||||
existing.Spec = helmChart.Spec
|
||||
if err := kubeClient.Update(ctx, &existing); err != nil {
|
||||
return namespacedName, err
|
||||
}
|
||||
helmChart = &existing
|
||||
logger.Successf("source updated")
|
||||
return namespacedName, nil
|
||||
}
|
||||
@@ -1,91 +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 TestCreateSourceChart(t *testing.T) {
|
||||
tmpl := map[string]string{
|
||||
"fluxns": allocateNamespace("flux-system"),
|
||||
}
|
||||
setupSourceChart(t, tmpl)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
name: "missing name",
|
||||
args: "create source chart --export",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
name: "missing source reference",
|
||||
args: "create source chart podinfo --export ",
|
||||
assert: assertError("chart source is required"),
|
||||
},
|
||||
{
|
||||
name: "missing chart name",
|
||||
args: "create source chart podinfo --source helmrepository/podinfo --export",
|
||||
assert: assertError("chart name or path is required"),
|
||||
},
|
||||
{
|
||||
name: "unknown source kind",
|
||||
args: "create source chart podinfo --source foobar/podinfo --export",
|
||||
assert: assertError(`invalid argument "foobar/podinfo" for "--source" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`),
|
||||
},
|
||||
{
|
||||
name: "basic chart",
|
||||
args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --export",
|
||||
assert: assertGoldenTemplateFile("testdata/create_source_chart/basic.yaml", tmpl),
|
||||
},
|
||||
{
|
||||
name: "chart with basic signature verification",
|
||||
args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider cosign --export",
|
||||
assert: assertGoldenTemplateFile("testdata/create_source_chart/verify_basic.yaml", tmpl),
|
||||
},
|
||||
{
|
||||
name: "unknown signature verification provider",
|
||||
args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider foobar --export",
|
||||
assert: assertError(`invalid argument "foobar" for "--verify-provider" flag: source OCI verify provider 'foobar' is not supported, must be one of: cosign`),
|
||||
},
|
||||
{
|
||||
name: "chart with complete signature verification",
|
||||
args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider cosign --verify-issuer foo --verify-subject bar --export",
|
||||
assert: assertGoldenTemplateFile("testdata/create_source_chart/verify_complete.yaml", tmpl),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args + " -n " + tmpl["fluxns"],
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupSourceChart(t *testing.T, tmpl map[string]string) {
|
||||
t.Helper()
|
||||
testEnv.CreateObjectFile("./testdata/create_source_chart/setup-source.yaml", tmpl, t)
|
||||
}
|
||||
@@ -44,26 +44,23 @@ import (
|
||||
)
|
||||
|
||||
type sourceGitFlags struct {
|
||||
url string
|
||||
branch string
|
||||
tag string
|
||||
semver string
|
||||
refName string
|
||||
commit string
|
||||
username string
|
||||
password string
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
keyRSABits flags.RSAKeyBits
|
||||
keyECDSACurve flags.ECDSACurve
|
||||
secretRef string
|
||||
proxySecretRef string
|
||||
provider flags.SourceGitProvider
|
||||
caFile string
|
||||
privateKeyFile string
|
||||
recurseSubmodules bool
|
||||
silent bool
|
||||
ignorePaths []string
|
||||
sparseCheckoutPaths []string
|
||||
url string
|
||||
branch string
|
||||
tag string
|
||||
semver string
|
||||
refName string
|
||||
commit string
|
||||
username string
|
||||
password string
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
keyRSABits flags.RSAKeyBits
|
||||
keyECDSACurve flags.ECDSACurve
|
||||
secretRef string
|
||||
caFile string
|
||||
privateKeyFile string
|
||||
recurseSubmodules bool
|
||||
silent bool
|
||||
ignorePaths []string
|
||||
}
|
||||
|
||||
var createSourceGitCmd = &cobra.Command{
|
||||
@@ -122,13 +119,7 @@ For private Git repositories, the basic authentication credentials are stored in
|
||||
--url=https://github.com/stefanprodan/podinfo \
|
||||
--branch=master \
|
||||
--username=username \
|
||||
--password=password
|
||||
|
||||
# Create a source for a Git repository using azure provider
|
||||
flux create source git podinfo \
|
||||
--url=https://dev.azure.com/foo/bar/_git/podinfo \
|
||||
--branch=master \
|
||||
--provider=azure`,
|
||||
--password=password`,
|
||||
RunE: createSourceGitCmdRun,
|
||||
}
|
||||
|
||||
@@ -139,23 +130,20 @@ func init() {
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.branch, "branch", "", "git branch")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.tag, "tag", "", "git tag")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.semver, "tag-semver", "", "git tag semver range")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.refName, "ref-name", "", "git reference name")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.refName, "ref-name", "", " git reference name")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.commit, "commit", "", "git commit")
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.username, "username", "u", "", "basic authentication username")
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.password, "password", "p", "", "basic authentication password")
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyAlgorithm, "ssh-key-algorithm", sourceGitArgs.keyAlgorithm.Description())
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyRSABits, "ssh-rsa-bits", sourceGitArgs.keyRSABits.Description())
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, "ssh-ecdsa-curve", sourceGitArgs.keyECDSACurve.Description())
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials or github app authentication")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.proxySecretRef, "proxy-secret-ref", "", "the name of an existing secret containing the proxy address and credentials")
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.provider, "provider", sourceGitArgs.provider.Description())
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
|
||||
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
|
||||
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
|
||||
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
|
||||
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)")
|
||||
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.sparseCheckoutPaths, "sparse-checkout-paths", nil, "set paths to sparse checkout in git resource (can specify multiple paths with commas: path1,path2)")
|
||||
|
||||
createSourceCmd.AddCommand(createSourceGitCmd)
|
||||
}
|
||||
@@ -191,6 +179,12 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("specifying a CA file is not supported for Git over SSH")
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
sourceLabels, err := parseLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -216,7 +210,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
RecurseSubmodules: sourceGitArgs.recurseSubmodules,
|
||||
Reference: &sourcev1.GitRepositoryRef{},
|
||||
Ignore: ignorePaths,
|
||||
SparseCheckout: sourceGitArgs.sparseCheckoutPaths,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,16 +236,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if sourceGitArgs.proxySecretRef != "" {
|
||||
gitRepository.Spec.ProxySecretRef = &meta.LocalObjectReference{
|
||||
Name: sourceGitArgs.proxySecretRef,
|
||||
}
|
||||
}
|
||||
|
||||
if provider := sourceGitArgs.provider.String(); provider != "" {
|
||||
gitRepository.Spec.Provider = provider
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return printExport(exportGit(&gitRepository))
|
||||
}
|
||||
@@ -290,7 +273,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
secretOpts.CACrt = caBundle
|
||||
secretOpts.CAFile = caBundle
|
||||
}
|
||||
secretOpts.Username = sourceGitArgs.username
|
||||
secretOpts.Password = sourceGitArgs.password
|
||||
@@ -299,7 +282,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
secretOpts.Username = sourceGitArgs.username
|
||||
secretOpts.Password = sourceGitArgs.password
|
||||
}
|
||||
secret, err := sourcesecret.GenerateGit(secretOpts)
|
||||
secret, err := sourcesecret.Generate(secretOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (r *reconciler) conditionFunc() (bool, error) {
|
||||
}
|
||||
|
||||
func TestCreateSourceGitExport(t *testing.T) {
|
||||
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --sparse-checkout-paths .cosign,non-existent-dir/ --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String()
|
||||
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -101,7 +101,7 @@ func TestCreateSourceGitExport(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no args",
|
||||
args: "create source git --url=https://github.com/stefanprodan/podinfo",
|
||||
args: "create secret git",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
@@ -134,36 +134,6 @@ func TestCreateSourceGitExport(t *testing.T) {
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --branch=test --interval=1m0s --export",
|
||||
assert: assertGoldenFile("testdata/create_source_git/source-git-branch.yaml"),
|
||||
},
|
||||
{
|
||||
name: "source with generic provider",
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --provider generic --branch=test --interval=1m0s --export",
|
||||
assert: assertGoldenFile("testdata/create_source_git/source-git-provider-generic.yaml"),
|
||||
},
|
||||
{
|
||||
name: "source with azure provider",
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider azure --branch=test --interval=1m0s --export",
|
||||
assert: assertGoldenFile("testdata/create_source_git/source-git-provider-azure.yaml"),
|
||||
},
|
||||
{
|
||||
name: "source with github provider",
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --provider github --branch=test --interval=1m0s --secret-ref appinfo --export",
|
||||
assert: assertGoldenFile("testdata/create_source_git/source-git-provider-github.yaml"),
|
||||
},
|
||||
{
|
||||
name: "source with invalid provider",
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider dummy --branch=test --interval=1m0s --export",
|
||||
assert: assertError("invalid argument \"dummy\" for \"--provider\" flag: source Git provider 'dummy' is not supported, must be one of: generic|azure|github"),
|
||||
},
|
||||
{
|
||||
name: "source with empty provider",
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider \"\" --branch=test --interval=1m0s --export",
|
||||
assert: assertError("invalid argument \"\" for \"--provider\" flag: no source Git provider given, please specify the Git provider name"),
|
||||
},
|
||||
{
|
||||
name: "source with no provider",
|
||||
args: "create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --branch=test --interval=1m0s --export --provider",
|
||||
assert: assertError("flag needs an argument: --provider"),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -204,13 +174,12 @@ func TestCreateSourceGit(t *testing.T) {
|
||||
ObservedGeneration: repo.GetGeneration(),
|
||||
}
|
||||
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
||||
repo.Status.Artifact = &meta.Artifact{
|
||||
repo.Status.Artifact = &sourcev1.Artifact{
|
||||
Path: "some-path",
|
||||
Revision: "v1",
|
||||
LastUpdateTime: metav1.Time{
|
||||
Time: time.Now(),
|
||||
},
|
||||
Digest: "sha256:1234567890abcdef",
|
||||
}
|
||||
repo.Status.ObservedGeneration = repo.GetGeneration()
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -31,8 +32,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"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/utils"
|
||||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
|
||||
@@ -41,8 +41,8 @@ import (
|
||||
var createSourceHelmCmd = &cobra.Command{
|
||||
Use: "helm [name]",
|
||||
Short: "Create or update a HelmRepository source",
|
||||
Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
|
||||
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
|
||||
Long: withPreviewNote(`The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
|
||||
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`),
|
||||
Example: ` # Create a source for an HTTPS public Helm repository
|
||||
flux create source helm podinfo \
|
||||
--url=https://stefanprodan.github.io/podinfo \
|
||||
@@ -114,6 +114,12 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if _, err := url.Parse(sourceHelmArgs.url); err != nil {
|
||||
return fmt.Errorf("url parse failed: %w", err)
|
||||
}
|
||||
@@ -191,12 +197,12 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Username: sourceHelmArgs.username,
|
||||
Password: sourceHelmArgs.password,
|
||||
CACrt: caBundle,
|
||||
TLSCrt: certFile,
|
||||
TLSKey: keyFile,
|
||||
CAFile: caBundle,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||
}
|
||||
secret, err := sourcesecret.GenerateHelm(secretOpts)
|
||||
secret, err := sourcesecret.Generate(secretOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
@@ -42,37 +43,25 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
|
||||
Example: ` # Create an OCIRepository for a public container image
|
||||
flux create source oci podinfo \
|
||||
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--tag=6.6.2 \
|
||||
--tag=6.1.6 \
|
||||
--interval=10m
|
||||
|
||||
# Create an OCIRepository with OIDC signature verification
|
||||
flux create source oci podinfo \
|
||||
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--tag=6.6.2 \
|
||||
--interval=10m \
|
||||
--verify-provider=cosign \
|
||||
--verify-subject="^https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2$" \
|
||||
--verify-issuer="^https://token.actions.githubusercontent.com$"
|
||||
`,
|
||||
RunE: createSourceOCIRepositoryCmdRun,
|
||||
}
|
||||
|
||||
type sourceOCIRepositoryFlags struct {
|
||||
url string
|
||||
tag string
|
||||
semver string
|
||||
digest string
|
||||
secretRef string
|
||||
proxySecretRef string
|
||||
serviceAccount string
|
||||
certSecretRef string
|
||||
verifyProvider flags.SourceOCIVerifyProvider
|
||||
verifySecretRef string
|
||||
verifyOIDCIssuer string
|
||||
verifySubject string
|
||||
ignorePaths []string
|
||||
provider flags.SourceOCIProvider
|
||||
insecure bool
|
||||
url string
|
||||
tag string
|
||||
semver string
|
||||
digest string
|
||||
secretRef string
|
||||
serviceAccount string
|
||||
certSecretRef string
|
||||
verifyProvider flags.SourceOCIVerifyProvider
|
||||
verifySecretRef string
|
||||
ignorePaths []string
|
||||
provider flags.SourceOCIProvider
|
||||
insecure bool
|
||||
}
|
||||
|
||||
var sourceOCIRepositoryArgs = newSourceOCIFlags()
|
||||
@@ -90,13 +79,10 @@ func init() {
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, "tag-semver", "", "the OCI artifact tag semver range")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.proxySecretRef, "proxy-secret-ref", "", "the name of an existing secret containing the proxy address and credentials")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
|
||||
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description())
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySecretRef, "verify-secret-ref", "", "the name of a secret to use for signature verification")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySubject, "verify-subject", "", "regular expression to use for the OIDC subject during signature verification")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifyOIDCIssuer, "verify-issuer", "", "regular expression to use for the OIDC issuer during signature verification")
|
||||
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
|
||||
createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP")
|
||||
|
||||
@@ -167,12 +153,6 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if secretName := sourceOCIRepositoryArgs.proxySecretRef; secretName != "" {
|
||||
repository.Spec.ProxySecretRef = &meta.LocalObjectReference{
|
||||
Name: secretName,
|
||||
}
|
||||
}
|
||||
|
||||
if secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != "" {
|
||||
repository.Spec.CertSecretRef = &meta.LocalObjectReference{
|
||||
Name: secretName,
|
||||
@@ -188,18 +168,8 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Name: secretName,
|
||||
}
|
||||
}
|
||||
verifyIssuer := sourceOCIRepositoryArgs.verifyOIDCIssuer
|
||||
verifySubject := sourceOCIRepositoryArgs.verifySubject
|
||||
if verifyIssuer != "" || verifySubject != "" {
|
||||
repository.Spec.Verify.MatchOIDCIdentity = []sourcev1.OIDCIdentityMatch{{
|
||||
Issuer: verifyIssuer,
|
||||
Subject: verifySubject,
|
||||
}}
|
||||
}
|
||||
} else if sourceOCIRepositoryArgs.verifySecretRef != "" {
|
||||
return fmt.Errorf("a verification provider must be specified when a secret is specified")
|
||||
} else if sourceOCIRepositoryArgs.verifyOIDCIssuer != "" || sourceOCIRepositoryArgs.verifySubject != "" {
|
||||
return fmt.Errorf("a verification provider must be specified when OIDC issuer/subject is specified")
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
|
||||
@@ -37,35 +37,10 @@ func TestCreateSourceOCI(t *testing.T) {
|
||||
assertFunc: assertError("url is required"),
|
||||
},
|
||||
{
|
||||
name: "verify secret specified but provider missing",
|
||||
name: "verify provider not specified",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-secret-ref=cosign-pub",
|
||||
assertFunc: assertError("a verification provider must be specified when a secret is specified"),
|
||||
},
|
||||
{
|
||||
name: "verify issuer specified but provider missing",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github.com",
|
||||
assertFunc: assertError("a verification provider must be specified when OIDC issuer/subject is specified"),
|
||||
},
|
||||
{
|
||||
name: "verify identity specified but provider missing",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-subject=developer",
|
||||
assertFunc: assertError("a verification provider must be specified when OIDC issuer/subject is specified"),
|
||||
},
|
||||
{
|
||||
name: "verify issuer specified but subject missing",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github --verify-provider=cosign --export",
|
||||
assertFunc: assertGoldenFile("./testdata/oci/export_with_issuer.golden"),
|
||||
},
|
||||
{
|
||||
name: "all verify fields set",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github verify-subject=stefanprodan --verify-provider=cosign --export",
|
||||
assertFunc: assertGoldenFile("./testdata/oci/export_with_issuer.golden"),
|
||||
},
|
||||
{
|
||||
name: "verify subject specified but issuer missing",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-subject=stefanprodan --verify-provider=cosign --export",
|
||||
assertFunc: assertGoldenFile("./testdata/oci/export_with_subject.golden"),
|
||||
},
|
||||
{
|
||||
name: "export manifest",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export",
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
@@ -31,8 +32,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
)
|
||||
|
||||
var createTenantCmd = &cobra.Command{
|
||||
@@ -58,10 +57,8 @@ const (
|
||||
)
|
||||
|
||||
type tenantFlags struct {
|
||||
namespaces []string
|
||||
clusterRole string
|
||||
account string
|
||||
skipNamespace bool
|
||||
namespaces []string
|
||||
clusterRole string
|
||||
}
|
||||
|
||||
var tenantArgs tenantFlags
|
||||
@@ -69,8 +66,6 @@ var tenantArgs tenantFlags
|
||||
func init() {
|
||||
createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant")
|
||||
createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
|
||||
createTenantCmd.Flags().StringVar(&tenantArgs.account, "with-service-account", "", "service account belonging to this tenant")
|
||||
createTenantCmd.Flags().BoolVar(&tenantArgs.skipNamespace, "skip-namespace", false, "skip namespace creation (namespace must exist already)")
|
||||
createCmd.AddCommand(createTenantCmd)
|
||||
}
|
||||
|
||||
@@ -112,17 +107,9 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
namespaces = append(namespaces, namespace)
|
||||
|
||||
accountName := tenant
|
||||
if tenantArgs.account != "" {
|
||||
accountName = tenantArgs.account
|
||||
}
|
||||
if err := validation.IsQualifiedName(accountName); len(err) > 0 {
|
||||
return fmt.Errorf("invalid service-account name '%s': %v", accountName, err)
|
||||
}
|
||||
|
||||
account := corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: accountName,
|
||||
Name: tenant,
|
||||
Namespace: ns,
|
||||
Labels: objLabels,
|
||||
},
|
||||
@@ -144,7 +131,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: accountName,
|
||||
Name: tenant,
|
||||
Namespace: ns,
|
||||
},
|
||||
},
|
||||
@@ -159,7 +146,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if createArgs.export {
|
||||
for i := range tenantArgs.namespaces {
|
||||
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i], tenantArgs.skipNamespace); err != nil {
|
||||
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -175,11 +162,9 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
for i := range tenantArgs.namespaces {
|
||||
if !tenantArgs.skipNamespace {
|
||||
logger.Actionf("applying namespace %s", namespaces[i].Name)
|
||||
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Actionf("applying namespace %s", namespaces[i].Name)
|
||||
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Actionf("applying service account %s", accounts[i].Name)
|
||||
@@ -288,24 +273,19 @@ func upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBindin
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, roleBinding rbacv1.RoleBinding, skipNamespace bool) error {
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
if !skipNamespace {
|
||||
namespace.TypeMeta = metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Namespace",
|
||||
}
|
||||
data, err = yaml.Marshal(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
|
||||
|
||||
printlnStdout("---")
|
||||
printlnStdout(resourceToString(data))
|
||||
func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, roleBinding rbacv1.RoleBinding) error {
|
||||
namespace.TypeMeta = metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Namespace",
|
||||
}
|
||||
data, err := yaml.Marshal(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("---")
|
||||
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
|
||||
fmt.Println(resourceToString(data))
|
||||
|
||||
account.TypeMeta = metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
@@ -315,10 +295,10 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
|
||||
|
||||
printlnStdout("---")
|
||||
printlnStdout(resourceToString(data))
|
||||
fmt.Println("---")
|
||||
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
|
||||
fmt.Println(resourceToString(data))
|
||||
|
||||
roleBinding.TypeMeta = metav1.TypeMeta{
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
@@ -329,8 +309,8 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol
|
||||
return err
|
||||
}
|
||||
|
||||
printlnStdout("---")
|
||||
printlnStdout(resourceToString(data))
|
||||
fmt.Println("---")
|
||||
fmt.Println(resourceToString(data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
//go:build e2e
|
||||
// +build e2e
|
||||
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateTenant(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
name: "no args",
|
||||
args: "create tenant",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
name: "no namespace",
|
||||
args: "create tenant dev-team --cluster-role=cluster-admin",
|
||||
assert: assertError("with-namespace is required"),
|
||||
},
|
||||
{
|
||||
name: "basic tenant",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-basic.yaml"),
|
||||
},
|
||||
{
|
||||
name: "tenant with custom serviceaccount",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --with-service-account=flux-tenant --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-service-account.yaml"),
|
||||
},
|
||||
{
|
||||
name: "tenant with custom cluster role",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=custom-role --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-cluster-role.yaml"),
|
||||
},
|
||||
{
|
||||
name: "tenant with skip namespace",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --skip-namespace --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-skip-namespace.yaml"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args,
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,142 +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
|
||||
|
||||
# Print the reconciliation history of a Helm release
|
||||
flux debug hr podinfo --show-history`,
|
||||
RunE: debugHelmReleaseCmdRun,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
}
|
||||
|
||||
type debugHelmReleaseFlags struct {
|
||||
showStatus bool
|
||||
showValues bool
|
||||
showHistory 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")
|
||||
debugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showHistory, "show-history", false, "print the reconciliation history of the Helm release")
|
||||
debugCmd.AddCommand(debugHelmReleaseCmd)
|
||||
}
|
||||
|
||||
func debugHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
flagsSet := 0
|
||||
if debugHelmReleaseArgs.showStatus {
|
||||
flagsSet++
|
||||
}
|
||||
if debugHelmReleaseArgs.showValues {
|
||||
flagsSet++
|
||||
}
|
||||
if debugHelmReleaseArgs.showHistory {
|
||||
flagsSet++
|
||||
}
|
||||
if flagsSet != 1 {
|
||||
return fmt.Errorf("exactly one of --show-status, --show-values, or --show-history must be set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
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))
|
||||
}
|
||||
|
||||
if debugHelmReleaseArgs.showHistory {
|
||||
if len(hr.Status.History) == 0 {
|
||||
hr.Status.History = helmv2.Snapshots{}
|
||||
}
|
||||
|
||||
history, err := yaml.Marshal(hr.Status.History)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootCmd.Println("# History documentation: https://fluxcd.io/flux/components/helm/helmreleases/#history")
|
||||
rootCmd.Print(string(history))
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,83 +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,
|
||||
},
|
||||
{
|
||||
"debug history",
|
||||
"debug helmrelease test-with-history --show-history --show-status=false",
|
||||
"testdata/debug_helmrelease/history.golden.yaml",
|
||||
tmpl,
|
||||
},
|
||||
{
|
||||
"debug history empty",
|
||||
"debug helmrelease test-values-inline --show-history --show-status=false",
|
||||
"testdata/debug_helmrelease/history-empty.golden.yaml",
|
||||
tmpl,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.arg + " -n=" + namespace,
|
||||
assert: assertGoldenTemplateFile(tt.goldenFile, tmpl),
|
||||
}
|
||||
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,164 +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/apis/meta"
|
||||
"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
|
||||
|
||||
# Print the reconciliation history of a Flux Kustomization
|
||||
flux debug ks podinfo --show-history`,
|
||||
RunE: debugKustomizationCmdRun,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
}
|
||||
|
||||
type debugKustomizationFlags struct {
|
||||
showStatus bool
|
||||
showVars bool
|
||||
showHistory 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")
|
||||
debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showHistory, "show-history", false, "print the reconciliation history of the Flux Kustomization")
|
||||
debugCmd.AddCommand(debugKustomizationCmd)
|
||||
}
|
||||
|
||||
func debugKustomizationCmdRun(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
flagsSet := 0
|
||||
if debugKustomizationArgs.showStatus {
|
||||
flagsSet++
|
||||
}
|
||||
if debugKustomizationArgs.showVars {
|
||||
flagsSet++
|
||||
}
|
||||
if debugKustomizationArgs.showHistory {
|
||||
flagsSet++
|
||||
}
|
||||
if flagsSet != 1 {
|
||||
return fmt.Errorf("exactly one of --show-status, --show-vars, or --show-history must be set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
if debugKustomizationArgs.showHistory {
|
||||
if len(ks.Status.History) == 0 {
|
||||
ks.Status.History = meta.History{}
|
||||
}
|
||||
|
||||
history, err := yaml.Marshal(ks.Status.History)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootCmd.Println("# History documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#history")
|
||||
rootCmd.Print(string(history))
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,82 +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,
|
||||
}, {
|
||||
"debug history",
|
||||
"debug ks test-with-history --show-history --show-status=false",
|
||||
"testdata/debug_kustomization/history.golden.yaml",
|
||||
tmpl,
|
||||
},
|
||||
{
|
||||
"debug history empty",
|
||||
"debug ks test --show-history --show-status=false",
|
||||
"testdata/debug_kustomization/history-empty.golden.yaml",
|
||||
tmpl,
|
||||
},
|
||||
}
|
||||
|
||||
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,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
Copyright 2020 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.
|
||||
@@ -19,14 +19,14 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta2"
|
||||
)
|
||||
|
||||
var deleteHelmReleaseCmd = &cobra.Command{
|
||||
Use: "helmrelease [name]",
|
||||
Aliases: []string{"hr"},
|
||||
Short: "Delete a HelmRelease resource",
|
||||
Long: "The delete helmrelease command removes the given HelmRelease from the cluster.",
|
||||
Long: withPreviewNote("The delete helmrelease command removes the given HelmRelease from the cluster."),
|
||||
Example: ` # Delete a Helm release and the Kubernetes resources created by it
|
||||
flux delete hr podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
|
||||
@@ -19,13 +19,13 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var deleteImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy [name]",
|
||||
Short: "Delete an ImagePolicy object",
|
||||
Long: `The delete image policy command deletes the given ImagePolicy from the cluster.`,
|
||||
Long: withPreviewNote(`The delete image policy command deletes the given ImagePolicy from the cluster.`),
|
||||
Example: ` # Delete an image policy
|
||||
flux delete image policy alpine3.x`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
|
||||
@@ -19,13 +19,13 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var deleteImageRepositoryCmd = &cobra.Command{
|
||||
Use: "repository [name]",
|
||||
Short: "Delete an ImageRepository object",
|
||||
Long: "The delete image repository command deletes the given ImageRepository from the cluster.",
|
||||
Long: withPreviewNote("The delete image repository command deletes the given ImageRepository from the cluster."),
|
||||
Example: ` # Delete an image repository
|
||||
flux delete image repository alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||
|
||||
@@ -19,13 +19,13 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1"
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
)
|
||||
|
||||
var deleteImageUpdateCmd = &cobra.Command{
|
||||
Use: "update [name]",
|
||||
Short: "Delete an ImageUpdateAutomation object",
|
||||
Long: `The delete image update command deletes the given ImageUpdateAutomation from the cluster.`,
|
||||
Long: withPreviewNote(`The delete image update command deletes the given ImageUpdateAutomation from the cluster.`),
|
||||
Example: ` # Delete an image update automation
|
||||
flux delete image update latest-images`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
|
||||
@@ -19,13 +19,13 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var deleteSourceBucketCmd = &cobra.Command{
|
||||
Use: "bucket [name]",
|
||||
Short: "Delete a Bucket source",
|
||||
Long: "The delete source bucket command deletes the given Bucket from the cluster.",
|
||||
Long: withPreviewNote("The delete source bucket command deletes the given Bucket from the cluster."),
|
||||
Example: ` # Delete a Bucket source
|
||||
flux delete source bucket podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
|
||||
@@ -1,40 +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"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
var deleteSourceChartCmd = &cobra.Command{
|
||||
Use: "chart [name]",
|
||||
Short: "Delete a HelmChart source",
|
||||
Long: "The delete source chart command deletes the given HelmChart from the cluster.",
|
||||
Example: ` # Delete a HelmChart
|
||||
flux delete source chart podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: helmChartType,
|
||||
object: universalAdapter{&sourcev1.HelmChart{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
deleteSourceCmd.AddCommand(deleteSourceChartCmd)
|
||||
}
|
||||
@@ -19,13 +19,13 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var deleteSourceHelmCmd = &cobra.Command{
|
||||
Use: "helm [name]",
|
||||
Short: "Delete a HelmRepository source",
|
||||
Long: "The delete source helm command deletes the given HelmRepository from the cluster.",
|
||||
Long: withPreviewNote("The delete source helm command deletes the given HelmRepository from the cluster."),
|
||||
Example: ` # Delete a Helm repository
|
||||
flux delete source helm podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
|
||||
@@ -19,7 +19,7 @@ package main
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var deleteSourceOCIRepositoryCmd = &cobra.Command{
|
||||
|
||||
@@ -21,9 +21,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fluxcd/pkg/oci"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
"github.com/google/go-containerregistry/pkg/crane"
|
||||
oci "github.com/fluxcd/pkg/oci/client"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||
@@ -32,7 +31,7 @@ import (
|
||||
var diffArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Diff Artifact",
|
||||
Long: `The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`,
|
||||
Long: withPreviewNote(`The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`),
|
||||
Example: `# Check if local files differ from remote
|
||||
flux diff artifact oci://ghcr.io/stefanprodan/manifests:podinfo:6.2.0 --path=./kustomize`,
|
||||
RunE: diffArtifactCmdRun,
|
||||
@@ -43,7 +42,6 @@ type diffArtifactFlags struct {
|
||||
creds string
|
||||
provider flags.SourceOCIProvider
|
||||
ignorePaths []string
|
||||
insecure bool
|
||||
}
|
||||
|
||||
var diffArtifactArgs = newDiffArtifactArgs()
|
||||
@@ -59,7 +57,6 @@ func init() {
|
||||
diffArtifactCmd.Flags().StringVar(&diffArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
|
||||
diffArtifactCmd.Flags().Var(&diffArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
|
||||
diffArtifactCmd.Flags().StringSliceVar(&diffArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
|
||||
diffArtifactCmd.Flags().BoolVar(&diffArtifactArgs.insecure, "insecure-registry", false, "allows the remote artifact to be pulled without TLS")
|
||||
diffCmd.AddCommand(diffArtifactCmd)
|
||||
}
|
||||
|
||||
@@ -85,22 +82,7 @@ func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
opts := oci.DefaultOptions()
|
||||
|
||||
if diffArtifactArgs.insecure {
|
||||
opts = append(opts, crane.Insecure)
|
||||
}
|
||||
|
||||
if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||
logger.Actionf("logging in to registry with provider credentials")
|
||||
opt, _, err := loginWithProvider(ctx, url, diffArtifactArgs.provider.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during login with provider: %w", err)
|
||||
}
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
|
||||
ociClient := oci.NewClient(opts)
|
||||
ociClient := oci.NewClient(oci.DefaultOptions())
|
||||
|
||||
if diffArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && diffArtifactArgs.creds != "" {
|
||||
logger.Actionf("logging in to registry with credentials")
|
||||
@@ -109,6 +91,18 @@ func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||
logger.Actionf("logging in to registry with provider credentials")
|
||||
ociProvider, err := diffArtifactArgs.provider.ToOCIProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("provider not supported: %w", err)
|
||||
}
|
||||
|
||||
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
|
||||
return fmt.Errorf("error during login with provider: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := ociClient.Diff(ctx, url, diffArtifactArgs.path, diffArtifactArgs.ignorePaths); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestDiffArtifact(t *testing.T) {
|
||||
tt.url = fmt.Sprintf(tt.url, dockerReg)
|
||||
_, err := executeCommand("push artifact " + tt.url + " --path=" + tt.pushFile + " --source=test --revision=test")
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("failed to push image: %w", err).Error())
|
||||
t.Fatalf(fmt.Errorf("failed to push image: %w", err).Error())
|
||||
}
|
||||
|
||||
cmd := cmdTestCase{
|
||||
|
||||
@@ -23,9 +23,8 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/build"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
)
|
||||
|
||||
var diffKsCmd = &cobra.Command{
|
||||
@@ -44,12 +43,7 @@ flux diff kustomization my-app --path ./path/to/local/manifests \
|
||||
# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.
|
||||
flux diff kustomization my-app --path ./path/to/local/manifests \
|
||||
--kustomization-file ./path/to/local/my-app.yaml \
|
||||
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"
|
||||
|
||||
# Run recursively on all encountered Kustomizations
|
||||
flux diff kustomization my-app --path ./path/to/local/manifests \
|
||||
--recursive \
|
||||
--local-sources GitRepository/flux-system/my-repo=./path/to/local/git`,
|
||||
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: diffKsCmdRun,
|
||||
}
|
||||
@@ -59,9 +53,6 @@ type diffKsFlags struct {
|
||||
path string
|
||||
ignorePaths []string
|
||||
progressBar bool
|
||||
strictSubst bool
|
||||
recursive bool
|
||||
localSources map[string]string
|
||||
}
|
||||
|
||||
var diffKsArgs diffKsFlags
|
||||
@@ -71,10 +62,6 @@ func init() {
|
||||
diffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, "progress-bar", true, "Boolean to set the progress bar. The default value is true.")
|
||||
diffKsCmd.Flags().StringSliceVar(&diffKsArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in .gitignore format")
|
||||
diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
|
||||
diffKsCmd.Flags().BoolVar(&diffKsArgs.strictSubst, "strict-substitute", false,
|
||||
"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.")
|
||||
diffKsCmd.Flags().BoolVarP(&diffKsArgs.recursive, "recursive", "r", false, "Recursively diff Kustomizations")
|
||||
diffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path")
|
||||
diffCmd.AddCommand(diffKsCmd)
|
||||
}
|
||||
|
||||
@@ -109,10 +96,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
|
||||
build.WithProgressBar(),
|
||||
build.WithIgnore(diffKsArgs.ignorePaths),
|
||||
build.WithStrictSubstitute(diffKsArgs.strictSubst),
|
||||
build.WithRecursive(diffKsArgs.recursive),
|
||||
build.WithLocalSources(diffKsArgs.localSources),
|
||||
build.WithSingleKustomization(),
|
||||
)
|
||||
} else {
|
||||
builder, err = build.NewBuilder(name, diffKsArgs.path,
|
||||
@@ -120,10 +103,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
build.WithTimeout(rootArgs.timeout),
|
||||
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
|
||||
build.WithIgnore(diffKsArgs.ignorePaths),
|
||||
build.WithStrictSubstitute(diffKsArgs.strictSubst),
|
||||
build.WithRecursive(diffKsArgs.recursive),
|
||||
build.WithLocalSources(diffKsArgs.localSources),
|
||||
build.WithSingleKustomization(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -153,12 +132,6 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
select {
|
||||
case <-sigc:
|
||||
if diffKsArgs.progressBar {
|
||||
err := builder.StopSpinner()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println("Build cancelled... exiting.")
|
||||
return builder.Cancel()
|
||||
case err := <-errChan:
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/build"
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
"github.com/fluxcd/pkg/ssa/normalize"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
@@ -98,12 +97,6 @@ func TestDiffKustomization(t *testing.T) {
|
||||
objectFile: "",
|
||||
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
|
||||
},
|
||||
{
|
||||
name: "diff with recursive",
|
||||
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --progress-bar=false --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization",
|
||||
objectFile: "./testdata/diff-kustomization/my-app.yaml",
|
||||
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-recursive.golden"),
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := map[string]string{
|
||||
@@ -152,7 +145,7 @@ func createObjectFromFile(objectFile string, templateValues map[string]string, t
|
||||
t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
|
||||
}
|
||||
|
||||
if err := normalize.UnstructuredList(clientObjects); err != nil {
|
||||
if err := ssa.SetNativeKindsDefaults(clientObjects); err != nil {
|
||||
t.Fatalf("Error setting native kinds defaults for '%s': %v", objectFile, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
|
||||
"github.com/fluxcd/pkg/envsubst"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var envsubstCmd = &cobra.Command{
|
||||
Use: "envsubst",
|
||||
Args: cobra.NoArgs,
|
||||
Short: "envsubst substitutes the values of environment variables",
|
||||
Long: withPreviewNote(`The envsubst command substitutes the values of environment variables
|
||||
in the string piped as standard input and writes the result to the standard output. This command can be used
|
||||
to replicate the behavior of the Flux Kustomization post-build substitutions.`),
|
||||
Example: ` # Run env var substitutions on the kustomization build output
|
||||
export cluster_region=eu-central-1
|
||||
kustomize build . | flux envsubst
|
||||
|
||||
# Run env var substitutions and error out if a variable is not set
|
||||
kustomize build . | flux envsubst --strict
|
||||
`,
|
||||
RunE: runEnvsubstCmd,
|
||||
}
|
||||
|
||||
type envsubstFlags struct {
|
||||
strict bool
|
||||
}
|
||||
|
||||
var envsubstArgs envsubstFlags
|
||||
|
||||
func init() {
|
||||
envsubstCmd.Flags().BoolVar(&envsubstArgs.strict, "strict", false,
|
||||
"fail if a variable without a default value is declared in the input but is missing from the environment")
|
||||
rootCmd.AddCommand(envsubstCmd)
|
||||
}
|
||||
|
||||
func runEnvsubstCmd(cmd *cobra.Command, args []string) error {
|
||||
stdin := bufio.NewScanner(rootCmd.InOrStdin())
|
||||
stdout := bufio.NewWriter(rootCmd.OutOrStdout())
|
||||
for stdin.Scan() {
|
||||
line, err := envsubst.EvalEnv(stdin.Text(), envsubstArgs.strict)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(stdout, line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stdout.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,50 +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 (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestEnvsubst(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
input, err := os.ReadFile("testdata/envsubst/file.yaml")
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
t.Setenv("REPO_NAME", "test")
|
||||
|
||||
output, err := executeCommandWithIn("envsubst", bytes.NewReader(input))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expected, err := os.ReadFile("testdata/envsubst/file.gold")
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(output).To(Equal(string(expected)))
|
||||
}
|
||||
|
||||
func TestEnvsubst_Strinct(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
input, err := os.ReadFile("testdata/envsubst/file.yaml")
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = executeCommandWithIn("envsubst --strict", bytes.NewReader(input))
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("variable not set (strict mode)"))
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -38,14 +39,14 @@ import (
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta2"
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
"github.com/fluxcd/flux2/v2/pkg/printers"
|
||||
@@ -112,12 +113,7 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
var diffRefNs bool
|
||||
// Build the base list options. When --all-namespaces is set we must NOT constrain the
|
||||
// query to a single namespace, otherwise we silently return a partial result set.
|
||||
clientListOpts := []client.ListOption{}
|
||||
if !eventArgs.allNamespaces {
|
||||
clientListOpts = append(clientListOpts, client.InNamespace(*kubeconfigArgs.Namespace))
|
||||
}
|
||||
clientListOpts := []client.ListOption{client.InNamespace(*kubeconfigArgs.Namespace)}
|
||||
var refListOpts [][]client.ListOption
|
||||
if eventArgs.forSelector != "" {
|
||||
kind, name := getKindNameFromSelector(eventArgs.forSelector)
|
||||
@@ -196,14 +192,11 @@ func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []cli
|
||||
|
||||
func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
|
||||
listOpts := &metav1.ListOptions{}
|
||||
clientListOpts = append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
|
||||
err := runtimeresource.FollowContinue(listOpts,
|
||||
func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
newEvents := &corev1.EventList{}
|
||||
opts := append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
|
||||
if options.Continue != "" {
|
||||
opts = append(opts, client.Continue(options.Continue))
|
||||
}
|
||||
if err := kubeclient.List(ctx, newEvents, opts...); err != nil {
|
||||
if err := kubeclient.List(ctx, newEvents, clientListOpts...); err != nil {
|
||||
return nil, fmt.Errorf("error getting events: %w", err)
|
||||
}
|
||||
el.Items = append(el.Items, newEvents.Items...)
|
||||
@@ -254,7 +247,7 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
|
||||
hdr = getHeaders(showNs)
|
||||
firstIteration = false
|
||||
}
|
||||
return printers.TablePrinter(hdr).Print(rootCmd.OutOrStdout(), [][]string{rows})
|
||||
return printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
|
||||
}
|
||||
|
||||
for _, refOpts := range refListOpts {
|
||||
@@ -429,7 +422,7 @@ var fluxKindMap = refMap{
|
||||
gvk: helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind),
|
||||
crossNamespaced: true,
|
||||
otherRefs: func(namespace, name string) []string {
|
||||
return []string{fmt.Sprintf("%s/%s-%s", sourcev1.HelmChartKind, namespace, name)}
|
||||
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
|
||||
},
|
||||
field: []string{"spec", "chart", "spec", "sourceRef"},
|
||||
},
|
||||
@@ -447,18 +440,17 @@ var fluxKindMap = refMap{
|
||||
crossNamespaced: true,
|
||||
field: []string{"spec", "imageRepositoryRef"},
|
||||
},
|
||||
sourcev1.HelmChartKind: {
|
||||
gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind),
|
||||
sourcev1b2.HelmChartKind: {
|
||||
gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmChartKind),
|
||||
crossNamespaced: true,
|
||||
field: []string{"spec", "sourceRef"},
|
||||
},
|
||||
sourcev1.GitRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)},
|
||||
sourcev1.OCIRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)},
|
||||
sourcev1.BucketKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)},
|
||||
sourcev1.HelmRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)},
|
||||
sourcev1b2.OCIRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.OCIRepositoryKind)},
|
||||
sourcev1b2.BucketKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.BucketKind)},
|
||||
sourcev1b2.HelmRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmRepositoryKind)},
|
||||
autov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)},
|
||||
imagev1.ImageRepositoryKind: {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)},
|
||||
swapi.ArtifactGeneratorKind: {gvk: swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)},
|
||||
}
|
||||
|
||||
func ignoreEvent(e corev1.Event) bool {
|
||||
|
||||
@@ -20,13 +20,11 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -80,7 +78,7 @@ spec:
|
||||
timeout: 1m0s
|
||||
url: ssh://git@github.com/example/repo
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: podinfo
|
||||
@@ -97,7 +95,7 @@ spec:
|
||||
version: '*'
|
||||
interval: 5m0s
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: podinfo
|
||||
@@ -106,7 +104,7 @@ spec:
|
||||
interval: 1m0s
|
||||
url: https://stefanprodan.github.io/podinfo
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
name: default-podinfo
|
||||
@@ -142,7 +140,7 @@ spec:
|
||||
address: https://hooks.slack.com/services/mock
|
||||
type: slack
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: podinfo
|
||||
@@ -421,108 +419,6 @@ func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event
|
||||
}
|
||||
}
|
||||
|
||||
// paginatedClient wraps a client.Client and simulates real Kubernetes API
|
||||
// pagination by splitting List results into pages of pageSize items,
|
||||
// using the ListMeta.Continue token.
|
||||
type paginatedClient struct {
|
||||
client.Client
|
||||
pageSize int
|
||||
}
|
||||
|
||||
func (c *paginatedClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
|
||||
listOpts := &client.ListOptions{}
|
||||
listOpts.ApplyOptions(opts)
|
||||
|
||||
// Fetch all results from the underlying client (without Limit/Continue).
|
||||
stripped := make([]client.ListOption, 0, len(opts))
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(client.Limit); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := o.(client.Continue); ok {
|
||||
continue
|
||||
}
|
||||
stripped = append(stripped, o)
|
||||
}
|
||||
if err := c.Client.List(ctx, list, stripped...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine the page window based on the Continue token.
|
||||
start := 0
|
||||
if listOpts.Continue != "" {
|
||||
n, err := strconv.Atoi(listOpts.Continue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid continue token: %w", err)
|
||||
}
|
||||
start = n
|
||||
}
|
||||
if start > len(items) {
|
||||
start = len(items)
|
||||
}
|
||||
|
||||
end := start + c.pageSize
|
||||
if end > len(items) {
|
||||
end = len(items)
|
||||
}
|
||||
|
||||
page := items[start:end]
|
||||
if err := meta.SetList(list, page); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the Continue token when there are more pages.
|
||||
listAccessor, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if end < len(items) {
|
||||
listAccessor.SetContinue(strconv.Itoa(end))
|
||||
} else {
|
||||
listAccessor.SetContinue("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_addEventsToList_pagination(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
objs, err := ssautil.ReadObjects(strings.NewReader(objects))
|
||||
g.Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
builder := fake.NewClientBuilder().WithScheme(utils.NewScheme())
|
||||
for _, obj := range objs {
|
||||
builder = builder.WithObjects(obj)
|
||||
}
|
||||
|
||||
eventList := &corev1.EventList{}
|
||||
for _, obj := range objs {
|
||||
infoEvent := createEvent(obj, eventv1.EventSeverityInfo, "Info Message", "Info Reason")
|
||||
warningEvent := createEvent(obj, eventv1.EventSeverityError, "Error Message", "Error Reason")
|
||||
eventList.Items = append(eventList.Items, infoEvent, warningEvent)
|
||||
}
|
||||
builder = builder.WithLists(eventList)
|
||||
c := builder.Build()
|
||||
|
||||
totalEvents := len(eventList.Items)
|
||||
g.Expect(totalEvents).To(BeNumerically(">", 2), "need more than 2 events to test pagination")
|
||||
|
||||
// Wrap the client to paginate at 2 items per page, forcing multiple
|
||||
// round-trips through FollowContinue.
|
||||
pc := &paginatedClient{Client: c, pageSize: 2}
|
||||
|
||||
el := &corev1.EventList{}
|
||||
err = addEventsToList(context.Background(), pc, el, nil)
|
||||
g.Expect(err).To(Not(HaveOccurred()))
|
||||
g.Expect(el.Items).To(HaveLen(totalEvents),
|
||||
"addEventsToList should collect all events across paginated responses")
|
||||
}
|
||||
|
||||
func kindNameIndexer(obj client.Object) []string {
|
||||
e, ok := obj.(*corev1.Event)
|
||||
if !ok {
|
||||
|
||||
@@ -109,13 +109,13 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func printExport(export any) error {
|
||||
func printExport(export interface{}) error {
|
||||
data, err := yaml.Marshal(export)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printlnStdout("---")
|
||||
printlnStdout(resourceToString(data))
|
||||
rootCmd.Println("---")
|
||||
rootCmd.Println(resourceToString(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var exportArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Export artifact objects",
|
||||
Long: `The export artifact sub-commands export artifacts objects in YAML format.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportCmd.AddCommand(exportArtifactCmd)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
)
|
||||
|
||||
var exportArtifactGeneratorCmd = &cobra.Command{
|
||||
Use: "generator [name]",
|
||||
Short: "Export ArtifactGenerator resources in YAML format",
|
||||
Long: "The export artifact generator command exports one or all ArtifactGenerator resources in YAML format.",
|
||||
Example: ` # Export all ArtifactGenerator resources
|
||||
flux export artifact generator --all > artifact-generators.yaml
|
||||
|
||||
# Export a specific generator
|
||||
flux export artifact generator my-generator > my-generator.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),
|
||||
RunE: exportCommand{
|
||||
object: artifactGeneratorAdapter{&swapi.ArtifactGenerator{}},
|
||||
list: artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportArtifactCmd.AddCommand(exportArtifactGeneratorCmd)
|
||||
}
|
||||
|
||||
// Export returns an ArtifactGenerator value which has
|
||||
// extraneous information stripped out.
|
||||
func exportArtifactGenerator(item *swapi.ArtifactGenerator) interface{} {
|
||||
gvk := swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)
|
||||
export := swapi.ArtifactGenerator{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: gvk.Kind,
|
||||
APIVersion: gvk.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: item.Name,
|
||||
Namespace: item.Namespace,
|
||||
Labels: item.Labels,
|
||||
Annotations: item.Annotations,
|
||||
},
|
||||
Spec: item.Spec,
|
||||
}
|
||||
return export
|
||||
}
|
||||
|
||||
func (ex artifactGeneratorAdapter) export() interface{} {
|
||||
return exportArtifactGenerator(ex.ArtifactGenerator)
|
||||
}
|
||||
|
||||
func (ex artifactGeneratorListAdapter) exportItem(i int) interface{} {
|
||||
return exportArtifactGenerator(&ex.ArtifactGeneratorList.Items[i])
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
Copyright 2020 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.
|
||||
@@ -20,14 +20,14 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta2"
|
||||
)
|
||||
|
||||
var exportHelmReleaseCmd = &cobra.Command{
|
||||
Use: "helmrelease [name]",
|
||||
Aliases: []string{"hr"},
|
||||
Short: "Export HelmRelease resources in YAML format",
|
||||
Long: "The export helmrelease command exports one or all HelmRelease resources in YAML format.",
|
||||
Long: withPreviewNote("The export helmrelease command exports one or all HelmRelease resources in YAML format."),
|
||||
Example: ` # Export all HelmRelease resources
|
||||
flux export helmrelease --all > kustomizations.yaml
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var exportImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy [name]",
|
||||
Short: "Export ImagePolicy resources in YAML format",
|
||||
Long: "The export image policy command exports one or all ImagePolicy resources in YAML format.",
|
||||
Long: withPreviewNote("The export image policy command exports one or all ImagePolicy resources in YAML format."),
|
||||
Example: ` # Export all ImagePolicy resources
|
||||
flux export image policy --all > image-policies.yaml
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var exportImageRepositoryCmd = &cobra.Command{
|
||||
Use: "repository [name]",
|
||||
Short: "Export ImageRepository resources in YAML format",
|
||||
Long: "The export image repository command exports one or all ImageRepository resources in YAML format.",
|
||||
Long: withPreviewNote("The export image repository command exports one or all ImageRepository resources in YAML format."),
|
||||
Example: ` # Export all ImageRepository resources
|
||||
flux export image repository --all > image-repositories.yaml
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1"
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
)
|
||||
|
||||
var exportImageUpdateCmd = &cobra.Command{
|
||||
Use: "update [name]",
|
||||
Short: "Export ImageUpdateAutomation resources in YAML format",
|
||||
Long: "The export image update command exports one or all ImageUpdateAutomation resources in YAML format.",
|
||||
Long: withPreviewNote("The export image update command exports one or all ImageUpdateAutomation resources in YAML format."),
|
||||
Example: ` # Export all ImageUpdateAutomation resources
|
||||
flux export image update --all > updates.yaml
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var exportSourceBucketCmd = &cobra.Command{
|
||||
Use: "bucket [name]",
|
||||
Short: "Export Bucket sources in YAML format",
|
||||
Long: "The export source git command exports one or all Bucket sources in YAML format.",
|
||||
Long: withPreviewNote("The export source git command exports one or all Bucket sources in YAML format."),
|
||||
Example: ` # Export all Bucket sources
|
||||
flux export source bucket --all > sources.yaml
|
||||
|
||||
|
||||
@@ -1,67 +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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
var exportSourceChartCmd = &cobra.Command{
|
||||
Use: "chart [name]",
|
||||
Short: "Export HelmChart sources in YAML format",
|
||||
Long: withPreviewNote("The export source chart command exports one or all HelmChart sources in YAML format."),
|
||||
Example: ` # Export all chart sources
|
||||
flux export source chart --all > sources.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||
RunE: exportCommand{
|
||||
list: helmChartListAdapter{&sourcev1.HelmChartList{}},
|
||||
object: helmChartAdapter{&sourcev1.HelmChart{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportSourceCmd.AddCommand(exportSourceChartCmd)
|
||||
}
|
||||
|
||||
func exportHelmChart(source *sourcev1.HelmChart) interface{} {
|
||||
gvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)
|
||||
export := sourcev1.HelmChart{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: gvk.Kind,
|
||||
APIVersion: gvk.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: source.Name,
|
||||
Namespace: source.Namespace,
|
||||
Labels: source.Labels,
|
||||
Annotations: source.Annotations,
|
||||
},
|
||||
Spec: source.Spec,
|
||||
}
|
||||
return export
|
||||
}
|
||||
|
||||
func (ex helmChartAdapter) export() interface{} {
|
||||
return exportHelmChart(ex.HelmChart)
|
||||
}
|
||||
|
||||
func (ex helmChartListAdapter) exportItem(i int) interface{} {
|
||||
return exportHelmChart(&ex.HelmChartList.Items[i])
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
var exportSourceExternalCmd = &cobra.Command{
|
||||
Use: "external [name]",
|
||||
Short: "Export ExternalArtifact sources in YAML format",
|
||||
Long: "The export source external command exports one or all ExternalArtifact sources in YAML format.",
|
||||
Example: ` # Export all ExternalArtifact sources
|
||||
flux export source external --all > sources.yaml
|
||||
|
||||
# Export a specific ExternalArtifact
|
||||
flux export source external my-artifact > source.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),
|
||||
RunE: exportWithSecretCommand{
|
||||
list: externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},
|
||||
object: externalArtifactAdapter{&sourcev1.ExternalArtifact{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportSourceCmd.AddCommand(exportSourceExternalCmd)
|
||||
}
|
||||
|
||||
func exportExternalArtifact(source *sourcev1.ExternalArtifact) any {
|
||||
gvk := sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)
|
||||
export := sourcev1.ExternalArtifact{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: gvk.Kind,
|
||||
APIVersion: gvk.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: source.Name,
|
||||
Namespace: source.Namespace,
|
||||
Labels: source.Labels,
|
||||
Annotations: source.Annotations,
|
||||
},
|
||||
Spec: source.Spec,
|
||||
}
|
||||
return export
|
||||
}
|
||||
|
||||
func getExternalArtifactSecret(source *sourcev1.ExternalArtifact) *types.NamespacedName {
|
||||
// ExternalArtifact does not have a secretRef in its spec, this satisfies the interface
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ex externalArtifactAdapter) secret() *types.NamespacedName {
|
||||
return getExternalArtifactSecret(ex.ExternalArtifact)
|
||||
}
|
||||
|
||||
func (ex externalArtifactListAdapter) secretItem(i int) *types.NamespacedName {
|
||||
return getExternalArtifactSecret(&ex.ExternalArtifactList.Items[i])
|
||||
}
|
||||
|
||||
func (ex externalArtifactAdapter) export() any {
|
||||
return exportExternalArtifact(ex.ExternalArtifact)
|
||||
}
|
||||
|
||||
func (ex externalArtifactListAdapter) exportItem(i int) any {
|
||||
return exportExternalArtifact(&ex.ExternalArtifactList.Items[i])
|
||||
}
|
||||
@@ -21,13 +21,13 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var exportSourceHelmCmd = &cobra.Command{
|
||||
Use: "helm [name]",
|
||||
Short: "Export HelmRepository sources in YAML format",
|
||||
Long: "The export source git command exports one or all HelmRepository sources in YAML format.",
|
||||
Long: withPreviewNote("The export source git command exports one or all HelmRepository sources in YAML format."),
|
||||
Example: ` # Export all HelmRepository sources
|
||||
flux export source helm --all > sources.yaml
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
var exportSourceOCIRepositoryCmd = &cobra.Command{
|
||||
|
||||
@@ -1,22 +1,6 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -74,12 +58,6 @@ func TestExport(t *testing.T) {
|
||||
"testdata/export/git-repo.yaml",
|
||||
tmpl,
|
||||
},
|
||||
{
|
||||
"source chart",
|
||||
"export source chart flux-system",
|
||||
"testdata/export/helm-chart.yaml",
|
||||
tmpl,
|
||||
},
|
||||
{
|
||||
"source helm",
|
||||
"export source helm flux-system",
|
||||
@@ -110,12 +88,6 @@ func TestExport(t *testing.T) {
|
||||
"testdata/export/bucket.yaml",
|
||||
tmpl,
|
||||
},
|
||||
{
|
||||
"source external",
|
||||
"export source external flux-system",
|
||||
"testdata/export/external-artifact.yaml",
|
||||
tmpl,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
|
||||
@@ -184,13 +184,13 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if get.list.len() == 0 {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("%s object '%s' not found in %s namespace",
|
||||
logger.Failuref("%s object '%s' not found in %s namespace",
|
||||
get.kind,
|
||||
args[0],
|
||||
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
|
||||
)
|
||||
} else if !getAll {
|
||||
return fmt.Errorf("no %s objects found in %s namespace",
|
||||
logger.Failuref("no %s objects found in %s namespace",
|
||||
get.kind,
|
||||
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta2"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||
@@ -87,7 +87,7 @@ var getAllCmd = &cobra.Command{
|
||||
|
||||
func logError(err error) {
|
||||
if !apimeta.IsNoMatchError(err) {
|
||||
logger.Failuref("%s", err.Error())
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getArtifactCmd = &cobra.Command{
|
||||
Use: "artifacts",
|
||||
Aliases: []string{"artifact"},
|
||||
Short: "Get artifact object status",
|
||||
Long: `The get artifact sub-commands print the status of artifact objects.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
getCmd.AddCommand(getArtifactCmd)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user