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

Compare commits

..

1 Commits

Author SHA1 Message Date
Hidde Beydals
4c0987a9a6 Concept: encrypt init command for SOPS bootstrap
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-07-13 00:38:46 +02:00
247 changed files with 923 additions and 7612 deletions

46
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,46 @@
---
name: Bug report
about: Create a report to help us improve Flux v2
title: ''
assignees: ''
---
<!--
Find out more about your support options and getting help at
https://fluxcd.io/support/
-->
### Describe the bug
A clear and concise description of what the bug is.
### To Reproduce
Steps to reproduce the behaviour:
1. Provide Flux install instructions
2. Provide a GitHub repository with Kubernetes manifests
### Expected behavior
A clear and concise description of what you expected to happen.
### Additional context
- Kubernetes version:
- Git provider:
- Container registry provider:
Below please provide the output of the following commands:
```cli
flux --version
flux check
kubectl -n <namespace> get all
kubectl -n <namespace> logs deploy/source-controller
kubectl -n <namespace> logs deploy/kustomize-controller
```

View File

@@ -1,85 +0,0 @@
---
name: Bug report
description: Create a report to help us improve Flux
body:
- type: markdown
attributes:
value: |
## Support
Find out more about your support options and getting help at: https://fluxcd.io/support/
- type: textarea
validations:
required: true
attributes:
label: Describe the bug
description: A clear description of what the bug is.
- type: textarea
validations:
required: true
attributes:
label: Steps to reproduce
description: |
Steps to reproduce the problem.
placeholder: |
For example:
1. Install Flux with the additional image automation controllers
2. Run command '...'
3. See error
- type: textarea
validations:
required: true
attributes:
label: Expected behavior
description: A brief description of what you expected to happen.
- type: textarea
attributes:
label: Screenshots and recordings
description: |
If applicable, add screenshots to help explain your problem. You can also record an asciinema session: https://asciinema.org/
- type: input
validations:
required: true
attributes:
label: OS / Distro
description: The OS / distro you are executing `flux` on. If not applicable, write `N/A`.
placeholder: e.g. Windows 10, Ubuntu 20.04, Arch Linux, macOS 10.15...
- type: input
validations:
required: true
attributes:
label: Flux version
description: Run `flux --version` to check. If not applicable, write `N/A`.
placeholder: e.g. 0.16.1
- type: textarea
validations:
required: true
attributes:
label: Flux check
description: Run `flux check` to check. If not applicable, write `N/A`.
placeholder: |
For example:
► checking prerequisites
✔ kubectl 1.21.0 >=1.18.0-0
✔ Kubernetes 1.21.1 >=1.16.0-0
► checking controllers
✔ all checks passed
- type: input
attributes:
label: Git provider
description: If applicable, add the Git provider you are having problems with, e.g. GitHub (Enterprise), GitLab, etc.
- type: input
attributes:
label: Container Registry provider
description: If applicable, add the Container Registry provider you are having problems with, e.g. DockerHub, GitHub Packages, Quay.io, etc.
- type: textarea
attributes:
label: Additional context
description: Add any other context about the problem here. This can be logs (e.g. output from `flux logs`), environment specific caveats, etc.
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fluxcd/.github/blob/main/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -8,6 +8,7 @@ pkgbase = flux-bin
arch = armv7h arch = armv7h
arch = aarch64 arch = aarch64
license = APACHE license = APACHE
optdepends = kubectl
source_x86_64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_amd64.tar.gz source_x86_64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_amd64.tar.gz
source_armv6h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz source_armv6h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz
source_armv7h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz source_armv7h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz

View File

