1
0
mirror of synced 2026-03-01 11:16:56 +00:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Hidde Beydals
026ab61ba7 Merge pull request #4258 from fluxcd/backport-4228-to-release/v2.1.x
[release/v2.1.x] Improve AUR package templates
2023-09-19 13:08:23 +02:00
Son Bui
8da931aed4 Improve AUR package templates
- remove armv6h #4224
- unique source name #4224
- improve pkgver/_srcver #4224
- fix source name in .SRCINFO

Signed-off-by: Son Bui <sonbv00@gmail.com>
(cherry picked from commit f9e7190a04)
2023-09-19 10:47:55 +00:00
Hidde Beydals
24c0de031a Merge pull request #4257 from fluxcd/backport-4233-to-release/v2.1.x
[release/v2.1.x] chore: remove support armv6h for aur package
2023-09-19 12:44:22 +02:00
Son Bui
df60d40f91 chore: remove support armv6h for aur package #4224
Signed-off-by: Son Bui <sonbv00@gmail.com>
(cherry picked from commit bc90e7cf01)
2023-09-19 10:24:21 +00:00
Hidde Beydals
188ce1bad1 Merge pull request #4256 from fluxcd/backport-4255-to-release/v2.1.x
[release/v2.1.x] tests/azure: update controller dependencies
2023-09-19 12:03:16 +02:00
Hidde Beydals
88bad4b7ae tests/azure: update controller dependencies
- github.com/fluxcd/helm-controller/api to v0.36.1
- github.com/fluxcd/image-automation-controller/api to v0.36.1
- github.com/fluxcd/source-controller/api to v1.1.1

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
(cherry picked from commit 1cba3e4476)
2023-09-19 09:33:32 +00:00
Stefan Prodan
4fc2df7b57 Merge pull request #4254 from fluxcd/backport-4251-to-release/v2.1.x
[release/v2.1.x] Update toolkit components
2023-09-19 12:02:11 +03:00
fluxcdbot
0fd975bf66 Update toolkit components
- helm-controller to v0.36.1
  https://github.com/fluxcd/helm-controller/blob/v0.36.1/CHANGELOG.md
- source-controller to v1.1.1
  https://github.com/fluxcd/source-controller/blob/v1.1.1/CHANGELOG.md
- image-automation-controller to v0.36.1
  https://github.com/fluxcd/image-automation-controller/blob/v0.36.1/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
(cherry picked from commit 0c47d738a9)
2023-09-19 08:02:40 +00:00
Stefan Prodan
40992037da Merge pull request #4250 from fluxcd/backport-4226-to-release/v2.1.x
[release/v2.1.x] Update description of kubeconfig specific flag
2023-09-18 13:47:49 +03:00
Somtochi Onyekwere
f94acd5ef3 update description of kubeconfig flags
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
(cherry picked from commit 1654791feb)
2023-09-18 10:32:29 +00:00
Max Jonas Werner
c87f48fdea Merge pull request #4249 from fluxcd/backport-4238-to-release/v2.1.x
[release/v2.1.x] Upgrade github.com/fluxcd/pkg/{git,git/gogit}
2023-09-18 11:34:14 +02:00
Max Jonas Werner
68ff588910 Upgrade github.com/fluxcd/pkg/{git,git/gogit}
This allows us to get rid of the replace directive consuming the
filepath-securejoin fork.

Signed-off-by: Max Jonas Werner <mail@makk.es>
(cherry picked from commit c721474e0b)
2023-09-18 11:18:08 +02:00
Stefan Prodan
f420cfbf7c Merge pull request #4196 from fluxcd/backport-4195-to-release/v2.1.x
[release/v2.1.x] build(deps): bump the ci group with 2 updates
2023-08-28 11:56:17 +03:00
dependabot[bot]
f3e99a6cfa build(deps): bump the ci group with 2 updates
Bumps the ci group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator).

Updates `actions/checkout` from 3.5.3 to 3.6.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](c85c95e3d7...f43a0e5ff2)

Updates `slsa-framework/slsa-github-generator` from 1.8.0 to 1.9.0
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ci
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 29f77d2cb3)
2023-08-28 08:25:56 +00:00
164 changed files with 1521 additions and 7196 deletions

3
.github/labels.yaml vendored
View File

@@ -47,6 +47,3 @@
- name: backport:release/v2.0.x
description: To be backported to release/v2.0.x
color: '#ffd700'
- name: backport:release/v2.1.x
description: To be backported to release/v2.1.x
color: '#ffd700'

View File

@@ -24,6 +24,6 @@ jobs:
name: action on ${{ matrix.version }}
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup flux
uses: ./action

View File

@@ -13,11 +13,11 @@ jobs:
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@b982d297e31f500652b2246cf26714796312bd23 # v2.2.0
uses: korthout/backport-action@bd68141f079bd036e45ea8149bc9d174d5a04703 # v1.4.0
# xref: https://github.com/korthout/backport-action#inputs
with:
# Use token to allow workflows to be triggered for the created PR

View File

@@ -21,9 +21,9 @@ jobs:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: |

View File

@@ -3,7 +3,7 @@ name: e2e-azure
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *'
- cron: '0 6 * * *'
push:
branches:
- main
@@ -23,32 +23,31 @@ permissions:
jobs:
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.
# This job is currently disabled since if always evaluates to false. 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
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: tests/azure/go.sum
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Flux CLI
run: |
make build
mkdir -p $HOME/.local/bin
mv ./bin/flux $HOME/.local/bin
working-directory: ./
- name: Setup SOPS
run: |
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux
chmod +x sops-v3.7.1.linux
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
mv sops-v3.7.1.linux $HOME/.local/bin/sops
- name: Setup Terraform
uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v2
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2
with:
terraform_version: 1.2.8
terraform_wrapper: false
@@ -62,64 +61,9 @@ jobs:
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
run: |
echo $HOME
echo $PATH
ls $HOME/.local/bin
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
cd ./tests/azure
go test -v -coverprofile cover.out -timeout 60m .
refactored-e2e-amd64-aks:
runs-on: ubuntu-22.04
defaults:
run:
working-directory: ./tests/integration
# 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@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/integration/go.sum
- name: Setup Flux CLI
run: make build
working-directory: ./
- name: Setup SOPS
run: |
mkdir -p $HOME/.local/bin
wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux
chmod +x $HOME/.local/bin/sops
env:
SOPS_VER: 3.7.1
- name: Authenticate to Azure
uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.4.6
with:
creds: '{"clientId":"${{ secrets.AZ_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.AZ_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZ_ARM_TENANT_ID }}"}'
- name: Set dynamic variables in .env
run: |
cat > .env <<EOF
export TF_VAR_tags='{ "environment"="github", "ci"="true", "repo"="flux2", "createdat"="$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)" }'
EOF
- name: Print .env for dynamic tag value reference
run: cat .env
- name: Run Azure e2e tests
env:
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_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
touch ./build/ssh/key
echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key
export GITREPO_SSH_PATH=build/ssh/key
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

View File

@@ -17,9 +17,9 @@ jobs:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: |

View File

@@ -1,92 +0,0 @@
name: e2e-gcp
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *'
push:
branches:
- main
paths:
- 'tests/**'
- '.github/workflows/e2e-gcp.yaml'
pull_request:
branches:
- main
paths:
- 'tests/**'
- '.github/workflows/e2e-gcp.yaml'
permissions:
contents: read
jobs:
e2e-gcp:
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@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/integration/go.sum
- name: Setup Flux CLI
run: make build
working-directory: ./
- name: Setup SOPS
run: |
mkdir -p $HOME/.local/bin
wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux
chmod +x $HOME/.local/bin/sops
env:
SOPS_VER: 3.7.1
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@67e9c72af6e0492df856527b474995862b7b6591 # v2.0.0
id: 'auth'
with:
credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'
token_format: 'access_token'
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@825196879a077b7efa50db2e88409f44de4635c2 # v2.0.0
- name: Setup QEMU
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Log into us-central1-docker.pkg.dev
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: us-central1-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
- name: Set dynamic variables in .env
run: |
cat > .env <<EOF
export TF_VAR_tags='{ "environment"="github", "ci"="true", "repo"="flux2", "createdat"="$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)" }'
EOF
- name: Print .env for dynamic tag value reference
run: cat .env
- name: Run GCP e2e tests
env:
TF_VAR_gcp_project_id: ${{ vars.TF_VAR_gcp_project_id }}
TF_VAR_gcp_region: ${{ vars.TF_VAR_gcp_region }}
TF_VAR_gcp_zone: ${{ vars.TF_VAR_gcp_zone }}
TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }}
TF_VAR_gcp_keyring: ${{ secrets.TF_VAR_gcp_keyring }}
TF_VAR_gcp_crypto_key: ${{ secrets.TF_VAR_gcp_crypto_key }}
GITREPO_SSH_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_CONTENTS }}
GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_PUB_CONTENTS }}
run: |
source .env
mkdir -p ./build/ssh
touch ./build/ssh/key
echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key
export GITREPO_SSH_PATH=build/ssh/key
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-gcp

View File

@@ -21,9 +21,9 @@ jobs:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: |

View File

@@ -19,16 +19,16 @@ jobs:
actions: read
contents: read
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Run analysis
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
with:
results_file: results.sarif
results_format: sarif
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: SARIF file
path: results.sarif

View File

@@ -20,33 +20,33 @@ jobs:
packages: write # needed for ghcr access
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Unshallow
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache: false
- name: Setup QEMU
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1
- name: Setup Syft
uses: anchore/sbom-action/download-syft@5ecf649a417b8ae17dc8383dc32d46c03f2312df # v0.15.1
uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3
- name: Setup Cosign
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Login to GitHub Container Registry
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -79,7 +79,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
id: run-goreleaser
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
uses: goreleaser/goreleaser-action@3fa32b8bb5620a2c1afe798654bbad59f9da4906 # v4.4.0
with:
version: latest
args: release --release-notes=output/notes.md --skip-validate
@@ -110,7 +110,7 @@ jobs:
id-token: write
packages: write
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Flux CLI
@@ -121,13 +121,13 @@ jobs:
VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -155,7 +155,7 @@ jobs:
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
- uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1
- name: Sign manifests
env:
COSIGN_EXPERIMENTAL: 1

View File

@@ -17,9 +17,9 @@ jobs:
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0
uses: fossa-contrib/fossa-action@6728dc6fe9a068c648d080c33829ffbe56565023 # v2.0.0
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
@@ -31,11 +31,11 @@ jobs:
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
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: |
@@ -64,9 +64,9 @@ jobs:
if: github.actor != 'dependabot[bot]'
steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: |

View File

@@ -17,7 +17,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
with:
# Configuration file

View File

@@ -18,9 +18,9 @@ jobs:
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: 1.20.x
cache-dependency-path: |

View File

@@ -19,7 +19,6 @@ all: test build
tidy:
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 ./...

View File

