Compare commits
25 Commits
fix-commit
...
v2.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a18d4f3450 | ||
|
|
95c5c5ab30 | ||
|
|
51532e75b2 | ||
|
|
cef3eef641 | ||
|
|
5e412ee73d | ||
|
|
b3e432cd8e | ||
|
|
62d65edddf | ||
|
|
47da5290ce | ||
|
|
23f9492707 | ||
|
|
cd16ff4aa4 | ||
|
|
a0d2c2520c | ||
|
|
026ab61ba7 | ||
|
|
8da931aed4 | ||
|
|
24c0de031a | ||
|
|
df60d40f91 | ||
|
|
188ce1bad1 | ||
|
|
88bad4b7ae | ||
|
|
4fc2df7b57 | ||
|
|
0fd975bf66 | ||
|
|
40992037da | ||
|
|
f94acd5ef3 | ||
|
|
c87f48fdea | ||
|
|
68ff588910 | ||
|
|
f420cfbf7c | ||
|
|
f3e99a6cfa |
3
.github/labels.yaml
vendored
3
.github/labels.yaml
vendored
@@ -47,6 +47,3 @@
|
|||||||
- name: backport:release/v2.0.x
|
- name: backport:release/v2.0.x
|
||||||
description: To be backported to release/v2.0.x
|
description: To be backported to release/v2.0.x
|
||||||
color: '#ffd700'
|
color: '#ffd700'
|
||||||
- name: backport:release/v2.1.x
|
|
||||||
description: To be backported to release/v2.1.x
|
|
||||||
color: '#ffd700'
|
|
||||||
|
|||||||
2
.github/workflows/action.yaml
vendored
2
.github/workflows/action.yaml
vendored
@@ -24,6 +24,6 @@ jobs:
|
|||||||
name: action on ${{ matrix.version }}
|
name: action on ${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup flux
|
- name: Setup flux
|
||||||
uses: ./action
|
uses: ./action
|
||||||
|
|||||||
4
.github/workflows/backport.yaml
vendored
4
.github/workflows/backport.yaml
vendored
@@ -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))
|
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Create backport PRs
|
- name: Create backport PRs
|
||||||
uses: korthout/backport-action@b982d297e31f500652b2246cf26714796312bd23 # v2.2.0
|
uses: korthout/backport-action@bd68141f079bd036e45ea8149bc9d174d5a04703 # v1.4.0
|
||||||
# xref: https://github.com/korthout/backport-action#inputs
|
# xref: https://github.com/korthout/backport-action#inputs
|
||||||
with:
|
with:
|
||||||
# Use token to allow workflows to be triggered for the created PR
|
# Use token to allow workflows to be triggered for the created PR
|
||||||
|
|||||||
4
.github/workflows/e2e-arm64.yaml
vendored
4
.github/workflows/e2e-arm64.yaml
vendored
@@ -21,9 +21,9 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
|
|||||||
84
.github/workflows/e2e-azure.yaml
vendored
84
.github/workflows/e2e-azure.yaml
vendored
@@ -3,7 +3,7 @@ name: e2e-azure
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 6 * * *'
|
- cron: '0 6 * * *'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -23,32 +23,31 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
e2e-amd64-aks:
|
e2e-amd64-aks:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
defaults:
|
# This job is currently disabled since if always evaluates to false. Remove the false check when Azure subscription is enabled
|
||||||
run:
|
|
||||||
working-directory: ./tests/azure
|
|
||||||
# This job is currently disabled. Remove the false check when Azure subscription is enabled.
|
|
||||||
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: tests/azure/go.sum
|
cache-dependency-path: |
|
||||||
|
**/go.sum
|
||||||
|
**/go.mod
|
||||||
- name: Setup Flux CLI
|
- name: Setup Flux CLI
|
||||||
run: |
|
run: |
|
||||||
make build
|
make build
|
||||||
mkdir -p $HOME/.local/bin
|
mkdir -p $HOME/.local/bin
|
||||||
mv ./bin/flux $HOME/.local/bin
|
mv ./bin/flux $HOME/.local/bin
|
||||||
working-directory: ./
|
|
||||||
- name: Setup SOPS
|
- name: Setup SOPS
|
||||||
run: |
|
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
|
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
|
mv sops-v3.7.1.linux $HOME/.local/bin/sops
|
||||||
chmod +x $HOME/.local/bin/sops
|
|
||||||
- name: Setup Terraform
|
- name: Setup Terraform
|
||||||
uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v2
|
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2
|
||||||
with:
|
with:
|
||||||
terraform_version: 1.2.8
|
terraform_version: 1.2.8
|
||||||
terraform_wrapper: false
|
terraform_wrapper: false
|
||||||
@@ -62,64 +61,9 @@ jobs:
|
|||||||
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||||
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||||
run: |
|
run: |
|
||||||
|
echo $HOME
|
||||||
|
echo $PATH
|
||||||
ls $HOME/.local/bin
|
ls $HOME/.local/bin
|
||||||
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
|
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 .
|
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
|
|
||||||
|
|||||||
4
.github/workflows/e2e-bootstrap.yaml
vendored
4
.github/workflows/e2e-bootstrap.yaml
vendored
@@ -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]'
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
|
|||||||
92
.github/workflows/e2e-gcp.yaml
vendored
92
.github/workflows/e2e-gcp.yaml
vendored
@@ -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
|
|
||||||
4
.github/workflows/e2e.yaml
vendored
4
.github/workflows/e2e.yaml
vendored
@@ -21,9 +21,9 @@ jobs:
|
|||||||
- 5000:5000
|
- 5000:5000
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
|
|||||||
6
.github/workflows/ossf.yaml
vendored
6
.github/workflows/ossf.yaml
vendored
@@ -19,16 +19,16 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Run analysis
|
- name: Run analysis
|
||||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_results: true
|
publish_results: true
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
|
|||||||
26
.github/workflows/release.yaml
vendored
26
.github/workflows/release.yaml
vendored
@@ -20,33 +20,33 @@ jobs:
|
|||||||
packages: write # needed for ghcr access
|
packages: write # needed for ghcr access
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Unshallow
|
- name: Unshallow
|
||||||
run: git fetch --prune --unshallow
|
run: git fetch --prune --unshallow
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache: false
|
cache: false
|
||||||
- name: Setup QEMU
|
- 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
|
- name: Setup Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1
|
||||||
- name: Setup Syft
|
- 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
|
- name: Setup Cosign
|
||||||
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
id: run-goreleaser
|
id: run-goreleaser
|
||||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
uses: goreleaser/goreleaser-action@3fa32b8bb5620a2c1afe798654bbad59f9da4906 # v4.4.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --release-notes=output/notes.md --skip-validate
|
args: release --release-notes=output/notes.md --skip-validate
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Setup Flux CLI
|
- name: Setup Flux CLI
|
||||||
@@ -121,13 +121,13 @@ jobs:
|
|||||||
VERSION=$(flux version --client | awk '{ print $NF }')
|
VERSION=$(flux version --client | awk '{ print $NF }')
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
--path="./flux-system" \
|
--path="./flux-system" \
|
||||||
--source=${{ github.repositoryUrl }} \
|
--source=${{ github.repositoryUrl }} \
|
||||||
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
|
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
|
||||||
- uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
- uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1
|
||||||
- name: Sign manifests
|
- name: Sign manifests
|
||||||
env:
|
env:
|
||||||
COSIGN_EXPERIMENTAL: 1
|
COSIGN_EXPERIMENTAL: 1
|
||||||
|
|||||||
12
.github/workflows/scan.yaml
vendored
12
.github/workflows/scan.yaml
vendored
@@ -17,9 +17,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.actor != 'dependabot[bot]'
|
if: github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Run FOSSA scan and upload build data
|
- 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:
|
with:
|
||||||
# FOSSA Push-Only API Token
|
# FOSSA Push-Only API Token
|
||||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||||
@@ -31,11 +31,11 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
@@ -64,9 +64,9 @@ jobs:
|
|||||||
if: github.actor != 'dependabot[bot]'
|
if: github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
|
|||||||
2
.github/workflows/sync-labels.yaml
vendored
2
.github/workflows/sync-labels.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
|
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
|
||||||
with:
|
with:
|
||||||
# Configuration file
|
# Configuration file
|
||||||
|
|||||||
4
.github/workflows/update.yaml
vendored
4
.github/workflows/update.yaml
vendored
@@ -18,9 +18,9 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -19,7 +19,6 @@ all: test build
|
|||||||
tidy:
|
tidy:
|
||||||
go mod tidy -compat=1.20
|
go mod tidy -compat=1.20
|
||||||
cd tests/azure && 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:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -5,7 +5,6 @@
|
|||||||
[](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
|
[](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
|
||||||
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
|
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
|
||||||
[](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
|
[](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
|
||||||
[](https://fluxcd.io/flux/security/slsa-assessment)
|
|
||||||
|
|
||||||
Flux is a tool for keeping Kubernetes clusters in sync with sources of
|
Flux is a tool for keeping Kubernetes clusters in sync with sources of
|
||||||
configuration (like Git repositories and OCI artifacts),
|
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/)
|
- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)
|
||||||
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
|
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
|
||||||
- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)
|
- [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/)**.
|
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
||||||
|
|
||||||
@@ -59,18 +58,18 @@ guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
|
|||||||
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
|
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
|
||||||
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
|
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
|
||||||
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
|
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
|
||||||
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)
|
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomization/)
|
||||||
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
|
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
|
||||||
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
|
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
|
||||||
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
|
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
|
||||||
- [Provider CRD](https://fluxcd.io/flux/components/notification/providers/)
|
- [Provider CRD](https://fluxcd.io/flux/components/notification/provider/)
|
||||||
- [Alert CRD](https://fluxcd.io/flux/components/notification/alerts/)
|
- [Alert CRD](https://fluxcd.io/flux/components/notification/alert/)
|
||||||
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receivers/)
|
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receiver/)
|
||||||
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
|
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
|
||||||
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
|
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
|
||||||
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)
|
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)
|
||||||
- [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)
|
- [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
Need help or want to contribute? Please see the links below. The Flux project is always looking for
|
Need help or want to contribute? Please see the links below. The Flux project is always looking for
|
||||||
|
|||||||
@@ -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)
|
- [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)
|
- [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).
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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
|
// notificationv1.Alert
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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
|
// notificationv1.Provider
|
||||||
|
|||||||
@@ -17,15 +17,11 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
"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/flags"
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
@@ -76,8 +72,6 @@ type bootstrapFlags struct {
|
|||||||
gpgPassphrase string
|
gpgPassphrase string
|
||||||
gpgKeyID string
|
gpgKeyID string
|
||||||
|
|
||||||
force bool
|
|
||||||
|
|
||||||
commitMessageAppendix string
|
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().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")
|
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
|
||||||
|
|
||||||
rootCmd.AddCommand(bootstrapCmd)
|
rootCmd.AddCommand(bootstrapCmd)
|
||||||
@@ -195,27 +188,3 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
|
|||||||
|
|
||||||
return m
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ the bootstrap command will perform an upgrade if needed.`,
|
|||||||
# Run bootstrap for a public repository on a personal account
|
# 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
|
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`,
|
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth --path=clusters/my-cluster`,
|
||||||
RunE: bootstrapBServerCmdRun,
|
RunE: bootstrapBServerCmdRun,
|
||||||
}
|
}
|
||||||
@@ -124,13 +124,6 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bootstrapArgs.force {
|
|
||||||
err = confirmBootstrap(ctx, kubeClient)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -146,13 +146,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bootstrapArgs.force {
|
|
||||||
err = confirmBootstrap(ctx, kubeClient)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -128,13 +128,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bootstrapArgs.force {
|
|
||||||
err = confirmBootstrap(ctx, kubeClient)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ the bootstrap command will perform an upgrade if needed.`,
|
|||||||
# Run bootstrap for a private repository hosted on a GitLab server
|
# Run bootstrap for a private repository hosted on a GitLab server
|
||||||
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
|
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
|
||||||
|
|
||||||
# Run bootstrap for an existing repository with a branch named main
|
# Run bootstrap for a an existing repository with a branch named main
|
||||||
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth
|
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth
|
||||||
|
|
||||||
# Run bootstrap for a private repository using Deploy Token authentication
|
# Run bootstrap for a private repository using Deploy Token authentication
|
||||||
@@ -145,13 +145,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bootstrapArgs.force {
|
|
||||||
err = confirmBootstrap(ctx, kubeClient)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
ociClient := oci.NewClient(oci.DefaultOptions())
|
ociClient := oci.NewClient(oci.DefaultOptions())
|
||||||
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {
|
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)
|
logger.Successf("artifact created at %s", buildArtifactArgs.output)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ import (
|
|||||||
v1 "k8s.io/api/apps/v1"
|
v1 "k8s.io/api/apps/v1"
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/version"
|
"github.com/fluxcd/pkg/version"
|
||||||
@@ -82,20 +80,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
fluxCheck()
|
fluxCheck()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
if !kubernetesCheck(kubernetesConstraints) {
|
||||||
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) {
|
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,18 +92,13 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Actionf("checking version in cluster")
|
|
||||||
if !fluxClusterVersionCheck(ctx, kubeClient) {
|
|
||||||
checkFailed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("checking controllers")
|
logger.Actionf("checking controllers")
|
||||||
if !componentsCheck(ctx, kubeClient) {
|
if !componentsCheck() {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Actionf("checking crds")
|
logger.Actionf("checking crds")
|
||||||
if !crdsCheck(ctx, kubeClient) {
|
if !crdsCheck() {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,11 +129,17 @@ func fluxCheck() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if latestSv.GreaterThan(curSv) {
|
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)
|
clientSet, err := kubernetes.NewForConfig(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
||||||
@@ -192,8 +178,21 @@ func kubernetesCheck(cfg *rest.Config, constraints []string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
|
func componentsCheck() bool {
|
||||||
statusChecker, err := status.NewStatusCheckerWithClient(kubeClient, checkArgs.pollInterval, rootArgs.timeout, logger)
|
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 {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -223,7 +222,15 @@ func componentsCheck(ctx context.Context, kubeClient client.Client) bool {
|
|||||||
return ok
|
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
|
ok := true
|
||||||
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
|
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
|
||||||
var list apiextensionsv1.CustomResourceDefinitionList
|
var list apiextensionsv1.CustomResourceDefinitionList
|
||||||
@@ -246,17 +253,3 @@ func crdsCheck(ctx context.Context, kubeClient client.Client) bool {
|
|||||||
}
|
}
|
||||||
return ok
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -131,8 +131,8 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for %s reconciliation", names.kind)
|
logger.Waitingf("waiting for %s reconciliation", names.kind)
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, object.asClientObject())); err != nil {
|
isReady(ctx, kubeClient, namespacedName, object)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("%s reconciliation completed", names.kind)
|
logger.Successf("%s reconciliation completed", names.kind)
|
||||||
@@ -165,6 +165,6 @@ func parseLabels() (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateObjectName(name string) bool {
|
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)
|
return r.MatchString(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-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"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
@@ -96,13 +97,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Generatef("generating Alert")
|
logger.Generatef("generating Alert")
|
||||||
}
|
}
|
||||||
|
|
||||||
alert := notificationv1b3.Alert{
|
alert := notificationv1b2.Alert{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: notificationv1b3.AlertSpec{
|
Spec: notificationv1b2.AlertSpec{
|
||||||
ProviderRef: meta.LocalObjectReference{
|
ProviderRef: meta.LocalObjectReference{
|
||||||
Name: alertArgs.providerRef,
|
Name: alertArgs.providerRef,
|
||||||
},
|
},
|
||||||
@@ -131,8 +132,8 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Alert reconciliation")
|
logger.Waitingf("waiting for Alert reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isStaticObjectReadyConditionFunc(kubeClient, namespacedName, &alert)); err != nil {
|
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Alert %s is ready", name)
|
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,
|
func upsertAlert(ctx context.Context, kubeClient client.Client,
|
||||||
alert *notificationv1b3.Alert) (types.NamespacedName, error) {
|
alert *notificationv1b2.Alert) (types.NamespacedName, error) {
|
||||||
namespacedName := types.NamespacedName{
|
namespacedName := types.NamespacedName{
|
||||||
Namespace: alert.GetNamespace(),
|
Namespace: alert.GetNamespace(),
|
||||||
Name: alert.GetName(),
|
Name: alert.GetName(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var existing notificationv1b3.Alert
|
var existing notificationv1b2.Alert
|
||||||
err := kubeClient.Get(ctx, namespacedName, &existing)
|
err := kubeClient.Get(ctx, namespacedName, &existing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
@@ -169,3 +170,23 @@ func upsertAlert(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("Alert updated")
|
logger.Successf("Alert updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"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")
|
logger.Waitingf("waiting for Provider reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isStaticObjectReadyConditionFunc(kubeClient, namespacedName, &provider)); err != nil {
|
isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,3 +167,23 @@ func upsertAlertProvider(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("Provider updated")
|
logger.Successf("Provider updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
@@ -302,8 +303,8 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for HelmRelease reconciliation")
|
logger.Waitingf("waiting for HelmRelease reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, &helmRelease)); err != nil {
|
isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("HelmRelease %s is ready", name)
|
logger.Successf("HelmRelease %s is ready", name)
|
||||||
@@ -343,6 +344,23 @@ func upsertHelmRelease(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
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 {
|
func validateStrategy(input string) bool {
|
||||||
allowedStrategy := []string{"Revision", "ChartVersion"}
|
allowedStrategy := []string{"Revision", "ChartVersion"}
|
||||||
|
|
||||||
|
|||||||
@@ -54,12 +54,13 @@ the status of the object.`),
|
|||||||
RunE: createImagePolicyRun}
|
RunE: createImagePolicyRun}
|
||||||
|
|
||||||
type imagePolicyFlags struct {
|
type imagePolicyFlags struct {
|
||||||
imageRef string
|
imageRef string
|
||||||
semver string
|
semver string
|
||||||
alpha string
|
alpha string
|
||||||
numeric string
|
numeric string
|
||||||
filterRegex string
|
filterRegex string
|
||||||
filterExtract string
|
filterExtract string
|
||||||
|
filterNumerical string
|
||||||
}
|
}
|
||||||
|
|
||||||
var imagePolicyArgs = imagePolicyFlags{}
|
var imagePolicyArgs = imagePolicyFlags{}
|
||||||
@@ -182,6 +183,7 @@ func validateExtractStr(template string, capNames []string) error {
|
|||||||
name, num, rest, ok := extract(template)
|
name, num, rest, ok := extract(template)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Malformed extract string, assume user didn't want this
|
// Malformed extract string, assume user didn't want this
|
||||||
|
template = template[1:]
|
||||||
return fmt.Errorf("--filter-extract is malformed")
|
return fmt.Errorf("--filter-extract is malformed")
|
||||||
}
|
}
|
||||||
template = rest
|
template = rest
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
@@ -262,8 +263,8 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Kustomization reconciliation")
|
logger.Waitingf("waiting for Kustomization reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, &kustomization)); err != nil {
|
isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Kustomization %s is ready", name)
|
logger.Successf("Kustomization %s is ready", name)
|
||||||
@@ -302,3 +303,28 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("Kustomization updated")
|
logger.Successf("Kustomization updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
@@ -138,8 +139,8 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Receiver reconciliation")
|
logger.Waitingf("waiting for Receiver reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, &receiver)); err != nil {
|
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Receiver %s is ready", name)
|
logger.Successf("Receiver %s is ready", name)
|
||||||
@@ -178,3 +179,23 @@ func upsertReceiver(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("Receiver updated")
|
logger.Successf("Receiver updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
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")
|
logger.Waitingf("waiting for Bucket source reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, bucket)); err != nil {
|
isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Bucket source reconciliation completed")
|
logger.Successf("Bucket source reconciliation completed")
|
||||||
@@ -246,3 +247,30 @@ func upsertBucket(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("Bucket source updated")
|
logger.Successf("Bucket source updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
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")
|
logger.Waitingf("waiting for GitRepository source reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, &gitRepository)); err != nil {
|
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("GitRepository source reconciliation completed")
|
logger.Successf("GitRepository source reconciliation completed")
|
||||||
@@ -367,3 +368,30 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("GitRepository source updated")
|
logger.Successf("GitRepository source updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -181,21 +181,12 @@ func TestCreateSourceGit(t *testing.T) {
|
|||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
repo.Status.ObservedGeneration = repo.GetGeneration()
|
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
"Failed",
|
"Failed",
|
||||||
command,
|
command,
|
||||||
assertError("failed message"),
|
assertError("failed message"),
|
||||||
func(repo *sourcev1.GitRepository) {
|
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{
|
newCondition := metav1.Condition{
|
||||||
Type: meta.ReadyCondition,
|
Type: meta.ReadyCondition,
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
@@ -204,7 +195,6 @@ func TestCreateSourceGit(t *testing.T) {
|
|||||||
ObservedGeneration: repo.GetGeneration(),
|
ObservedGeneration: repo.GetGeneration(),
|
||||||
}
|
}
|
||||||
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
||||||
repo.Status.ObservedGeneration = repo.GetGeneration()
|
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
"NoArtifact",
|
"NoArtifact",
|
||||||
@@ -220,7 +210,6 @@ func TestCreateSourceGit(t *testing.T) {
|
|||||||
ObservedGeneration: repo.GetGeneration(),
|
ObservedGeneration: repo.GetGeneration(),
|
||||||
}
|
}
|
||||||
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
||||||
repo.Status.ObservedGeneration = repo.GetGeneration()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"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")
|
logger.Waitingf("waiting for HelmRepository source reconciliation")
|
||||||
readyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository)
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
if helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
|
isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil {
|
||||||
// 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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("HelmRepository source reconciliation completed")
|
logger.Successf("HelmRepository source reconciliation completed")
|
||||||
@@ -282,3 +279,30 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("source updated")
|
logger.Successf("source updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
@@ -50,18 +51,16 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type sourceOCIRepositoryFlags struct {
|
type sourceOCIRepositoryFlags struct {
|
||||||
url string
|
url string
|
||||||
tag string
|
tag string
|
||||||
semver string
|
semver string
|
||||||
digest string
|
digest string
|
||||||
secretRef string
|
secretRef string
|
||||||
serviceAccount string
|
serviceAccount string
|
||||||
certSecretRef string
|
certSecretRef string
|
||||||
verifyProvider flags.SourceOCIVerifyProvider
|
ignorePaths []string
|
||||||
verifySecretRef string
|
provider flags.SourceOCIProvider
|
||||||
ignorePaths []string
|
insecure bool
|
||||||
provider flags.SourceOCIProvider
|
|
||||||
insecure bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceOCIRepositoryArgs = newSourceOCIFlags()
|
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.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.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
||||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
|
||||||
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description())
|
|
||||||
createSourceOCIRepositoryCmd.Flags().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().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")
|
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 {
|
if createArgs.export {
|
||||||
return printExport(exportOCIRepository(repository))
|
return printExport(exportOCIRepository(repository))
|
||||||
}
|
}
|
||||||
@@ -191,8 +175,8 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for OCIRepository reconciliation")
|
logger.Waitingf("waiting for OCIRepository reconciliation")
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
isObjectReadyConditionFunc(kubeClient, namespacedName, repository)); err != nil {
|
isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("OCIRepository reconciliation completed")
|
logger.Successf("OCIRepository reconciliation completed")
|
||||||
@@ -234,3 +218,30 @@ func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("OCIRepository updated")
|
logger.Successf("OCIRepository updated")
|
||||||
return namespacedName, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ func TestCreateSourceOCI(t *testing.T) {
|
|||||||
args: "create source oci podinfo",
|
args: "create source oci podinfo",
|
||||||
assertFunc: assertError("url is required"),
|
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",
|
name: "export manifest",
|
||||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export",
|
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",
|
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"),
|
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 {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteAlertCmd = &cobra.Command{
|
var deleteAlertCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteAlertProviderCmd = &cobra.Command{
|
var deleteAlertProviderCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import (
|
|||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
@@ -62,14 +62,8 @@ var eventsCmd = &cobra.Command{
|
|||||||
# Display events for flux resources in all namespaces
|
# Display events for flux resources in all namespaces
|
||||||
flux events -A
|
flux events -A
|
||||||
|
|
||||||
# Display events for a Kustomization named podinfo
|
# Display events for flux resources
|
||||||
flux events --for Kustomization/podinfo
|
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,
|
RunE: eventsCmdRun,
|
||||||
}
|
}
|
||||||
@@ -90,7 +84,7 @@ func init() {
|
|||||||
"indicate if the events should be streamed")
|
"indicate if the events should be streamed")
|
||||||
eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "",
|
eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "",
|
||||||
"get events for a particular object")
|
"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)
|
rootCmd.AddCommand(eventsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,10 +92,6 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := validateEventTypes(eventArgs.filterTypes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -113,33 +103,21 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var diffRefNs bool
|
var diffRefNs bool
|
||||||
clientListOpts := []client.ListOption{client.InNamespace(*kubeconfigArgs.Namespace)}
|
clientListOpts := getListOpt(namespace, eventArgs.forSelector)
|
||||||
var refListOpts [][]client.ListOption
|
var refListOpts [][]client.ListOption
|
||||||
if eventArgs.forSelector != "" {
|
if eventArgs.forSelector != "" {
|
||||||
kind, name := getKindNameFromSelector(eventArgs.forSelector)
|
refs, err := getObjectRef(ctx, kubeclient, eventArgs.forSelector, *kubeconfigArgs.Namespace)
|
||||||
if kind == "" {
|
|
||||||
return fmt.Errorf("--for selector must be of format <kind>[/<name>]")
|
|
||||||
}
|
|
||||||
|
|
||||||
refInfoKind, err := fluxKindMap.getRefInfo(kind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
for _, ref := range refs {
|
||||||
refKind, refName, refNs := utils.ParseObjectKindNameNamespace(ref)
|
kind, name, refNs := utils.ParseObjectKindNameNamespace(ref)
|
||||||
if refNs != namespace {
|
if refNs != namespace {
|
||||||
diffRefNs = true
|
diffRefNs = true
|
||||||
}
|
|
||||||
refOpt := []client.ListOption{getListOpt(refKind, refName), client.InNamespace(refNs)}
|
|
||||||
refListOpts = append(refListOpts, refOpt)
|
|
||||||
}
|
}
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
headers := getHeaders(showNamespace)
|
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) {
|
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 {
|
func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
|
||||||
listOpts := &metav1.ListOptions{}
|
listOpts := &metav1.ListOptions{}
|
||||||
clientListOpts = append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
|
|
||||||
err := runtimeresource.FollowContinue(listOpts,
|
err := runtimeresource.FollowContinue(listOpts,
|
||||||
func(options metav1.ListOptions) (runtime.Object, error) {
|
func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
newEvents := &corev1.EventList{}
|
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)
|
return nil, fmt.Errorf("error getting events: %w", err)
|
||||||
}
|
}
|
||||||
el.Items = append(el.Items, newEvents.Items...)
|
el.Items = append(el.Items, newEvents.Items...)
|
||||||
@@ -206,22 +185,21 @@ func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.E
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getListOpt(kind, name string) client.ListOption {
|
func getListOpt(namespace, selector string) []client.ListOption {
|
||||||
var sel fields.Selector
|
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
|
||||||
if name == "" {
|
if selector != "" {
|
||||||
sel = fields.OneTermEqualSelector("involvedObject.kind", kind)
|
kind, name := utils.ParseObjectKindName(selector)
|
||||||
} else {
|
sel := fields.AndSelectors(
|
||||||
sel = fields.AndSelectors(
|
|
||||||
fields.OneTermEqualSelector("involvedObject.kind", kind),
|
fields.OneTermEqualSelector("involvedObject.kind", kind),
|
||||||
fields.OneTermEqualSelector("involvedObject.name", name))
|
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 {
|
func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error {
|
||||||
event := &corev1.EventList{}
|
event := &corev1.EventList{}
|
||||||
listOpts = append(listOpts, client.Limit(cmdutil.DefaultChunkSize))
|
|
||||||
eventWatch, err := kubeclient.Watch(ctx, event, listOpts...)
|
eventWatch, err := kubeclient.Watch(ctx, event, listOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -247,7 +225,12 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
|
|||||||
hdr = getHeaders(showNs)
|
hdr = getHeaders(showNs)
|
||||||
firstIteration = false
|
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 {
|
for _, refOpts := range refListOpts {
|
||||||
@@ -256,7 +239,8 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
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())
|
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.
|
// 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
|
// 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.
|
// 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
|
// the resource has no source ref
|
||||||
if len(ref.field) == 0 {
|
if len(ref.field) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -313,30 +303,31 @@ func getObjectRef(ctx context.Context, kubeclient client.Client, ref refInfo, na
|
|||||||
|
|
||||||
obj := &unstructured.Unstructured{}
|
obj := &unstructured.Unstructured{}
|
||||||
obj.SetGroupVersionKind(schema.GroupVersionKind{
|
obj.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
Kind: ref.gvk.Kind,
|
Kind: kind,
|
||||||
Version: ref.gvk.Version,
|
Version: ref.gv.Version,
|
||||||
Group: ref.gvk.Group,
|
Group: ref.gv.Group,
|
||||||
})
|
})
|
||||||
objName := types.NamespacedName{
|
objName := types.NamespacedName{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := kubeclient.Get(ctx, objName, obj); err != nil {
|
err = kubeclient.Get(ctx, objName, obj)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
refKind := ref.kind
|
refKind := ref.kind
|
||||||
if refKind == "" {
|
if refKind == "" {
|
||||||
kindField := append(ref.field, "kind")
|
kindField := append(ref.field, "kind")
|
||||||
specKind, ok, err := unstructured.NestedString(obj.Object, kindField...)
|
refKind, ok, err = unstructured.NestedString(obj.Object, kindField...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName)
|
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName)
|
||||||
}
|
}
|
||||||
refKind = specKind
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nameField := append(ref.field, "name")
|
nameField := append(ref.field, "name")
|
||||||
@@ -386,71 +377,49 @@ func (r refMap) hasKind(kind string) bool {
|
|||||||
return err == nil
|
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 {
|
type refInfo struct {
|
||||||
// gvk is the group version kind of the resource
|
gv schema.GroupVersion
|
||||||
gvk schema.GroupVersionKind
|
kind string
|
||||||
// kind is the kind that the resource references if it's not static
|
|
||||||
kind string
|
|
||||||
// crossNamespaced indicates if this resource uses cross namespaced references
|
|
||||||
crossNamespaced bool
|
crossNamespaced bool
|
||||||
// otherRefs returns other reference that might not be directly accessible
|
otherRefs func(namespace, name string) []string
|
||||||
// from the spec of the object
|
field []string
|
||||||
otherRefs func(namespace, name string) []string
|
|
||||||
field []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var fluxKindMap = refMap{
|
var fluxKindMap = refMap{
|
||||||
kustomizev1.KustomizationKind: {
|
kustomizev1.KustomizationKind: {
|
||||||
gvk: kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind),
|
gv: kustomizev1.GroupVersion,
|
||||||
crossNamespaced: true,
|
crossNamespaced: true,
|
||||||
field: []string{"spec", "sourceRef"},
|
field: []string{"spec", "sourceRef"},
|
||||||
},
|
},
|
||||||
helmv2.HelmReleaseKind: {
|
helmv2.HelmReleaseKind: {
|
||||||
gvk: helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind),
|
gv: helmv2.GroupVersion,
|
||||||
crossNamespaced: true,
|
crossNamespaced: true,
|
||||||
otherRefs: func(namespace, name string) []string {
|
otherRefs: func(namespace, name string) []string {
|
||||||
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
|
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
|
||||||
},
|
},
|
||||||
field: []string{"spec", "chart", "spec", "sourceRef"},
|
field: []string{"spec", "chart", "spec", "sourceRef"},
|
||||||
},
|
},
|
||||||
notificationv1b3.AlertKind: {
|
notificationv1b2.AlertKind: {
|
||||||
gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.AlertKind),
|
gv: notificationv1b2.GroupVersion,
|
||||||
kind: notificationv1b3.ProviderKind,
|
kind: notificationv1b2.ProviderKind,
|
||||||
crossNamespaced: false,
|
crossNamespaced: false,
|
||||||
field: []string{"spec", "providerRef"},
|
field: []string{"spec", "providerRef"},
|
||||||
},
|
},
|
||||||
notificationv1.ReceiverKind: {gvk: notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)},
|
notificationv1.ReceiverKind: {gv: notificationv1.GroupVersion},
|
||||||
notificationv1b3.ProviderKind: {gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.ProviderKind)},
|
notificationv1b2.ProviderKind: {gv: notificationv1b2.GroupVersion},
|
||||||
imagev1.ImagePolicyKind: {
|
imagev1.ImagePolicyKind: {
|
||||||
gvk: imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind),
|
gv: imagev1.GroupVersion,
|
||||||
kind: imagev1.ImageRepositoryKind,
|
kind: imagev1.ImageRepositoryKind,
|
||||||
crossNamespaced: true,
|
crossNamespaced: true,
|
||||||
field: []string{"spec", "imageRepositoryRef"},
|
field: []string{"spec", "imageRepositoryRef"},
|
||||||
},
|
},
|
||||||
sourcev1b2.HelmChartKind: {
|
sourcev1.GitRepositoryKind: {gv: sourcev1.GroupVersion},
|
||||||
gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmChartKind),
|
sourcev1b2.OCIRepositoryKind: {gv: sourcev1b2.GroupVersion},
|
||||||
crossNamespaced: true,
|
sourcev1b2.BucketKind: {gv: sourcev1b2.GroupVersion},
|
||||||
field: []string{"spec", "sourceRef"},
|
sourcev1b2.HelmRepositoryKind: {gv: sourcev1b2.GroupVersion},
|
||||||
},
|
sourcev1b2.HelmChartKind: {gv: sourcev1b2.GroupVersion},
|
||||||
sourcev1.GitRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)},
|
autov1.ImageUpdateAutomationKind: {gv: autov1.GroupVersion},
|
||||||
sourcev1b2.OCIRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.OCIRepositoryKind)},
|
imagev1.ImageRepositoryKind: {gv: imagev1.GroupVersion},
|
||||||
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)},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ignoreEvent(e corev1.Event) bool {
|
func ignoreEvent(e corev1.Event) bool {
|
||||||
@@ -468,19 +437,7 @@ func ignoreEvent(e corev1.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKindNameFromSelector(selector string) (string, string) {
|
// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/events/events.go#L347
|
||||||
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
|
|
||||||
|
|
||||||
// SortableEvents implements sort.Interface for []api.Event by time
|
// SortableEvents implements sort.Interface for []api.Event by time
|
||||||
type SortableEvents []corev1.Event
|
type SortableEvents []corev1.Event
|
||||||
|
|||||||
@@ -27,11 +27,21 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"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"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
"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"
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||||
"github.com/fluxcd/pkg/ssa"
|
"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"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
)
|
)
|
||||||
@@ -118,7 +128,7 @@ spec:
|
|||||||
name: podinfo-chart
|
name: podinfo-chart
|
||||||
version: '*'
|
version: '*'
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: webapp
|
name: webapp
|
||||||
@@ -131,7 +141,7 @@ spec:
|
|||||||
providerRef:
|
providerRef:
|
||||||
name: slack
|
name: slack
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: slack
|
name: slack
|
||||||
@@ -163,7 +173,7 @@ func Test_getObjectRef(t *testing.T) {
|
|||||||
objs, err := ssa.ReadObjects(strings.NewReader(objects))
|
objs, err := ssa.ReadObjects(strings.NewReader(objects))
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
|
||||||
builder := fake.NewClientBuilder().WithScheme(utils.NewScheme())
|
builder := fake.NewClientBuilder().WithScheme(getScheme())
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
builder = builder.WithObjects(obj)
|
builder = builder.WithObjects(obj)
|
||||||
}
|
}
|
||||||
@@ -206,12 +216,6 @@ func Test_getObjectRef(t *testing.T) {
|
|||||||
namespace: "default",
|
namespace: "default",
|
||||||
want: []string{"ImageRepository/acr-podinfo.flux-system"},
|
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",
|
name: "Empty Ref for Provider",
|
||||||
selector: "Provider/slack",
|
selector: "Provider/slack",
|
||||||
@@ -228,13 +232,11 @@ func Test_getObjectRef(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
kind, name := getKindNameFromSelector(tt.selector)
|
got, err := getObjectRef(context.Background(), c, tt.selector, tt.namespace)
|
||||||
infoRef, err := fluxKindMap.getRefInfo(kind)
|
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
g.Expect(err).To(HaveOccurred())
|
g.Expect(err).To(HaveOccurred())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
got, err := getObjectRef(context.Background(), c, infoRef, name, tt.namespace)
|
|
||||||
|
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
g.Expect(got).To(Equal(tt.want))
|
g.Expect(got).To(Equal(tt.want))
|
||||||
@@ -247,7 +249,7 @@ func Test_getRows(t *testing.T) {
|
|||||||
objs, err := ssa.ReadObjects(strings.NewReader(objects))
|
objs, err := ssa.ReadObjects(strings.NewReader(objects))
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
|
||||||
builder := fake.NewClientBuilder().WithScheme(utils.NewScheme())
|
builder := fake.NewClientBuilder().WithScheme(getScheme())
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
builder = builder.WithObjects(obj)
|
builder = builder.WithObjects(obj)
|
||||||
}
|
}
|
||||||
@@ -259,7 +261,6 @@ func Test_getRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
builder = builder.WithLists(eventList)
|
builder = builder.WithLists(eventList)
|
||||||
builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer)
|
builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer)
|
||||||
builder.WithIndex(&corev1.Event{}, "involvedObject.kind", kindIndexer)
|
|
||||||
c := builder.Build()
|
c := builder.Build()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -319,16 +320,6 @@ func Test_getRows(t *testing.T) {
|
|||||||
{"flux-system", "<unknown>", "info", "Info Reason", "GitRepository/flux-system", "Info Message"},
|
{"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",
|
name: "HelmRelease with crossnamespaced HelmRepository",
|
||||||
selector: "HelmRelease/podinfo",
|
selector: "HelmRelease/podinfo",
|
||||||
@@ -342,19 +333,6 @@ func Test_getRows(t *testing.T) {
|
|||||||
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
|
{"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 {
|
for _, tt := range tests {
|
||||||
@@ -363,49 +341,59 @@ func Test_getRows(t *testing.T) {
|
|||||||
|
|
||||||
var refs []string
|
var refs []string
|
||||||
var refNs, refKind, refName string
|
var refNs, refKind, refName string
|
||||||
var clientOpts = []client.ListOption{client.InNamespace(tt.namespace)}
|
|
||||||
if tt.selector != "" {
|
if tt.selector != "" {
|
||||||
kind, name := getKindNameFromSelector(tt.selector)
|
refs, err = getObjectRef(context.Background(), c, tt.selector, tt.namespace)
|
||||||
infoRef, err := fluxKindMap.getRefInfo(kind)
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
|
||||||
|
clientOpts := getTestListOpt(tt.namespace, tt.selector)
|
||||||
var refOpts [][]client.ListOption
|
var refOpts [][]client.ListOption
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
refKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref)
|
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)
|
showNs := tt.namespace == "" || (refNs != "" && refNs != tt.namespace)
|
||||||
rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)
|
rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
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 {
|
func getTestListOpt(namespace, selector string) []client.ListOption {
|
||||||
var sel fields.Selector
|
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
|
||||||
if name == "" {
|
if selector != "" {
|
||||||
sel = fields.OneTermEqualSelector("involvedObject.kind", kind)
|
sel := fields.OneTermEqualSelector("involvedObject.kind/name", selector)
|
||||||
} else {
|
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
|
||||||
sel = fields.OneTermEqualSelector("involvedObject.kind/name", fmt.Sprintf("%s/%s", kind, name))
|
|
||||||
}
|
}
|
||||||
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 {
|
func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event {
|
||||||
return corev1.Event{
|
return corev1.Event{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: obj.GetNamespace(),
|
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,
|
Name: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType,
|
||||||
},
|
},
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
@@ -427,12 +415,3 @@ func kindNameIndexer(obj client.Object) []string {
|
|||||||
|
|
||||||
return []string{fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name)}
|
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}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportAlertCmd = &cobra.Command{
|
var exportAlertCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportAlertProviderCmd = &cobra.Command{
|
var exportAlertProviderCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type exportableWithSecretList interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type exportWithSecretCommand struct {
|
type exportWithSecretCommand struct {
|
||||||
|
apiType
|
||||||
object exportableWithSecret
|
object exportableWithSecret
|
||||||
list exportableWithSecretList
|
list exportableWithSecretList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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"
|
"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{
|
var getAlertCmd = &cobra.Command{
|
||||||
@@ -78,9 +76,8 @@ func init() {
|
|||||||
|
|
||||||
func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||||
item := s.Items[i]
|
item := s.Items[i]
|
||||||
status, msg := string(metav1.ConditionTrue), "Alert is Ready"
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s alertListAdapter) headers(includeNamespace bool) []string {
|
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 {
|
func (s alertListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
|
||||||
return false
|
item := s.Items[i]
|
||||||
|
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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{
|
var getAlertProviderCmd = &cobra.Command{
|
||||||
@@ -75,7 +74,7 @@ func init() {
|
|||||||
|
|
||||||
func (s alertProviderListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
func (s alertProviderListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||||
item := s.Items[i]
|
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)
|
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 {
|
func (s alertProviderListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
|
||||||
return false
|
item := s.Items[i]
|
||||||
|
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var getAllCmd = &cobra.Command{
|
var getAllCmd = &cobra.Command{
|
||||||
@@ -63,11 +63,11 @@ var getAllCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
apiType: alertProviderType,
|
apiType: alertProviderType,
|
||||||
list: alertProviderListAdapter{¬ificationv1b3.ProviderList{}},
|
list: alertProviderListAdapter{¬ificationv1b2.ProviderList{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
apiType: alertType,
|
apiType: alertType,
|
||||||
list: &alertListAdapter{¬ificationv1b3.AlertList{}},
|
list: &alertListAdapter{¬ificationv1b2.AlertList{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var getHelmReleaseCmd = &cobra.Command{
|
var getHelmReleaseCmd = &cobra.Command{
|
||||||
@@ -77,7 +75,7 @@ func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, incl
|
|||||||
revision := item.Status.LastAppliedRevision
|
revision := item.Status.LastAppliedRevision
|
||||||
status, msg := statusAndMessage(item.Status.Conditions)
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
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)
|
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
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 {
|
if item.Status.LastAutomationRunTime != nil {
|
||||||
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
|
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind), lastRun,
|
return append(nameColumns(&item, includeNamespace, includeKind), lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
|
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
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)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
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 {
|
func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||||
item := s.Items[i]
|
item := s.Items[i]
|
||||||
status, msg := statusAndMessage(item.Status.Conditions)
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s receiverListAdapter) headers(includeNamespace bool) []string {
|
func (s receiverListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
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)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a bucketListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
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
|
// Message may still contain reference of e.g. commit chart was build from
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a helmChartListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
@@ -86,7 +85,7 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
|
|||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
@@ -83,16 +81,11 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
|
|||||||
if item.GetArtifact() != nil {
|
if item.GetArtifact() != nil {
|
||||||
revision = item.GetArtifact().Revision
|
revision = item.GetArtifact().Revision
|
||||||
}
|
}
|
||||||
var status, msg string
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
if item.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
|
|
||||||
status, msg = string(metav1.ConditionTrue), "Helm repository is Ready"
|
|
||||||
} else {
|
|
||||||
status, msg = statusAndMessage(item.Status.Conditions)
|
|
||||||
}
|
|
||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
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)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
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 {
|
func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
@@ -36,7 +34,6 @@ import (
|
|||||||
|
|
||||||
var installCmd = &cobra.Command{
|
var installCmd = &cobra.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Short: "Install or upgrade Flux",
|
Short: "Install or upgrade Flux",
|
||||||
Long: `The install command deploys Flux in the specified namespace.
|
Long: `The install command deploys Flux in the specified namespace.
|
||||||
If a previous version is installed, then an in-place upgrade will be performed.`,
|
If a previous version is installed, then an in-place upgrade will be performed.`,
|
||||||
@@ -75,7 +72,6 @@ type installFlags struct {
|
|||||||
tokenAuth bool
|
tokenAuth bool
|
||||||
clusterDomain string
|
clusterDomain string
|
||||||
tolerationKeys []string
|
tolerationKeys []string
|
||||||
force bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var installArgs = NewInstallFlags()
|
var installArgs = NewInstallFlags()
|
||||||
@@ -102,7 +98,6 @@ func init() {
|
|||||||
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
|
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
|
||||||
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
|
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
|
||||||
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
|
"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")
|
installCmd.Flags().MarkHidden("manifests")
|
||||||
|
|
||||||
rootCmd.AddCommand(installCmd)
|
rootCmd.AddCommand(installCmd)
|
||||||
@@ -188,35 +183,6 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("manifests build completed")
|
logger.Successf("manifests build completed")
|
||||||
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
|
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))
|
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("install failed: %w", err)
|
return fmt.Errorf("install failed: %w", err)
|
||||||
|
|||||||
@@ -37,11 +37,6 @@ func TestInstall(t *testing.T) {
|
|||||||
args: "install --namespace='@#[]'",
|
args: "install --namespace='@#[]'",
|
||||||
assert: assertError("namespace must be a valid DNS label: \"@#[]\""),
|
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 {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ type logsFlags struct {
|
|||||||
fluxNamespace string
|
fluxNamespace string
|
||||||
allNamespaces bool
|
allNamespaces bool
|
||||||
sinceTime string
|
sinceTime string
|
||||||
sinceDuration time.Duration
|
sinceSeconds time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var logsArgs = logsFlags{
|
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().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().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().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.")
|
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)
|
rootCmd.AddCommand(logsCmd)
|
||||||
}
|
}
|
||||||
@@ -129,8 +129,8 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logOpts.TailLines = &logsArgs.tail
|
logOpts.TailLines = &logsArgs.tail
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceDuration != 0 {
|
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceSeconds != 0 {
|
||||||
return fmt.Errorf("at most one of `sinceTime` or `sinceDuration` may be specified")
|
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(logsArgs.sinceTime) > 0 {
|
if len(logsArgs.sinceTime) > 0 {
|
||||||
@@ -141,9 +141,9 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logOpts.SinceTime = &t
|
logOpts.SinceTime = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
if logsArgs.sinceDuration != 0 {
|
if logsArgs.sinceSeconds != 0 {
|
||||||
// round up to the nearest second
|
// 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
|
logOpts.SinceSeconds = &sec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func TestLogsSinceTimeInvalid(t *testing.T) {
|
|||||||
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
|
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
|
||||||
cmd := cmdTestCase{
|
cmd := cmdTestCase{
|
||||||
args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z",
|
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)
|
cmd.runTestCmd(t)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
log.SetLogger(logr.New(log.NullLogSink{}))
|
|
||||||
|
|
||||||
// Ensure tests print consistent timestamps regardless of timezone
|
// Ensure tests print consistent timestamps regardless of timezone
|
||||||
os.Setenv("TZ", "UTC")
|
os.Setenv("TZ", "UTC")
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import (
|
|||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
@@ -113,8 +112,7 @@ func (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstruc
|
|||||||
}
|
}
|
||||||
obj.SetResourceVersion(createObj.GetResourceVersion())
|
obj.SetResourceVersion(createObj.GetResourceVersion())
|
||||||
err = m.client.Status().Update(context.Background(), obj)
|
err = m.client.Status().Update(context.Background(), obj)
|
||||||
// Updating status of static objects results in not found error.
|
if err != nil {
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +182,7 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String())
|
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{
|
k8sClient, err := client.NewWithWatch(cfg, client.Options{
|
||||||
Scheme: utils.NewScheme(),
|
Scheme: utils.NewScheme(),
|
||||||
})
|
})
|
||||||
@@ -205,9 +203,6 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
|
|||||||
|
|
||||||
useExistingCluster := true
|
useExistingCluster := true
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
|
config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
testEnv := &envtest.Environment{
|
testEnv := &envtest.Environment{
|
||||||
UseExistingCluster: &useExistingCluster,
|
UseExistingCluster: &useExistingCluster,
|
||||||
Config: config,
|
Config: config,
|
||||||
@@ -315,7 +310,7 @@ func assertGoldenTemplateFile(goldenFile string, templateValues map[string]strin
|
|||||||
if len(templateValues) > 0 {
|
if len(templateValues) > 0 {
|
||||||
fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually")
|
fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually")
|
||||||
} else {
|
} 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 fmt.Errorf("failed to update golden file '%s': %v", goldenFile, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -342,6 +337,8 @@ type cmdTestCase struct {
|
|||||||
// Tests use assertFunc to assert on an output, success or failure. This
|
// Tests use assertFunc to assert on an output, success or failure. This
|
||||||
// can be a function defined by the test or existing function above.
|
// can be a function defined by the test or existing function above.
|
||||||
assert assertFunc
|
assert assertFunc
|
||||||
|
// Filename that contains yaml objects to load into Kubernetes
|
||||||
|
objectFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
||||||
|
|||||||
@@ -22,13 +22,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/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
|
// The test environment is long running process shared between tests, initialized
|
||||||
@@ -37,8 +34,6 @@ import (
|
|||||||
var testEnv *testEnvKubeManager
|
var testEnv *testEnvKubeManager
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
log.SetLogger(logr.New(log.NullLogSink{}))
|
|
||||||
|
|
||||||
// Ensure tests print consistent timestamps regardless of timezone
|
// Ensure tests print consistent timestamps regardless of timezone
|
||||||
os.Setenv("TZ", "UTC")
|
os.Setenv("TZ", "UTC")
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/google/go-containerregistry/pkg/crane"
|
"github.com/google/go-containerregistry/pkg/crane"
|
||||||
"github.com/google/go-containerregistry/pkg/logs"
|
"github.com/google/go-containerregistry/pkg/logs"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"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"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -260,20 +261,17 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ociClient := client.NewClient(opts)
|
ociClient := client.NewClient(opts)
|
||||||
digestURL, err := ociClient.Push(ctx, url, path,
|
digestURL, err := ociClient.Push(ctx, url, path, meta, pushArtifactArgs.ignorePaths)
|
||||||
client.WithPushMetadata(meta),
|
|
||||||
client.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pushing artifact failed: %w", err)
|
return fmt.Errorf("pushing artifact failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
digest, err := name.NewDigest(digestURL)
|
digest, err := reg.NewDigest(digestURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("artifact digest parsing failed: %w", err)
|
return fmt.Errorf("artifact digest parsing failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := name.NewTag(url)
|
tag, err := reg.NewTag(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("artifact tag parsing failed: %w", err)
|
return fmt.Errorf("artifact tag parsing failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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 := ¬ificationv1.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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@@ -31,6 +30,8 @@ import (
|
|||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
@@ -60,7 +61,6 @@ type reconcilable interface {
|
|||||||
GetAnnotations() map[string]string
|
GetAnnotations() map[string]string
|
||||||
SetAnnotations(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?
|
lastHandledReconcileRequest() string // what was the last handled reconcile request?
|
||||||
successMessage() string // what do you want to tell people when successfully reconciled?
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if reconcile.object.isStatic() {
|
|
||||||
logger.Successf("reconciliation not supported by the object")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if reconcile.object.isSuspended() {
|
if reconcile.object.isSuspended() {
|
||||||
return fmt.Errorf("resource is suspended")
|
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)
|
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()
|
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
|
||||||
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)
|
readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)
|
||||||
@@ -135,23 +140,16 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconciliationHandled(kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionWithContextFunc {
|
func reconciliationHandled(ctx context.Context, kubeClient client.Client,
|
||||||
return func(ctx context.Context) (bool, error) {
|
namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc {
|
||||||
|
return func() (bool, error) {
|
||||||
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
|
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
isProgressing := apimeta.IsStatusConditionPresentAndEqual(reconcilableConditions(obj),
|
||||||
if obj.lastHandledReconcileRequest() == lastHandledReconcileAt {
|
meta.ReadyCondition, metav1.ConditionUnknown)
|
||||||
return false, nil
|
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt && !isProgressing, nil
|
||||||
}
|
|
||||||
|
|
||||||
result, err := kstatusCompute(obj.asClientObject())
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Status == kstatus.CurrentStatus, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,3 +175,23 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client,
|
|||||||
return kubeClient.Patch(ctx, object, patch)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
44
cmd/flux/reconcile_alert.go
Normal file
44
cmd/flux/reconcile_alert.go
Normal 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{¬ificationv1.Alert{}},
|
||||||
|
}.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reconcileCmd.AddCommand(reconcileAlertCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj alertAdapter) lastHandledReconcileRequest() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
93
cmd/flux/reconcile_alertprovider.go
Normal file
93
cmd/flux/reconcile_alertprovider.go
Normal 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
|
||||||
|
}
|
||||||
@@ -81,7 +81,3 @@ func (obj helmReleaseAdapter) getSource() (reconcileSource, types.NamespacedName
|
|||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj helmReleaseAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,7 +48,3 @@ func (obj imageRepositoryAdapter) lastHandledReconcileRequest() string {
|
|||||||
func (obj imageRepositoryAdapter) successMessage() string {
|
func (obj imageRepositoryAdapter) successMessage() string {
|
||||||
return fmt.Sprintf("scan fetched %d tags", obj.Status.LastScanResult.TagCount)
|
return fmt.Sprintf("scan fetched %d tags", obj.Status.LastScanResult.TagCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj imageRepositoryAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ func init() {
|
|||||||
reconcileImageCmd.AddCommand(reconcileImageUpdateCmd)
|
reconcileImageCmd.AddCommand(reconcileImageUpdateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj imageUpdateAutomationAdapter) suspended() bool {
|
||||||
|
return obj.ImageUpdateAutomation.Spec.Suspend
|
||||||
|
}
|
||||||
|
|
||||||
func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {
|
func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {
|
||||||
return obj.Status.GetLastHandledReconcileRequest()
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
}
|
}
|
||||||
@@ -56,7 +60,3 @@ func (obj imageUpdateAutomationAdapter) successMessage() string {
|
|||||||
}
|
}
|
||||||
return "automation not yet run"
|
return "automation not yet run"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj imageUpdateAutomationAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -88,7 +88,3 @@ func (obj kustomizationAdapter) getSource() (reconcileSource, types.NamespacedNa
|
|||||||
Namespace: obj.Spec.SourceRef.Namespace,
|
Namespace: obj.Spec.SourceRef.Namespace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj kustomizationAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,9 +17,18 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
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{
|
var reconcileReceiverCmd = &cobra.Command{
|
||||||
@@ -29,20 +38,62 @@ var reconcileReceiverCmd = &cobra.Command{
|
|||||||
Example: ` # Trigger a reconciliation for an existing receiver
|
Example: ` # Trigger a reconciliation for an existing receiver
|
||||||
flux reconcile receiver main`,
|
flux reconcile receiver main`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileReceiverCmdRun,
|
||||||
apiType: receiverType,
|
|
||||||
object: receiverAdapter{¬ificationv1.Receiver{}},
|
|
||||||
}.run,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reconcileCmd.AddCommand(reconcileReceiverCmd)
|
reconcileCmd.AddCommand(reconcileReceiverCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj receiverAdapter) lastHandledReconcileRequest() string {
|
func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
return obj.Status.GetLastHandledReconcileRequest()
|
if len(args) < 1 {
|
||||||
}
|
return fmt.Errorf("receiver name is required")
|
||||||
|
}
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
func (obj receiverAdapter) isStatic() bool {
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
return false
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,3 @@ func (obj bucketAdapter) lastHandledReconcileRequest() string {
|
|||||||
func (obj bucketAdapter) successMessage() string {
|
func (obj bucketAdapter) successMessage() string {
|
||||||
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj bucketAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -84,7 +84,3 @@ func (obj helmChartAdapter) getSource() (reconcileSource, types.NamespacedName)
|
|||||||
Namespace: obj.Namespace,
|
Namespace: obj.Namespace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj helmChartAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,7 +48,3 @@ func (obj gitRepositoryAdapter) lastHandledReconcileRequest() string {
|
|||||||
func (obj gitRepositoryAdapter) successMessage() string {
|
func (obj gitRepositoryAdapter) successMessage() string {
|
||||||
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj gitRepositoryAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -60,7 +60,3 @@ func (obj helmRepositoryAdapter) successMessage() string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj helmRepositoryAdapter) isStatic() bool {
|
|
||||||
return obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,7 +48,3 @@ func (obj ociRepositoryAdapter) lastHandledReconcileRequest() string {
|
|||||||
func (obj ociRepositoryAdapter) successMessage() string {
|
func (obj ociRepositoryAdapter) successMessage() string {
|
||||||
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj ociRepositoryAdapter) isStatic() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
|
|||||||
logger.Successf("%s annotated", reconcile.kind)
|
logger.Successf("%s annotated", reconcile.kind)
|
||||||
|
|
||||||
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ type resumable interface {
|
|||||||
copyable
|
copyable
|
||||||
statusable
|
statusable
|
||||||
setUnsuspended()
|
setUnsuspended()
|
||||||
isStatic() bool
|
|
||||||
successMessage() string
|
successMessage() string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,12 +212,8 @@ func (resume resumeCommand) reconcile(ctx context.Context, res resumable) reconc
|
|||||||
|
|
||||||
logger.Waitingf("waiting for %s reconciliation", resume.kind)
|
logger.Waitingf("waiting for %s reconciliation", resume.kind)
|
||||||
|
|
||||||
readyConditionFunc := isObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject())
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
if res.isStatic() {
|
isReady(ctx, resume.client, namespacedName, res)); err != nil {
|
||||||
readyConditionFunc = isStaticObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {
|
|
||||||
return reconcileResponse{
|
return reconcileResponse{
|
||||||
resumable: res,
|
resumable: res,
|
||||||
err: err,
|
err: err,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeAlertCmd = &cobra.Command{
|
var resumeAlertCmd = &cobra.Command{
|
||||||
@@ -44,7 +44,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj alertAdapter) getObservedGeneration() int64 {
|
func (obj alertAdapter) getObservedGeneration() int64 {
|
||||||
return 0
|
return obj.Alert.Status.ObservedGeneration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj alertAdapter) setUnsuspended() {
|
func (obj alertAdapter) setUnsuspended() {
|
||||||
@@ -55,10 +55,6 @@ func (obj alertAdapter) successMessage() string {
|
|||||||
return "Alert reconciliation completed"
|
return "Alert reconciliation completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a alertAdapter) isStatic() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a alertListAdapter) resumeItem(i int) resumable {
|
func (a alertListAdapter) resumeItem(i int) resumable {
|
||||||
return &alertAdapter{&a.AlertList.Items[i]}
|
return &alertAdapter{&a.AlertList.Items[i]}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{¬ificationv1.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]}
|
|
||||||
}
|
|
||||||
@@ -24,20 +24,19 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
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{
|
var statsCmd = &cobra.Command{
|
||||||
@@ -111,14 +110,14 @@ func runStatsCmd(cmd *cobra.Command, args []string) error {
|
|||||||
Group: helmv2.GroupVersion.Group,
|
Group: helmv2.GroupVersion.Group,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: notificationv1b3.AlertKind,
|
Kind: notificationv1b2.AlertKind,
|
||||||
Version: notificationv1b3.GroupVersion.Version,
|
Version: notificationv1b2.GroupVersion.Version,
|
||||||
Group: notificationv1b3.GroupVersion.Group,
|
Group: notificationv1b2.GroupVersion.Group,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: notificationv1b3.ProviderKind,
|
Kind: notificationv1b2.ProviderKind,
|
||||||
Version: notificationv1b3.GroupVersion.Version,
|
Version: notificationv1b2.GroupVersion.Version,
|
||||||
Group: notificationv1b3.GroupVersion.Group,
|
Group: notificationv1b2.GroupVersion.Group,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: notificationv1.ReceiverKind,
|
Kind: notificationv1.ReceiverKind,
|
||||||
|
|||||||
@@ -17,9 +17,17 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
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"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"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
|
// 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
|
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) {
|
func buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error) {
|
||||||
var objRefs []object.ObjMetadata
|
var objRefs []object.ObjMetadata
|
||||||
for _, deployment := range components {
|
for _, deployment := range components {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendAlertCmd = &cobra.Command{
|
var suspendAlertCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -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{¬ificationv1.Provider{}},
|
|
||||||
list: &alertProviderListAdapter{¬ificationv1.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]}
|
|
||||||
}
|
|
||||||
424
cmd/flux/testdata/cluster_info/gitrepositories.yaml
vendored
424
cmd/flux/testdata/cluster_info/gitrepositories.yaml
vendored
@@ -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: {}
|
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
|
|
||||||
data.password
|
data.password
|
||||||
± value change
|
± value change
|
||||||
- *** (before)
|
- ******
|
||||||
+ *** (after)
|
+ *****
|
||||||
|
|
||||||
|
|||||||
2
cmd/flux/testdata/export/alert.yaml
vendored
2
cmd/flux/testdata/export/alert.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: flux-system
|
name: flux-system
|
||||||
|
|||||||
4
cmd/flux/testdata/export/objects.yaml
vendored
4
cmd/flux/testdata/export/objects.yaml
vendored
@@ -4,7 +4,7 @@ kind: Namespace
|
|||||||
metadata:
|
metadata:
|
||||||
name: {{ .fluxns }}
|
name: {{ .fluxns }}
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: slack
|
name: slack
|
||||||
@@ -14,7 +14,7 @@ spec:
|
|||||||
channel: 'A channel with spacess'
|
channel: 'A channel with spacess'
|
||||||
address: https://hooks.slack.com/services/mock
|
address: https://hooks.slack.com/services/mock
|
||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Alert
|
kind: Alert
|
||||||
metadata:
|
metadata:
|
||||||
name: flux-system
|
name: flux-system
|
||||||
|
|||||||
2
cmd/flux/testdata/export/provider.yaml
vendored
2
cmd/flux/testdata/export/provider.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
apiVersion: notification.toolkit.fluxcd.io/v1beta3
|
apiVersion: notification.toolkit.fluxcd.io/v1beta2
|
||||||
kind: Provider
|
kind: Provider
|
||||||
metadata:
|
metadata:
|
||||||
name: slack
|
name: slack
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -31,10 +31,10 @@ import (
|
|||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/cli-utils/pkg/object"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"github.com/fluxcd/cli-utils/pkg/object"
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
"github.com/fluxcd/pkg/ssa"
|
"github.com/fluxcd/pkg/ssa"
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
"github.com/fluxcd/flux2/v2/pkg/uninstall"
|
"github.com/fluxcd/flux2/v2/pkg/uninstall"
|
||||||
@@ -60,6 +59,16 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
if !uninstallArgs.dryRun && !uninstallArgs.silent {
|
||||||
|
prompt := promptui.Prompt{
|
||||||
|
Label: "Are you sure you want to delete Flux and its custom resource definitions",
|
||||||
|
IsConfirm: true,
|
||||||
|
}
|
||||||
|
if _, err := prompt.Run(); err != nil {
|
||||||
|
return fmt.Errorf("aborting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -68,27 +77,6 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !uninstallArgs.dryRun && !uninstallArgs.silent {
|
|
||||||
info, err := getFluxClusterInfo(ctx, kubeClient)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.IsNotFound(err) {
|
|
||||||
return fmt.Errorf("cluster info unavailable: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
promptLabel := "Are you sure you want to delete Flux and its custom resource definitions"
|
|
||||||
if !installManagedByFlux(info.managedBy) {
|
|
||||||
promptLabel = fmt.Sprintf("Flux is managed by %s! Are you sure you want to delete Flux and its CRDs using Flux CLI", info.managedBy)
|
|
||||||
}
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: promptLabel,
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
if _, err := prompt.Run(); err != nil {
|
|
||||||
return fmt.Errorf("aborting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("deleting components in %s namespace", *kubeconfigArgs.Namespace)
|
logger.Actionf("deleting components in %s namespace", *kubeconfigArgs.Namespace)
|
||||||
uninstall.Components(ctx, logger, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun)
|
uninstall.Components(ctx, logger, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun)
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user