@@ -8,7 +8,8 @@ pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/" url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64") arch=("x86_64" "armv6h" "armv7h" "aarch64")
license=("APACHE") license=("APACHE")
optdepends=('bash-completion: auto-completion for flux in Bash', optdepends=('kubectl: for apply actions on the Kubernetes cluster',
'bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH') 'zsh-completions: auto-completion for flux in ZSH')
source_x86_64=( source_x86_64=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz" "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"

View File

@@ -10,6 +10,7 @@ pkgbase = flux-go
license = APACHE license = APACHE
makedepends = go makedepends = go
depends = glibc depends = glibc
optdepends = kubectl
provides = flux-bin provides = flux-bin
conflicts = flux-bin conflicts = flux-bin
replaces = flux-cli replaces = flux-cli

View File

@@ -13,7 +13,8 @@ conflicts=("flux-bin")
replaces=("flux-cli") replaces=("flux-cli")
depends=("glibc") depends=("glibc")
makedepends=('go>=1.16', 'kustomize>=3.0') makedepends=('go>=1.16', 'kustomize>=3.0')
optdepends=('bash-completion: auto-completion for flux in Bash', optdepends=('kubectl: for apply actions on the Kubernetes cluster',
'bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH') 'zsh-completions: auto-completion for flux in ZSH')
source=( source=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz" "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz"

View File

@@ -10,6 +10,7 @@ pkgbase = flux-scm
license = APACHE license = APACHE
makedepends = go makedepends = go
depends = glibc depends = glibc
optdepends = kubectl
provides = flux-bin provides = flux-bin
conflicts = flux-bin conflicts = flux-bin
source = git+https://github.com/fluxcd/flux2.git source = git+https://github.com/fluxcd/flux2.git

View File

@@ -12,7 +12,8 @@ provides=("flux-bin")
conflicts=("flux-bin") conflicts=("flux-bin")
depends=("glibc") depends=("glibc")
makedepends=('go>=1.16', 'kustomize>=3.0') makedepends=('go>=1.16', 'kustomize>=3.0')
optdepends=('bash-completion: auto-completion for flux in Bash', optdepends=('kubectl: for apply actions on the Kubernetes cluster',
'bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH') 'zsh-completions: auto-completion for flux in ZSH')
source=( source=(
"git+https://github.com/fluxcd/flux2.git" "git+https://github.com/fluxcd/flux2.git"

View File

@@ -1,42 +0,0 @@
# Flux GitHub runners
How to provision GitHub Actions self-hosted runners for Flux conformance testing.
## ARM64 Instance specs
In order to add a new runner to the GitHub Actions pool,
first create an instance on Oracle Cloud with the following configuration:
- OS: Canonical Ubuntu 20.04
- Shape: VM.Standard.A1.Flex
- OCPU Count: 2
- Memory (GB): 12
- Network Bandwidth (Gbps): 2
- Local Disk: Block Storage Only
Note that the instance image source must be **Canonical Ubuntu** instead of the default Oracle Linux.
## ARM64 Instance setup
- SSH into a newly created instance
```shell
ssh ubuntu@<instance-public-IP>
```
- Create the action runner dir
```shell
mkdir -p actions-runner && cd actions-runner
```
- Download the provisioning script
```shell
curl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/arm64.sh > arm64.sh \
&& chmod +x ./arm64.sh
```
- Retrieve the GitHub runner token from the repository [settings page](https://github.com/fluxcd/flux2/settings/actions/runners/new?arch=arm64&os=linux)
- Run the provisioning script passing the token as the first argument
```shell
sudo ./arm64.sh <TOKEN>
```
- Reboot the instance
```shell
sudo reboot
```
- Navigate to the GitHub repository [runners page](https://github.com/fluxcd/flux2/settings/actions/runners) and check the runner status

View File

@@ -1,73 +0,0 @@
#!/usr/bin/env bash
# Copyright 2021 The Flux authors. All rights reserved.
#
# 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.
# This script is meant to be run locally and in CI to validate the Kubernetes
# manifests (including Flux custom resources) before changes are merged into
# the branch synced by Flux in-cluster.
set -eu
REPOSITORY_TOKEN=$1
REPOSITORY_URL=${2:-https://github.com/fluxcd/flux2}
KIND_VERSION=0.11.1
KUBECTL_VERSION=1.21.2
KUSTOMIZE_VERSION=4.1.3
GITHUB_RUNNER_VERSION=2.278.0
PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq"
# install prerequisites
apt-get update \
&& apt-get install -y -q ${PACKAGES} \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# install docker
curl -fsSL https://get.docker.com -o get-docker.sh \
&& chmod +x get-docker.sh
./get-docker.sh
systemctl enable docker.service
systemctl enable containerd.service
usermod -aG docker ubuntu
# install kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-arm64
install -o root -g root -m 0755 kind /usr/local/bin/kind
# install kubectl
curl -LO "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/arm64/kubectl"
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# install kustomize
curl -Lo ./kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_arm64.tar.gz \
&& tar -zxvf kustomize.tar.gz \
&& rm kustomize.tar.gz
install -o root -g root -m 0755 kustomize /usr/local/bin/kustomize
# download runner
curl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \
&& tar xzf actions-runner-linux-arm64.tar.gz \
&& rm actions-runner-linux-arm64.tar.gz
# install runner dependencies
./bin/installdependencies.sh
# register runner with GitHub
sudo -u ubuntu ./config.sh --unattended --url ${REPOSITORY_URL} --token ${REPOSITORY_TOKEN}
# start runner
./svc.sh install
./svc.sh start

View File

@@ -26,14 +26,11 @@ jobs:
go-version: 1.16.x go-version: 1.16.x
- name: Setup Kubernetes - name: Setup Kubernetes
uses: engineerd/setup-kind@v0.5.0 uses: engineerd/setup-kind@v0.5.0
with:
version: v0.11.1
image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Build - name: Build
run: | run: |
make cmd/flux/.manifests.done make cmd/flux/manifests
go build -o /tmp/flux ./cmd/flux go build -o /tmp/flux ./cmd/flux
- name: Set outputs - name: Set outputs
id: vars id: vars
@@ -64,13 +61,6 @@ jobs:
--team=team-z --team=team-z
env: env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: libgit2
run: |
/tmp/flux create source git test-libgit2 \
--url=ssh://git@github.com/fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} \
--git-implementation=libgit2 \
--secret-ref=flux-system \
--branch=main
- name: uninstall - name: uninstall
run: | run: |
/tmp/flux uninstall -s --keep-namespace /tmp/flux uninstall -s --keep-namespace

View File

@@ -3,13 +3,15 @@ name: e2e-arm64
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ main, update-components, arm64-e2e ] branches: [ main, update-components ]
jobs: jobs:
ampere: ampere:
# Runner info # Runner info
# Owner: Stefan Prodan # Owner: Stefan Prodan
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners # VM: Oracle Cloud VM.Standard.A1.Flex 4CPU 24GB RAM
# OS: Linux 5.4.0-1045-oracle #49-Ubuntu SMP aarch64
# Packages: docker, kind, kubectl, kustomize
runs-on: [self-hosted, Linux, ARM64] runs-on: [self-hosted, Linux, ARM64]
steps: steps:
- name: Checkout - name: Checkout
@@ -23,16 +25,85 @@ jobs:
run: | run: |
echo ::set-output name=CLUSTER::arm64-${GITHUB_SHA:0:7}-$(date +%s) echo ::set-output name=CLUSTER::arm64-${GITHUB_SHA:0:7}-$(date +%s)
echo ::set-output name=CONTEXT::kind-arm64-${GITHUB_SHA:0:7}-$(date +%s) echo ::set-output name=CONTEXT::kind-arm64-${GITHUB_SHA:0:7}-$(date +%s)
- name: Run unit tests
run: make test
- name: Check if working tree is dirty
run: |
if [[ $(git diff --stat) != '' ]]; then
git diff
echo 'run make test and commit changes'
exit 1
fi
- name: Build - name: Build
run: | run: |
make build go build -o /tmp/flux ./cmd/flux
- name: Setup Kubernetes Kind - name: Setup Kubernetes Kind
run: | run: |
kind create cluster --name ${{ steps.prep.outputs.CLUSTER }} --kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} kind create cluster --name ${{ steps.prep.outputs.CLUSTER }}
- name: Run e2e tests - name: flux check --pre
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e run: |
/tmp/flux check --pre \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux install
run: |
/tmp/flux install \
--components-extra=image-reflector-controller,image-automation-controller \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux create source git
run: |
/tmp/flux create source git podinfo-gogit \
--git-implementation=go-git \
--url https://github.com/stefanprodan/podinfo \
--tag-semver=">1.0.0" \
--context ${{ steps.prep.outputs.CONTEXT }}
/tmp/flux create source git podinfo-libgit2 \
--git-implementation=libgit2 \
--url https://github.com/stefanprodan/podinfo \
--branch="master" \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux create kustomization
run: |
/tmp/flux create kustomization podinfo \
--source=podinfo-gogit \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m \
--validation=client \
--health-check="Deployment/frontend.dev" \
--health-check="Deployment/backend.dev" \
--health-check-timeout=3m \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux create tenant
run: |
/tmp/flux create tenant dev-team \
--with-namespace=apps \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux create helmrelease
run: |
/tmp/flux -n apps create source helm podinfo \
--url https://stefanprodan.github.io/podinfo \
--context ${{ steps.prep.outputs.CONTEXT }}
/tmp/flux -n apps create hr podinfo-helm \
--source=HelmRepository/podinfo \
--chart=podinfo \
--chart-version="6.0.x" \
--service-account=dev-team \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux get all
run: |
/tmp/flux get all --all-namespaces \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: flux uninstall
run: |
/tmp/flux uninstall -s \
--context ${{ steps.prep.outputs.CONTEXT }}
- name: Debug failure
if: failure()
run: |
kubectl --context ${{ steps.prep.outputs.CONTEXT }} -n flux-system get all
/tmp/flux logs --all-namespaces
- name: Cleanup - name: Cleanup
if: always() if: always()
run: | run: |
kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }} kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }}
rm /tmp/${{ steps.prep.outputs.CLUSTER }}

View File

@@ -1,66 +0,0 @@
name: e2e-azure
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *'
push:
branches: [ azure* ]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.16-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.16.x
- name: Install libgit2
run: |
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9
echo "deb http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
echo "deb-src http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update
sudo apt-get install -y --allow-downgrades libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable
- name: Setup Flux CLI
run: |
make build
mkdir -p $HOME/.local/bin
mv ./bin/flux $HOME/.local/bin
- name: Setup SOPS
run: |
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux
chmod +x sops-v3.7.1.linux
mkdir -p $HOME/.local/bin
mv sops-v3.7.1.linux $HOME/.local/bin/sops
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.0.7
terraform_wrapper: false
- name: Setup Azure CLI
run: |
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
- name: Run Azure e2e tests
env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
run: |
echo $HOME
echo $PATH
ls $HOME/.local/bin
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
cd ./tests/azure
go test -v -coverprofile cover.out -timeout 60m .

View File

@@ -26,23 +26,17 @@ jobs:
- name: Setup Kubernetes - name: Setup Kubernetes
uses: engineerd/setup-kind@v0.5.0 uses: engineerd/setup-kind@v0.5.0
with: with:
version: v0.11.1 version: "v0.10.0"
image: kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729 image: kindest/node:v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab
config: .github/kind/config.yaml # disable KIND-net config: .github/kind/config.yaml # disable KIND-net
- name: Setup envtest
uses: fluxcd/pkg/actions/envtest@main
with:
version: "1.21.x"
- name: Setup Calico for network policy - name: Setup Calico for network policy
run: | run: |
kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Run tests - name: Run test
run: make test run: make test
- name: Run e2e tests
run: TEST_KUBECONFIG=$HOME/.kube/config make e2e
- name: Check if working tree is dirty - name: Check if working tree is dirty
run: | run: |
if [[ $(git diff --stat) != '' ]]; then if [[ $(git diff --stat) != '' ]]; then
@@ -93,15 +87,10 @@ jobs:
--path="./deploy/overlays/dev" \ --path="./deploy/overlays/dev" \
--prune=true \ --prune=true \
--interval=5m \ --interval=5m \
--validation=client \
--health-check="Deployment/frontend.dev" \ --health-check="Deployment/frontend.dev" \
--health-check="Deployment/backend.dev" \ --health-check="Deployment/backend.dev" \
--health-check-timeout=3m --health-check-timeout=3m
- name: flux trace
run: |
/tmp/flux trace frontend \
--kind=deployment \
--api-version=apps/v1 \
--namespace=dev
- name: flux reconcile kustomization --with-source - name: flux reconcile kustomization --with-source
run: | run: |
/tmp/flux reconcile kustomization podinfo --with-source /tmp/flux reconcile kustomization podinfo --with-source
@@ -175,6 +164,26 @@ jobs:
--chart=podinfo \ --chart=podinfo \
--chart-version="5.0.x" \ --chart-version="5.0.x" \
--service-account=dev-team --service-account=dev-team
- name: flux create image repository
run: |
/tmp/flux create image repository podinfo \
--image=ghcr.io/stefanprodan/podinfo \
--interval=1m
- name: flux create image policy
run: |
/tmp/flux create image policy podinfo \
--image-ref=podinfo \
--interval=1m \
--select-semver=5.0.x
- name: flux create image policy podinfo-select-alpha
run: |
/tmp/flux create image policy podinfo-alpha \
--image-ref=podinfo \
--interval=1m \
--select-alpha=desc
- name: flux get image policy
run: |
/tmp/flux get image policy podinfo | grep '5.0.3'
- name: flux2-kustomize-helm-example - name: flux2-kustomize-helm-example
run: | run: |
/tmp/flux create source git flux-system \ /tmp/flux create source git flux-system \
@@ -184,7 +193,7 @@ jobs:
/tmp/flux create kustomization flux-system \ /tmp/flux create kustomization flux-system \
--source=flux-system \ --source=flux-system \
--path=./clusters/staging --path=./clusters/staging
kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=2m
- name: flux check - name: flux check
run: | run: |
/tmp/flux check /tmp/flux check
@@ -196,7 +205,6 @@ jobs:
run: | run: |
kubectl version --client --short kubectl version --client --short
kubectl -n flux-system get all kubectl -n flux-system get all
kubectl -n flux-system describe pods
kubectl -n flux-system get kustomizations -oyaml kubectl -n flux-system get kustomizations -oyaml
kubectl -n flux-system logs deploy/source-controller kubectl -n flux-system logs deploy/source-controller
kubectl -n flux-system logs deploy/kustomize-controller kubectl -n flux-system logs deploy/kustomize-controller

View File

@@ -50,7 +50,7 @@ jobs:
uses: fluxcd/pkg//actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Generate manifests - name: Generate manifests
run: | run: |
make cmd/flux/.manifests.done make cmd/flux/manifests
./manifests/scripts/bundle.sh "" ./output manifests.tar.gz ./manifests/scripts/bundle.sh "" ./output manifests.tar.gz
kustomize build ./manifests/install > ./output/install.yaml kustomize build ./manifests/install > ./output/install.yaml
- name: Build CRDs - name: Build CRDs

View File

@@ -31,7 +31,7 @@ jobs:
uses: fluxcd/pkg//actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Build manifests - name: Build manifests
run: | run: |
make cmd/flux/.manifests.done make cmd/flux/manifests
- name: Run Snyk to check for vulnerabilities - name: Run Snyk to check for vulnerabilities
uses: snyk/actions/golang@master uses: snyk/actions/golang@master
continue-on-error: true continue-on-error: true

1
.gitignore vendored
View File

@@ -19,7 +19,6 @@ dist/
bin/ bin/
output/ output/
cmd/flux/manifests/ cmd/flux/manifests/
cmd/flux/.manifests.done
# Docs # Docs
site/ site/

View File

@@ -49,17 +49,9 @@ brews:
folder: Formula folder: Formula
homepage: "https://fluxcd.io/" homepage: "https://fluxcd.io/"
description: "Flux CLI" description: "Flux CLI"
install: | dependencies:
bin.install "flux" - name: kubectl
type: optional
bash_output = Utils.safe_popen_read(bin/"flux", "completion", "bash")
(bash_completion/"flux").write bash_output
zsh_output = Utils.safe_popen_read(bin/"flux", "completion", "zsh")
(zsh_completion/"_flux").write zsh_output
fish_output = Utils.safe_popen_read(bin/"flux", "completion", "fish")
(fish_completion/"flux.fish").write fish_output
test: | test: |
system "#{bin}/flux --version" system "#{bin}/flux --version"
publishers: publishers:

View File

@@ -77,16 +77,6 @@ You can run the unit tests by simply doing
make test make test
``` ```
The e2e test suite uses [kind](https://kind.sigs.k8s.io/) for running kubernetes cluster inside docker containers. You can run the e2e tests by simply doing
```bash
make setup-kind
make e2e
# When done
make cleanup-kind
```
## Acceptance policy ## Acceptance policy
These things will make a PR more likely to be accepted: These things will make a PR more likely to be accepted:

View File

@@ -3,7 +3,7 @@ FROM alpine:3.13 as builder
RUN apk add --no-cache ca-certificates curl RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64 ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.22.2 ARG KUBECTL_VER=1.20.4
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \ RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \ -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \

View File

@@ -1,15 +1,5 @@
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"') VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | tr -d '"')
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done EMBEDDED_MANIFESTS_TARGET=cmd/flux/manifests
TEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig
ENVTEST_BIN_VERSION?=latest
KUBEBUILDER_ASSETS?="$(shell $(SETUP_ENVTEST) use -i $(ENVTEST_BIN_VERSION) -p path)"
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d))) rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d)))
@@ -24,51 +14,17 @@ fmt:
vet: vet:
go vet ./... go vet ./...
setup-kind: test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
kind create cluster --name=flux-e2e-test --kubeconfig=$(TEST_KUBECONFIG) --config=.github/kind/config.yaml go test ./... -coverprofile cover.out
kubectl --kubeconfig=$(TEST_KUBECONFIG) apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
kubectl --kubeconfig=$(TEST_KUBECONFIG) -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
cleanup-kind:
kind delete cluster --name=flux-e2e-test
rm $(TEST_KUBECONFIG)
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet setup-envtest
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -coverprofile cover.out --tags=unit
e2e: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
TEST_KUBECONFIG=$(TEST_KUBECONFIG) go test ./cmd/flux/... -coverprofile e2e.cover.out --tags=e2e -v -failfast
test-with-kind: setup-envtest
make setup-kind
make e2e
make cleanup-kind
$(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json) $(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)
./manifests/scripts/bundle.sh ./manifests/scripts/bundle.sh
touch $@
build: $(EMBEDDED_MANIFESTS_TARGET) build: $(EMBEDDED_MANIFESTS_TARGET)
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(VERSION)" -o ./bin/flux ./cmd/flux CGO_ENABLED=0 go build -o ./bin/flux ./cmd/flux
install: install:
go install cmd/flux go install cmd/flux
install-dev: install-dev:
CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/flux CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/flux
# Find or download setup-envtest
setup-envtest:
ifeq (, $(shell which setup-envtest))
@{ \
set -e ;\
SETUP_ENVTEST_TMP_DIR=$$(mktemp -d) ;\
cd $$SETUP_ENVTEST_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-runtime/tools/setup-envtest@latest ;\
rm -rf $$SETUP_ENVTEST_TMP_DIR ;\
}
SETUP_ENVTEST=$(GOBIN)/setup-envtest
else
SETUP_ENVTEST=$(shell which setup-envtest)
endif

View File