@@ -5,7 +5,6 @@
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2/badge)](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://fluxcd.io/flux/security/slsa-assessment)
Flux is a tool for keeping Kubernetes clusters in sync with sources of
configuration (like Git repositories and OCI artifacts),
@@ -33,7 +32,7 @@ For more comprehensive documentation, see the following guides:
- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)
- [Manage Kubernetes secrets with Flux and SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
@@ -59,13 +58,13 @@ guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomization/)
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
- [Provider CRD](https://fluxcd.io/flux/components/notification/providers/)
- [Alert CRD](https://fluxcd.io/flux/components/notification/alerts/)
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receivers/)
- [Provider CRD](https://fluxcd.io/flux/components/notification/provider/)
- [Alert CRD](https://fluxcd.io/flux/components/notification/alert/)
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receiver/)
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)

View File

@@ -18,5 +18,5 @@ The Flux GitHub Action can be used to automate various tasks in CI, such as:
- [Push Kubernetes manifests to container registries](https://fluxcd.io/flux/flux-gh-action/#push-kubernetes-manifests-to-container-registries)
- [Run end-to-end testing with Flux and Kubernetes Kind](https://fluxcd.io/flux/flux-gh-action/#end-to-end-testing)
For more information, please see the [Flux GitHub Action documentation](https://fluxcd.io/flux/flux-gh-action/).
For more information, please see the [Flux GitHub Action documentation](/flux/flux-gh-action.md).

View File

@@ -19,7 +19,7 @@ package main
import (
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
// notificationv1.Alert

View File

@@ -19,7 +19,7 @@ package main
import (
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
// notificationv1.Provider

View File

@@ -17,15 +17,11 @@ limitations under the License.
package main
import (
"context"
"crypto/elliptic"
"fmt"
"strings"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
@@ -76,8 +72,6 @@ type bootstrapFlags struct {
gpgPassphrase string
gpgKeyID string
force bool
commitMessageAppendix string
}
@@ -135,7 +129,6 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a diffrent tool such as Helm")
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
rootCmd.AddCommand(bootstrapCmd)
@@ -195,27 +188,3 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
return m
}
// confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation.
// It returns a nil error if Flux is not installed or the user confirms overriding an existing installation
func confirmBootstrap(ctx context.Context, kubeClient client.Client) error {
installed := true
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("cluster info unavailable: %w", err)
}
installed = false
}
if installed {
err = confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("bootstrap cancelled")
}
return err
}
}
return nil
}

View File

@@ -56,7 +56,7 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a public repository on a personal account
flux bootstrap bitbucket-server --owner=<user> --repository=<repository name> --private=false --personal --hostname=<domain> --token-auth --path=clusters/my-cluster
# Run bootstrap for an existing repository with a branch named main
# Run bootstrap for a an existing repository with a branch named main
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth --path=clusters/my-cluster`,
RunE: bootstrapBServerCmdRun,
}
@@ -124,13 +124,6 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
return err
}
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err

View File

@@ -146,13 +146,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
return err
}
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err

View File

@@ -1,275 +0,0 @@
/*
Copyright 2023 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"
"time"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/bootstrap"
"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/v2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync"
)
var bootstrapGiteaCmd = &cobra.Command{
Use: "gitea",
Short: "Deploy Flux on a cluster connected to a Gitea repository",
Long: `The bootstrap gitea command creates the Gitea 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,
the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a Gitea personal access token and export it as an env var
export GITEA_TOKEN=<my-token>
# Run bootstrap for a private repository owned by a Gitea organization
flux bootstrap gitea --owner=<organization> --repository=<repository name> --path=clusters/my-cluster
# Run bootstrap for a private repository and assign organization teams to it
flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> --path=clusters/my-cluster
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level> --path=clusters/my-cluster
# Run bootstrap for a public repository on a personal account
flux bootstrap gitea --owner=<user> --repository=<repository name> --private=false --personal=true --path=clusters/my-cluster
# Run bootstrap for a private repository hosted on Gitea Enterprise using SSH auth
flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain> --path=clusters/my-cluster
# Run bootstrap for a private repository hosted on Gitea Enterprise using HTTPS auth
flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster
# Run bootstrap for an existing repository with a branch named main
flux bootstrap gitea --owner=<organization> --repository=<repository name> --branch=main --path=clusters/my-cluster`,
RunE: bootstrapGiteaCmdRun,
}
type giteaFlags struct {
owner string
repository string
interval time.Duration
personal bool
private bool
hostname string
path flags.SafeRelativePath
teams []string
readWriteKey bool
reconcile bool
}
const (
gtDefaultPermission = "maintain"
gtDefaultDomain = "gitea.com"
gtTokenEnvVar = "GITEA_TOKEN"
)
var giteaArgs giteaFlags
func init() {
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.owner, "owner", "", "Gitea user or organization name")
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.repository, "repository", "", "Gitea repository name")
bootstrapGiteaCmd.Flags().StringSliceVar(&giteaArgs.teams, "team", []string{}, "Gitea team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)")
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.personal, "personal", false, "if true, the owner is assumed to be a Gitea user; otherwise an org")
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.private, "private", true, "if true, the repository is setup or configured as private")
bootstrapGiteaCmd.Flags().DurationVar(&giteaArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.hostname, "hostname", gtDefaultDomain, "Gitea hostname")
bootstrapGiteaCmd.Flags().Var(&giteaArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
bootstrapCmd.AddCommand(bootstrapGiteaCmd)
}
func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
gtToken := os.Getenv(gtTokenEnvVar)
if gtToken == "" {
var err error
gtToken, err = readPasswordFromStdin("Please enter your Gitea personal access token (PAT): ")
if err != nil {
return fmt.Errorf("could not read token: %w", err)
}
}
if err := bootstrapValidate(); err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err
} else {
bootstrapArgs.version = ver
}
manifestsBase, err := buildEmbeddedManifestBase()
if err != nil {
return err
}
defer os.RemoveAll(manifestsBase)
var caBundle []byte
if bootstrapArgs.caFile != "" {
var err error
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
// Build Gitea provider
providerCfg := provider.Config{
Provider: provider.GitProviderGitea,
Hostname: giteaArgs.hostname,
Token: gtToken,
CaBundle: caBundle,
}
providerClient, err := provider.BuildGitProvider(providerCfg)
if err != nil {
return err
}
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
defer os.RemoveAll(tmpDir)
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
Transport: git.HTTPS,
Username: giteaArgs.owner,
Password: gtToken,
CAFile: caBundle,
}, clientOpts...)
if err != nil {
return fmt.Errorf("failed to create a Git client: %w", err)
}
// Install manifest config
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: *kubeconfigArgs.Namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
NetworkPolicy: bootstrapArgs.networkPolicy,
LogLevel: bootstrapArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: rootArgs.defaults.ManifestFile,
Timeout: rootArgs.timeout,
TargetPath: giteaArgs.path.ToSlash(),
ClusterDomain: bootstrapArgs.clusterDomain,
TolerationKeys: bootstrapArgs.tolerationKeys,
}
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
installOptions.BaseURL = customBaseURL
}
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: *kubeconfigArgs.Namespace,
TargetPath: giteaArgs.path.ToSlash(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
if bootstrapArgs.tokenAuth {
secretOpts.Username = "git"
secretOpts.Password = gtToken
secretOpts.CAFile = caBundle
} else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = giteaArgs.hostname
if bootstrapArgs.sshHostname != "" {
secretOpts.SSHHostname = bootstrapArgs.sshHostname
}
}
// Sync manifest config
syncOpts := sync.Options{
Interval: giteaArgs.interval,
Name: *kubeconfigArgs.Namespace,
Namespace: *kubeconfigArgs.Namespace,
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
TargetPath: giteaArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
}
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
if err != nil {
return err
}
// Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(giteaArgs.owner, giteaArgs.repository, giteaArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(giteaArgs.teams, gtDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(giteaArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
}
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
if bootstrapArgs.tokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
}
if !giteaArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
if giteaArgs.reconcile {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
}
// Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil {
return err
}
// Run
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
}

View File

@@ -128,13 +128,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return err
}
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err

View File

@@ -64,7 +64,7 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a private repository hosted on a GitLab server
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for an existing repository with a branch named main
# Run bootstrap for a an existing repository with a branch named main
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth
# Run bootstrap for a private repository using Deploy Token authentication
@@ -145,13 +145,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err
}
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err

View File

@@ -89,7 +89,7 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
ociClient := oci.NewClient(oci.DefaultOptions())
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {
return fmt.Errorf("building artifact failed, error: %w", err)
return fmt.Errorf("bulding artifact failed, error: %w", err)
}
logger.Successf("artifact created at %s", buildArtifactArgs.output)

View File

@@ -18,7 +18,6 @@ package main
import (
"context"
"fmt"
"os"
"time"
@@ -27,7 +26,6 @@ import (
v1 "k8s.io/api/apps/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/version"
@@ -82,20 +80,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
fluxCheck()
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
if err != nil {
return fmt.Errorf("Kubernetes client initialization failed: %s", err.Error())
}
kubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()})
if err != nil {
return err
}
if !kubernetesCheck(cfg, kubernetesConstraints) {
if !kubernetesCheck(kubernetesConstraints) {
checkFailed = true
}
@@ -107,18 +92,13 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
return nil
}
logger.Actionf("checking version in cluster")
if !fluxClusterVersionCheck(ctx, kubeClient) {
checkFailed = true
}
logger.Actionf("checking controllers")
if !componentsCheck(ctx, kubeClient) {
if !componentsCheck() {
checkFailed = true
}
logger.Actionf("checking crds")
if !crdsCheck(ctx, kubeClient) {
if !crdsCheck() {
checkFailed = true
}
@@ -149,11 +129,17 @@ func fluxCheck() {
return
}
if latestSv.GreaterThan(curSv) {
logger.Failuref("flux %s <%s (new CLI version is available, please upgrade)", curSv, latestSv)
logger.Failuref("flux %s <%s (new version is available, please upgrade)", curSv, latestSv)
}
}
func kubernetesCheck(cfg *rest.Config, constraints []string) bool {
func kubernetesCheck(constraints []string) bool {
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
return false
}
clientSet, err := kubernetes.NewForConfig(cfg)
if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
@@ -192,8 +178,21 @@ func kubernetesCheck(cfg *rest.Config, constraints []string) bool {
return true
}
func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
statusChecker, err := status.NewStatusCheckerWithClient(kubeClient, checkArgs.pollInterval, rootArgs.timeout, logger)
func componentsCheck() bool {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
if err != nil {
return false
}
statusChecker, err := status.NewStatusChecker(kubeConfig, checkArgs.pollInterval, rootArgs.timeout, logger)
if err != nil {
return false
}
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return false
}
@@ -223,7 +222,15 @@ func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
return ok
}
func crdsCheck(ctx context.Context, kubeClient client.Client) bool {
func crdsCheck() bool {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return false
}
ok := true
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
var list apiextensionsv1.CustomResourceDefinitionList
@@ -246,17 +253,3 @@ func crdsCheck(ctx context.Context, kubeClient client.Client) bool {
}
return ok
}
func fluxClusterVersionCheck(ctx context.Context, kubeClient client.Client) bool {
clusterInfo, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
logger.Failuref("checking failed: %s", err.Error())
return false
}
if clusterInfo.distribution() != "" {
logger.Successf("distribution: %s", clusterInfo.distribution())
}
logger.Successf("bootstrapped: %t", clusterInfo.bootstrapped)
return true
}

View File

@@ -1,126 +0,0 @@
/*
Copyright 2023 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/manifoldco/promptui"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/fluxcd/flux2/v2/pkg/manifestgen"
)
// bootstrapLabels are labels put on a resource by kustomize-controller. These labels on the CRD indicates
// that flux has been bootstrapped.
var bootstrapLabels = []string{
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group),
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group),
}
// fluxClusterInfo contains information about an existing flux installation on a cluster.
type fluxClusterInfo struct {
// bootstrapped indicates that Flux was installed using the `flux bootstrap` command.
bootstrapped bool
// managedBy is the name of the tool being used to manage the installation of Flux.
managedBy string
// partOf indicates which distribution the instance is a part of.
partOf string
// version is the Flux version number in semver format.
version string
}
// getFluxClusterInfo returns information on the Flux installation running on the cluster.
// If an error occurred, the returned error will be non-nil.
//
// This function retrieves the GitRepository CRD from the cluster and checks it
// for a set of labels used to determine the Flux version and how Flux was installed.
// It returns the NotFound error from the underlying library if it was unable to find
// the GitRepository CRD and this can be used to check if Flux is installed.
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, error) {
var info fluxClusterInfo
crdMetadata := &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
Kind: "CustomResourceDefinition",
},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("gitrepositories.%s", sourcev1.GroupVersion.Group),
},
}
if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil {
return info, err
}
info.version = crdMetadata.Labels[manifestgen.VersionLabelKey]
var present bool
for _, l := range bootstrapLabels {
_, present = crdMetadata.Labels[l]
}
if present {
info.bootstrapped = true
}
// the `app.kubernetes.io/managed-by` label is not set by flux but might be set by other
// tools used to install Flux e.g Helm.
if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok {
info.managedBy = manager
}
if partOf, ok := crdMetadata.Labels[manifestgen.PartOfLabelKey]; ok {
info.partOf = partOf
}
return info, nil
}
// confirmFluxInstallOverride displays a prompt to the user so that they can confirm before overriding
// a Flux installation. It returns nil if the installation should continue,
// promptui.ErrAbort if the user doesn't confirm, or an error encountered.
func confirmFluxInstallOverride(info fluxClusterInfo) error {
// no need to display prompt if installation is managed by Flux
if installManagedByFlux(info.managedBy) {
return nil
}
display := fmt.Sprintf("Flux %s has been installed on this cluster with %s!", info.version, info.managedBy)
fmt.Fprintln(rootCmd.ErrOrStderr(), display)
prompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to override the %s installation? Y/N", info.managedBy),
IsConfirm: true,
}
_, err := prompt.Run()
return err
}
func (info fluxClusterInfo) distribution() string {
distribution := info.version
if info.partOf != "" {
distribution = fmt.Sprintf("%s-%s", info.partOf, info.version)
}
return distribution
}
func installManagedByFlux(manager string) bool {
return manager == "" || manager == "flux"
}

View File

@@ -1,141 +0,0 @@
/*
Copyright 2023 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"
"testing"
. "github.com/onsi/gomega"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/pkg/ssa"
)
func Test_getFluxClusterInfo(t *testing.T) {
g := NewWithT(t)
f, err := os.Open("./testdata/cluster_info/gitrepositories.yaml")
g.Expect(err).To(BeNil())
objs, err := ssa.ReadObjects(f)
g.Expect(err).To(Not(HaveOccurred()))
gitrepo := objs[0]
tests := []struct {
name string
labels map[string]string
wantErr bool
wantInfo fluxClusterInfo
}{
{
name: "no git repository CRD present",
wantErr: true,
},
{
name: "CRD with kustomize-controller labels",
labels: map[string]string{
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system",
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
"app.kubernetes.io/version": "v2.1.0",
},
wantInfo: fluxClusterInfo{
version: "v2.1.0",
bootstrapped: true,
},
},
{
name: "CRD with kustomize-controller labels and managed-by label",
labels: map[string]string{
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system",
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
"app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/managed-by": "flux",
},
wantInfo: fluxClusterInfo{
version: "v2.1.0",
bootstrapped: true,
managedBy: "flux",
},
},
{
name: "CRD with only managed-by label",
labels: map[string]string{
"app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/managed-by": "helm",
},
wantInfo: fluxClusterInfo{
version: "v2.1.0",
managedBy: "helm",
},
},
{
name: "CRD with no labels",
labels: map[string]string{},
wantInfo: fluxClusterInfo{},
},
{
name: "CRD with only version label",
labels: map[string]string{
"app.kubernetes.io/version": "v2.1.0",
},
wantInfo: fluxClusterInfo{
version: "v2.1.0",
},
},
{
name: "CRD with version and part-of labels",
labels: map[string]string{
"app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/part-of": "flux",
},
wantInfo: fluxClusterInfo{
version: "v2.1.0",
partOf: "flux",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
newscheme := runtime.NewScheme()
apiextensionsv1.AddToScheme(newscheme)
builder := fake.NewClientBuilder().WithScheme(newscheme)
if tt.labels != nil {
gitrepo.SetLabels(tt.labels)
builder = builder.WithRuntimeObjects(gitrepo)
}
client := builder.Build()
info, err := getFluxClusterInfo(context.Background(), client)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
g.Expect(errors.IsNotFound(err)).To(BeTrue())
} else {
g.Expect(err).To(Not(HaveOccurred()))
}
g.Expect(info).To(BeEquivalentTo(tt.wantInfo))
})
}
}

View File

@@ -131,8 +131,8 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
}
logger.Waitingf("waiting for %s reconciliation", names.kind)
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, object.asClientObject())); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReady(ctx, kubeClient, namespacedName, object)); err != nil {
return err
}
logger.Successf("%s reconciliation completed", names.kind)
@@ -165,6 +165,6 @@ func parseLabels() (map[string]string, error) {
}
func validateObjectName(name string) bool {
r := regexp.MustCompile(`^[a-z0-9]([a-z0-9\-]){0,61}[a-z0-9]$`)
r := regexp.MustCompile("^[a-z0-9]([a-z0-9\\-]){0,61}[a-z0-9]$")
return r.MatchString(name)
}

View File

@@ -22,13 +22,14 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils"
@@ -96,13 +97,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating Alert")
}
alert := notificationv1b3.Alert{
alert := notificationv1b2.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Labels: sourceLabels,
},
Spec: notificationv1b3.AlertSpec{
Spec: notificationv1b2.AlertSpec{
ProviderRef: meta.LocalObjectReference{
Name: alertArgs.providerRef,
},
@@ -131,8 +132,8 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isStaticObjectReadyConditionFunc(kubeClient, namespacedName, &alert)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert %s is ready", name)
@@ -140,13 +141,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
}
func upsertAlert(ctx context.Context, kubeClient client.Client,
alert *notificationv1b3.Alert) (types.NamespacedName, error) {
alert *notificationv1b2.Alert) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: alert.GetNamespace(),
Name: alert.GetName(),
}
var existing notificationv1b3.Alert
var existing notificationv1b2.Alert
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
@@ -169,3 +170,23 @@ func upsertAlert(ctx context.Context, kubeClient client.Client,
logger.Successf("Alert updated")
return namespacedName, nil
}
func isAlertReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1b2.Alert) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil {
return false, err
}
if c := apimeta.FindStatusCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -22,12 +22,13 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils"
@@ -126,8 +127,8 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for Provider reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isStaticObjectReadyConditionFunc(kubeClient, namespacedName, &provider)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
return err
}
@@ -166,3 +167,23 @@ func upsertAlertProvider(ctx context.Context, kubeClient client.Client,
logger.Successf("Provider updated")
return namespacedName, nil
}
func isAlertProviderReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, provider)
if err != nil {
return false, err
}
if c := apimeta.FindStatusCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -32,6 +32,7 @@ import (
"github.com/spf13/cobra"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
@@ -302,8 +303,8 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, &helmRelease)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
return err
}
logger.Successf("HelmRelease %s is ready", name)
@@ -343,6 +344,23 @@ func upsertHelmRelease(ctx context.Context, kubeClient client.Client,
return namespacedName, nil
}
func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if helmRelease.Generation != helmRelease.Status.ObservedGeneration {
return false, nil
}
return apimeta.IsStatusConditionTrue(helmRelease.Status.Conditions, meta.ReadyCondition), nil
}
}
func validateStrategy(input string) bool {
allowedStrategy := []string{"Revision", "ChartVersion"}

View File

@@ -54,12 +54,13 @@ the status of the object.`),
RunE: createImagePolicyRun}
type imagePolicyFlags struct {
imageRef string
semver string
alpha string
numeric string
filterRegex string
filterExtract string
imageRef string
semver string
alpha string
numeric string
filterRegex string
filterExtract string
filterNumerical string
}
var imagePolicyArgs = imagePolicyFlags{}
@@ -182,6 +183,7 @@ func validateExtractStr(template string, capNames []string) error {
name, num, rest, ok := extract(template)
if !ok {
// Malformed extract string, assume user didn't want this
template = template[1:]
return fmt.Errorf("--filter-extract is malformed")
}
template = rest

View File

@@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
@@ -262,8 +263,8 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, &kustomization)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err
}
logger.Successf("Kustomization %s is ready", name)
@@ -302,3 +303,28 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client,
logger.Successf("Kustomization updated")
return namespacedName, nil
}
func isKustomizationReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if kustomization.Generation != kustomization.Status.ObservedGeneration {
return false, nil
}
if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
@@ -138,8 +139,8 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, &receiver)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver %s is ready", name)
@@ -178,3 +179,23 @@ func upsertReceiver(ctx context.Context, kubeClient client.Client,
logger.Successf("Receiver updated")
return namespacedName, nil
}
func isReceiverReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, receiver)
if err != nil {
return false, err
}
if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -31,6 +31,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -203,8 +204,8 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for Bucket source reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, bucket)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil {
return err
}
logger.Successf("Bucket source reconciliation completed")
@@ -246,3 +247,30 @@ func upsertBucket(ctx context.Context, kubeClient client.Client,
logger.Successf("Bucket source updated")
return namespacedName, nil
}
func isBucketReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, bucket)
if err != nil {
return false, err
}
if c := conditions.Get(bucket, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != bucket.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -35,6 +35,7 @@ import (
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
@@ -324,8 +325,8 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for GitRepository source reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, &gitRepository)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
return err
}
logger.Successf("GitRepository source reconciliation completed")
@@ -367,3 +368,30 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client,
logger.Successf("GitRepository source updated")
return namespacedName, nil
}
func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, gitRepository *sourcev1.GitRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, gitRepository)
if err != nil {
return false, err
}
if c := conditions.Get(gitRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != gitRepository.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -181,21 +181,12 @@ func TestCreateSourceGit(t *testing.T) {
Time: time.Now(),
},
}
repo.Status.ObservedGeneration = repo.GetGeneration()
},
}, {
"Failed",
command,
assertError("failed message"),
func(repo *sourcev1.GitRepository) {
stalledCondition := metav1.Condition{
Type: meta.StalledCondition,
Status: metav1.ConditionTrue,
Reason: sourcev1.URLInvalidReason,
Message: "failed message",
ObservedGeneration: repo.GetGeneration(),
}
apimeta.SetStatusCondition(&repo.Status.Conditions, stalledCondition)
newCondition := metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionFalse,
@@ -204,7 +195,6 @@ func TestCreateSourceGit(t *testing.T) {
ObservedGeneration: repo.GetGeneration(),
}
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
repo.Status.ObservedGeneration = repo.GetGeneration()
},
}, {
"NoArtifact",
@@ -220,7 +210,6 @@ func TestCreateSourceGit(t *testing.T) {
ObservedGeneration: repo.GetGeneration(),
}
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
repo.Status.ObservedGeneration = repo.GetGeneration()
},
},
}

