Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a76c26822 | ||
|
|
9d9fff5796 | ||
|
|
0a92c61b09 | ||
|
|
546be76f55 | ||
|
|
d770f3f53f | ||
|
|
254cc131ae | ||
|
|
70509ffcb4 | ||
|
|
4cc2326c7f | ||
|
|
0133caaec4 | ||
|
|
7ae4f28920 | ||
|
|
b1eb0270e9 | ||
|
|
03b6de1169 | ||
|
|
9d3f75d111 | ||
|
|
5c41924b2f | ||
|
|
7cf7cf2f1e | ||
|
|
2679731bde | ||
|
|
ad73370cd7 | ||
|
|
18acae57bd | ||
|
|
b427356eca | ||
|
|
2e6ca16a4a | ||
|
|
e98f1142a6 | ||
|
|
06fa8f75c9 | ||
|
|
8cbd4e8172 | ||
|
|
83c7994266 | ||
|
|
43843581b6 | ||
|
|
7e03d64e8a | ||
|
|
c6f4d71187 | ||
|
|
69c3b90fea | ||
|
|
75309b4c93 | ||
|
|
433f13a7ed | ||
|
|
8896a1e73e | ||
|
|
54758b1692 | ||
|
|
375e00c79c | ||
|
|
e2454d91f1 | ||
|
|
6894f6f3bf | ||
|
|
d45501a129 | ||
|
|
def92e14ee | ||
|
|
11708d4189 | ||
|
|
2bc64bf419 | ||
|
|
3a3bdc62c8 | ||
|
|
72294b2a56 | ||
|
|
94940a20ef | ||
|
|
219ff2ef7d | ||
|
|
bc2de741b8 | ||
|
|
5eabd4e898 | ||
|
|
e8d6d5fe5c | ||
|
|
55bd93ff79 | ||
|
|
b34b2d779b | ||
|
|
103ed2be65 | ||
|
|
cc32c1be07 | ||
|
|
a3ba9817a3 | ||
|
|
6d5f1b17ad | ||
|
|
0d5d5fce46 | ||
|
|
375edffd15 | ||
|
|
d1982e64b2 | ||
|
|
cec8b5336c | ||
|
|
8f78263455 | ||
|
|
cb96bca6aa | ||
|
|
c18d0b9217 | ||
|
|
d1970185b9 | ||
|
|
794d3ee2f5 | ||
|
|
daeef98dfb | ||
|
|
4146df1f02 | ||
|
|
78f4dfa48d | ||
|
|
8b68d7d7e2 | ||
|
|
ba1bba17ad | ||
|
|
70f2b5028f | ||
|
|
4f4f0d70a4 | ||
|
|
9607b07e65 | ||
|
|
3570fab0f9 | ||
|
|
dd0f17d7a5 | ||
|
|
2290880389 | ||
|
|
351d287d88 | ||
|
|
eba6706f15 | ||
|
|
7f425efa6b | ||
|
|
d40685ab62 | ||
|
|
f795e3eeb8 | ||
|
|
d3944c0204 | ||
|
|
4086ab15fa | ||
|
|
446a367094 | ||
|
|
bd250c9871 | ||
|
|
91b3788362 | ||
|
|
658dbb9ea8 | ||
|
|
1257b9cbc8 |
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
```
|
|
||||||
85
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
85
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
2
.github/workflows/bootstrap.yaml
vendored
2
.github/workflows/bootstrap.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
uses: fluxcd/pkg//actions/kustomize@main
|
uses: fluxcd/pkg//actions/kustomize@main
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make cmd/flux/manifests
|
make cmd/flux/.manifests.done
|
||||||
go build -o /tmp/flux ./cmd/flux
|
go build -o /tmp/flux ./cmd/flux
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: vars
|
id: vars
|
||||||
|
|||||||
82
.github/workflows/e2e-arm64.yaml
vendored
82
.github/workflows/e2e-arm64.yaml
vendored
@@ -3,7 +3,7 @@ name: e2e-arm64
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main, update-components ]
|
branches: [ main, update-components, arm64-e2e ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ampere:
|
ampere:
|
||||||
@@ -23,86 +23,16 @@ 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: |
|
||||||
go build -o /tmp/flux ./cmd/flux
|
make build
|
||||||
- name: Setup Kubernetes Kind
|
- name: Setup Kubernetes Kind
|
||||||
run: |
|
run: |
|
||||||
kind create cluster --name ${{ steps.prep.outputs.CLUSTER }}
|
kind create cluster --name ${{ steps.prep.outputs.CLUSTER }} --kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||||
- name: flux check --pre
|
- name: Run e2e tests
|
||||||
run: |
|
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e
|
||||||
/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
|
|
||||||
kubectl --context ${{ steps.prep.outputs.CONTEXT }} -n flux-system describe pods
|
|
||||||
/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 }}
|
||||||
|
|||||||
28
.github/workflows/e2e.yaml
vendored
28
.github/workflows/e2e.yaml
vendored
@@ -29,14 +29,20 @@ jobs:
|
|||||||
version: v0.11.1
|
version: v0.11.1
|
||||||
image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
|
image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
|
||||||
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 test
|
- name: Run tests
|
||||||
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
|
||||||
@@ -170,26 +176,6 @@ 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 \
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -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
|
make cmd/flux/.manifests.done
|
||||||
./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
|
||||||
|
|||||||
2
.github/workflows/scan.yaml
vendored
2
.github/workflows/scan.yaml
vendored
@@ -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
|
make cmd/flux/.manifests.done
|
||||||
- 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
1
.gitignore
vendored
@@ -19,6 +19,7 @@ dist/
|
|||||||
bin/
|
bin/
|
||||||
output/
|
output/
|
||||||
cmd/flux/manifests/
|
cmd/flux/manifests/
|
||||||
|
cmd/flux/.manifests.done
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
site/
|
site/
|
||||||
|
|||||||
@@ -77,6 +77,16 @@ 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:
|
||||||
|
|||||||
54
Makefile
54
Makefile
@@ -1,5 +1,15 @@
|
|||||||
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | tr -d '"')
|
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"')
|
||||||
EMBEDDED_MANIFESTS_TARGET=cmd/flux/manifests
|
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done
|
||||||
|
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)))
|
||||||
|
|
||||||
@@ -14,17 +24,51 @@ fmt:
|
|||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|
||||||
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
|
setup-kind:
|
||||||
go test ./... -coverprofile cover.out
|
kind create cluster --name=flux-e2e-test --kubeconfig=$(TEST_KUBECONFIG) --config=.github/kind/config.yaml
|
||||||
|
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 -o ./bin/flux ./cmd/flux
|
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(VERSION)" -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
|
||||||
|
|||||||
62
README.md
62
README.md
@@ -20,59 +20,15 @@ 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 installation
|
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project.
|
||||||
|
|
||||||
With [Homebrew](https://brew.sh) for macOS and Linux:
|
## Quickstart and documentation
|
||||||
|
|
||||||
```sh
|
To get started check out this [guide](https://fluxcd.io/docs/get-started/)
|
||||||
brew install fluxcd/tap/flux
|
on how to bootstrap Flux on Kubernetes and deploy a sample application in a GitOps manner.
|
||||||
```
|
|
||||||
|
|
||||||
With [GoFish](https://gofi.sh) for Windows, macOS and Linux:
|
For more comprehensive documentation, see the following guides:
|
||||||
|
- [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/)
|
||||||
@@ -133,7 +89,9 @@ new contributors and there are a multitude of ways to get involved.
|
|||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
Check out our **[events calendar](https://fluxcd.io/community/#talks)**,
|
Check out our **[events calendar](https://fluxcd.io/#calendar)**,
|
||||||
both with upcoming talks you can attend or past events videos you can watch.
|
both with upcoming talks, events and meetings you can attend.
|
||||||
|
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!
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ 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 {
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ 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 {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ func buildEmbeddedManifestBase() (string, error) {
|
|||||||
if !isEmbeddedVersion(bootstrapArgs.version) {
|
if !isEmbeddedVersion(bootstrapArgs.version) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
tmpBaseDir, err := ioutil.TempDir("", "flux-manifests-")
|
tmpBaseDir, err := os.MkdirTemp("", "flux-manifests-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -118,7 +117,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 := ioutil.TempDir("", "flux-bootstrap-")
|
tmpDir, err := os.MkdirTemp("", "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)
|
||||||
}
|
}
|
||||||
@@ -200,6 +199,15 @@ 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),
|
||||||
@@ -209,6 +217,7 @@ 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),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup bootstrapper with constructed configs
|
// Setup bootstrapper with constructed configs
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -146,7 +145,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lazy go-git repository
|
// Lazy go-git repository
|
||||||
tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
|
tmpDir, err := os.MkdirTemp("", "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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -159,7 +158,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lazy go-git repository
|
// Lazy go-git repository
|
||||||
tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
|
tmpDir, err := os.MkdirTemp("", "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)
|
||||||
}
|
}
|
||||||
|
|||||||
37
cmd/flux/check_test.go
Normal file
37
cmd/flux/check_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// +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())
|
||||||
|
}
|
||||||
|
|
||||||
|
clientVersion := strings.TrimPrefix(versions["clientVersion"].GitVersion, "v")
|
||||||
|
serverVersion := strings.TrimPrefix(versions["serverVersion"].GitVersion, "v")
|
||||||
|
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "check --pre",
|
||||||
|
assert: assertGoldenTemplateFile("testdata/check/check_pre.golden", map[string]string{
|
||||||
|
"clientVersion": clientVersion,
|
||||||
|
"serverVersion": serverVersion,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
@@ -17,7 +17,18 @@ 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{
|
||||||
@@ -29,3 +40,77 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -27,12 +28,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) && compdef _flux flux
|
. <(flux completion zsh)
|
||||||
|
|
||||||
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) && compdef _flux flux
|
command -v flux >/dev/null && . <(flux completion zsh)
|
||||||
|
|
||||||
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}:
|
||||||
|
|
||||||
@@ -43,6 +44,8 @@ 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")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
|
||||||
"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,13 +182,16 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Install: &helmv2.Install{
|
|
||||||
CreateNamespace: helmReleaseArgs.createNamespace,
|
|
||||||
},
|
|
||||||
Suspend: false,
|
Suspend: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if helmReleaseArgs.createNamespace {
|
||||||
|
helmRelease.Spec.Install = &helmv2.Install{
|
||||||
|
CreateNamespace: helmReleaseArgs.createNamespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if helmReleaseArgs.saName != "" {
|
if helmReleaseArgs.saName != "" {
|
||||||
helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
|
helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
|
||||||
}
|
}
|
||||||
@@ -201,7 +204,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 := ioutil.ReadFile(v)
|
data, err := os.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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
logger.Actionf("git secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,5 +112,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,5 +109,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -112,7 +111,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", name)
|
tmpDir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -135,7 +134,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if sourceHelmArgs.secretRef != "" {
|
if sourceBucketArgs.secretRef != "" {
|
||||||
bucket.Spec.SecretRef = &meta.LocalObjectReference{
|
bucket.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
Name: sourceBucketArgs.secretRef,
|
Name: sourceBucketArgs.secretRef,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -57,6 +56,7 @@ 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,6 +136,7 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -178,7 +179,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 := ioutil.TempDir("", name)
|
tmpDir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -273,12 +274,14 @@ 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)
|
||||||
prompt := promptui.Prompt{
|
if !sourceGitArgs.silent {
|
||||||
Label: "Have you added the deploy key to your repository",
|
prompt := promptui.Prompt{
|
||||||
IsConfirm: true,
|
Label: "Have you added the deploy key to your repository",
|
||||||
}
|
IsConfirm: true,
|
||||||
if _, err := prompt.Run(); err != nil {
|
}
|
||||||
return fmt.Errorf("aborting")
|
if _, err := prompt.Run(); err != nil {
|
||||||
|
return fmt.Errorf("aborting")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Actionf("applying secret with repository credentials")
|
logger.Actionf("applying secret with repository credentials")
|
||||||
|
|||||||
131
cmd/flux/create_source_git_test.go
Normal file
131
cmd/flux/create_source_git_test.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// +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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -106,7 +105,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", name)
|
tmpDir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{¬ificationv1.Alert{}},
|
object: universalAdapter{¬ificationv1.Alert{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{¬ificationv1.Provider{}},
|
object: universalAdapter{¬ificationv1.Provider{}},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ 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{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{}},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ 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{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{¬ificationv1.Receiver{}},
|
object: universalAdapter{¬ificationv1.Receiver{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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{¬ificationv1.Alert{}},
|
object: alertAdapter{¬ificationv1.Alert{}},
|
||||||
list: alertListAdapter{¬ificationv1.AlertList{}},
|
list: alertListAdapter{¬ificationv1.AlertList{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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{¬ificationv1.Provider{}},
|
object: alertProviderAdapter{¬ificationv1.Provider{}},
|
||||||
list: alertProviderListAdapter{¬ificationv1.ProviderList{}},
|
list: alertProviderListAdapter{¬ificationv1.ProviderList{}},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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{}},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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{¬ificationv1.ReceiverList{}},
|
list: receiverListAdapter{¬ificationv1.ReceiverList{}},
|
||||||
object: receiverAdapter{¬ificationv1.Receiver{}},
|
object: receiverAdapter{¬ificationv1.Receiver{}},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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{}},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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{}},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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{}},
|
||||||
|
|||||||
115
cmd/flux/get.go
115
cmd/flux/get.go
@@ -25,6 +25,9 @@ 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"
|
||||||
@@ -32,6 +35,26 @@ 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",
|
||||||
@@ -42,6 +65,7 @@ type GetFlags struct {
|
|||||||
allNamespaces bool
|
allNamespaces bool
|
||||||
noHeader bool
|
noHeader bool
|
||||||
statusSelector string
|
statusSelector string
|
||||||
|
watch bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var getArgs GetFlags
|
var getArgs GetFlags
|
||||||
@@ -50,6 +74,7 @@ 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.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", "",
|
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")
|
"specify the status condition name and the desired state to filter the get result, e.g. ready=false")
|
||||||
rootCmd.AddCommand(getCmd)
|
rootCmd.AddCommand(getCmd)
|
||||||
@@ -102,7 +127,8 @@ 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 {
|
||||||
@@ -123,13 +149,17 @@ 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)
|
||||||
@@ -141,28 +171,93 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
if !getArgs.noHeader {
|
if !getArgs.noHeader {
|
||||||
header = get.list.headers(getArgs.allNamespaces)
|
header = get.list.headers(getArgs.allNamespaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rows, err := getRowsToPrint(getAll, get.list)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.PrintTable(cmd.OutOrStderr(), header, rows)
|
||||||
|
|
||||||
|
if getAll {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {
|
||||||
noFilter := true
|
noFilter := true
|
||||||
var conditionType, conditionStatus string
|
var conditionType, conditionStatus string
|
||||||
if getArgs.statusSelector != "" {
|
if getArgs.statusSelector != "" {
|
||||||
parts := strings.SplitN(getArgs.statusSelector, "=", 2)
|
parts := strings.SplitN(getArgs.statusSelector, "=", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return fmt.Errorf("expected status selector in type=status format, but found: %s", getArgs.statusSelector)
|
return nil, fmt.Errorf("expected status selector in type=status format, but found: %s", getArgs.statusSelector)
|
||||||
}
|
}
|
||||||
conditionType = parts[0]
|
conditionType = parts[0]
|
||||||
conditionStatus = parts[1]
|
conditionStatus = parts[1]
|
||||||
noFilter = false
|
noFilter = false
|
||||||
}
|
}
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
for i := 0; i < get.list.len(); i++ {
|
for i := 0; i < list.len(); i++ {
|
||||||
if noFilter || get.list.statusSelectorMatches(i, conditionType, conditionStatus) {
|
if noFilter || list.statusSelectorMatches(i, conditionType, conditionStatus) {
|
||||||
row := get.list.summariseItem(i, getArgs.allNamespaces, getAll)
|
row := list.summariseItem(i, getArgs.allNamespaces, getAll)
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
utils.PrintTable(os.Stdout, header, rows)
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
if getAll {
|
//
|
||||||
fmt.Println()
|
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +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"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -32,10 +34,40 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||||
apiType: alertType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &alertListAdapter{¬ificationv1.AlertList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: alertType,
|
||||||
|
list: &alertListAdapter{¬ificationv1.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{
|
||||||
|
¬ificationv1.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() {
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ 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"
|
||||||
)
|
)
|
||||||
@@ -29,10 +32,40 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||||
apiType: alertProviderType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: alertProviderListAdapter{¬ificationv1.ProviderList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: alertProviderType,
|
||||||
|
list: alertProviderListAdapter{¬ificationv1.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{
|
||||||
|
¬ificationv1.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() {
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ 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 := getSourceAllCmd.RunE(cmd, args)
|
err := validateWatchOption(cmd, "all")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = getSourceAllCmd.RunE(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError(err)
|
logError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ 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{
|
||||||
@@ -31,10 +33,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||||
apiType: helmReleaseType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: helmReleaseType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ 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() {
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ 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,
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ 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/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -31,10 +34,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||||
apiType: imagePolicyType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: imagePolicyType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,11 +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/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -35,10 +37,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||||
apiType: imageRepositoryType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: imageRepositoryType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,11 +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/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -35,10 +37,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||||
apiType: imageUpdateAutomationType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: imageUpdateAutomationType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,10 +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/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -32,10 +34,40 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
apiType: kustomizationType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &kustomizationListAdapter{&kustomizev1.KustomizationList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: kustomizationType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,10 +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"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -32,10 +34,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||||
apiType: receiverType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: receiverListAdapter{¬ificationv1.ReceiverList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: receiverType,
|
||||||
|
list: receiverListAdapter{¬ificationv1.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{¬ificationv1.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() {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ 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() {
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ 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,
|
||||||
|
|||||||
@@ -17,10 +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"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -34,10 +36,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||||
apiType: bucketType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &bucketListAdapter{&sourcev1.BucketList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: bucketType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,10 +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"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -34,10 +36,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||||
apiType: helmChartType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: helmChartType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,10 +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"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -34,10 +36,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||||
apiType: gitRepositoryType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: gitRepositoryType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -17,10 +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"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
@@ -34,10 +36,37 @@ 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`,
|
||||||
RunE: getCommand{
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||||
apiType: helmRepositoryType,
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
|
get := getCommand{
|
||||||
}.run,
|
apiType: helmRepositoryType,
|
||||||
|
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() {
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ 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 {
|
||||||
|
|||||||
56
cmd/flux/helmrelease_test.go
Normal file
56
cmd/flux/helmrelease_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// +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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,10 @@ 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 {
|
||||||
@@ -100,6 +104,10 @@ 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 {
|
||||||
|
|||||||
48
cmd/flux/image_test.go
Normal file
48
cmd/flux/image_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// +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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -132,7 +131,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Generatef("generating manifests")
|
logger.Generatef("generating manifests")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
|
tmpDir, err := os.MkdirTemp("", rootArgs.namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ 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 {
|
||||||
|
|||||||
56
cmd/flux/kustomization_test.go
Normal file
56
cmd/flux/kustomization_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// +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 --validation=client --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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,12 +26,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"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"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@@ -42,16 +44,19 @@ 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,
|
||||||
}
|
}
|
||||||
@@ -64,6 +69,8 @@ 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{
|
||||||
@@ -78,6 +85,8 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +96,6 @@ 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
|
||||||
@@ -102,7 +110,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
|
||||||
}
|
}
|
||||||
@@ -115,6 +123,24 @@ 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)
|
||||||
|
|||||||
63
cmd/flux/logs_test.go
Normal file
63
cmd/flux/logs_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// +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)
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ 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"
|
||||||
@@ -108,11 +109,15 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
62
cmd/flux/main_e2e_test.go
Normal file
62
cmd/flux/main_e2e_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// +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
|
||||||
|
}
|
||||||
315
cmd/flux/main_test.go
Normal file
315
cmd/flux/main_test.go
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/mattn/go-shellwords"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextNamespaceId int64
|
||||||
|
|
||||||
|
// Return a unique namespace with the specified prefix, for tests to create
|
||||||
|
// objects that won't collide with each other.
|
||||||
|
func allocateNamespace(prefix string) string {
|
||||||
|
id := atomic.AddInt64(&nextNamespaceId, 1)
|
||||||
|
return fmt.Sprintf("%s-%d", prefix, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readYamlObjects(rdr io.Reader) ([]unstructured.Unstructured, error) {
|
||||||
|
objects := []unstructured.Unstructured{}
|
||||||
|
reader := k8syaml.NewYAMLReader(bufio.NewReader(rdr))
|
||||||
|
for {
|
||||||
|
doc, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unstructuredObj := &unstructured.Unstructured{}
|
||||||
|
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(doc), len(doc))
|
||||||
|
err = decoder.Decode(unstructuredObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objects = append(objects, *unstructuredObj)
|
||||||
|
}
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A KubeManager that can create objects that are subject to a test.
|
||||||
|
type testEnvKubeManager struct {
|
||||||
|
client client.WithWatch
|
||||||
|
testEnv *envtest.Environment
|
||||||
|
kubeConfigPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testEnvKubeManager) CreateObjectFile(objectFile string, templateValues map[string]string, t *testing.T) {
|
||||||
|
buf, err := os.ReadFile(objectFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error reading file '%s': %v", objectFile, err)
|
||||||
|
}
|
||||||
|
content, err := executeTemplate(string(buf), templateValues)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error evaluating template file '%s': '%v'", objectFile, err)
|
||||||
|
}
|
||||||
|
clientObjects, err := readYamlObjects(strings.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
|
||||||
|
}
|
||||||
|
err = m.CreateObjects(clientObjects, t)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Error creating test objects: '%v'", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testEnvKubeManager) CreateObjects(clientObjects []unstructured.Unstructured, t *testing.T) error {
|
||||||
|
for _, obj := range clientObjects {
|
||||||
|
// First create the object then set its status if present in the
|
||||||
|
// yaml file. Make a copy first since creating an object may overwrite
|
||||||
|
// the status.
|
||||||
|
createObj := obj.DeepCopy()
|
||||||
|
err := m.client.Create(context.Background(), createObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj.SetResourceVersion(createObj.GetResourceVersion())
|
||||||
|
err = m.client.Status().Update(context.Background(), &obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testEnvKubeManager) Stop() error {
|
||||||
|
if m.testEnv == nil {
|
||||||
|
return fmt.Errorf("do nothing because testEnv is nil")
|
||||||
|
}
|
||||||
|
return m.testEnv.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager, error) {
|
||||||
|
switch testClusterMode {
|
||||||
|
case TestEnvClusterMode:
|
||||||
|
useExistingCluster := false
|
||||||
|
testEnv := &envtest.Environment{
|
||||||
|
UseExistingCluster: &useExistingCluster,
|
||||||
|
CRDDirectoryPaths: []string{"manifests"},
|
||||||
|
}
|
||||||
|
cfg, err := testEnv.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user, err := testEnv.ControlPlane.AddUser(envtest.User{
|
||||||
|
Name: "envtest-admin",
|
||||||
|
Groups: []string{"system:masters"},
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeConfig, err := user.KubeConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String())
|
||||||
|
os.WriteFile(tmpFilename, kubeConfig, 0644)
|
||||||
|
k8sClient, err := client.NewWithWatch(cfg, client.Options{
|
||||||
|
Scheme: utils.NewScheme(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &testEnvKubeManager{
|
||||||
|
testEnv: testEnv,
|
||||||
|
client: k8sClient,
|
||||||
|
kubeConfigPath: tmpFilename,
|
||||||
|
}, nil
|
||||||
|
case ExistingClusterMode:
|
||||||
|
// TEST_KUBECONFIG is mandatory to prevent destroying a current cluster accidentally.
|
||||||
|
testKubeConfig := os.Getenv("TEST_KUBECONFIG")
|
||||||
|
if testKubeConfig == "" {
|
||||||
|
return nil, fmt.Errorf("environment variable TEST_KUBECONFIG is required to run tests against an existing cluster")
|
||||||
|
}
|
||||||
|
|
||||||
|
useExistingCluster := true
|
||||||
|
config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
|
||||||
|
testEnv := &envtest.Environment{
|
||||||
|
UseExistingCluster: &useExistingCluster,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
cfg, err := testEnv.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k8sClient, err := client.NewWithWatch(cfg, client.Options{
|
||||||
|
Scheme: utils.NewScheme(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &testEnvKubeManager{
|
||||||
|
testEnv: testEnv,
|
||||||
|
client: k8sClient,
|
||||||
|
kubeConfigPath: testKubeConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function that sets an expectation on the output of a command. Tests can
|
||||||
|
// either implement this directly or use a helper below.
|
||||||
|
type assertFunc func(output string, err error) error
|
||||||
|
|
||||||
|
// Assemble multiple assertFuncs into a single assertFunc
|
||||||
|
func assert(fns ...assertFunc) assertFunc {
|
||||||
|
return func(output string, err error) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if assertErr := fn(output, err); assertErr != nil {
|
||||||
|
return assertErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the command to run without error
|
||||||
|
func assertSuccess() assertFunc {
|
||||||
|
return func(output string, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Expected success but was error: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the command to fail with the specified error
|
||||||
|
func assertError(expected string) assertFunc {
|
||||||
|
return func(output string, err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Expected error but was success")
|
||||||
|
}
|
||||||
|
if expected != err.Error() {
|
||||||
|
return fmt.Errorf("Expected error '%v' but got '%v'", expected, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the command to succeed with the expected test output.
|
||||||
|
func assertGoldenValue(expected string) assertFunc {
|
||||||
|
return assert(
|
||||||
|
assertSuccess(),
|
||||||
|
func(output string, err error) error {
|
||||||
|
diff := cmp.Diff(expected, output)
|
||||||
|
if diff != "" {
|
||||||
|
return fmt.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename that contains the expected test output.
|
||||||
|
func assertGoldenFile(goldenFile string) assertFunc {
|
||||||
|
return assertGoldenTemplateFile(goldenFile, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename that contains the expected test output. The golden file is a template that
|
||||||
|
// is pre-processed with the specified templateValues.
|
||||||
|
func assertGoldenTemplateFile(goldenFile string, templateValues map[string]string) assertFunc {
|
||||||
|
goldenFileContents, fileErr := os.ReadFile(goldenFile)
|
||||||
|
return func(output string, err error) error {
|
||||||
|
if fileErr != nil {
|
||||||
|
return fmt.Errorf("Error reading golden file '%s': %s", goldenFile, fileErr)
|
||||||
|
}
|
||||||
|
var expectedOutput string
|
||||||
|
if len(templateValues) > 0 {
|
||||||
|
expectedOutput, err = executeTemplate(string(goldenFileContents), templateValues)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error executing golden template file '%s': %s", goldenFile, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expectedOutput = string(goldenFileContents)
|
||||||
|
}
|
||||||
|
if assertErr := assertGoldenValue(expectedOutput)(output, err); assertErr != nil {
|
||||||
|
return fmt.Errorf("Mismatch from golden file '%s': %v", goldenFile, assertErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestClusterMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestEnvClusterMode = TestClusterMode(iota + 1)
|
||||||
|
ExistingClusterMode
|
||||||
|
)
|
||||||
|
|
||||||
|
// Structure used for each test to load objects into kubernetes, run
|
||||||
|
// commands and assert on the expected output.
|
||||||
|
type cmdTestCase struct {
|
||||||
|
// The command line arguments to test.
|
||||||
|
args string
|
||||||
|
// Tests use assertFunc to assert on an output, success or failure. This
|
||||||
|
// can be a function defined by the test or existing function above.
|
||||||
|
assert assertFunc
|
||||||
|
// Filename that contains yaml objects to load into Kubernetes
|
||||||
|
objectFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
||||||
|
actual, testErr := executeCommand(cmd.args)
|
||||||
|
if assertErr := cmd.assert(actual, testErr); assertErr != nil {
|
||||||
|
t.Error(assertErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTemplate(content string, templateValues map[string]string) (string, error) {
|
||||||
|
tmpl := template.Must(template.New("golden").Parse(content))
|
||||||
|
var out bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&out, templateValues); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return out.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the command and return the captured output.
|
||||||
|
func executeCommand(cmd string) (string, error) {
|
||||||
|
args, err := shellwords.Parse(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
rootCmd.SetOut(buf)
|
||||||
|
rootCmd.SetErr(buf)
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
|
||||||
|
logger.stderr = rootCmd.ErrOrStderr()
|
||||||
|
|
||||||
|
_, err = rootCmd.ExecuteC()
|
||||||
|
result := buf.String()
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
48
cmd/flux/main_unit_test.go
Normal file
48
cmd/flux/main_unit_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// +build unit
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The test environment is long running process shared between tests, initialized
|
||||||
|
// by a `TestMain` function depending on how the test is involved and which tests
|
||||||
|
// are a part of the build.
|
||||||
|
var testEnv *testEnvKubeManager
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// Ensure tests print consistent timestamps regardless of timezone
|
||||||
|
os.Setenv("TZ", "UTC")
|
||||||
|
|
||||||
|
// Creating the test env manager sets rootArgs client flags
|
||||||
|
km, err := NewTestEnvKubeManager(TestEnvClusterMode)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("error creating kube manager: '%w'", err))
|
||||||
|
}
|
||||||
|
testEnv = km
|
||||||
|
rootArgs.kubeconfig = testEnv.kubeConfigPath
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
km.Stop()
|
||||||
|
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestNamespace(namespace string, t *testing.T) {
|
||||||
|
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
|
||||||
|
err := testEnv.client.Create(context.Background(), ns)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create namespace: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = testEnv.client.Delete(context.Background(), ns)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -39,6 +39,13 @@ type adapter interface {
|
|||||||
asClientObject() client.Object
|
asClientObject() client.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyable is an interface for a wrapper or alias from which we can
|
||||||
|
// get a deep copied client.Object, required when you e.g. want to
|
||||||
|
// calculate a patch.
|
||||||
|
type copyable interface {
|
||||||
|
deepCopyClientObject() client.Object
|
||||||
|
}
|
||||||
|
|
||||||
// listAdapater is the analogue to adapter, but for lists; the
|
// listAdapater is the analogue to adapter, but for lists; the
|
||||||
// controller runtime distinguishes between methods dealing with
|
// controller runtime distinguishes between methods dealing with
|
||||||
// objects and lists.
|
// objects and lists.
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ func (a receiverAdapter) asClientObject() client.Object {
|
|||||||
return a.Receiver
|
return a.Receiver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a receiverAdapter) deepCopyClientObject() client.Object {
|
||||||
|
return a.Receiver.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
// notificationv1.Receiver
|
// notificationv1.Receiver
|
||||||
|
|
||||||
type receiverListAdapter struct {
|
type receiverListAdapter struct {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type reconcileCommand struct {
|
|||||||
|
|
||||||
type reconcilable interface {
|
type reconcilable interface {
|
||||||
adapter // to be able to load from the cluster
|
adapter // to be able to load from the cluster
|
||||||
|
copyable // to be able to calculate patches
|
||||||
suspendable // to tell if it's suspended
|
suspendable // to tell if it's suspended
|
||||||
|
|
||||||
// these are implemented by anything embedding metav1.ObjectMeta
|
// these are implemented by anything embedding metav1.ObjectMeta
|
||||||
@@ -142,6 +143,7 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client,
|
|||||||
if err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()); err != nil {
|
if err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
patch := client.MergeFrom(obj.deepCopyClientObject())
|
||||||
if ann := obj.GetAnnotations(); ann == nil {
|
if ann := obj.GetAnnotations(); ann == nil {
|
||||||
obj.SetAnnotations(map[string]string{
|
obj.SetAnnotations(map[string]string{
|
||||||
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
|
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
|
||||||
@@ -150,7 +152,7 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client,
|
|||||||
ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
|
ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
|
||||||
obj.SetAnnotations(ann)
|
obj.SetAnnotations(ann)
|
||||||
}
|
}
|
||||||
return kubeClient.Update(ctx, obj.asClientObject())
|
return kubeClient.Patch(ctx, obj.asClientObject(), patch)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ var reconcileAlertCmd = &cobra.Command{
|
|||||||
Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
|
Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger a reconciliation for an existing alert
|
Example: ` # Trigger a reconciliation for an existing alert
|
||||||
flux reconcile alert main`,
|
flux reconcile alert main`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileCommand{
|
||||||
apiType: alertType,
|
apiType: alertType,
|
||||||
object: alertAdapter{¬ificationv1.Alert{}},
|
object: alertAdapter{¬ificationv1.Alert{}},
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ var reconcileAlertProviderCmd = &cobra.Command{
|
|||||||
Long: `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`,
|
Long: `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger a reconciliation for an existing provider
|
Example: ` # Trigger a reconciliation for an existing provider
|
||||||
flux reconcile alert-provider slack`,
|
flux reconcile alert-provider slack`,
|
||||||
RunE: reconcileAlertProviderCmdRun,
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||||
|
RunE: reconcileAlertProviderCmdRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ The reconcile kustomization command triggers a reconciliation of a HelmRelease r
|
|||||||
|
|
||||||
# Trigger a reconciliation of the HelmRelease's source and apply changes
|
# Trigger a reconciliation of the HelmRelease's source and apply changes
|
||||||
flux reconcile hr podinfo --with-source`,
|
flux reconcile hr podinfo --with-source`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||||
RunE: reconcileWithSourceCommand{
|
RunE: reconcileWithSourceCommand{
|
||||||
apiType: helmReleaseType,
|
apiType: helmReleaseType,
|
||||||
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ var reconcileImageRepositoryCmd = &cobra.Command{
|
|||||||
Long: `The reconcile image repository command triggers a reconciliation of an ImageRepository resource and waits for it to finish.`,
|
Long: `The reconcile image repository command triggers a reconciliation of an ImageRepository resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger an scan for an existing image repository
|
Example: ` # Trigger an scan for an existing image repository
|
||||||
flux reconcile image repository alpine`,
|
flux reconcile image repository alpine`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileCommand{
|
||||||
apiType: imageRepositoryType,
|
apiType: imageRepositoryType,
|
||||||
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ var reconcileImageUpdateCmd = &cobra.Command{
|
|||||||
Long: `The reconcile image update command triggers a reconciliation of an ImageUpdateAutomation resource and waits for it to finish.`,
|
Long: `The reconcile image update command triggers a reconciliation of an ImageUpdateAutomation resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger an automation run for an existing image update automation
|
Example: ` # Trigger an automation run for an existing image update automation
|
||||||
flux reconcile image update latest-images`,
|
flux reconcile image update latest-images`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileCommand{
|
||||||
apiType: imageUpdateAutomationType,
|
apiType: imageUpdateAutomationType,
|
||||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ The reconcile kustomization command triggers a reconciliation of a Kustomization
|
|||||||
|
|
||||||
# Trigger a sync of the Kustomization's source and apply changes
|
# Trigger a sync of the Kustomization's source and apply changes
|
||||||
flux reconcile kustomization podinfo --with-source`,
|
flux reconcile kustomization podinfo --with-source`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: reconcileWithSourceCommand{
|
RunE: reconcileWithSourceCommand{
|
||||||
apiType: kustomizationType,
|
apiType: kustomizationType,
|
||||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ var reconcileReceiverCmd = &cobra.Command{
|
|||||||
Long: `The reconcile receiver command triggers a reconciliation of a Receiver resource and waits for it to finish.`,
|
Long: `The reconcile receiver command triggers a reconciliation of a Receiver resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger a reconciliation for an existing receiver
|
Example: ` # Trigger a reconciliation for an existing receiver
|
||||||
flux reconcile receiver main`,
|
flux reconcile receiver main`,
|
||||||
RunE: reconcileReceiverCmdRun,
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||||
|
RunE: reconcileReceiverCmdRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ var reconcileSourceBucketCmd = &cobra.Command{
|
|||||||
Long: `The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.`,
|
Long: `The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger a reconciliation for an existing source
|
Example: ` # Trigger a reconciliation for an existing source
|
||||||
flux reconcile source bucket podinfo`,
|
flux reconcile source bucket podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileCommand{
|
||||||
apiType: bucketType,
|
apiType: bucketType,
|
||||||
object: bucketAdapter{&sourcev1.Bucket{}},
|
object: bucketAdapter{&sourcev1.Bucket{}},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ var reconcileSourceGitCmd = &cobra.Command{
|
|||||||
Long: `The reconcile source command triggers a reconciliation of a GitRepository resource and waits for it to finish.`,
|
Long: `The reconcile source command triggers a reconciliation of a GitRepository resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger a git pull for an existing source
|
Example: ` # Trigger a git pull for an existing source
|
||||||
flux reconcile source git podinfo`,
|
flux reconcile source git podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileCommand{
|
||||||
apiType: gitRepositoryType,
|
apiType: gitRepositoryType,
|
||||||
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ var reconcileSourceHelmCmd = &cobra.Command{
|
|||||||
Long: `The reconcile source command triggers a reconciliation of a HelmRepository resource and waits for it to finish.`,
|
Long: `The reconcile source command triggers a reconciliation of a HelmRepository resource and waits for it to finish.`,
|
||||||
Example: ` # Trigger a reconciliation for an existing source
|
Example: ` # Trigger a reconciliation for an existing source
|
||||||
flux reconcile source helm podinfo`,
|
flux reconcile source helm podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||||
RunE: reconcileCommand{
|
RunE: reconcileCommand{
|
||||||
apiType: helmRepositoryType,
|
apiType: helmRepositoryType,
|
||||||
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var resumeAlertCmd = &cobra.Command{
|
|||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Alert
|
Example: ` # Resume reconciliation for an existing Alert
|
||||||
flux resume alert main`,
|
flux resume alert main`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: alertType,
|
apiType: alertType,
|
||||||
object: alertAdapter{¬ificationv1.Alert{}},
|
object: alertAdapter{¬ificationv1.Alert{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ var resumeHrCmd = &cobra.Command{
|
|||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Helm release
|
Example: ` # Resume reconciliation for an existing Helm release
|
||||||
flux resume hr podinfo`,
|
flux resume hr podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: helmReleaseType,
|
apiType: helmReleaseType,
|
||||||
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ var resumeImageRepositoryCmd = &cobra.Command{
|
|||||||
Long: `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`,
|
Long: `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`,
|
||||||
Example: ` # Resume reconciliation for an existing ImageRepository
|
Example: ` # Resume reconciliation for an existing ImageRepository
|
||||||
flux resume image repository alpine`,
|
flux resume image repository alpine`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: imageRepositoryType,
|
apiType: imageRepositoryType,
|
||||||
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ var resumeImageUpdateCmd = &cobra.Command{
|
|||||||
Long: `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`,
|
Long: `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`,
|
||||||
Example: ` # Resume reconciliation for an existing ImageUpdateAutomation
|
Example: ` # Resume reconciliation for an existing ImageUpdateAutomation
|
||||||
flux resume image update latest-images`,
|
flux resume image update latest-images`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: imageUpdateAutomationType,
|
apiType: imageUpdateAutomationType,
|
||||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ var resumeKsCmd = &cobra.Command{
|
|||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Kustomization
|
Example: ` # Resume reconciliation for an existing Kustomization
|
||||||
flux resume ks podinfo`,
|
flux resume ks podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: kustomizationType,
|
apiType: kustomizationType,
|
||||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user