@@ -20,15 +20,59 @@ Flux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a
set of composable APIs and specialized tools for building Continuous set of composable APIs and specialized tools for building Continuous
Delivery on top of Kubernetes. Delivery on top of Kubernetes.
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project. ## Flux installation
## Quickstart and documentation With [Homebrew](https://brew.sh) for macOS and Linux:
To get started check out this [guide](https://fluxcd.io/docs/get-started/) ```sh
on how to bootstrap Flux on Kubernetes and deploy a sample application in a GitOps manner. brew install fluxcd/tap/flux
```
For more comprehensive documentation, see the following guides: With [GoFish](https://gofi.sh) for Windows, macOS and Linux:
- [Ways of structuring your repositories](https://fluxcd.io/docs/guides/repository-structure/)
```sh
gofish install flux
```
With Bash for macOS and Linux:
```sh
curl -s https://fluxcd.io/install.sh | sudo bash
# enable completions in ~/.bash_profile
. <(flux completion bash)
```
Arch Linux (AUR) packages:
- [flux-bin](https://aur.archlinux.org/packages/flux-bin): install the latest
stable version using a pre-build binary (recommended)
- [flux-go](https://aur.archlinux.org/packages/flux-go): build the latest
stable version from source code
- [flux-scm](https://aur.archlinux.org/packages/flux-scm): build the latest
(unstable) version from source code from our git `main` branch
Binaries for macOS AMD64/ARM64, Linux AMD64/ARM/ARM64 and Windows are available to
download on the [release page](https://github.com/fluxcd/flux2/releases).
A multi-arch container image with `kubectl` and `flux` is available on Docker Hub and GitHub:
* `docker.io/fluxcd/flux-cli:<version>`
* `ghcr.io/fluxcd/flux-cli:<version>`
Verify that your cluster satisfies the prerequisites with:
```sh
flux check --pre
```
## Get started
To get started with Flux, start [browsing the
documentation](https://fluxcd.io/docs/) or get started with one of
the following guides:
- [Get started with Flux](https://fluxcd.io/docs/get-started/)
- [Manage Helm Releases](https://fluxcd.io/docs/guides/helmreleases/) - [Manage Helm Releases](https://fluxcd.io/docs/guides/helmreleases/)
- [Automate image updates to Git](https://fluxcd.io/docs/guides/image-update/) - [Automate image updates to Git](https://fluxcd.io/docs/guides/image-update/)
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/docs/guides/mozilla-sops/) - [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/docs/guides/mozilla-sops/)
@@ -89,9 +133,7 @@ new contributors and there are a multitude of ways to get involved.
### Events ### Events
Check out our **[events calendar](https://fluxcd.io/#calendar)**, Check out our **[events calendar](https://fluxcd.io/community/#talks)**,
both with upcoming talks, events and meetings you can attend. both with upcoming talks you can attend or past events videos you can watch.
Or view the **[resources section](https://fluxcd.io/resources)**
with past events videos you can watch.
We look forward to seeing you with us! We look forward to seeing you with us!

View File

@@ -10,21 +10,11 @@ Usage:
run: flux -v run: flux -v
``` ```
Note that this action can only be used on GitHub **Linux AMD64** runners.
The latest stable version of the `flux` binary is downloaded from The latest stable version of the `flux` binary is downloaded from
GitHub [releases](https://github.com/fluxcd/flux2/releases) GitHub [releases](https://github.com/fluxcd/flux2/releases)
and placed at `/usr/local/bin/flux`. and placed at `/usr/local/bin/flux`.
Note that this action can only be used on GitHub **Linux** runners.
You can change the arch (defaults to `amd64`) with:
```yaml
steps:
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
with:
arch: arm64 # can be amd64, arm64 or arm
```
You can download a specific version with: You can download a specific version with:
```yaml ```yaml

View File

@@ -8,24 +8,19 @@ inputs:
version: version:
description: "Flux version e.g. 0.8.0 (defaults to latest stable release)" description: "Flux version e.g. 0.8.0 (defaults to latest stable release)"
required: false required: false
arch:
description: "arch can be amd64, arm64 or arm"
required: true
default: "amd64"
runs: runs:
using: composite using: composite
steps: steps:
- name: "Download flux binary to tmp" - name: "Download flux binary to tmp"
shell: bash shell: bash
run: | run: |
ARCH=${{ inputs.arch }}
VERSION=${{ inputs.version }} VERSION=${{ inputs.version }}
if [ -z $VERSION ]; then if [ -z $VERSION ]; then
VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-) VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
fi fi
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_${ARCH}.tar.gz" BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz"
curl -sL ${BIN_URL} -o /tmp/flux.tar.gz curl -sL ${BIN_URL} -o /tmp/flux.tar.gz
mkdir -p /tmp/flux mkdir -p /tmp/flux
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz

View File

@@ -37,10 +37,6 @@ func (a alertAdapter) asClientObject() client.Object {
return a.Alert return a.Alert
} }
func (a alertAdapter) deepCopyClientObject() client.Object {
return a.Alert.DeepCopy()
}
// notificationv1.Alert // notificationv1.Alert
type alertListAdapter struct { type alertListAdapter struct {

View File

@@ -37,10 +37,6 @@ func (a alertProviderAdapter) asClientObject() client.Object {
return a.Provider return a.Provider
} }
func (a alertProviderAdapter) deepCopyClientObject() client.Object {
return a.Provider.DeepCopy()
}
// notificationv1.Provider // notificationv1.Provider
type alertProviderListAdapter struct { type alertProviderListAdapter struct {

View File

@@ -19,8 +19,7 @@ package main
import ( import (
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"os" "io/ioutil"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -68,10 +67,6 @@ type bootstrapFlags struct {
authorName string authorName string
authorEmail string authorEmail string
gpgKeyRingPath string
gpgPassphrase string
gpgKeyID string
commitMessageAppendix string commitMessageAppendix string
} }
@@ -123,10 +118,6 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, "author-name", "Flux", "author name for Git commits") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, "author-name", "Flux", "author name for Git commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, "author-email", "", "author email for Git commits") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, "author-email", "", "author email for Git commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyRingPath, "gpg-key-ring", "", "path to GPG key ring for signing commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, "gpg-passphrase", "", "passphrase for decrypting GPG private key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, "gpg-key-id", "", "key id for selecting a particular key")
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().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
@@ -154,7 +145,7 @@ func buildEmbeddedManifestBase() (string, error) {
if !isEmbeddedVersion(bootstrapArgs.version) { if !isEmbeddedVersion(bootstrapArgs.version) {
return "", nil return "", nil
} }
tmpBaseDir, err := os.MkdirTemp("", "flux-manifests-") tmpBaseDir, err := ioutil.TempDir("", "flux-manifests-")
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -183,10 +174,6 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
m := make(map[string]string, len(s)) m := make(map[string]string, len(s))
for _, v := range s { for _, v := range s {
m[v] = defaultPermission m[v] = defaultPermission
if s := strings.Split(v, ":"); len(s) == 2 {
m[s[0]] = s[1]
}
} }
return m return m
} }

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@@ -117,7 +118,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
defer os.RemoveAll(manifestsBase) defer os.RemoveAll(manifestsBase)
// Lazy go-git repository // Lazy go-git repository
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-") tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
if err != nil { if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
@@ -171,14 +172,8 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits) secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
// Configure repository URL to match auth config for sync // Configure repository URL to match auth config for sync.
repositoryURL.User = url.User(gitArgs.username)
// Override existing user when user is not already set
// or when a username was passed in
if repositoryURL.User == nil || gitArgs.username != "git" {
repositoryURL.User = url.User(gitArgs.username)
}
repositoryURL.Scheme = "ssh" repositoryURL.Scheme = "ssh"
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
repositoryURL.Host = bootstrapArgs.sshHostname repositoryURL.Host = bootstrapArgs.sshHostname
@@ -205,15 +200,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
RecurseSubmodules: bootstrapArgs.recurseSubmodules, RecurseSubmodules: bootstrapArgs.recurseSubmodules,
} }
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)
}
}
// Bootstrap config // Bootstrap config
bootstrapOpts := []bootstrap.GitOption{ bootstrapOpts := []bootstrap.GitOption{
bootstrap.WithRepositoryURL(gitArgs.url), bootstrap.WithRepositoryURL(gitArgs.url),
@@ -223,8 +209,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext), bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithPostGenerateSecretFunc(promptPublicKey), bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"time" "time"
@@ -52,9 +53,6 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a private repository and assign organization teams to it # Run bootstrap for a private repository and assign organization teams to it
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug>
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level>
# Run bootstrap for a repository path # Run bootstrap for a repository path
flux bootstrap github --owner=<organization> --repository=<repository name> --path=dev-cluster flux bootstrap github --owner=<organization> --repository=<repository name> --path=dev-cluster
@@ -96,7 +94,7 @@ var githubArgs githubFlags
func init() { func init() {
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name") bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name") bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name")
bootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, "team", []string{}, "GitHub 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)") bootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, "team", []string{}, "GitHub team to be given maintainer access (also accepts comma-separated values)")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org") bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is setup or configured as private") bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is setup or configured as private")
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval") bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")
@@ -148,7 +146,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
} }
// Lazy go-git repository // Lazy go-git repository
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-") tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
if err != nil { if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"regexp" "regexp"
"strings" "strings"
@@ -158,7 +159,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
} }
// Lazy go-git repository // Lazy go-git repository
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-") tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
if err != nil { if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }

View File

@@ -18,12 +18,15 @@ package main
import ( import (
"context" "context"
"encoding/json"
"os" "os"
"os/exec"
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/spf13/cobra" "github.com/spf13/cobra"
v1 "k8s.io/api/apps/v1" v1 "k8s.io/api/apps/v1"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@@ -53,11 +56,8 @@ type checkFlags struct {
extraComponents []string extraComponents []string
} }
var kubernetesConstraints = []string{ type kubectlVersion struct {
">=1.19.0-0", ClientVersion *apimachineryversion.Info `json:"clientVersion"`
">=1.16.11-0 <=1.16.15-0",
">=1.17.7-0 <=1.17.17-0",
">=1.18.4-0 <=1.18.20-0",
} }
var checkArgs checkFlags var checkArgs checkFlags
@@ -73,12 +73,19 @@ func init() {
} }
func runCheckCmd(cmd *cobra.Command, args []string) error { func runCheckCmd(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
logger.Actionf("checking prerequisites") logger.Actionf("checking prerequisites")
checkFailed := false checkFailed := false
fluxCheck() fluxCheck()
if !kubernetesCheck(kubernetesConstraints) { if !kubectlCheck(ctx, ">=1.18.0-0") {
checkFailed = true
}
if !kubernetesCheck(">=1.16.0-0") {
checkFailed = true checkFailed = true
} }
@@ -123,7 +130,43 @@ func fluxCheck() {
} }
} }
func kubernetesCheck(constraints []string) bool { func kubectlCheck(ctx context.Context, constraint string) bool {
_, err := exec.LookPath("kubectl")
if err != nil {
logger.Failuref("kubectl not found")
return false
}
kubectlArgs := []string{"version", "--client", "--output", "json"}
output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil {
logger.Failuref("kubectl version can't be determined")
return false
}
kv := &kubectlVersion{}
if err = json.Unmarshal([]byte(output), kv); err != nil {
logger.Failuref("kubectl version output can't be unmarshalled")
return false
}
v, err := version.ParseVersion(kv.ClientVersion.GitVersion)
if err != nil {
logger.Failuref("kubectl version can't be parsed")
return false
}
c, _ := semver.NewConstraint(constraint)
if !c.Check(v) {
logger.Failuref("kubectl version %s < %s", v.Original(), constraint)
return false
}
logger.Successf("kubectl %s %s", v.String(), constraint)
return true
}
func kubernetesCheck(constraint string) bool {
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext) cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error()) logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
@@ -148,23 +191,13 @@ func kubernetesCheck(constraints []string) bool {
return false return false
} }
var valid bool c, _ := semver.NewConstraint(constraint)
var vrange string if !c.Check(v) {
for _, constraint := range constraints { logger.Failuref("Kubernetes version %s < %s", v.Original(), constraint)
c, _ := semver.NewConstraint(constraint)
if c.Check(v) {
valid = true
vrange = constraint
break
}
}
if !valid {
logger.Failuref("Kubernetes version %s does not match %s", v.Original(), constraints[0])
return false return false
} }
logger.Successf("Kubernetes %s %s", v.String(), vrange) logger.Successf("Kubernetes %s %s", v.String(), constraint)
return true return true
} }

View File

@@ -1,35 +0,0 @@
// +build e2e
package main
import (
"context"
"encoding/json"
"strings"
"testing"
"github.com/fluxcd/flux2/internal/utils"
"k8s.io/apimachinery/pkg/version"
)
func TestCheckPre(t *testing.T) {
jsonOutput, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, "version", "--output", "json")
if err != nil {
t.Fatalf("Error running utils.ExecKubectlCommand: %v", err.Error())
}
var versions map[string]version.Info
if err := json.Unmarshal([]byte(jsonOutput), &versions); err != nil {
t.Fatalf("Error unmarshalling: %v", err.Error())
}
serverVersion := strings.TrimPrefix(versions["serverVersion"].GitVersion, "v")
cmd := cmdTestCase{
args: "check --pre",
assert: assertGoldenTemplateFile("testdata/check/check_pre.golden", map[string]string{
"serverVersion": serverVersion,
}),
}
cmd.runTestCmd(t)
}

View File

@@ -17,18 +17,7 @@ limitations under the License.
package main package main
import ( import (
"context"
"strings"
"github.com/fluxcd/flux2/internal/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
memory "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
) )
var completionCmd = &cobra.Command{ var completionCmd = &cobra.Command{
@@ -40,77 +29,3 @@ var completionCmd = &cobra.Command{
func init() { func init() {
rootCmd.AddCommand(completionCmd) rootCmd.AddCommand(completionCmd)
} }
func contextsCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
rawConfig, err := utils.ClientConfig(rootArgs.kubeconfig, rootArgs.kubecontext).RawConfig()
if err != nil {
return completionError(err)
}
var comps []string
for name := range rawConfig.Contexts {
if strings.HasPrefix(name, toComplete) {
comps = append(comps, name)
}
}
return comps, cobra.ShellCompDirectiveNoFileComp
}
func resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return completionError(err)
}
dc, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return completionError(err)
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return completionError(err)
}
client, err := dynamic.NewForConfig(cfg)
if err != nil {
return completionError(err)
}
var dr dynamic.ResourceInterface
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
dr = client.Resource(mapping.Resource).Namespace(rootArgs.namespace)
} else {
dr = client.Resource(mapping.Resource)
}
list, err := dr.List(ctx, metav1.ListOptions{})
if err != nil {
return completionError(err)
}
var comps []string
for _, item := range list.Items {
name := item.GetName()
if strings.HasPrefix(name, toComplete) {
comps = append(comps, name)
}
}
return comps, cobra.ShellCompDirectiveNoFileComp
}
}
func completionError(err error) ([]string, cobra.ShellCompDirective) {
cobra.CompError(err.Error())
return nil, cobra.ShellCompDirectiveError
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -28,12 +27,12 @@ var completionZshCmd = &cobra.Command{
Short: "Generates zsh completion scripts", Short: "Generates zsh completion scripts",
Example: `To load completion run Example: `To load completion run
. <(flux completion zsh) . <(flux completion zsh) && compdef _flux flux
To configure your zsh shell to load completions for each session add to your zshrc To configure your zsh shell to load completions for each session add to your zshrc
# ~/.zshrc or ~/.profile # ~/.zshrc or ~/.profile
command -v flux >/dev/null && . <(flux completion zsh) command -v flux >/dev/null && . <(flux completion zsh) && compdef _flux flux
or write a cached file in one of the completion directories in your ${fpath}: or write a cached file in one of the completion directories in your ${fpath}:
@@ -44,8 +43,6 @@ mv _flux ~/.oh-my-zsh/completions # oh-my-zsh
mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto`, mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenZshCompletion(os.Stdout) rootCmd.GenZshCompletion(os.Stdout)
// Cobra doesn't source zsh completion file, explicitly doing it here
fmt.Println("compdef _flux flux")
}, },
} }