View File

@@ -23,6 +23,7 @@ import (
"os"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@@ -230,12 +231,8 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for HelmRepository source reconciliation")
readyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository)
if helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
// HelmRepository type OCI is a static object.
readyConditionFunc = isStaticObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository)
}
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil {
return err
}
logger.Successf("HelmRepository source reconciliation completed")
@@ -282,3 +279,30 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client,
logger.Successf("source updated")
return namespacedName, nil
}
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, helmRepository)
if err != nil {
return false, err
}
if c := conditions.Get(helmRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != helmRepository.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -29,6 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -50,18 +51,16 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
}
type sourceOCIRepositoryFlags struct {
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
url string
tag string
semver string
digest string
secretRef string
serviceAccount string
certSecretRef string
ignorePaths []string
provider flags.SourceOCIProvider
insecure bool
}
var sourceOCIRepositoryArgs = newSourceOCIFlags()
@@ -81,8 +80,6 @@ func init() {
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
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().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")
@@ -159,19 +156,6 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
}
}
if provider := sourceOCIRepositoryArgs.verifyProvider.String(); provider != "" {
repository.Spec.Verify = &sourcev1.OCIRepositoryVerification{
Provider: provider,
}
if secretName := sourceOCIRepositoryArgs.verifySecretRef; secretName != "" {
repository.Spec.Verify.SecretRef = &meta.LocalObjectReference{
Name: secretName,
}
}
} else if sourceOCIRepositoryArgs.verifySecretRef != "" {
return fmt.Errorf("a verification provider must be specified when a secret is specified")
}
if createArgs.export {
return printExport(exportOCIRepository(repository))
}
@@ -191,8 +175,8 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Waitingf("waiting for OCIRepository reconciliation")
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
isObjectReadyConditionFunc(kubeClient, namespacedName, repository)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
return err
}
logger.Successf("OCIRepository reconciliation completed")
@@ -234,3 +218,30 @@ func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
logger.Successf("OCIRepository updated")
return namespacedName, nil
}
func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, ociRepository)
if err != nil {
return false, err
}
if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != ociRepository.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -36,11 +36,6 @@ func TestCreateSourceOCI(t *testing.T) {
args: "create source oci podinfo",
assertFunc: assertError("url is required"),
},
{
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: "export manifest",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export",
@@ -51,11 +46,6 @@ func TestCreateSourceOCI(t *testing.T) {
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
},
{
name: "export manifest with verify secret",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --verify-provider=cosign --verify-secret-ref=cosign-pub --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_verify_secret.golden"),
},
}
for _, tt := range tests {

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var deleteAlertCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var deleteAlertProviderCmd = &cobra.Command{

View File

@@ -44,7 +44,7 @@ import (
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"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -62,14 +62,8 @@ var eventsCmd = &cobra.Command{
# Display events for flux resources in all namespaces
flux events -A
# Display events for a Kustomization named podinfo
# Display events for flux resources
flux events --for Kustomization/podinfo
# Display events for all Kustomizations in default namespace
flux events --for Kustomization -n default
# Display warning events for alert resources
flux events --for Alert/podinfo --types warning
`,
RunE: eventsCmdRun,
}
@@ -90,7 +84,7 @@ func init() {
"indicate if the events should be streamed")
eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "",
"get events for a particular object")
eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types (valid types are: Normal, Warning)")
eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types")
rootCmd.AddCommand(eventsCmd)
}
@@ -98,10 +92,6 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
if err := validateEventTypes(eventArgs.filterTypes); err != nil {
return err
}
kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
@@ -113,33 +103,21 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
}
var diffRefNs bool
clientListOpts := []client.ListOption{client.InNamespace(*kubeconfigArgs.Namespace)}
clientListOpts := getListOpt(namespace, eventArgs.forSelector)
var refListOpts [][]client.ListOption
if eventArgs.forSelector != "" {
kind, name := getKindNameFromSelector(eventArgs.forSelector)
if kind == "" {
return fmt.Errorf("--for selector must be of format <kind>[/<name>]")
}
refInfoKind, err := fluxKindMap.getRefInfo(kind)
refs, err := getObjectRef(ctx, kubeclient, eventArgs.forSelector, *kubeconfigArgs.Namespace)
if err != nil {
return err
}
clientListOpts = append(clientListOpts, getListOpt(refInfoKind.gvk.Kind, name))
if name != "" {
refs, err := getObjectRef(ctx, kubeclient, refInfoKind, name, *kubeconfigArgs.Namespace)
if err != nil {
return err
}
for _, ref := range refs {
refKind, refName, refNs := utils.ParseObjectKindNameNamespace(ref)
if refNs != namespace {
diffRefNs = true
}
refOpt := []client.ListOption{getListOpt(refKind, refName), client.InNamespace(refNs)}
refListOpts = append(refListOpts, refOpt)
for _, ref := range refs {
kind, name, refNs := utils.ParseObjectKindNameNamespace(ref)
if refNs != namespace {
diffRefNs = true
}
refSelector := fmt.Sprintf("%s/%s", kind, name)
refListOpts = append(refListOpts, getListOpt(refNs, refSelector))
}
}
@@ -162,7 +140,8 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
headers := getHeaders(showNamespace)
return printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows)
err = printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows)
return err
}
func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) ([][]string, error) {
@@ -192,11 +171,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{}
if err := kubeclient.List(ctx, newEvents, clientListOpts...); err != nil {
err := kubeclient.List(ctx, newEvents, clientListOpts...)
if err != nil {
return nil, fmt.Errorf("error getting events: %w", err)
}
el.Items = append(el.Items, newEvents.Items...)
@@ -206,22 +185,21 @@ func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.E
return err
}
func getListOpt(kind, name string) client.ListOption {
var sel fields.Selector
if name == "" {
sel = fields.OneTermEqualSelector("involvedObject.kind", kind)
} else {
sel = fields.AndSelectors(
func getListOpt(namespace, selector string) []client.ListOption {
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
if selector != "" {
kind, name := utils.ParseObjectKindName(selector)
sel := fields.AndSelectors(
fields.OneTermEqualSelector("involvedObject.kind", kind),
fields.OneTermEqualSelector("involvedObject.name", name))
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
}
return client.MatchingFieldsSelector{Selector: sel}
return clientListOpts
}
func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error {
event := &corev1.EventList{}
listOpts = append(listOpts, client.Limit(cmdutil.DefaultChunkSize))
eventWatch, err := kubeclient.Watch(ctx, event, listOpts...)
if err != nil {
return err
@@ -247,7 +225,12 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
hdr = getHeaders(showNs)
firstIteration = false
}
return printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
err = printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
if err != nil {
return err
}
return nil
}
for _, refOpts := range refListOpts {
@@ -256,7 +239,8 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
return err
}
go func() {
if err := receiveEventChan(ctx, refEventWatch, handleEvent); err != nil {
err := receiveEventChan(ctx, refEventWatch, handleEvent)
if err != nil {
logger.Failuref("error watching events: %s", err.Error())
}
}()
@@ -305,7 +289,13 @@ func getEventRow(e corev1.Event, showNs bool) []string {
// getObjectRef is used to get the metadata of a resource that the selector(in the format <kind/name>) references.
// It returns an empty string if the resource doesn't reference any resource
// and a string with the format `<kind>/<name>.<namespace>` if it does.
func getObjectRef(ctx context.Context, kubeclient client.Client, ref refInfo, name, ns string) ([]string, error) {
func getObjectRef(ctx context.Context, kubeclient client.Client, selector string, ns string) ([]string, error) {
kind, name := utils.ParseObjectKindName(selector)
ref, err := fluxKindMap.getRefInfo(kind)
if err != nil {
return nil, fmt.Errorf("error getting groupversion: %w", err)
}
// the resource has no source ref
if len(ref.field) == 0 {
return nil, nil
@@ -313,30 +303,31 @@ func getObjectRef(ctx context.Context, kubeclient client.Client, ref refInfo, na
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Kind: ref.gvk.Kind,
Version: ref.gvk.Version,
Group: ref.gvk.Group,
Kind: kind,
Version: ref.gv.Version,
Group: ref.gv.Group,
})
objName := types.NamespacedName{
Namespace: ns,
Name: name,
}
if err := kubeclient.Get(ctx, objName, obj); err != nil {
err = kubeclient.Get(ctx, objName, obj)
if err != nil {
return nil, err
}
var ok bool
refKind := ref.kind
if refKind == "" {
kindField := append(ref.field, "kind")
specKind, ok, err := unstructured.NestedString(obj.Object, kindField...)
refKind, ok, err = unstructured.NestedString(obj.Object, kindField...)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName)
}
refKind = specKind
}
nameField := append(ref.field, "name")
@@ -386,71 +377,49 @@ func (r refMap) hasKind(kind string) bool {
return err == nil
}
// validateEventTypes checks that the event types passed into the function
// is either equal to `Normal` or `Warning` which are currently the two supported types.
// https://github.com/kubernetes/kubernetes/blob/a8a1abc25cad87333840cd7d54be2efaf31a3177/staging/src/k8s.io/api/core/v1/types.go#L6212
func validateEventTypes(eventTypes []string) error {
for _, t := range eventTypes {
if !strings.EqualFold(corev1.EventTypeWarning, t) && !strings.EqualFold(corev1.EventTypeNormal, t) {
return fmt.Errorf("type '%s' not supported. Supported types are Normal, Warning", t)
}
}
return nil
}
type refInfo struct {
// gvk is the group version kind of the resource
gvk schema.GroupVersionKind
// kind is the kind that the resource references if it's not static
kind string
// crossNamespaced indicates if this resource uses cross namespaced references
gv schema.GroupVersion
kind string
crossNamespaced bool
// otherRefs returns other reference that might not be directly accessible
// from the spec of the object
otherRefs func(namespace, name string) []string
field []string
otherRefs func(namespace, name string) []string
field []string
}
var fluxKindMap = refMap{
kustomizev1.KustomizationKind: {
gvk: kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind),
gv: kustomizev1.GroupVersion,
crossNamespaced: true,
field: []string{"spec", "sourceRef"},
},
helmv2.HelmReleaseKind: {
gvk: helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind),
gv: helmv2.GroupVersion,
crossNamespaced: true,
otherRefs: func(namespace, name string) []string {
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
},
field: []string{"spec", "chart", "spec", "sourceRef"},
},
notificationv1b3.AlertKind: {
gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.AlertKind),
kind: notificationv1b3.ProviderKind,
notificationv1b2.AlertKind: {
gv: notificationv1b2.GroupVersion,
kind: notificationv1b2.ProviderKind,
crossNamespaced: false,
field: []string{"spec", "providerRef"},
},
notificationv1.ReceiverKind: {gvk: notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)},
notificationv1b3.ProviderKind: {gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.ProviderKind)},
notificationv1.ReceiverKind: {gv: notificationv1.GroupVersion},
notificationv1b2.ProviderKind: {gv: notificationv1b2.GroupVersion},
imagev1.ImagePolicyKind: {
gvk: imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind),
gv: imagev1.GroupVersion,
kind: imagev1.ImageRepositoryKind,
crossNamespaced: true,
field: []string{"spec", "imageRepositoryRef"},
},
sourcev1b2.HelmChartKind: {
gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmChartKind),
crossNamespaced: true,
field: []string{"spec", "sourceRef"},
},
sourcev1.GitRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)},
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)},
sourcev1.GitRepositoryKind: {gv: sourcev1.GroupVersion},
sourcev1b2.OCIRepositoryKind: {gv: sourcev1b2.GroupVersion},
sourcev1b2.BucketKind: {gv: sourcev1b2.GroupVersion},
sourcev1b2.HelmRepositoryKind: {gv: sourcev1b2.GroupVersion},
sourcev1b2.HelmChartKind: {gv: sourcev1b2.GroupVersion},
autov1.ImageUpdateAutomationKind: {gv: autov1.GroupVersion},
imagev1.ImageRepositoryKind: {gv: imagev1.GroupVersion},
}
func ignoreEvent(e corev1.Event) bool {
@@ -468,19 +437,7 @@ func ignoreEvent(e corev1.Event) bool {
return false
}
func getKindNameFromSelector(selector string) (string, string) {
kind, name := utils.ParseObjectKindName(selector)
// if there's no slash in the selector utils.ParseObjectKindName returns the
// input string as the name but here we want it as the kind instead
if kind == "" && name != "" {
kind = name
name = ""
}
return kind, name
}
// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/4ecd7bd0f0799f191335a331ca3c6a397a888233/pkg/cmd/events/events.go#L294
// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/events/events.go#L347
// SortableEvents implements sort.Interface for []api.Event by time
type SortableEvents []corev1.Event

View File

@@ -27,11 +27,21 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
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"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/ssa"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
)
@@ -118,7 +128,7 @@ spec:
name: podinfo-chart
version: '*'
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: webapp
@@ -131,7 +141,7 @@ spec:
providerRef:
name: slack
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
name: slack
@@ -163,7 +173,7 @@ func Test_getObjectRef(t *testing.T) {
objs, err := ssa.ReadObjects(strings.NewReader(objects))
g.Expect(err).To(Not(HaveOccurred()))
builder := fake.NewClientBuilder().WithScheme(utils.NewScheme())
builder := fake.NewClientBuilder().WithScheme(getScheme())
for _, obj := range objs {
builder = builder.WithObjects(obj)
}
@@ -206,12 +216,6 @@ func Test_getObjectRef(t *testing.T) {
namespace: "default",
want: []string{"ImageRepository/acr-podinfo.flux-system"},
},
{
name: "Source Ref for ImagePolicy (lowercased)",
selector: "imagepolicy/podinfo",
namespace: "default",
want: []string{"ImageRepository/acr-podinfo.flux-system"},
},
{
name: "Empty Ref for Provider",
selector: "Provider/slack",
@@ -228,13 +232,11 @@ func Test_getObjectRef(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
kind, name := getKindNameFromSelector(tt.selector)
infoRef, err := fluxKindMap.getRefInfo(kind)
got, err := getObjectRef(context.Background(), c, tt.selector, tt.namespace)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
got, err := getObjectRef(context.Background(), c, infoRef, name, tt.namespace)
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(got).To(Equal(tt.want))
@@ -247,7 +249,7 @@ func Test_getRows(t *testing.T) {
objs, err := ssa.ReadObjects(strings.NewReader(objects))
g.Expect(err).To(Not(HaveOccurred()))
builder := fake.NewClientBuilder().WithScheme(utils.NewScheme())
builder := fake.NewClientBuilder().WithScheme(getScheme())
for _, obj := range objs {
builder = builder.WithObjects(obj)
}
@@ -259,7 +261,6 @@ func Test_getRows(t *testing.T) {
}
builder = builder.WithLists(eventList)
builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer)
builder.WithIndex(&corev1.Event{}, "involvedObject.kind", kindIndexer)
c := builder.Build()
tests := []struct {
@@ -319,16 +320,6 @@ func Test_getRows(t *testing.T) {
{"flux-system", "<unknown>", "info", "Info Reason", "GitRepository/flux-system", "Info Message"},
},
},
{
name: "All Kustomization (lowercased selector)",
selector: "kustomization",
expected: [][]string{
{"default", "<unknown>", "error", "Error Reason", "Kustomization/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "Kustomization/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "Kustomization/flux-system", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "Kustomization/flux-system", "Info Message"},
},
},
{
name: "HelmRelease with crossnamespaced HelmRepository",
selector: "HelmRelease/podinfo",
@@ -342,19 +333,6 @@ func Test_getRows(t *testing.T) {
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
},
},
{
name: "HelmRelease with crossnamespaced HelmRepository (lowercased)",
selector: "helmrelease/podinfo",
namespace: "default",
expected: [][]string{
{"default", "<unknown>", "error", "Error Reason", "HelmRelease/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "HelmRelease/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "HelmRepository/podinfo", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "HelmRepository/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "HelmChart/default-podinfo", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
},
},
}
for _, tt := range tests {
@@ -363,49 +341,59 @@ func Test_getRows(t *testing.T) {
var refs []string
var refNs, refKind, refName string
var clientOpts = []client.ListOption{client.InNamespace(tt.namespace)}
if tt.selector != "" {
kind, name := getKindNameFromSelector(tt.selector)
infoRef, err := fluxKindMap.getRefInfo(kind)
clientOpts = append(clientOpts, getTestListOpt(infoRef.gvk.Kind, name))
if name != "" {
g.Expect(err).To(Not(HaveOccurred()))
refs, err = getObjectRef(context.Background(), c, infoRef, name, tt.namespace)
g.Expect(err).To(Not(HaveOccurred()))
}
refs, err = getObjectRef(context.Background(), c, tt.selector, tt.namespace)
g.Expect(err).To(Not(HaveOccurred()))
}
g.Expect(err).To(Not(HaveOccurred()))
clientOpts := getTestListOpt(tt.namespace, tt.selector)
var refOpts [][]client.ListOption
for _, ref := range refs {
refKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref)
refOpts = append(refOpts, []client.ListOption{client.InNamespace(refNs), getTestListOpt(refKind, refName)})
refSelector := fmt.Sprintf("%s/%s", refKind, refName)
refOpts = append(refOpts, getTestListOpt(refNs, refSelector))
}
showNs := tt.namespace == "" || (refNs != "" && refNs != tt.namespace)
rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(rows).To(ConsistOf(tt.expected))
g.Expect(rows).To(Equal(tt.expected))
})
}
}
func getTestListOpt(kind, name string) client.ListOption {
var sel fields.Selector
if name == "" {
sel = fields.OneTermEqualSelector("involvedObject.kind", kind)
} else {
sel = fields.OneTermEqualSelector("involvedObject.kind/name", fmt.Sprintf("%s/%s", kind, name))
func getTestListOpt(namespace, selector string) []client.ListOption {
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
if selector != "" {
sel := fields.OneTermEqualSelector("involvedObject.kind/name", selector)
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
}
return client.MatchingFieldsSelector{Selector: sel}
return clientListOpts
}
func getScheme() *runtime.Scheme {
newscheme := runtime.NewScheme()
corev1.AddToScheme(newscheme)
kustomizev1.AddToScheme(newscheme)
helmv2beta1.AddToScheme(newscheme)
notificationv1.AddToScheme(newscheme)
notificationv1b2.AddToScheme(newscheme)
imagev1.AddToScheme(newscheme)
autov1.AddToScheme(newscheme)
sourcev1.AddToScheme(newscheme)
sourcev1b2.AddToScheme(newscheme)
return newscheme
}
func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event {
return corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Namespace: obj.GetNamespace(),
// name of event needs to be unique
// name of event needs to be unique so fak
Name: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType,
},
Reason: reason,
@@ -427,12 +415,3 @@ func kindNameIndexer(obj client.Object) []string {
return []string{fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name)}
}
func kindIndexer(obj client.Object) []string {
e, ok := obj.(*corev1.Event)
if !ok {
panic(fmt.Sprintf("Expected a Event, got %T", e))
}
return []string{e.InvolvedObject.Kind}
}

View File

@@ -20,7 +20,7 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var exportAlertCmd = &cobra.Command{

View File

@@ -20,7 +20,7 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var exportAlertProviderCmd = &cobra.Command{

View File

@@ -46,6 +46,7 @@ type exportableWithSecretList interface {
}
type exportWithSecretCommand struct {
apiType
object exportableWithSecret
list exportableWithSecretList
}

View File

@@ -19,14 +19,12 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var getAlertCmd = &cobra.Command{
@@ -78,9 +76,8 @@ func init() {
func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := string(metav1.ConditionTrue), "Alert is Ready"
return append(nameColumns(&item, includeNamespace, includeKind),
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (s alertListAdapter) headers(includeNamespace bool) []string {
@@ -92,5 +89,6 @@ func (s alertListAdapter) headers(includeNamespace bool) []string {
}
func (s alertListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
return false
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -20,10 +20,9 @@ import (
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var getAlertProviderCmd = &cobra.Command{
@@ -75,7 +74,7 @@ func init() {
func (s alertProviderListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := string(metav1.ConditionTrue), "Provider is Ready"
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind), status, msg)
}
@@ -88,5 +87,6 @@ func (s alertProviderListAdapter) headers(includeNamespace bool) []string {
}
func (s alertProviderListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
return false
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -24,7 +24,7 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var getAllCmd = &cobra.Command{
@@ -63,11 +63,11 @@ var getAllCmd = &cobra.Command{
},
{
apiType: alertProviderType,
list: alertProviderListAdapter{&notificationv1b3.ProviderList{}},
list: alertProviderListAdapter{&notificationv1b2.ProviderList{}},
},
{
apiType: alertType,
list: &alertListAdapter{&notificationv1b3.AlertList{}},
list: &alertListAdapter{&notificationv1b2.AlertList{}},
},
}

View File

@@ -19,13 +19,11 @@ package main
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
"strings"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
)
var getHelmReleaseCmd = &cobra.Command{
@@ -77,7 +75,7 @@ func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, incl
revision := item.Status.LastAppliedRevision
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,11 +19,10 @@ package main
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
@@ -83,7 +82,7 @@ func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
}
return append(nameColumns(&item, includeNamespace, includeKind),
lastScan, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,11 +19,10 @@ package main
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
@@ -82,8 +81,7 @@ func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace
if item.Status.LastAutomationRunTime != nil {
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
}
return append(nameColumns(&item, includeNamespace, includeKind), lastRun,
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
return append(nameColumns(&item, includeNamespace, includeKind), lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,10 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
@@ -84,7 +83,7 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,10 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
@@ -75,8 +74,7 @@ func init() {
func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (s receiverListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,10 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -86,7 +85,7 @@ func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeK
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a bucketListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,10 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -87,7 +86,7 @@ func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, inclu
// Message may still contain reference of e.g. commit chart was build from
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a helmChartListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,10 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
@@ -86,7 +85,7 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,11 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -83,16 +81,11 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
if item.GetArtifact() != nil {
revision = item.GetArtifact().Revision
}
var status, msg string
if item.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
status, msg = string(metav1.ConditionTrue), "Helm repository is Ready"
} else {
status, msg = statusAndMessage(item.Status.Conditions)
}
status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {

View File

@@ -19,10 +19,9 @@ package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -86,7 +85,7 @@ func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {

View File

@@ -23,9 +23,7 @@ import (
"path/filepath"
"time"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
@@ -36,7 +34,6 @@ import (
var installCmd = &cobra.Command{
Use: "install",
Args: cobra.NoArgs,
Short: "Install or upgrade Flux",
Long: `The install command deploys Flux in the specified namespace.
If a previous version is installed, then an in-place upgrade will be performed.`,
@@ -75,7 +72,6 @@ type installFlags struct {
tokenAuth bool
clusterDomain string
tolerationKeys []string
force bool
}
var installArgs = NewInstallFlags()
@@ -102,7 +98,6 @@ func init() {
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
installCmd.Flags().BoolVar(&installArgs.force, "force", false, "override existing Flux installation if it's managed by a diffrent tool such as Helm")
installCmd.Flags().MarkHidden("manifests")
rootCmd.AddCommand(installCmd)
@@ -188,35 +183,6 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
installed := true
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("cluster info unavailable: %w", err)
}
installed = false
}
if info.bootstrapped {
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade",
info.version)
}
if installed && !installArgs.force {
err := confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("installation cancelled")
}
return err
}
}
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
if err != nil {
return fmt.Errorf("install failed: %w", err)

View File

@@ -37,11 +37,6 @@ func TestInstall(t *testing.T) {
args: "install --namespace='@#[]'",
assert: assertError("namespace must be a valid DNS label: \"@#[]\""),
},
{
name: "invalid sub-command",
args: "install unexpectedPosArg --namespace=example",
assert: assertError(`unknown command "unexpectedPosArg" for "flux install"`),
},
}
for _, tt := range tests {

View File

@@ -74,7 +74,7 @@ type logsFlags struct {
fluxNamespace string
allNamespaces bool
sinceTime string
sinceDuration time.Duration
sinceSeconds time.Duration
}
var logsArgs = logsFlags{
@@ -91,7 +91,7 @@ func init() {
logsCmd.Flags().Int64VarP(&logsArgs.tail, "tail", "", logsArgs.tail, "lines of recent log file to display")
logsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running")
logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces")
logsCmd.Flags().DurationVar(&logsArgs.sinceDuration, "since", logsArgs.sinceDuration, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
logsCmd.Flags().DurationVar(&logsArgs.sinceSeconds, "since", logsArgs.sinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
logsCmd.Flags().StringVar(&logsArgs.sinceTime, "since-time", logsArgs.sinceTime, "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
rootCmd.AddCommand(logsCmd)
}
@@ -129,8 +129,8 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
logOpts.TailLines = &logsArgs.tail
}
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceDuration != 0 {
return fmt.Errorf("at most one of `sinceTime` or `sinceDuration` may be specified")
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceSeconds != 0 {
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
}
if len(logsArgs.sinceTime) > 0 {
@@ -141,9 +141,9 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
logOpts.SinceTime = &t
}
if logsArgs.sinceDuration != 0 {
if logsArgs.sinceSeconds != 0 {
// round up to the nearest second
sec := int64(logsArgs.sinceDuration.Round(time.Second).Seconds())
sec := int64(logsArgs.sinceSeconds.Round(time.Second).Seconds())
logOpts.SinceSeconds = &sec
}

View File

@@ -82,7 +82,7 @@ func TestLogsSinceTimeInvalid(t *testing.T) {
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z",
assert: assertError("at most one of `sinceTime` or `sinceDuration` may be specified"),
assert: assertError("at most one of `sinceTime` or `sinceSeconds` may be specified"),
}
cmd.runTestCmd(t)
}

View File

@@ -25,15 +25,10 @@ import (
"os"
"testing"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/log"
"github.com/fluxcd/flux2/v2/internal/utils"
)
func TestMain(m *testing.M) {
log.SetLogger(logr.New(log.NullLogSink{}))
// Ensure tests print consistent timestamps regardless of timezone
os.Setenv("TZ", "UTC")

View File

@@ -34,7 +34,6 @@ import (
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/google/go-cmp/cmp"
"github.com/mattn/go-shellwords"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd"
@@ -113,8 +112,7 @@ func (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstruc
}
obj.SetResourceVersion(createObj.GetResourceVersion())
err = m.client.Status().Update(context.Background(), obj)
// Updating status of static objects results in not found error.
if err != nil && !errors.IsNotFound(err) {
if err != nil {
return err
}
}
@@ -184,7 +182,7 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
}
tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String())
os.WriteFile(tmpFilename, kubeConfig, 0o600)
os.WriteFile(tmpFilename, kubeConfig, 0644)
k8sClient, err := client.NewWithWatch(cfg, client.Options{
Scheme: utils.NewScheme(),
})
@@ -205,9 +203,6 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
useExistingCluster := true
config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
if err != nil {
return nil, err
}
testEnv := &envtest.Environment{
UseExistingCluster: &useExistingCluster,
Config: config,
@@ -315,7 +310,7 @@ func assertGoldenTemplateFile(goldenFile string, templateValues map[string]strin
if len(templateValues) > 0 {
fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually")
} else {
if err := os.WriteFile(goldenFile, []byte(output), 0o600); err != nil {
if err := os.WriteFile(goldenFile, []byte(output), 0644); err != nil {
return fmt.Errorf("failed to update golden file '%s': %v", goldenFile, err)
}
return nil
@@ -342,6 +337,8 @@ type cmdTestCase struct {
// Tests use assertFunc to assert on an output, success or failure. This
// can be a function defined by the test or existing function above.
assert assertFunc
// Filename that contains yaml objects to load into Kubernetes
objectFile string
}
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {

View File

@@ -22,13 +22,10 @@ package main
import (
"context"
"fmt"
"os"
"testing"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/log"
"os"
"testing"
)
// The test environment is long running process shared between tests, initialized
@@ -37,8 +34,6 @@ import (
var testEnv *testEnvKubeManager
func TestMain(m *testing.M) {
log.SetLogger(logr.New(log.NullLogSink{}))
// Ensure tests print consistent timestamps regardless of timezone
os.Setenv("TZ", "UTC")

View File

@@ -111,12 +111,8 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
return err
}
if meta.Source != "" {
logger.Successf("source %s", meta.Source)
}
if meta.Revision != "" {
logger.Successf("revision %s", meta.Revision)
}
logger.Successf("source %s", meta.Source)
logger.Successf("revision %s", meta.Revision)
logger.Successf("digest %s", meta.Digest)
logger.Successf("artifact content extracted to %s", pullArtifactArgs.output)

View File

@@ -28,6 +28,7 @@ import (
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/logs"
"github.com/google/go-containerregistry/pkg/name"
reg "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/spf13/cobra"
@@ -260,20 +261,17 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
}
ociClient := client.NewClient(opts)
digestURL, err := ociClient.Push(ctx, url, path,
client.WithPushMetadata(meta),
client.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),
)
digestURL, err := ociClient.Push(ctx, url, path, meta, pushArtifactArgs.ignorePaths)
if err != nil {
return fmt.Errorf("pushing artifact failed: %w", err)
}
digest, err := name.NewDigest(digestURL)
digest, err := reg.NewDigest(digestURL)
if err != nil {
return fmt.Errorf("artifact digest parsing failed: %w", err)
}
tag, err := name.NewTag(url)
tag, err := reg.NewTag(url)
if err != nil {
return fmt.Errorf("artifact tag parsing failed: %w", err)
}

View File

@@ -1,149 +0,0 @@
/*
Copyright 2023 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"
kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/object"
"github.com/fluxcd/pkg/runtime/patch"
)
// objectStatusType is the type of object in terms of status when computing the
// readiness of an object. Readiness check method depends on the type of object.
// For a dynamic object, Ready status condition is considered only for the
// latest generation of the object. For a static object that don't have any
// condition, the object generation is not considered.
type objectStatusType int
const (
objectStatusDynamic objectStatusType = iota
objectStatusStatic
)
// isObjectReady determines if an object is ready using the kstatus.Compute()
// result. statusType helps differenciate between static and dynamic objects to
// accurately check the object's readiness. A dynamic object may have some extra
// considerations depending on the object.
func isObjectReady(obj client.Object, statusType objectStatusType) (bool, error) {
observedGen, err := object.GetStatusObservedGeneration(obj)
if err != nil && err != object.ErrObservedGenerationNotFound {
return false, err
}
if statusType == objectStatusDynamic {
// Object not reconciled yet.
if observedGen < 1 {
return false, nil
}
cobj, ok := obj.(meta.ObjectWithConditions)
if !ok {
return false, fmt.Errorf("unable to get conditions from object")
}
if c := apimeta.FindStatusCondition(cobj.GetConditions(), meta.ReadyCondition); c != nil {
// Ensure that the ready condition is for the latest generation of
// the object.
// NOTE: Some APIs like ImageUpdateAutomation and HelmRelease don't
// support per condition observed generation yet. Per condition
// observed generation for them are always zero.
// There are two strategies used across different object kinds to
// check the latest ready condition:
// - check that the ready condition's generation matches the
// object's generation.
// - check that the observed generation of the object in the
// status matches the object's generation.
//
// TODO: Once ImageUpdateAutomation and HelmRelease APIs have per
// condition observed generation, remove the object's observed
// generation and object's generation check (the second condition
// below). Also, try replacing this readiness check function with
// fluxcd/pkg/ssa's ResourceManager.Wait(), which uses kstatus
// internally to check readiness of the objects.
if c.ObservedGeneration != 0 && c.ObservedGeneration != obj.GetGeneration() {
return false, nil
}
if c.ObservedGeneration == 0 && observedGen != obj.GetGeneration() {
return false, nil
}
} else {
return false, nil
}
}
u, err := patch.ToUnstructured(obj)
if err != nil {
return false, err
}
result, err := kstatus.Compute(u)
if err != nil {
return false, err
}
switch result.Status {
case kstatus.CurrentStatus:
return true, nil
case kstatus.InProgressStatus:
return false, nil
default:
return false, fmt.Errorf(result.Message)
}
}
// isObjectReadyConditionFunc returns a wait.ConditionFunc to be used with
// wait.Poll* while polling for an object with dynamic status to be ready.
func isObjectReadyConditionFunc(kubeClient client.Client, namespaceName types.NamespacedName, obj client.Object) wait.ConditionWithContextFunc {
return func(ctx context.Context) (bool, error) {
err := kubeClient.Get(ctx, namespaceName, obj)
if err != nil {
return false, err
}
return isObjectReady(obj, objectStatusDynamic)
}
}
// isStaticObjectReadyConditionFunc returns a wait.ConditionFunc to be used with
// wait.Poll* while polling for an object with static or no status to be
// ready.
func isStaticObjectReadyConditionFunc(kubeClient client.Client, namespaceName types.NamespacedName, obj client.Object) wait.ConditionWithContextFunc {
return func(ctx context.Context) (bool, error) {
err := kubeClient.Get(ctx, namespaceName, obj)
if err != nil {
return false, err
}
return isObjectReady(obj, objectStatusStatic)
}
}
// kstatusCompute returns the kstatus computed result of a given object.
func kstatusCompute(obj client.Object) (result *kstatus.Result, err error) {
u, err := patch.ToUnstructured(obj)
if err != nil {
return result, err
}
return kstatus.Compute(u)
}

View File

@@ -1,139 +0,0 @@
/*
Copyright 2023 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
)
func Test_isObjectReady(t *testing.T) {
// Ready object.
readyObj := &sourcev1.GitRepository{}
readyObj.Generation = 1
readyObj.Status.ObservedGeneration = 1
conditions.MarkTrue(readyObj, meta.ReadyCondition, "foo1", "bar1")
// Not ready object.
notReadyObj := readyObj.DeepCopy()
conditions.MarkFalse(notReadyObj, meta.ReadyCondition, "foo2", "bar2")
// Not reconciled object.
notReconciledObj := readyObj.DeepCopy()
notReconciledObj.Status = sourcev1.GitRepositoryStatus{ObservedGeneration: -1}
// No condition.
noConditionObj := readyObj.DeepCopy()
noConditionObj.Status = sourcev1.GitRepositoryStatus{ObservedGeneration: 1}
// Outdated condition.
readyObjOutdated := readyObj.DeepCopy()
readyObjOutdated.Generation = 2
// Object without per condition observed generation.
oldObj := readyObj.DeepCopy()
readyTrueCondn := conditions.TrueCondition(meta.ReadyCondition, "foo3", "bar3")
oldObj.Status.Conditions = []metav1.Condition{*readyTrueCondn}
// Outdated object without per condition observed generation.
oldObjOutdated := oldObj.DeepCopy()
oldObjOutdated.Generation = 2
// Empty status object.
staticObj := readyObj.DeepCopy()
staticObj.Status = sourcev1.GitRepositoryStatus{}
// No status object.
noStatusObj := &notificationv1.Provider{}
noStatusObj.Generation = 1
type args struct {
obj client.Object
statusType objectStatusType
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "dynamic ready",
args: args{obj: readyObj, statusType: objectStatusDynamic},
want: true,
},
{
name: "dynamic not ready",
args: args{obj: notReadyObj, statusType: objectStatusDynamic},
want: false,
},
{
name: "dynamic not reconciled",
args: args{obj: notReconciledObj, statusType: objectStatusDynamic},
want: false,
},
{
name: "dynamic not condition",
args: args{obj: noConditionObj, statusType: objectStatusDynamic},
want: false,
},
{
name: "dynamic ready outdated",
args: args{obj: readyObjOutdated, statusType: objectStatusDynamic},
want: false,
},
{
name: "dynamic ready without per condition gen",
args: args{obj: oldObj, statusType: objectStatusDynamic},
want: true,
},
{
name: "dynamic outdated ready status without per condition gen",
args: args{obj: oldObjOutdated, statusType: objectStatusDynamic},
want: false,
},
{
name: "static empty status",
args: args{obj: staticObj, statusType: objectStatusStatic},
want: true,
},
{
name: "static no status",
args: args{obj: noStatusObj, statusType: objectStatusStatic},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := isObjectReady(tt.args.obj, tt.args.statusType)
if (err != nil) != tt.wantErr {
t.Errorf("isObjectReady() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("isObjectReady() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -21,7 +21,6 @@ import (
"fmt"
"time"
kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,6 +30,8 @@ import (
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils"
@@ -60,7 +61,6 @@ type reconcilable interface {
GetAnnotations() map[string]string
SetAnnotations(map[string]string)
isStatic() bool // is it a static object that does not have a reconciler?
lastHandledReconcileRequest() string // what was the last handled reconcile request?
successMessage() string // what do you want to tell people when successfully reconciled?
}
@@ -101,11 +101,6 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
return err
}
if reconcile.object.isStatic() {
logger.Successf("reconciliation not supported by the object")
return nil
}
if reconcile.object.isSuspended() {
return fmt.Errorf("resource is suspended")
}
@@ -117,10 +112,20 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
}
logger.Successf("%s annotated", reconcile.kind)
if reconcile.kind == notificationv1b2.AlertKind || reconcile.kind == notificationv1.ReceiverKind {
if err = wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReconcileReady(ctx, kubeClient, namespacedName, reconcile.object)); err != nil {
return err
}
logger.Successf(reconcile.object.successMessage())
return nil
}
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err
}
readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)
@@ -135,23 +140,16 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
return nil
}
func reconciliationHandled(kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionWithContextFunc {
return func(ctx context.Context) (bool, error) {
func reconciliationHandled(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
if err != nil {
return false, err
}
if obj.lastHandledReconcileRequest() == lastHandledReconcileAt {
return false, nil
}
result, err := kstatusCompute(obj.asClientObject())
if err != nil {
return false, err
}
return result.Status == kstatus.CurrentStatus, nil
isProgressing := apimeta.IsStatusConditionPresentAndEqual(reconcilableConditions(obj),
meta.ReadyCondition, metav1.ConditionUnknown)
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt && !isProgressing, nil
}
}
@@ -177,3 +175,23 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client,
return kubeClient.Patch(ctx, object, patch)
})
}
func isReconcileReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
if err != nil {
return false, err
}
if c := apimeta.FindStatusCondition(reconcilableConditions(obj), meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -0,0 +1,44 @@
/*
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.
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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var reconcileAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Reconcile an Alert",
Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing alert
flux reconcile alert main`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
RunE: reconcileCommand{
apiType: alertType,
object: alertAdapter{&notificationv1.Alert{}},
}.run,
}
func init() {
reconcileCmd.AddCommand(reconcileAlertCmd)
}
func (obj alertAdapter) lastHandledReconcileRequest() string {
return ""
}

View File

@@ -0,0 +1,93 @@
/*
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.
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"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils"
)
var reconcileAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Reconcile a Provider",
Long: `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing provider
flux reconcile alert-provider slack`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
RunE: reconcileAlertProviderCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileAlertProviderCmd)
}
func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Provider name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Name: name,
}
logger.Actionf("annotating Provider %s in %s namespace", name, *kubeconfigArgs.Namespace)
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
if alertProvider.Annotations == nil {
alertProvider.Annotations = map[string]string{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
alertProvider.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &alertProvider); err != nil {
return err
}
logger.Successf("Provider annotated")
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil {
return err
}
logger.Successf("Provider reconciliation completed")
return nil
}

View File

@@ -81,7 +81,3 @@ func (obj helmReleaseAdapter) getSource() (reconcileSource, types.NamespacedName
Namespace: ns,
}
}
func (obj helmReleaseAdapter) isStatic() bool {
return false
}

View File

@@ -48,7 +48,3 @@ func (obj imageRepositoryAdapter) lastHandledReconcileRequest() string {
func (obj imageRepositoryAdapter) successMessage() string {
return fmt.Sprintf("scan fetched %d tags", obj.Status.LastScanResult.TagCount)
}
func (obj imageRepositoryAdapter) isStatic() bool {
return false
}

View File

@@ -43,6 +43,10 @@ func init() {
reconcileImageCmd.AddCommand(reconcileImageUpdateCmd)
}
func (obj imageUpdateAutomationAdapter) suspended() bool {
return obj.ImageUpdateAutomation.Spec.Suspend
}
func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {
return obj.Status.GetLastHandledReconcileRequest()
}
@@ -56,7 +60,3 @@ func (obj imageUpdateAutomationAdapter) successMessage() string {
}
return "automation not yet run"
}
func (obj imageUpdateAutomationAdapter) isStatic() bool {
return false
}

View File

@@ -88,7 +88,3 @@ func (obj kustomizationAdapter) getSource() (reconcileSource, types.NamespacedNa
Namespace: obj.Spec.SourceRef.Namespace,
}
}
func (obj kustomizationAdapter) isStatic() bool {
return false
}

View File

@@ -17,9 +17,18 @@ limitations under the License.
package main
import (
"context"
"fmt"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils"
)
var reconcileReceiverCmd = &cobra.Command{
@@ -29,20 +38,62 @@ var reconcileReceiverCmd = &cobra.Command{
Example: ` # Trigger a reconciliation for an existing receiver
flux reconcile receiver main`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
RunE: reconcileCommand{
apiType: receiverType,
object: receiverAdapter{&notificationv1.Receiver{}},
}.run,
RunE: reconcileReceiverCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileReceiverCmd)
}
func (obj receiverAdapter) lastHandledReconcileRequest() string {
return obj.Status.GetLastHandledReconcileRequest()
}
func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("receiver name is required")
}
name := args[0]
func (obj receiverAdapter) isStatic() bool {
return false
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
if receiver.Spec.Suspend {
return fmt.Errorf("resource is suspended")
}
logger.Actionf("annotating Receiver %s in %s namespace", name, *kubeconfigArgs.Namespace)
if receiver.Annotations == nil {
receiver.Annotations = map[string]string{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
receiver.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver annotated")
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver reconciliation completed")
return nil
}

View File

@@ -48,7 +48,3 @@ func (obj bucketAdapter) lastHandledReconcileRequest() string {
func (obj bucketAdapter) successMessage() string {
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
}
func (obj bucketAdapter) isStatic() bool {
return false
}

View File

@@ -84,7 +84,3 @@ func (obj helmChartAdapter) getSource() (reconcileSource, types.NamespacedName)
Namespace: obj.Namespace,
}
}
func (obj helmChartAdapter) isStatic() bool {
return false
}

View File

@@ -48,7 +48,3 @@ func (obj gitRepositoryAdapter) lastHandledReconcileRequest() string {
func (obj gitRepositoryAdapter) successMessage() string {
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
}
func (obj gitRepositoryAdapter) isStatic() bool {
return false
}

View File

@@ -60,7 +60,3 @@ func (obj helmRepositoryAdapter) successMessage() string {
}
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
}
func (obj helmRepositoryAdapter) isStatic() bool {
return obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI
}

View File

@@ -48,7 +48,3 @@ func (obj ociRepositoryAdapter) lastHandledReconcileRequest() string {
func (obj ociRepositoryAdapter) successMessage() string {
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
}
func (obj ociRepositoryAdapter) isStatic() bool {
return false
}

View File

@@ -82,8 +82,8 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
logger.Successf("%s annotated", reconcile.kind)
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err
}

View File

@@ -56,7 +56,6 @@ type resumable interface {
copyable
statusable
setUnsuspended()
isStatic() bool
successMessage() string
}
@@ -213,12 +212,8 @@ func (resume resumeCommand) reconcile(ctx context.Context, res resumable) reconc
logger.Waitingf("waiting for %s reconciliation", resume.kind)
readyConditionFunc := isObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject())
if res.isStatic() {
readyConditionFunc = isStaticObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject())
}
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReady(ctx, resume.client, namespacedName, res)); err != nil {
return reconcileResponse{
resumable: res,
err: err,

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var resumeAlertCmd = &cobra.Command{
@@ -44,7 +44,7 @@ func init() {
}
func (obj alertAdapter) getObservedGeneration() int64 {
return 0
return obj.Alert.Status.ObservedGeneration
}
func (obj alertAdapter) setUnsuspended() {
@@ -55,10 +55,6 @@ func (obj alertAdapter) successMessage() string {
return "Alert reconciliation completed"
}
func (a alertAdapter) isStatic() bool {
return true
}
func (a alertListAdapter) resumeItem(i int) resumable {
return &alertAdapter{&a.AlertList.Items[i]}
}

View File

@@ -1,64 +0,0 @@
/*
Copyright 2023 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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
)
var resumeAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Resume a suspended Provider",
Long: `The resume command marks a previously suspended Provider resource for reconciliation and waits for it to
finish the apply.`,
Example: ` # Resume reconciliation for an existing Provider
flux resume alert-provider main
# Resume reconciliation for multiple Providers
flux resume alert-provider main-1 main-2`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
RunE: resumeCommand{
apiType: alertProviderType,
list: &alertProviderListAdapter{&notificationv1.ProviderList{}},
}.run,
}
func init() {
resumeCmd.AddCommand(resumeAlertProviderCmd)
}
func (obj alertProviderAdapter) getObservedGeneration() int64 {
return 0
}
func (obj alertProviderAdapter) setUnsuspended() {
obj.Provider.Spec.Suspend = false
}
func (obj alertProviderAdapter) successMessage() string {
return "Provider reconciliation completed"
}
func (a alertProviderAdapter) isStatic() bool {
return true
}
func (a alertProviderListAdapter) resumeItem(i int) resumable {
return &alertProviderAdapter{&a.ProviderList.Items[i]}
}

View File

@@ -24,20 +24,19 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/printers"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
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"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/printers"
)
var statsCmd = &cobra.Command{
@@ -111,14 +110,14 @@ func runStatsCmd(cmd *cobra.Command, args []string) error {
Group: helmv2.GroupVersion.Group,
},
{
Kind: notificationv1b3.AlertKind,
Version: notificationv1b3.GroupVersion.Version,
Group: notificationv1b3.GroupVersion.Group,
Kind: notificationv1b2.AlertKind,
Version: notificationv1b2.GroupVersion.Version,
Group: notificationv1b2.GroupVersion.Group,
},
{
Kind: notificationv1b3.ProviderKind,
Version: notificationv1b3.GroupVersion.Version,
Group: notificationv1b3.GroupVersion.Group,
Kind: notificationv1b2.ProviderKind,
Version: notificationv1b2.GroupVersion.Version,
Group: notificationv1b2.GroupVersion.Group,
},
{
Kind: notificationv1.ReceiverKind,

View File

@@ -17,9 +17,17 @@ limitations under the License.
package main
import (
"github.com/fluxcd/cli-utils/pkg/object"
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// statusable is used to see if a resource is considered ready in the usual way
@@ -36,6 +44,43 @@ type oldConditions interface {
GetStatusConditions() *[]metav1.Condition
}
func statusableConditions(object statusable) []metav1.Condition {
if s, ok := object.(meta.ObjectWithConditions); ok {
return s.GetConditions()
}
if s, ok := object.(oldConditions); ok {
return *s.GetStatusConditions()
}
return []metav1.Condition{}
}
func isReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, object statusable) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, object.asClientObject())
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if object.GetGeneration() != object.getObservedGeneration() {
return false, nil
}
if c := apimeta.FindStatusCondition(statusableConditions(object), meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}
func buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error) {
var objRefs []object.ObjMetadata
for _, deployment := range components {

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var suspendAlertCmd = &cobra.Command{

View File

@@ -1,56 +0,0 @@
/*
Copyright 2023 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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
)
var suspendAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Suspend reconciliation of Provider",
Long: `The suspend command disables the reconciliation of a Provider resource.`,
Example: ` # Suspend reconciliation for an existing Provider
flux suspend alert-provider main
# Suspend reconciliation for multiple Providers
flux suspend alert-providers main-1 main-2`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
RunE: suspendCommand{
apiType: alertProviderType,
object: &alertProviderAdapter{&notificationv1.Provider{}},
list: &alertProviderListAdapter{&notificationv1.ProviderList{}},
}.run,
}
func init() {
suspendCmd.AddCommand(suspendAlertProviderCmd)
}
func (obj alertProviderAdapter) isSuspended() bool {
return obj.Provider.Spec.Suspend
}
func (obj alertProviderAdapter) setSuspended() {
obj.Provider.Spec.Suspend = true
}
func (a alertProviderListAdapter) item(i int) suspendable {
return &alertProviderAdapter{&a.ProviderList.Items[i]}
}

View File

@@ -1,424 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.12.0
name: gitrepositories.source.toolkit.fluxcd.io
spec:
group: source.toolkit.fluxcd.io
names:
kind: GitRepository
listKind: GitRepositoryList
plural: gitrepositories
shortNames:
- gitrepo
singular: gitrepository
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.url
name: URL
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].message
name: Status
type: string
name: v1
schema:
openAPIV3Schema:
description: GitRepository is the Schema for the gitrepositories API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: GitRepositorySpec specifies the required configuration to
produce an Artifact for a Git repository.
properties:
ignore:
description: Ignore overrides the set of excluded patterns in the
.sourceignore format (which is the same as .gitignore). If not provided,
a default will be used, consult the documentation for your version
to find out what those are.
type: string
include:
description: Include specifies a list of GitRepository resources which
Artifacts should be included in the Artifact produced for this GitRepository.
items:
description: GitRepositoryInclude specifies a local reference to
a GitRepository which Artifact (sub-)contents must be included,
and where they should be placed.
properties:
fromPath:
description: FromPath specifies the path to copy contents from,
defaults to the root of the Artifact.
type: string
repository:
description: GitRepositoryRef specifies the GitRepository which
Artifact contents must be included.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
toPath:
description: ToPath specifies the path to copy contents to,
defaults to the name of the GitRepositoryRef.
type: string
required:
- repository
type: object
type: array
interval:
description: Interval at which the GitRepository URL is checked for
updates. This interval is approximate and may be subject to jitter
to ensure efficient use of resources.
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
type: string
proxySecretRef:
description: ProxySecretRef specifies the Secret containing the proxy
configuration to use while communicating with the Git server.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
recurseSubmodules:
description: RecurseSubmodules enables the initialization of all submodules
within the GitRepository as cloned from the URL, using their default
settings.
type: boolean
ref:
description: Reference specifies the Git reference to resolve and
monitor for changes, defaults to the 'master' branch.
properties:
branch:
description: Branch to check out, defaults to 'master' if no other
field is defined.
type: string
commit:
description: "Commit SHA to check out, takes precedence over all
reference fields. \n This can be combined with Branch to shallow
clone the branch, in which the commit is expected to exist."
type: string
name:
description: "Name of the reference to check out; takes precedence
over Branch, Tag and SemVer. \n It must be a valid Git reference:
https://git-scm.com/docs/git-check-ref-format#_description Examples:
\"refs/heads/main\", \"refs/tags/v0.1.0\", \"refs/pull/420/head\",
\"refs/merge-requests/1/head\""
type: string
semver:
description: SemVer tag expression to check out, takes precedence
over Tag.
type: string
tag:
description: Tag to check out, takes precedence over Branch.
type: string
type: object
secretRef:
description: SecretRef specifies the Secret containing authentication
credentials for the GitRepository. For HTTPS repositories the Secret
must contain 'username' and 'password' fields for basic auth or
'bearerToken' field for token auth. For SSH repositories the Secret
must contain 'identity' and 'known_hosts' fields.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
suspend:
description: Suspend tells the controller to suspend the reconciliation
of this GitRepository.
type: boolean
timeout:
default: 60s
description: Timeout for Git operations like cloning, defaults to
60s.
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$
type: string
url:
description: URL specifies the Git repository URL, it can be an HTTP/S
or SSH address.
pattern: ^(http|https|ssh)://.*$
type: string
verify:
description: Verification specifies the configuration to verify the
Git commit signature(s).
properties:
mode:
default: HEAD
description: "Mode specifies which Git object(s) should be verified.
\n The variants \"head\" and \"HEAD\" both imply the same thing,
i.e. verify the commit that the HEAD of the Git repository points
to. The variant \"head\" solely exists to ensure backwards compatibility."
enum:
- head
- HEAD
- Tag
- TagAndHEAD
type: string
secretRef:
description: SecretRef specifies the Secret containing the public
keys of trusted Git authors.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
required:
- secretRef
type: object
required:
- interval
- url
type: object
status:
default:
observedGeneration: -1
description: GitRepositoryStatus records the observed state of a Git repository.
properties:
artifact:
description: Artifact represents the last successful GitRepository
reconciliation.
properties:
digest:
description: Digest is the digest of the file in the form of '<algorithm>:<checksum>'.
pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$
type: string
lastUpdateTime:
description: LastUpdateTime is the timestamp corresponding to
the last update of the Artifact.
format: date-time
type: string
metadata:
additionalProperties:
type: string
description: Metadata holds upstream information such as OCI annotations.
type: object
path:
description: Path is the relative file path of the Artifact. It
can be used to locate the file in the root of the Artifact storage
on the local file system of the controller managing the Source.
type: string
revision:
description: Revision is a human-readable identifier traceable
in the origin source system. It can be a Git commit SHA, Git
tag, a Helm chart version, etc.
type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url:
description: URL is the HTTP address of the Artifact as exposed
by the controller managing the Source. It can be used to retrieve
the Artifact for consumption, e.g. by another controller applying
the Artifact contents.
type: string
required:
- lastUpdateTime
- path
- revision
- url
type: object
conditions:
description: Conditions holds the conditions for the GitRepository.
items:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
includedArtifacts:
description: IncludedArtifacts contains a list of the last successfully
included Artifacts as instructed by GitRepositorySpec.Include.
items:
description: Artifact represents the output of a Source reconciliation.
properties:
digest:
description: Digest is the digest of the file in the form of
'<algorithm>:<checksum>'.
pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$
type: string
lastUpdateTime:
description: LastUpdateTime is the timestamp corresponding to
the last update of the Artifact.
format: date-time
type: string
metadata:
additionalProperties:
type: string
description: Metadata holds upstream information such as OCI
annotations.
type: object
path:
description: Path is the relative file path of the Artifact.
It can be used to locate the file in the root of the Artifact
storage on the local file system of the controller managing
the Source.
type: string
revision:
description: Revision is a human-readable identifier traceable
in the origin source system. It can be a Git commit SHA, Git
tag, a Helm chart version, etc.
type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url:
description: URL is the HTTP address of the Artifact as exposed
by the controller managing the Source. It can be used to retrieve
the Artifact for consumption, e.g. by another controller applying
the Artifact contents.
type: string
required:
- lastUpdateTime
- path
- revision
- url
type: object
type: array
lastHandledReconcileAt:
description: LastHandledReconcileAt holds the value of the most recent
reconcile request value, so a change of the annotation value can
be detected.
type: string
observedGeneration:
description: ObservedGeneration is the last observed generation of
the GitRepository object.
format: int64
type: integer
observedIgnore:
description: ObservedIgnore is the observed exclusion patterns used
for constructing the source artifact.
type: string
observedInclude:
description: ObservedInclude is the observed list of GitRepository
resources used to produce the current Artifact.
items:
description: GitRepositoryInclude specifies a local reference to
a GitRepository which Artifact (sub-)contents must be included,
and where they should be placed.
properties:
fromPath:
description: FromPath specifies the path to copy contents from,
defaults to the root of the Artifact.
type: string
repository:
description: GitRepositoryRef specifies the GitRepository which
Artifact contents must be included.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
toPath:
description: ToPath specifies the path to copy contents to,
defaults to the name of the GitRepositoryRef.
type: string
required:
- repository
type: object
type: array
observedRecurseSubmodules:
description: ObservedRecurseSubmodules is the observed resource submodules
configuration used to produce the current Artifact.
type: boolean
sourceVerificationMode:
description: SourceVerificationMode is the last used verification
mode indicating which Git object(s) have been verified.
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -8,6 +8,6 @@
data.password
± value change
- *** (before)
+ *** (after)
- ******
+ *****

View File

@@ -1,5 +1,5 @@
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: flux-system

View File

@@ -4,7 +4,7 @@ kind: Namespace
metadata:
name: {{ .fluxns }}
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
name: slack
@@ -14,7 +14,7 @@ spec:
channel: 'A channel with spacess'
address: https://hooks.slack.com/services/mock
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: flux-system

View File

@@ -1,5 +1,5 @@
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
name: slack

View File

@@ -1,15 +0,0 @@
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 10m0s
ref:
tag: 6.3.5
url: oci://ghcr.io/stefanprodan/manifests/podinfo
verify:
provider: cosign
secretRef:
name: cosign-pub

View File

@@ -31,10 +31,10 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/fluxcd/cli-utils/pkg/object"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/pkg/ssa"

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