View File

@@ -20,7 +20,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "io/ioutil"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@@ -182,27 +182,18 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}, },
}, },
}, },
Install: &helmv2.Install{
CreateNamespace: helmReleaseArgs.createNamespace,
},
Suspend: false, Suspend: false,
}, },
} }
if helmReleaseArgs.createNamespace {
if helmRelease.Spec.Install == nil {
helmRelease.Spec.Install = &helmv2.Install{}
}
helmRelease.Spec.Install.CreateNamespace = helmReleaseArgs.createNamespace
}
if helmReleaseArgs.saName != "" { if helmReleaseArgs.saName != "" {
helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
} }
if helmReleaseArgs.crds != "" { if helmReleaseArgs.crds != "" {
if helmRelease.Spec.Install == nil {
helmRelease.Spec.Install = &helmv2.Install{}
}
helmRelease.Spec.Install.CRDs = helmv2.Create helmRelease.Spec.Install.CRDs = helmv2.Create
helmRelease.Spec.Upgrade = &helmv2.Upgrade{CRDs: helmv2.CRDsPolicy(helmReleaseArgs.crds.String())} helmRelease.Spec.Upgrade = &helmv2.Upgrade{CRDs: helmv2.CRDsPolicy(helmReleaseArgs.crds.String())}
} }
@@ -210,7 +201,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
if len(helmReleaseArgs.valuesFiles) > 0 { if len(helmReleaseArgs.valuesFiles) > 0 {
valuesMap := make(map[string]interface{}) valuesMap := make(map[string]interface{})
for _, v := range helmReleaseArgs.valuesFiles { for _, v := range helmReleaseArgs.valuesFiles {
data, err := os.ReadFile(v) data, err := ioutil.ReadFile(v)
if err != nil { if err != nil {
return fmt.Errorf("reading values from %s failed: %w", v, err) return fmt.Errorf("reading values from %s failed: %w", v, err)
} }

View File

@@ -28,7 +28,7 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var createImagePolicyCmd = &cobra.Command{ var createImagePolicyCmd = &cobra.Command{
@@ -105,7 +105,7 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
Labels: labels, Labels: labels,
}, },
Spec: imagev1.ImagePolicySpec{ Spec: imagev1.ImagePolicySpec{
ImageRepositoryRef: meta.NamespacedObjectReference{ ImageRepositoryRef: meta.LocalObjectReference{
Name: imagePolicyArgs.imageRef, Name: imagePolicyArgs.imageRef,
}, },
}, },

View File

@@ -26,7 +26,7 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var createImageRepositoryCmd = &cobra.Command{ var createImageRepositoryCmd = &cobra.Command{

View File

@@ -22,7 +22,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"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )

View File

@@ -31,7 +31,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
@@ -49,6 +49,7 @@ var createKsCmd = &cobra.Command{
--path="./examples/contour/" \ --path="./examples/contour/" \
--prune=true \ --prune=true \
--interval=10m \ --interval=10m \
--validation=client \
--health-check="Deployment/contour.projectcontour" \ --health-check="Deployment/contour.projectcontour" \
--health-check="DaemonSet/envoy.projectcontour" \ --health-check="DaemonSet/envoy.projectcontour" \
--health-check-timeout=3m --health-check-timeout=3m
@@ -59,7 +60,8 @@ var createKsCmd = &cobra.Command{
--source=GitRepository/webapp \ --source=GitRepository/webapp \
--path="./deploy/overlays/dev" \ --path="./deploy/overlays/dev" \
--prune=true \ --prune=true \
--interval=5m --interval=5m \
--validation=client
# Create a Kustomization using a source from a different namespace # Create a Kustomization using a source from a different namespace
flux create kustomization podinfo \ flux create kustomization podinfo \
@@ -67,7 +69,8 @@ var createKsCmd = &cobra.Command{
--source=GitRepository/podinfo.flux-system \ --source=GitRepository/podinfo.flux-system \
--path="./deploy/overlays/dev" \ --path="./deploy/overlays/dev" \
--prune=true \ --prune=true \
--interval=5m --interval=5m \
--validation=client
# Create a Kustomization resource that references a Bucket # Create a Kustomization resource that references a Bucket
flux create kustomization secrets \ flux create kustomization secrets \
@@ -105,8 +108,6 @@ func init() {
createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description()) createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption") createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization") createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
createKsCmd.Flags().MarkDeprecated("validation", "this arg is no longer used, all resources are validated using server-side apply dry-run")
createCmd.AddCommand(createKsCmd) createCmd.AddCommand(createKsCmd)
} }
@@ -157,6 +158,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
Namespace: kustomizationArgs.source.Namespace, Namespace: kustomizationArgs.source.Namespace,
}, },
Suspend: false, Suspend: false,
Validation: kustomizationArgs.validation,
TargetNamespace: kustomizationArgs.targetNamespace, TargetNamespace: kustomizationArgs.targetNamespace,
}, },
} }

View File

@@ -183,7 +183,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
if err := upsertSecret(ctx, kubeClient, s); err != nil { if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err return err
} }
logger.Actionf("git secret '%s' created in '%s' namespace", name, rootArgs.namespace) logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil return nil
} }

View File

@@ -112,6 +112,5 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("helm secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil return nil
} }

View File

@@ -109,6 +109,5 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("tls secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil return nil
} }

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -111,7 +112,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
tmpDir, err := os.MkdirTemp("", name) tmpDir, err := ioutil.TempDir("", name)
if err != nil { if err != nil {
return err return err
} }
@@ -134,7 +135,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
}, },
}, },
} }
if sourceBucketArgs.secretRef != "" { if sourceHelmArgs.secretRef != "" {
bucket.Spec.SecretRef = &meta.LocalObjectReference{ bucket.Spec.SecretRef = &meta.LocalObjectReference{
Name: sourceBucketArgs.secretRef, Name: sourceBucketArgs.secretRef,
} }

View File

@@ -20,6 +20,7 @@ import (
"context" "context"
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"io/ioutil"
"net/url" "net/url"
"os" "os"
@@ -56,7 +57,6 @@ type sourceGitFlags struct {
caFile string caFile string
privateKeyFile string privateKeyFile string
recurseSubmodules bool recurseSubmodules bool
silent bool
} }
var createSourceGitCmd = &cobra.Command{ var createSourceGitCmd = &cobra.Command{
@@ -136,7 +136,6 @@ func init() {
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false, createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces") "when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
createSourceCmd.AddCommand(createSourceGitCmd) createSourceCmd.AddCommand(createSourceGitCmd)
} }
@@ -179,7 +178,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("recurse submodules requires --git-implementation=%s", sourcev1.GoGitImplementation) return fmt.Errorf("recurse submodules requires --git-implementation=%s", sourcev1.GoGitImplementation)
} }
tmpDir, err := os.MkdirTemp("", name) tmpDir, err := ioutil.TempDir("", name)
if err != nil { if err != nil {
return err return err
} }
@@ -274,14 +273,12 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok { if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
logger.Generatef("deploy key: %s", ppk) logger.Generatef("deploy key: %s", ppk)
if !sourceGitArgs.silent { prompt := promptui.Prompt{
prompt := promptui.Prompt{ Label: "Have you added the deploy key to your repository",
Label: "Have you added the deploy key to your repository", IsConfirm: true,
IsConfirm: true, }
} if _, err := prompt.Run(); err != nil {
if _, err := prompt.Run(); err != nil { return fmt.Errorf("aborting")
return fmt.Errorf("aborting")
}
} }
} }
logger.Actionf("applying secret with repository credentials") logger.Actionf("applying secret with repository credentials")

View File

@@ -1,131 +0,0 @@
// +build unit
package main
import (
"context"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"testing"
"time"
)
var pollInterval = 50 * time.Millisecond
var testTimeout = 10 * time.Second
// Update the GitRepository once created to exercise test specific behavior
type reconcileFunc func(repo *sourcev1.GitRepository)
// reconciler waits for an object to be created, then invokes a test supplied
// function to mutate that object, simulating a controller.
// Test should invoke run() to run the background reconciler task which
// polls to wait for the object to exist before applying the update function.
// Any errors from the reconciler are asserted on test completion.
type reconciler struct {
client client.Client
name types.NamespacedName
reconcile reconcileFunc
}
// Start the background task that waits for the object to exist then applies
// the update function.
func (r *reconciler) run(t *testing.T) {
result := make(chan error)
go func() {
defer close(result)
err := wait.PollImmediate(
pollInterval,
testTimeout,
r.conditionFunc)
result <- err
}()
t.Cleanup(func() {
if err := <-result; err != nil {
t.Errorf("Failure from test reconciler: '%v':", err.Error())
}
})
}
// A ConditionFunction that waits for the named GitRepository to be created,
// then sets the ready condition to true.
func (r *reconciler) conditionFunc() (bool, error) {
var repo sourcev1.GitRepository
if err := r.client.Get(context.Background(), r.name, &repo); err != nil {
if errors.IsNotFound(err) {
return false, nil // Keep polling until object is created
}
return true, err
}
r.reconcile(&repo)
err := r.client.Status().Update(context.Background(), &repo)
return true, err
}
func TestCreateSourceGit(t *testing.T) {
// Default command used for multiple tests
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --timeout=" + testTimeout.String()
cases := []struct {
name string
args string
assert assertFunc
reconcile reconcileFunc
}{
{
"NoArgs",
"create source git",
assertError("GitRepository source name is required"),
nil,
}, {
"Succeeded",
command,
assertGoldenFile("testdata/create_source_git/success.golden"),
func(repo *sourcev1.GitRepository) {
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionTrue, sourcev1.GitOperationSucceedReason, "succeeded message")
repo.Status.Artifact = &sourcev1.Artifact{
Path: "some-path",
Revision: "v1",
}
},
}, {
"Failed",
command,
assertError("failed message"),
func(repo *sourcev1.GitRepository) {
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionFalse, sourcev1.URLInvalidReason, "failed message")
},
}, {
"NoArtifact",
command,
assertError("GitRepository source reconciliation completed but no artifact was found"),
func(repo *sourcev1.GitRepository) {
// Updated with no artifact
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionTrue, sourcev1.GitOperationSucceedReason, "succeeded message")
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ns := allocateNamespace("podinfo")
setupTestNamespace(ns, t)
if tc.reconcile != nil {
r := reconciler{
client: testEnv.client,
name: types.NamespacedName{Namespace: ns, Name: "podinfo"},
reconcile: tc.reconcile,
}
r.run(t)
}
cmd := cmdTestCase{
args: tc.args + " -n=" + ns,
assert: tc.assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"net/url" "net/url"
"os" "os"
@@ -105,7 +106,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
tmpDir, err := os.MkdirTemp("", name) tmpDir, err := ioutil.TempDir("", name)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -148,7 +148,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
} }
if createArgs.export { if createArgs.export {
for i := range tenantArgs.namespaces { for i, _ := range tenantArgs.namespaces {
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil { if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
return err return err
} }
@@ -164,7 +164,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
for i := range tenantArgs.namespaces { for i, _ := range tenantArgs.namespaces {
logger.Actionf("applying namespace %s", namespaces[i].Name) logger.Actionf("applying namespace %s", namespaces[i].Name)
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil { if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
return err return err

View File

@@ -28,7 +28,6 @@ var deleteAlertCmd = &cobra.Command{
Long: "The delete alert command removes the given Alert from the cluster.", Long: "The delete alert command removes the given Alert from the cluster.",
Example: ` # Delete an Alert and the Kubernetes resources created by it Example: ` # Delete an Alert and the Kubernetes resources created by it
flux delete alert main`, flux delete alert main`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: alertType, apiType: alertType,
object: universalAdapter{&notificationv1.Alert{}}, object: universalAdapter{&notificationv1.Alert{}},

View File

@@ -28,7 +28,6 @@ var deleteAlertProviderCmd = &cobra.Command{
Long: "The delete alert-provider command removes the given Provider from the cluster.", Long: "The delete alert-provider command removes the given Provider from the cluster.",
Example: ` # Delete a Provider and the Kubernetes resources created by it Example: ` # Delete a Provider and the Kubernetes resources created by it
flux delete alert-provider slack`, flux delete alert-provider slack`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: alertProviderType, apiType: alertProviderType,
object: universalAdapter{&notificationv1.Provider{}}, object: universalAdapter{&notificationv1.Provider{}},

View File

@@ -29,7 +29,6 @@ var deleteHelmReleaseCmd = &cobra.Command{
Long: "The delete helmrelease command removes the given HelmRelease from the cluster.", Long: "The delete helmrelease command removes the given HelmRelease from the cluster.",
Example: ` # Delete a Helm release and the Kubernetes resources created by it Example: ` # Delete a Helm release and the Kubernetes resources created by it
flux delete hr podinfo`, flux delete hr podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: helmReleaseType, apiType: helmReleaseType,
object: universalAdapter{&helmv2.HelmRelease{}}, object: universalAdapter{&helmv2.HelmRelease{}},

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var deleteImagePolicyCmd = &cobra.Command{ var deleteImagePolicyCmd = &cobra.Command{
@@ -28,7 +28,6 @@ var deleteImagePolicyCmd = &cobra.Command{
Long: "The delete image policy command deletes the given ImagePolicy from the cluster.", Long: "The delete image policy command deletes the given ImagePolicy from the cluster.",
Example: ` # Delete an image policy Example: ` # Delete an image policy
flux delete image policy alpine3.x`, flux delete image policy alpine3.x`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: imagePolicyType, apiType: imagePolicyType,
object: universalAdapter{&imagev1.ImagePolicy{}}, object: universalAdapter{&imagev1.ImagePolicy{}},

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var deleteImageRepositoryCmd = &cobra.Command{ var deleteImageRepositoryCmd = &cobra.Command{
@@ -28,7 +28,6 @@ var deleteImageRepositoryCmd = &cobra.Command{
Long: "The delete image repository command deletes the given ImageRepository from the cluster.", Long: "The delete image repository command deletes the given ImageRepository from the cluster.",
Example: ` # Delete an image repository Example: ` # Delete an image repository
flux delete image repository alpine`, flux delete image repository alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: imageRepositoryType, apiType: imageRepositoryType,
object: universalAdapter{&imagev1.ImageRepository{}}, object: universalAdapter{&imagev1.ImageRepository{}},

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
) )
var deleteImageUpdateCmd = &cobra.Command{ var deleteImageUpdateCmd = &cobra.Command{
@@ -28,7 +28,6 @@ var deleteImageUpdateCmd = &cobra.Command{
Long: "The delete image update command deletes the given ImageUpdateAutomation from the cluster.", Long: "The delete image update command deletes the given ImageUpdateAutomation from the cluster.",
Example: ` # Delete an image update automation Example: ` # Delete an image update automation
flux delete image update latest-images`, flux delete image update latest-images`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: imageUpdateAutomationType, apiType: imageUpdateAutomationType,
object: universalAdapter{&autov1.ImageUpdateAutomation{}}, object: universalAdapter{&autov1.ImageUpdateAutomation{}},

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var deleteKsCmd = &cobra.Command{ var deleteKsCmd = &cobra.Command{
@@ -29,7 +29,6 @@ var deleteKsCmd = &cobra.Command{
Long: "The delete kustomization command deletes the given Kustomization from the cluster.", Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
Example: ` # Delete a kustomization and the Kubernetes resources created by it Example: ` # Delete a kustomization and the Kubernetes resources created by it
flux delete kustomization podinfo`, flux delete kustomization podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: kustomizationType, apiType: kustomizationType,
object: universalAdapter{&kustomizev1.Kustomization{}}, object: universalAdapter{&kustomizev1.Kustomization{}},

View File

@@ -28,7 +28,6 @@ var deleteReceiverCmd = &cobra.Command{
Long: "The delete receiver command removes the given Receiver from the cluster.", Long: "The delete receiver command removes the given Receiver from the cluster.",
Example: ` # Delete an Receiver and the Kubernetes resources created by it Example: ` # Delete an Receiver and the Kubernetes resources created by it
flux delete receiver main`, flux delete receiver main`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: receiverType, apiType: receiverType,
object: universalAdapter{&notificationv1.Receiver{}}, object: universalAdapter{&notificationv1.Receiver{}},

View File

@@ -28,7 +28,6 @@ var deleteSourceBucketCmd = &cobra.Command{
Long: "The delete source bucket command deletes the given Bucket from the cluster.", Long: "The delete source bucket command deletes the given Bucket from the cluster.",
Example: ` # Delete a Bucket source Example: ` # Delete a Bucket source
flux delete source bucket podinfo`, flux delete source bucket podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: bucketType, apiType: bucketType,
object: universalAdapter{&sourcev1.Bucket{}}, object: universalAdapter{&sourcev1.Bucket{}},

View File

@@ -28,7 +28,6 @@ var deleteSourceGitCmd = &cobra.Command{
Long: "The delete source git command deletes the given GitRepository from the cluster.", Long: "The delete source git command deletes the given GitRepository from the cluster.",
Example: ` # Delete a Git repository Example: ` # Delete a Git repository
flux delete source git podinfo`, flux delete source git podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: gitRepositoryType, apiType: gitRepositoryType,
object: universalAdapter{&sourcev1.GitRepository{}}, object: universalAdapter{&sourcev1.GitRepository{}},

View File

@@ -28,7 +28,6 @@ var deleteSourceHelmCmd = &cobra.Command{
Long: "The delete source helm command deletes the given HelmRepository from the cluster.", Long: "The delete source helm command deletes the given HelmRepository from the cluster.",
Example: ` # Delete a Helm repository Example: ` # Delete a Helm repository
flux delete source helm podinfo`, flux delete source helm podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
RunE: deleteCommand{ RunE: deleteCommand{
apiType: helmRepositoryType, apiType: helmRepositoryType,
object: universalAdapter{&sourcev1.HelmRepository{}}, object: universalAdapter{&sourcev1.HelmRepository{}},

39
cmd/flux/encrypt.go Normal file
View File

@@ -0,0 +1,39 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
)
var encryptCmd = &cobra.Command{
Use: "encrypt",
Short: "Encrypt secrets using SOPS",
Long: "The encrypt sub-commands initialise and manage Secret encryption using SOPS.",
}
type encryptFlags struct {
export bool
}
var encryptArgs encryptFlags
func init() {
encryptCmd.PersistentFlags().BoolVar(&encryptArgs.export, "export", false, "export in YAML format to stdout")
rootCmd.AddCommand(encryptCmd)
}

113
cmd/flux/encrypt_init.go Normal file
View File

@@ -0,0 +1,113 @@
/*
Copyright 2021 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"
"io/ioutil"
"os"
"path/filepath"
"filippo.io/age"
"github.com/fluxcd/flux2/internal/utils"
"github.com/go-git/go-git/v5"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var encryptInitCmd = &cobra.Command{
Use: "init",
Short: "Init SOPS encryption with age identity",
Long: "The encryption init command creates a new age identity and writes a .sops.yaml file to the current working directory.",
Example: ` # Init SOPS encryption with a new age identity
flux encryption init`,
RunE: encryptInitCmdRun,
}
func init() {
encryptCmd.AddCommand(encryptInitCmd)
}
func encryptInitCmdRun(cmd *cobra.Command, args []string) error {
// Confirm our current path is in a Git repository
path, err := os.Getwd()
if err != nil {
return err
}
if _, err := git.PlainOpen(path); err != nil {
if err == git.ErrRepositoryNotExists {
err = fmt.Errorf("'%s' is not in a Git repository", path)
}
return err
}
// Abort early if .sops.yaml already exists
sopsCfgPath := filepath.Join(path, ".sops.yaml")
if _, err := os.Stat(sopsCfgPath); err == nil || os.IsExist(err) {
return fmt.Errorf("'%s' already contains a .sops.yaml config", path)
}
// Generate a new identity
i, err := age.GenerateX25519Identity()
if err != nil {
return err
}
logger.Successf("Generated identity %s", i.Recipient().String())
// Attempt to configure identity in .sops.yaml
const sopsCfg = `creation_rules:
- path_regex: .*.yaml
encrypted_regex: ^(data|stringData)$
age: %s
`
if err := ioutil.WriteFile(sopsCfgPath, []byte(fmt.Sprintf(sopsCfg, i.Recipient().String())), 0644); err != nil {
logger.Failuref("Failed to write recipient to .sops.yaml file")
return err
}
logger.Successf("Configured recipient in .sops.yaml file")
// Init client
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
// Create a secret
secret := &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "sops-age",
Namespace: rootArgs.namespace,
},
StringData: map[string]string{
"flux-auto.age": i.String(),
},
}
if err := kubeClient.Create(ctx, secret); err != nil {
return err
}
logger.Successf(`Secret '%s' with private key created`, secret.Name)
// TODO(hidde): lookup kustomize based on path ref? Do direct cluster mutation? (Preferably not!)
// Feels something is missing in general to provide a user experience improving bridge between "die hard"
// `--export` and "please do not do this" direct-apply-to-cluster.
return nil
}

View File

@@ -32,7 +32,6 @@ var exportAlertCmd = &cobra.Command{
# Export a Alert # Export a Alert
flux export alert main > main.yaml`, flux export alert main > main.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
RunE: exportCommand{ RunE: exportCommand{
object: alertAdapter{&notificationv1.Alert{}}, object: alertAdapter{&notificationv1.Alert{}},
list: alertListAdapter{&notificationv1.AlertList{}}, list: alertListAdapter{&notificationv1.AlertList{}},

View File

@@ -32,7 +32,6 @@ var exportAlertProviderCmd = &cobra.Command{
# Export a Provider # Export a Provider
flux export alert-provider slack > slack.yaml`, flux export alert-provider slack > slack.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
RunE: exportCommand{ RunE: exportCommand{
object: alertProviderAdapter{&notificationv1.Provider{}}, object: alertProviderAdapter{&notificationv1.Provider{}},
list: alertProviderListAdapter{&notificationv1.ProviderList{}}, list: alertProviderListAdapter{&notificationv1.ProviderList{}},

View File

@@ -33,7 +33,6 @@ var exportHelmReleaseCmd = &cobra.Command{
# Export a HelmRelease # Export a HelmRelease
flux export hr my-app > app-release.yaml`, flux export hr my-app > app-release.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
RunE: exportCommand{ RunE: exportCommand{
object: helmReleaseAdapter{&helmv2.HelmRelease{}}, object: helmReleaseAdapter{&helmv2.HelmRelease{}},
list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}}, list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}},

View File

@@ -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"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var exportImagePolicyCmd = &cobra.Command{ var exportImagePolicyCmd = &cobra.Command{
@@ -32,7 +32,6 @@ var exportImagePolicyCmd = &cobra.Command{
# Export a specific policy # Export a specific policy
flux export image policy alpine1x > alpine1x.yaml`, flux export image policy alpine1x > alpine1x.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
RunE: exportCommand{ RunE: exportCommand{
object: imagePolicyAdapter{&imagev1.ImagePolicy{}}, object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}}, list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},

View File

@@ -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"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var exportImageRepositoryCmd = &cobra.Command{ var exportImageRepositoryCmd = &cobra.Command{
@@ -32,7 +32,6 @@ var exportImageRepositoryCmd = &cobra.Command{
# Export a specific ImageRepository resource # Export a specific ImageRepository resource
flux export image repository alpine > alpine.yaml`, flux export image repository alpine > alpine.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
RunE: exportCommand{ RunE: exportCommand{
object: imageRepositoryAdapter{&imagev1.ImageRepository{}}, object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}}, list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},

View File

@@ -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"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
) )
var exportImageUpdateCmd = &cobra.Command{ var exportImageUpdateCmd = &cobra.Command{
@@ -32,7 +32,6 @@ var exportImageUpdateCmd = &cobra.Command{
# Export a specific automation # Export a specific automation
flux export image update latest-images > latest.yaml`, flux export image update latest-images > latest.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
RunE: exportCommand{ RunE: exportCommand{
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}}, object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}}, list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},

View File

@@ -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"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var exportKsCmd = &cobra.Command{ var exportKsCmd = &cobra.Command{
@@ -33,7 +33,6 @@ var exportKsCmd = &cobra.Command{
# Export a Kustomization # Export a Kustomization
flux export kustomization my-app > kustomization.yaml`, flux export kustomization my-app > kustomization.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: exportCommand{ RunE: exportCommand{
object: kustomizationAdapter{&kustomizev1.Kustomization{}}, object: kustomizationAdapter{&kustomizev1.Kustomization{}},
list: kustomizationListAdapter{&kustomizev1.KustomizationList{}}, list: kustomizationListAdapter{&kustomizev1.KustomizationList{}},

View File

@@ -32,7 +32,6 @@ var exportReceiverCmd = &cobra.Command{
# Export a Receiver # Export a Receiver
flux export receiver main > main.yaml`, flux export receiver main > main.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
RunE: exportCommand{ RunE: exportCommand{
list: receiverListAdapter{&notificationv1.ReceiverList{}}, list: receiverListAdapter{&notificationv1.ReceiverList{}},
object: receiverAdapter{&notificationv1.Receiver{}}, object: receiverAdapter{&notificationv1.Receiver{}},

View File

@@ -33,7 +33,6 @@ var exportSourceBucketCmd = &cobra.Command{
# Export a Bucket source including the static credentials # Export a Bucket source including the static credentials
flux export source bucket my-bucket --with-credentials > source.yaml`, flux export source bucket my-bucket --with-credentials > source.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
RunE: exportWithSecretCommand{ RunE: exportWithSecretCommand{
list: bucketListAdapter{&sourcev1.BucketList{}}, list: bucketListAdapter{&sourcev1.BucketList{}},
object: bucketAdapter{&sourcev1.Bucket{}}, object: bucketAdapter{&sourcev1.Bucket{}},

View File

@@ -33,7 +33,6 @@ var exportSourceGitCmd = &cobra.Command{
# Export a GitRepository source including the SSH key pair or basic auth credentials # Export a GitRepository source including the SSH key pair or basic auth credentials
flux export source git my-private-repo --with-credentials > source.yaml`, flux export source git my-private-repo --with-credentials > source.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
RunE: exportWithSecretCommand{ RunE: exportWithSecretCommand{
object: gitRepositoryAdapter{&sourcev1.GitRepository{}}, object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
list: gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}}, list: gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},

View File

@@ -33,7 +33,6 @@ var exportSourceHelmCmd = &cobra.Command{
# Export a HelmRepository source including the basic auth credentials # Export a HelmRepository source including the basic auth credentials
flux export source helm my-private-repo --with-credentials > source.yaml`, flux export source helm my-private-repo --with-credentials > source.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
RunE: exportWithSecretCommand{ RunE: exportWithSecretCommand{
list: helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}}, list: helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}}, object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},

View File

@@ -25,9 +25,6 @@ import (
"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"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
watchtools "k8s.io/client-go/tools/watch"
"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"
@@ -35,26 +32,6 @@ import (
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
type deriveType func(runtime.Object) (summarisable, error)
type typeMap map[string]deriveType
func (m typeMap) registerCommand(t string, f deriveType) error {
if _, ok := m[t]; ok {
return fmt.Errorf("duplicate type function %s", t)
}
m[t] = f
return nil
}
func (m typeMap) execute(t string, obj runtime.Object) (summarisable, error) {
f, ok := m[t]
if !ok {
return nil, fmt.Errorf("unsupported type %s", t)
}
return f(obj)
}
var getCmd = &cobra.Command{ var getCmd = &cobra.Command{
Use: "get", Use: "get",
Short: "Get the resources and their status", Short: "Get the resources and their status",
@@ -62,10 +39,7 @@ var getCmd = &cobra.Command{
} }
type GetFlags struct { type GetFlags struct {
allNamespaces bool allNamespaces bool
noHeader bool
statusSelector string
watch bool
} }
var getArgs GetFlags var getArgs GetFlags
@@ -73,10 +47,6 @@ var getArgs GetFlags
func init() { func init() {
getCmd.PersistentFlags().BoolVarP(&getArgs.allNamespaces, "all-namespaces", "A", false, getCmd.PersistentFlags().BoolVarP(&getArgs.allNamespaces, "all-namespaces", "A", false,
"list the requested object(s) across all namespaces") "list the requested object(s) across all namespaces")
getCmd.PersistentFlags().BoolVarP(&getArgs.noHeader, "no-header", "", false, "skip the header when printing the results")
getCmd.PersistentFlags().BoolVarP(&getArgs.watch, "watch", "w", false, "After listing/getting the requested object, watch for changes.")
getCmd.PersistentFlags().StringVar(&getArgs.statusSelector, "status-selector", "",
"specify the status condition name and the desired state to filter the get result, e.g. ready=false")
rootCmd.AddCommand(getCmd) rootCmd.AddCommand(getCmd)
} }
@@ -84,7 +54,6 @@ type summarisable interface {
listAdapter listAdapter
summariseItem(i int, includeNamespace bool, includeKind bool) []string summariseItem(i int, includeNamespace bool, includeKind bool) []string
headers(includeNamespace bool) []string headers(includeNamespace bool) []string
statusSelectorMatches(i int, conditionType, conditionStatus string) bool
} }
// --- these help with implementations of summarisable // --- these help with implementations of summarisable
@@ -96,20 +65,6 @@ func statusAndMessage(conditions []metav1.Condition) (string, string) {
return string(metav1.ConditionFalse), "waiting to be reconciled" return string(metav1.ConditionFalse), "waiting to be reconciled"
} }
func statusMatches(conditionType, conditionStatus string, conditions []metav1.Condition) bool {
// we don't use apimeta.FindStatusCondition because we'd like to use EqualFold to compare two strings
var c *metav1.Condition
for i := range conditions {
if strings.EqualFold(conditions[i].Type, conditionType) {
c = &conditions[i]
}
}
if c != nil {
return strings.EqualFold(string(c.Status), conditionStatus)
}
return false
}
func nameColumns(item named, includeNamespace bool, includeKind bool) []string { func nameColumns(item named, includeNamespace bool, includeKind bool) []string {
name := item.GetName() name := item.GetName()
if includeKind { if includeKind {
@@ -127,8 +82,7 @@ var namespaceHeader = []string{"Namespace"}
type getCommand struct { type getCommand struct {
apiType apiType
list summarisable list summarisable
funcMap typeMap
} }
func (get getCommand) run(cmd *cobra.Command, args []string) error { func (get getCommand) run(cmd *cobra.Command, args []string) error {
@@ -149,17 +103,13 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
listOpts = append(listOpts, client.MatchingFields{"metadata.name": args[0]}) listOpts = append(listOpts, client.MatchingFields{"metadata.name": args[0]})
} }
getAll := cmd.Use == "all"
if getArgs.watch {
return get.watch(ctx, kubeClient, cmd, args, listOpts)
}
err = kubeClient.List(ctx, get.list.asClientList(), listOpts...) err = kubeClient.List(ctx, get.list.asClientList(), listOpts...)
if err != nil { if err != nil {
return err return err
} }
getAll := cmd.Use == "all"
if get.list.len() == 0 { if get.list.len() == 0 {
if !getAll { if !getAll {
logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace) logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace)
@@ -167,97 +117,16 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
return nil return nil
} }
var header []string header := get.list.headers(getArgs.allNamespaces)
if !getArgs.noHeader { var rows [][]string
header = get.list.headers(getArgs.allNamespaces) for i := 0; i < get.list.len(); i++ {
row := get.list.summariseItem(i, getArgs.allNamespaces, getAll)
rows = append(rows, row)
} }
utils.PrintTable(os.Stdout, header, rows)
rows, err := getRowsToPrint(getAll, get.list)
if err != nil {
return err
}
utils.PrintTable(cmd.OutOrStdout(), header, rows)
if getAll { if getAll {
fmt.Println() fmt.Println()
} }
return nil
}
func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {
noFilter := true
var conditionType, conditionStatus string
if getArgs.statusSelector != "" {
parts := strings.SplitN(getArgs.statusSelector, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("expected status selector in type=status format, but found: %s", getArgs.statusSelector)
}
conditionType = parts[0]
conditionStatus = parts[1]
noFilter = false
}
var rows [][]string
for i := 0; i < list.len(); i++ {
if noFilter || list.statusSelectorMatches(i, conditionType, conditionStatus) {
row := list.summariseItem(i, getArgs.allNamespaces, getAll)
rows = append(rows, row)
}
}
return rows, nil
}
//
// watch starts a client-side watch of one or more resources.
func (get *getCommand) watch(ctx context.Context, kubeClient client.WithWatch, cmd *cobra.Command, args []string, listOpts []client.ListOption) error {
w, err := kubeClient.Watch(ctx, get.list.asClientList(), listOpts...)
if err != nil {
return err
}
_, err = watchUntil(ctx, w, get)
if err != nil {
return err
}
return nil
}
func watchUntil(ctx context.Context, w watch.Interface, get *getCommand) (bool, error) {
firstIteration := true
_, error := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
objToPrint := e.Object
sink, err := get.funcMap.execute(get.apiType.kind, objToPrint)
if err != nil {
return false, err
}
var header []string
if !getArgs.noHeader {
header = sink.headers(getArgs.allNamespaces)
}
rows, err := getRowsToPrint(false, sink)
if err != nil {
return false, err
}
if firstIteration {
utils.PrintTable(os.Stdout, header, rows)
firstIteration = false
} else {
utils.PrintTable(os.Stdout, []string{}, rows)
}
return false, nil
})
return false, error
}
func validateWatchOption(cmd *cobra.Command, toMatch string) error {
w, _ := cmd.Flags().GetBool("watch")
if cmd.Use == toMatch && w {
return fmt.Errorf("expected a single resource type, but found %s", cmd.Use)
}
return nil return nil
} }

View File

@@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -34,40 +32,10 @@ var getAlertCmd = &cobra.Command{
Long: "The get alert command prints the statuses of the resources.", Long: "The get alert command prints the statuses of the resources.",
Example: ` # List all Alerts and their status Example: ` # List all Alerts and their status
flux get alerts`, flux get alerts`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: alertType,
get := getCommand{ list: &alertListAdapter{&notificationv1.AlertList{}},
apiType: alertType, }.run,
list: &alertListAdapter{&notificationv1.AlertList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*notificationv1.Alert)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v alert", obj)
}
sink := alertListAdapter{
&notificationv1.AlertList{
Items: []notificationv1.Alert{
*o,
},
},
}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -87,8 +55,3 @@ func (s alertListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (s alertListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,10 +17,7 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -32,40 +29,10 @@ var getAlertProviderCmd = &cobra.Command{
Long: "The get alert-provider command prints the statuses of the resources.", Long: "The get alert-provider command prints the statuses of the resources.",
Example: ` # List all Providers and their status Example: ` # List all Providers and their status
flux get alert-providers`, flux get alert-providers`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: alertProviderType,
get := getCommand{ list: alertProviderListAdapter{&notificationv1.ProviderList{}},
apiType: alertProviderType, }.run,
list: alertProviderListAdapter{&notificationv1.ProviderList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*notificationv1.Provider)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v alert-provider", obj)
}
sink := alertProviderListAdapter{
&notificationv1.ProviderList{
Items: []notificationv1.Provider{
*o,
},
},
}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -85,8 +52,3 @@ func (s alertProviderListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (s alertProviderListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -36,12 +36,7 @@ var getAllCmd = &cobra.Command{
# List all resources in all namespaces # List all resources in all namespaces
flux get all --all-namespaces`, flux get all --all-namespaces`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := validateWatchOption(cmd, "all") err := getSourceAllCmd.RunE(cmd, args)
if err != nil {
return err
}
err = getSourceAllCmd.RunE(cmd, args)
if err != nil { if err != nil {
logError(err) logError(err)
} }

View File

@@ -17,13 +17,11 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
) )
var getHelmReleaseCmd = &cobra.Command{ var getHelmReleaseCmd = &cobra.Command{
@@ -33,37 +31,10 @@ var getHelmReleaseCmd = &cobra.Command{
Long: "The get helmreleases command prints the statuses of the resources.", Long: "The get helmreleases command prints the statuses of the resources.",
Example: ` # List all Helm releases and their status Example: ` # List all Helm releases and their status
flux get helmreleases`, flux get helmreleases`,
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: helmReleaseType,
get := getCommand{ list: &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
apiType: helmReleaseType, }.run,
list: &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*helmv2.HelmRelease)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v helmrelease", obj)
}
sink := helmReleaseListAdapter{&helmv2.HelmReleaseList{
Items: []helmv2.HelmRelease{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -85,8 +56,3 @@ func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (a helmReleaseListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -25,9 +25,6 @@ var getImageCmd = &cobra.Command{
Aliases: []string{"image"}, Aliases: []string{"image"},
Short: "Get image automation object status", Short: "Get image automation object status",
Long: "The get image sub-commands print the status of image automation objects.", Long: "The get image sub-commands print the status of image automation objects.",
RunE: func(cmd *cobra.Command, args []string) error {
return validateWatchOption(cmd, "images")
},
} }
func init() { func init() {

View File

@@ -21,8 +21,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var getImageAllCmd = &cobra.Command{ var getImageAllCmd = &cobra.Command{
@@ -35,11 +35,6 @@ var getImageAllCmd = &cobra.Command{
# List all image objects in all namespaces # List all image objects in all namespaces
flux get images all --all-namespaces`, flux get images all --all-namespaces`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := validateWatchOption(cmd, "all")
if err != nil {
return err
}
var allImageCmd = []getCommand{ var allImageCmd = []getCommand{
{ {
apiType: imageRepositoryType, apiType: imageRepositoryType,

View File

@@ -17,12 +17,9 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var getImagePolicyCmd = &cobra.Command{ var getImagePolicyCmd = &cobra.Command{
@@ -34,37 +31,10 @@ var getImagePolicyCmd = &cobra.Command{
# List image policies from all namespaces # List image policies from all namespaces
flux get image policy --all-namespaces`, flux get image policy --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: imagePolicyType,
get := getCommand{ list: &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
apiType: imagePolicyType, }.run,
list: &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*imagev1.ImagePolicy)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v policy", obj)
}
sink := imagePolicyListAdapter{&imagev1.ImagePolicyList{
Items: []imagev1.ImagePolicy{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -84,8 +54,3 @@ func (s imagePolicyListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (s imagePolicyListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,15 +17,13 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
var getImageRepositoryCmd = &cobra.Command{ var getImageRepositoryCmd = &cobra.Command{
@@ -37,37 +35,10 @@ var getImageRepositoryCmd = &cobra.Command{
# List image repositories from all namespaces # List image repositories from all namespaces
flux get image repository --all-namespaces`, flux get image repository --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: imageRepositoryType,
get := getCommand{ list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
apiType: imageRepositoryType, }.run,
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*imagev1.ImageRepository)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v repository", obj)
}
sink := imageRepositoryListAdapter{&imagev1.ImageRepositoryList{
Items: []imagev1.ImageRepository{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -92,8 +63,3 @@ func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (s imageRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,15 +17,13 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
) )
var getImageUpdateCmd = &cobra.Command{ var getImageUpdateCmd = &cobra.Command{
@@ -37,37 +35,10 @@ var getImageUpdateCmd = &cobra.Command{
# List image update automations from all namespaces # List image update automations from all namespaces
flux get image update --all-namespaces`, flux get image update --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: imageUpdateAutomationType,
get := getCommand{ list: &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
apiType: imageUpdateAutomationType, }.run,
list: &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*autov1.ImageUpdateAutomation)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v update", obj)
}
sink := imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{
Items: []autov1.ImageUpdateAutomation{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -91,8 +62,3 @@ func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []strin
} }
return headers return headers
} }
func (s imageUpdateAutomationListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,14 +17,12 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{
@@ -34,40 +32,10 @@ var getKsCmd = &cobra.Command{
Long: "The get kustomizations command prints the statuses of the resources.", Long: "The get kustomizations command prints the statuses of the resources.",
Example: ` # List all kustomizations and their status Example: ` # List all kustomizations and their status
flux get kustomizations`, flux get kustomizations`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: kustomizationType,
get := getCommand{ list: &kustomizationListAdapter{&kustomizev1.KustomizationList{}},
apiType: kustomizationType, }.run,
list: &kustomizationListAdapter{&kustomizev1.KustomizationList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*kustomizev1.Kustomization)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v kustomization", obj)
}
sink := kustomizationListAdapter{
&kustomizev1.KustomizationList{
Items: []kustomizev1.Kustomization{
*o,
},
},
}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -89,8 +57,3 @@ func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (a kustomizationListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -34,37 +32,10 @@ var getReceiverCmd = &cobra.Command{
Long: "The get receiver command prints the statuses of the resources.", Long: "The get receiver command prints the statuses of the resources.",
Example: ` # List all Receiver and their status Example: ` # List all Receiver and their status
flux get receivers`, flux get receivers`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: receiverType,
get := getCommand{ list: receiverListAdapter{&notificationv1.ReceiverList{}},
apiType: receiverType, }.run,
list: receiverListAdapter{&notificationv1.ReceiverList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*notificationv1.Receiver)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v receiver", obj)
}
sink := receiverListAdapter{&notificationv1.ReceiverList{
Items: []notificationv1.Receiver{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -84,8 +55,3 @@ func (s receiverListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (s receiverListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := s.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -25,10 +25,6 @@ var getSourceCmd = &cobra.Command{
Aliases: []string{"source"}, Aliases: []string{"source"},
Short: "Get source statuses", Short: "Get source statuses",
Long: "The get source sub-commands print the statuses of the sources.", Long: "The get source sub-commands print the statuses of the sources.",
RunE: func(cmd *cobra.Command, args []string) error {
return validateWatchOption(cmd, "sources")
},
} }
func init() { func init() {

View File

@@ -34,11 +34,6 @@ var getSourceAllCmd = &cobra.Command{
# List all sources in all namespaces # List all sources in all namespaces
flux get sources all --all-namespaces`, flux get sources all --all-namespaces`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := validateWatchOption(cmd, "all")
if err != nil {
return err
}
var allSourceCmd = []getCommand{ var allSourceCmd = []getCommand{
{ {
apiType: bucketType, apiType: bucketType,

View File

@@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
@@ -36,37 +34,10 @@ var getSourceBucketCmd = &cobra.Command{
# List buckets from all namespaces # List buckets from all namespaces
flux get sources helm --all-namespaces`, flux get sources helm --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: bucketType,
get := getCommand{ list: &bucketListAdapter{&sourcev1.BucketList{}},
apiType: bucketType, }.run,
list: &bucketListAdapter{&sourcev1.BucketList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.Bucket)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v bucket", obj)
}
sink := &bucketListAdapter{&sourcev1.BucketList{
Items: []sourcev1.Bucket{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -91,8 +62,3 @@ func (a bucketListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (a bucketListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
@@ -36,37 +34,10 @@ var getSourceHelmChartCmd = &cobra.Command{
# List Helm charts from all namespaces # List Helm charts from all namespaces
flux get sources chart --all-namespaces`, flux get sources chart --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: helmChartType,
get := getCommand{ list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
apiType: helmChartType, }.run,
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.HelmChart)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v chart", obj)
}
sink := &helmChartListAdapter{&sourcev1.HelmChartList{
Items: []sourcev1.HelmChart{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -91,8 +62,3 @@ func (a helmChartListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (a helmChartListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
@@ -36,37 +34,10 @@ var getSourceGitCmd = &cobra.Command{
# List Git repositories from all namespaces # List Git repositories from all namespaces
flux get sources git --all-namespaces`, flux get sources git --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: gitRepositoryType,
get := getCommand{ list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
apiType: gitRepositoryType, }.run,
list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.GitRepository)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v git", obj)
}
sink := &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{
Items: []sourcev1.GitRepository{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -91,8 +62,3 @@ func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (a gitRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -17,12 +17,10 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
@@ -36,37 +34,10 @@ var getSourceHelmCmd = &cobra.Command{
# List Helm repositories from all namespaces # List Helm repositories from all namespaces
flux get sources helm --all-namespaces`, flux get sources helm --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)), RunE: getCommand{
RunE: func(cmd *cobra.Command, args []string) error { apiType: helmRepositoryType,
get := getCommand{ list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
apiType: helmRepositoryType, }.run,
list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.HelmRepository)
if !ok {
return nil, fmt.Errorf("Impossible to cast type %#v helm", obj)
}
sink := &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{
Items: []sourcev1.HelmRepository{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
} }
func init() { func init() {
@@ -91,8 +62,3 @@ func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {
} }
return headers return headers
} }
func (a helmRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -37,10 +37,6 @@ func (h helmReleaseAdapter) asClientObject() client.Object {
return h.HelmRelease return h.HelmRelease
} }
func (h helmReleaseAdapter) deepCopyClientObject() client.Object {
return h.HelmRelease.DeepCopy()
}
// helmv2.HelmReleaseList // helmv2.HelmReleaseList
type helmReleaseListAdapter struct { type helmReleaseListAdapter struct {

View File

@@ -1,56 +0,0 @@
// +build e2e
package main
import "testing"
func TestHelmReleaseFromGit(t *testing.T) {
cases := []struct {
args string
goldenFile string
}{
{
"create source git thrfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.0.0",
"testdata/helmrelease/create_source_git.golden",
},
{
"create helmrelease thrfg --source=GitRepository/thrfg --chart=./charts/podinfo",
"testdata/helmrelease/create_helmrelease_from_git.golden",
},
{
"get helmrelease thrfg",
"testdata/helmrelease/get_helmrelease_from_git.golden",
},
{
"reconcile helmrelease thrfg --with-source",
"testdata/helmrelease/reconcile_helmrelease_from_git.golden",
},
{
"suspend helmrelease thrfg",
"testdata/helmrelease/suspend_helmrelease_from_git.golden",
},
{
"resume helmrelease thrfg",
"testdata/helmrelease/resume_helmrelease_from_git.golden",
},
{
"delete helmrelease thrfg --silent",
"testdata/helmrelease/delete_helmrelease_from_git.golden",
},
}
namespace := allocateNamespace("thrfg")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases {
cmd := cmdTestCase{
args: tc.args + " -n=" + namespace,
assert: assertGoldenTemplateFile(tc.goldenFile, map[string]string{"ns": namespace}),
}
cmd.runTestCmd(t)
}
}

View File

@@ -19,8 +19,8 @@ package main
import ( import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
) )
// These are general-purpose adapters for attaching methods to, for // These are general-purpose adapters for attaching methods to, for
@@ -42,10 +42,6 @@ func (a imageRepositoryAdapter) asClientObject() client.Object {
return a.ImageRepository return a.ImageRepository
} }
func (a imageRepositoryAdapter) deepCopyClientObject() client.Object {
return a.ImageRepository.DeepCopy()
}
// imagev1.ImageRepositoryList // imagev1.ImageRepositoryList
type imageRepositoryListAdapter struct { type imageRepositoryListAdapter struct {
@@ -104,10 +100,6 @@ func (a imageUpdateAutomationAdapter) asClientObject() client.Object {
return a.ImageUpdateAutomation return a.ImageUpdateAutomation
} }
func (a imageUpdateAutomationAdapter) deepCopyClientObject() client.Object {
return a.ImageUpdateAutomation.DeepCopy()
}
// autov1.ImageUpdateAutomationList // autov1.ImageUpdateAutomationList
type imageUpdateAutomationListAdapter struct { type imageUpdateAutomationListAdapter struct {

View File

@@ -1,48 +0,0 @@
// +build e2e
package main
import "testing"
func TestImageScanning(t *testing.T) {
cases := []struct {
args string
goldenFile string
}{
{
"create image repository podinfo --image=ghcr.io/stefanprodan/podinfo --interval=10m",
"testdata/image/create_image_repository.golden",
},
{
"create image policy podinfo-semver --image-ref=podinfo --interval=10m --select-semver=5.0.x",
"testdata/image/create_image_policy.golden",
},
{
"get image policy podinfo-semver",
"testdata/image/get_image_policy_semver.golden",
},
{
`create image policy podinfo-regex --image-ref=podinfo --interval=10m --select-semver=">4.0.0" --filter-regex="5\.0\.0"`,
"testdata/image/create_image_policy.golden",
},
{
"get image policy podinfo-regex",
"testdata/image/get_image_policy_regex.golden",
},
}
namespace := allocateNamespace("tis")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases {
cmd := cmdTestCase{
args: tc.args + " -n=" + namespace,
assert: assertGoldenFile(tc.goldenFile),
}
cmd.runTestCmd(t)
}
}

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -41,13 +42,13 @@ If a previous version is installed, then an in-place upgrade will be performed.`
flux install --version=latest --namespace=flux-system flux install --version=latest --namespace=flux-system
# Install a specific version and a series of components # Install a specific version and a series of components
flux install --version=v0.0.7 --components="source-controller,kustomize-controller" flux install --dry-run --version=v0.0.7 --components="source-controller,kustomize-controller"
# Install Flux onto tainted Kubernetes nodes # Install Flux onto tainted Kubernetes nodes
flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux
# Dry-run install # Dry-run install with manifests preview
flux install --export | kubectl apply --dry-run=client -f- flux install --dry-run --verbose
# Write install manifests to file # Write install manifests to file
flux install --export > flux-system.yaml`, flux install --export > flux-system.yaml`,
@@ -102,7 +103,6 @@ func init() {
"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().MarkHidden("manifests") installCmd.Flags().MarkHidden("manifests")
installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64") installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
installCmd.Flags().MarkDeprecated("dry-run", "use 'flux install --export | kubectl apply --dry-run=client -f-'")
rootCmd.AddCommand(installCmd) rootCmd.AddCommand(installCmd)
} }
@@ -132,7 +132,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating manifests") logger.Generatef("generating manifests")
} }
tmpDir, err := os.MkdirTemp("", rootArgs.namespace) tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
if err != nil { if err != nil {
return err return err
} }
@@ -189,19 +189,25 @@ 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", rootArgs.namespace) logger.Actionf("installing components in %s namespace", rootArgs.namespace)
applyOutput := utils.ModeStderrOS
if rootArgs.verbose {
applyOutput = utils.ModeOS
}
kubectlArgs := []string{"apply", "-f", filepath.Join(tmpDir, manifest.Path)}
if installArgs.dryRun {
kubectlArgs = append(kubectlArgs, "--dry-run=client")
applyOutput = utils.ModeOS
}
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("install failed: %w", err)
}
if installArgs.dryRun { if installArgs.dryRun {
logger.Successf("install dry-run finished") logger.Successf("install dry-run finished")
return nil return nil
} }
applyOutput, err := utils.Apply(ctx, rootArgs.kubeconfig, rootArgs.kubecontext, filepath.Join(tmpDir, manifest.Path))
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
fmt.Fprintln(os.Stderr, applyOutput)
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext) kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return fmt.Errorf("install failed: %w", err) return fmt.Errorf("install failed: %w", err)

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
// kustomizev1.Kustomization // kustomizev1.Kustomization
@@ -37,10 +37,6 @@ func (a kustomizationAdapter) asClientObject() client.Object {
return a.Kustomization return a.Kustomization
} }
func (a kustomizationAdapter) deepCopyClientObject() client.Object {
return a.Kustomization.DeepCopy()
}
// kustomizev1.KustomizationList // kustomizev1.KustomizationList
type kustomizationListAdapter struct { type kustomizationListAdapter struct {

View File

@@ -1,56 +0,0 @@
// +build e2e
package main
import "testing"
func TestKustomizationFromGit(t *testing.T) {
cases := []struct {
args string
goldenFile string
}{
{
"create source git tkfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.0.0",
"testdata/kustomization/create_source_git.golden",
},
{
"create kustomization tkfg --source=tkfg --path=./deploy/overlays/dev --prune=true --interval=5m --health-check=Deployment/frontend.dev --health-check=Deployment/backend.dev --health-check-timeout=3m",
"testdata/kustomization/create_kustomization_from_git.golden",
},
{
"get kustomization tkfg",
"testdata/kustomization/get_kustomization_from_git.golden",
},
{
"reconcile kustomization tkfg --with-source",
"testdata/kustomization/reconcile_kustomization_from_git.golden",
},
{
"suspend kustomization tkfg",
"testdata/kustomization/suspend_kustomization_from_git.golden",
},
{
"resume kustomization tkfg",
"testdata/kustomization/resume_kustomization_from_git.golden",
},
{
"delete kustomization tkfg --silent",
"testdata/kustomization/delete_kustomization_from_git.golden",
},
}
namespace := allocateNamespace("tkfg")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases {
cmd := cmdTestCase{
args: tc.args + " -n=" + namespace,
assert: assertGoldenTemplateFile(tc.goldenFile, map[string]string{"ns": namespace}),
}
cmd.runTestCmd(t)
}
}

View File

@@ -24,18 +24,14 @@ import (
"html/template" "html/template"
"io" "io"
"os" "os"
"sort"
"strings" "strings"
"sync" "sync"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
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/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/podutils"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@@ -46,19 +42,16 @@ var logsCmd = &cobra.Command{
Short: "Display formatted logs for Flux components", Short: "Display formatted logs for Flux components",
Long: "The logs command displays formatted logs from various Flux components.", Long: "The logs command displays formatted logs from various Flux components.",
Example: ` # Print the reconciliation logs of all Flux custom resources in your cluster Example: ` # Print the reconciliation logs of all Flux custom resources in your cluster
flux logs --all-namespaces flux logs --all-namespaces
# Print all logs of all Flux custom resources newer than 2 minutes
flux logs --all-namespaces --since=2m
# Stream logs for a particular log level # Stream logs for a particular log level
flux logs --follow --level=error --all-namespaces flux logs --follow --level=error --all-namespaces
# Filter logs by kind, name and namespace # Filter logs by kind, name and namespace
flux logs --kind=Kustomization --name=podinfo --namespace=default flux logs --kind=Kustomization --name=podinfo --namespace=default
# Print logs when Flux is installed in a different namespace than flux-system # Print logs when Flux is installed in a different namespace than flux-system
flux logs --flux-namespace=my-namespace flux logs --flux-namespace=my-namespace
`, `,
RunE: logsCmdRun, RunE: logsCmdRun,
} }
@@ -71,8 +64,6 @@ type logsFlags struct {
name string name string
fluxNamespace string fluxNamespace string
allNamespaces bool allNamespaces bool
sinceTime string
sinceSeconds time.Duration
} }
var logsArgs = &logsFlags{ var logsArgs = &logsFlags{
@@ -87,8 +78,6 @@ 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.sinceSeconds, "since", logsArgs.sinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
logsCmd.Flags().StringVar(&logsArgs.sinceTime, "since-time", logsArgs.sinceTime, "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
rootCmd.AddCommand(logsCmd) rootCmd.AddCommand(logsCmd)
} }
@@ -98,6 +87,7 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
var pods []corev1.Pod
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext) cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
@@ -112,7 +102,7 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no argument required") return fmt.Errorf("no argument required")
} }
pods, err := getPods(ctx, clientset, fluxSelector) pods, err = getPods(ctx, clientset, fluxSelector)
if err != nil { if err != nil {
return err return err
} }
@@ -125,24 +115,6 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
logOpts.TailLines = &logsArgs.tail logOpts.TailLines = &logsArgs.tail
} }
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceSeconds != 0 {
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
}
if len(logsArgs.sinceTime) > 0 {
t, err := util.ParseRFC3339(logsArgs.sinceTime, metav1.Now)
if err != nil {
return fmt.Errorf("%s is not a valid (RFC3339) time", logsArgs.sinceTime)
}
logOpts.SinceTime = &t
}
if logsArgs.sinceSeconds != 0 {
// round up to the nearest second
sec := int64(logsArgs.sinceSeconds.Round(time.Second).Seconds())
logOpts.SinceSeconds = &sec
}
var requests []rest.ResponseWrapper var requests []rest.ResponseWrapper
for _, pod := range pods { for _, pod := range pods {
req := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts) req := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts)
@@ -176,17 +148,7 @@ func getPods(ctx context.Context, c *kubernetes.Clientset, label string) ([]core
if err != nil { if err != nil {
return ret, err return ret, err
} }
pods := []*corev1.Pod{} ret = append(ret, podList.Items...)
for i := range podList.Items {
pod := podList.Items[i]
pods = append(pods, &pod)
}
if len(pods) > 0 {
// sort pods to prioritize running pods over others
sort.Sort(podutils.ByLogging(pods))
ret = append(ret, *pods[0])
}
} }
return ret, nil return ret, nil

View File

@@ -1,63 +0,0 @@
// +build unit
package main
import (
"testing"
)
func TestLogsNoArgs(t *testing.T) {
cmd := cmdTestCase{
args: "logs",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsAllNamespaces(t *testing.T) {
cmd := cmdTestCase{
args: "logs --all-namespaces",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsSince(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=2m",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsSinceInvalid(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=XXX",
assert: assertError(`invalid argument "XXX" for "--since" flag: time: invalid duration "XXX"`),
}
cmd.runTestCmd(t)
}
func TestLogsSinceTime(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since-time=2021-08-06T14:26:25.546Z",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsSinceTimeInvalid(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since-time=XXX",
assert: assertError("XXX is not a valid (RFC3339) time"),
}
cmd.runTestCmd(t)
}
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z",
assert: assertError("at most one of `sinceTime` or `sinceSeconds` may be specified"),
}
cmd.runTestCmd(t)
}

View File

@@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
@@ -43,7 +42,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
flux check --pre flux check --pre
# Install the latest version of Flux # Install the latest version of Flux
flux install flux install --version=master
# Create a source for a public Git repository # Create a source for a public Git repository
flux create source git webapp-latest \ flux create source git webapp-latest \
@@ -66,6 +65,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
--path="./deploy/webapp/" \ --path="./deploy/webapp/" \
--prune=true \ --prune=true \
--interval=5m \ --interval=5m \
--validation=client \
--health-check="Deployment/backend.webapp" \ --health-check="Deployment/backend.webapp" \
--health-check="Deployment/frontend.webapp" \ --health-check="Deployment/frontend.webapp" \
--health-check-timeout=2m --health-check-timeout=2m
@@ -108,15 +108,11 @@ var rootArgs = NewRootFlags()
func init() { func init() {
rootCmd.PersistentFlags().StringVarP(&rootArgs.namespace, "namespace", "n", rootArgs.defaults.Namespace, "the namespace scope for this operation") rootCmd.PersistentFlags().StringVarP(&rootArgs.namespace, "namespace", "n", rootArgs.defaults.Namespace, "the namespace scope for this operation")
rootCmd.RegisterFlagCompletionFunc("namespace", resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind("Namespace")))
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation") rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects") rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "", rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "",
"absolute path to the kubeconfig file") "absolute path to the kubeconfig file")
rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", "kubernetes context to use") rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", "kubernetes context to use")
rootCmd.RegisterFlagCompletionFunc("context", contextsCompletionFunc)
rootCmd.DisableAutoGenTag = true rootCmd.DisableAutoGenTag = true
} }

View File

@@ -1,62 +0,0 @@
// +build e2e
package main
import (
"context"
"fmt"
"os"
"testing"
"github.com/fluxcd/flux2/internal/utils"
)
func TestMain(m *testing.M) {
// Ensure tests print consistent timestamps regardless of timezone
os.Setenv("TZ", "UTC")
testEnv, err := NewTestEnvKubeManager(ExistingClusterMode)
if err != nil {
panic(fmt.Errorf("error creating kube manager: '%w'", err))
}
rootArgs.kubeconfig = testEnv.kubeConfigPath
// Install Flux.
output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller")
if err != nil {
panic(fmt.Errorf("install falied: %s error:'%w'", output, err))
}
// Run tests
code := m.Run()
// Uninstall Flux
output, err = executeCommand("uninstall -s --keep-namespace")
if err != nil {
panic(fmt.Errorf("uninstall falied: %s error:'%w'", output, err))
}
// Delete namespace and wait for finalisation
kubectlArgs := []string{"delete", "namespace", "flux-system"}
_, err = utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil {
panic(fmt.Errorf("delete namespace error:'%w'", err))
}
testEnv.Stop()
os.Exit(code)
}
func setupTestNamespace(namespace string) (func(), error) {
kubectlArgs := []string{"create", "namespace", namespace}
_, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil {
return nil, err
}
return func() {
kubectlArgs := []string{"delete", "namespace", namespace}
utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
}, nil
}

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