Compare commits
57 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 |
2
.github/workflows/bootstrap.yaml
vendored
2
.github/workflows/bootstrap.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: fluxcd/pkg//actions/kustomize@main
|
||||
- name: Build
|
||||
run: |
|
||||
make cmd/flux/manifests
|
||||
make cmd/flux/.manifests.done
|
||||
go build -o /tmp/flux ./cmd/flux
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
|
||||
82
.github/workflows/e2e-arm64.yaml
vendored
82
.github/workflows/e2e-arm64.yaml
vendored
@@ -3,7 +3,7 @@ name: e2e-arm64
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main, update-components ]
|
||||
branches: [ main, update-components, arm64-e2e ]
|
||||
|
||||
jobs:
|
||||
ampere:
|
||||
@@ -23,86 +23,16 @@ jobs:
|
||||
run: |
|
||||
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)
|
||||
- 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
|
||||
run: |
|
||||
go build -o /tmp/flux ./cmd/flux
|
||||
make build
|
||||
- name: Setup Kubernetes Kind
|
||||
run: |
|
||||
kind create cluster --name ${{ steps.prep.outputs.CLUSTER }}
|
||||
- name: flux check --pre
|
||||
run: |
|
||||
/tmp/flux check --pre \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux install
|
||||
run: |
|
||||
/tmp/flux install \
|
||||
--components-extra=image-reflector-controller,image-automation-controller \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux create source git
|
||||
run: |
|
||||
/tmp/flux create source git podinfo-gogit \
|
||||
--git-implementation=go-git \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--tag-semver=">1.0.0" \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
/tmp/flux create source git podinfo-libgit2 \
|
||||
--git-implementation=libgit2 \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--branch="master" \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux create kustomization
|
||||
run: |
|
||||
/tmp/flux create kustomization podinfo \
|
||||
--source=podinfo-gogit \
|
||||
--path="./deploy/overlays/dev" \
|
||||
--prune=true \
|
||||
--interval=5m \
|
||||
--validation=client \
|
||||
--health-check="Deployment/frontend.dev" \
|
||||
--health-check="Deployment/backend.dev" \
|
||||
--health-check-timeout=3m \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux create tenant
|
||||
run: |
|
||||
/tmp/flux create tenant dev-team \
|
||||
--with-namespace=apps \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux create helmrelease
|
||||
run: |
|
||||
/tmp/flux -n apps create source helm podinfo \
|
||||
--url https://stefanprodan.github.io/podinfo \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
|
||||
/tmp/flux -n apps create hr podinfo-helm \
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo \
|
||||
--chart-version="6.0.x" \
|
||||
--service-account=dev-team \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux get all
|
||||
run: |
|
||||
/tmp/flux get all --all-namespaces \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: flux uninstall
|
||||
run: |
|
||||
/tmp/flux uninstall -s \
|
||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
||||
- name: Debug failure
|
||||
if: failure()
|
||||
run: |
|
||||
kubectl --context ${{ steps.prep.outputs.CONTEXT }} -n flux-system get all
|
||||
kubectl --context ${{ steps.prep.outputs.CONTEXT }} -n flux-system describe pods
|
||||
/tmp/flux logs --all-namespaces
|
||||
kind create cluster --name ${{ steps.prep.outputs.CLUSTER }} --kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||
- name: Run e2e tests
|
||||
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
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
|
||||
image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
|
||||
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
|
||||
run: |
|
||||
kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
|
||||
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg//actions/kustomize@main
|
||||
- name: Run test
|
||||
- name: Run tests
|
||||
run: make test
|
||||
- name: Run e2e tests
|
||||
run: TEST_KUBECONFIG=$HOME/.kube/config make e2e
|
||||
- name: Check if working tree is dirty
|
||||
run: |
|
||||
if [[ $(git diff --stat) != '' ]]; then
|
||||
@@ -170,26 +176,6 @@ jobs:
|
||||
--chart=podinfo \
|
||||
--chart-version="5.0.x" \
|
||||
--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
|
||||
run: |
|
||||
/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
|
||||
- name: Generate manifests
|
||||
run: |
|
||||
make cmd/flux/manifests
|
||||
make cmd/flux/.manifests.done
|
||||
./manifests/scripts/bundle.sh "" ./output manifests.tar.gz
|
||||
kustomize build ./manifests/install > ./output/install.yaml
|
||||
- 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
|
||||
- name: Build manifests
|
||||
run: |
|
||||
make cmd/flux/manifests
|
||||
make cmd/flux/.manifests.done
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/golang@master
|
||||
continue-on-error: true
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@ dist/
|
||||
bin/
|
||||
output/
|
||||
cmd/flux/manifests/
|
||||
cmd/flux/.manifests.done
|
||||
|
||||
# Docs
|
||||
site/
|
||||
|
||||
@@ -77,6 +77,16 @@ You can run the unit tests by simply doing
|
||||
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
|
||||
|
||||
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 '"')
|
||||
EMBEDDED_MANIFESTS_TARGET=cmd/flux/manifests
|
||||
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"')
|
||||
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)))
|
||||
|
||||
@@ -14,17 +24,51 @@ fmt:
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
|
||||
go test ./... -coverprofile cover.out
|
||||
setup-kind:
|
||||
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)
|
||||
./manifests/scripts/bundle.sh
|
||||
touch $@
|
||||
|
||||
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:
|
||||
go install cmd/flux
|
||||
|
||||
install-dev:
|
||||
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
|
||||
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
|
||||
brew install fluxcd/tap/flux
|
||||
```
|
||||
To get started check out this [guide](https://fluxcd.io/docs/get-started/)
|
||||
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:
|
||||
|
||||
```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/)
|
||||
For more comprehensive documentation, see the following guides:
|
||||
- [Ways of structuring your repositories](https://fluxcd.io/docs/guides/repository-structure/)
|
||||
- [Manage Helm Releases](https://fluxcd.io/docs/guides/helmreleases/)
|
||||
- [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/)
|
||||
@@ -133,7 +89,9 @@ new contributors and there are a multitude of ways to get involved.
|
||||
|
||||
### Events
|
||||
|
||||
Check out our **[events calendar](https://fluxcd.io/community/#talks)**,
|
||||
both with upcoming talks you can attend or past events videos you can watch.
|
||||
Check out our **[events calendar](https://fluxcd.io/#calendar)**,
|
||||
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!
|
||||
|
||||
@@ -199,6 +199,15 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
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
|
||||
bootstrapOpts := []bootstrap.GitOption{
|
||||
bootstrap.WithRepositoryURL(gitArgs.url),
|
||||
@@ -208,6 +217,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
|
||||
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
|
||||
bootstrap.WithLogger(logger),
|
||||
bootstrap.WithCABundle(caBundle),
|
||||
}
|
||||
|
||||
// Setup bootstrapper with constructed configs
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"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{
|
||||
@@ -29,3 +40,77 @@ var completionCmd = &cobra.Command{
|
||||
func init() {
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -27,12 +28,12 @@ var completionZshCmd = &cobra.Command{
|
||||
Short: "Generates zsh completion scripts",
|
||||
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
|
||||
|
||||
# ~/.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}:
|
||||
|
||||
@@ -43,6 +44,8 @@ mv _flux ~/.oh-my-zsh/completions # oh-my-zsh
|
||||
mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
rootCmd.GenZshCompletion(os.Stdout)
|
||||
// Cobra doesn't source zsh completion file, explicitly doing it here
|
||||
fmt.Println("compdef _flux flux")
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -182,13 +182,16 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
},
|
||||
},
|
||||
Install: &helmv2.Install{
|
||||
CreateNamespace: helmReleaseArgs.createNamespace,
|
||||
},
|
||||
Suspend: false,
|
||||
},
|
||||
}
|
||||
|
||||
if helmReleaseArgs.createNamespace {
|
||||
helmRelease.Spec.Install = &helmv2.Install{
|
||||
CreateNamespace: helmReleaseArgs.createNamespace,
|
||||
}
|
||||
}
|
||||
|
||||
if helmReleaseArgs.saName != "" {
|
||||
helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
},
|
||||
}
|
||||
if sourceHelmArgs.secretRef != "" {
|
||||
if sourceBucketArgs.secretRef != "" {
|
||||
bucket.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: sourceBucketArgs.secretRef,
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ type sourceGitFlags struct {
|
||||
caFile string
|
||||
privateKeyFile string
|
||||
recurseSubmodules bool
|
||||
silent bool
|
||||
}
|
||||
|
||||
var createSourceGitCmd = &cobra.Command{
|
||||
@@ -135,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().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
|
||||
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
|
||||
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
|
||||
|
||||
createSourceCmd.AddCommand(createSourceGitCmd)
|
||||
}
|
||||
@@ -272,12 +274,14 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||
logger.Generatef("deploy key: %s", ppk)
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Have you added the deploy key to your repository",
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return fmt.Errorf("aborting")
|
||||
if !sourceGitArgs.silent {
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Have you added the deploy key to your repository",
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return fmt.Errorf("aborting")
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ var deleteAlertCmd = &cobra.Command{
|
||||
Long: "The delete alert command removes the given Alert from the cluster.",
|
||||
Example: ` # Delete an Alert and the Kubernetes resources created by it
|
||||
flux delete alert main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: alertType,
|
||||
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.",
|
||||
Example: ` # Delete a Provider and the Kubernetes resources created by it
|
||||
flux delete alert-provider slack`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: alertProviderType,
|
||||
object: universalAdapter{¬ificationv1.Provider{}},
|
||||
|
||||
@@ -29,6 +29,7 @@ var deleteHelmReleaseCmd = &cobra.Command{
|
||||
Long: "The delete helmrelease command removes the given HelmRelease from the cluster.",
|
||||
Example: ` # Delete a Helm release and the Kubernetes resources created by it
|
||||
flux delete hr podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: helmReleaseType,
|
||||
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.",
|
||||
Example: ` # Delete an image policy
|
||||
flux delete image policy alpine3.x`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: imagePolicyType,
|
||||
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.",
|
||||
Example: ` # Delete an image repository
|
||||
flux delete image repository alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: imageRepositoryType,
|
||||
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.",
|
||||
Example: ` # Delete an image update automation
|
||||
flux delete image update latest-images`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: imageUpdateAutomationType,
|
||||
object: universalAdapter{&autov1.ImageUpdateAutomation{}},
|
||||
|
||||
@@ -29,6 +29,7 @@ var deleteKsCmd = &cobra.Command{
|
||||
Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
|
||||
Example: ` # Delete a kustomization and the Kubernetes resources created by it
|
||||
flux delete kustomization podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: kustomizationType,
|
||||
object: universalAdapter{&kustomizev1.Kustomization{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var deleteReceiverCmd = &cobra.Command{
|
||||
Long: "The delete receiver command removes the given Receiver from the cluster.",
|
||||
Example: ` # Delete an Receiver and the Kubernetes resources created by it
|
||||
flux delete receiver main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: receiverType,
|
||||
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.",
|
||||
Example: ` # Delete a Bucket source
|
||||
flux delete source bucket podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: bucketType,
|
||||
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.",
|
||||
Example: ` # Delete a Git repository
|
||||
flux delete source git podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: gitRepositoryType,
|
||||
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.",
|
||||
Example: ` # Delete a Helm repository
|
||||
flux delete source helm podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
RunE: deleteCommand{
|
||||
apiType: helmRepositoryType,
|
||||
object: universalAdapter{&sourcev1.HelmRepository{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var exportAlertCmd = &cobra.Command{
|
||||
|
||||
# Export a Alert
|
||||
flux export alert main > main.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||
RunE: exportCommand{
|
||||
object: alertAdapter{¬ificationv1.Alert{}},
|
||||
list: alertListAdapter{¬ificationv1.AlertList{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var exportAlertProviderCmd = &cobra.Command{
|
||||
|
||||
# Export a Provider
|
||||
flux export alert-provider slack > slack.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||
RunE: exportCommand{
|
||||
object: alertProviderAdapter{¬ificationv1.Provider{}},
|
||||
list: alertProviderListAdapter{¬ificationv1.ProviderList{}},
|
||||
|
||||
@@ -33,6 +33,7 @@ var exportHelmReleaseCmd = &cobra.Command{
|
||||
|
||||
# Export a HelmRelease
|
||||
flux export hr my-app > app-release.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
RunE: exportCommand{
|
||||
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||
list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var exportImagePolicyCmd = &cobra.Command{
|
||||
|
||||
# Export a specific policy
|
||||
flux export image policy alpine1x > alpine1x.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: exportCommand{
|
||||
object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
|
||||
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var exportImageRepositoryCmd = &cobra.Command{
|
||||
|
||||
# Export a specific ImageRepository resource
|
||||
flux export image repository alpine > alpine.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||
RunE: exportCommand{
|
||||
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var exportImageUpdateCmd = &cobra.Command{
|
||||
|
||||
# Export a specific automation
|
||||
flux export image update latest-images > latest.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
RunE: exportCommand{
|
||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
||||
|
||||
@@ -33,6 +33,7 @@ var exportKsCmd = &cobra.Command{
|
||||
|
||||
# Export a Kustomization
|
||||
flux export kustomization my-app > kustomization.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: exportCommand{
|
||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||
list: kustomizationListAdapter{&kustomizev1.KustomizationList{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var exportReceiverCmd = &cobra.Command{
|
||||
|
||||
# Export a Receiver
|
||||
flux export receiver main > main.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||
RunE: exportCommand{
|
||||
list: receiverListAdapter{¬ificationv1.ReceiverList{}},
|
||||
object: receiverAdapter{¬ificationv1.Receiver{}},
|
||||
|
||||
@@ -33,6 +33,7 @@ var exportSourceBucketCmd = &cobra.Command{
|
||||
|
||||
# Export a Bucket source including the static credentials
|
||||
flux export source bucket my-bucket --with-credentials > source.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
RunE: exportWithSecretCommand{
|
||||
list: bucketListAdapter{&sourcev1.BucketList{}},
|
||||
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
|
||||
flux export source git my-private-repo --with-credentials > source.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||
RunE: exportWithSecretCommand{
|
||||
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||
list: gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
|
||||
|
||||
@@ -33,6 +33,7 @@ var exportSourceHelmCmd = &cobra.Command{
|
||||
|
||||
# Export a HelmRepository source including the basic auth credentials
|
||||
flux export source helm my-private-repo --with-credentials > source.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
RunE: exportWithSecretCommand{
|
||||
list: helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
|
||||
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||
|
||||
@@ -177,7 +177,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.PrintTable(os.Stdout, header, rows)
|
||||
utils.PrintTable(cmd.OutOrStderr(), header, rows)
|
||||
|
||||
if getAll {
|
||||
fmt.Println()
|
||||
|
||||
@@ -34,6 +34,7 @@ var getAlertCmd = &cobra.Command{
|
||||
Long: "The get alert command prints the statuses of the resources.",
|
||||
Example: ` # List all Alerts and their status
|
||||
flux get alerts`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: alertType,
|
||||
|
||||
@@ -32,6 +32,7 @@ var getAlertProviderCmd = &cobra.Command{
|
||||
Long: "The get alert-provider command prints the statuses of the resources.",
|
||||
Example: ` # List all Providers and their status
|
||||
flux get alert-providers`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: alertProviderType,
|
||||
|
||||
@@ -33,6 +33,7 @@ var getHelmReleaseCmd = &cobra.Command{
|
||||
Long: "The get helmreleases command prints the statuses of the resources.",
|
||||
Example: ` # List all Helm releases and their status
|
||||
flux get helmreleases`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: helmReleaseType,
|
||||
|
||||
@@ -34,6 +34,7 @@ var getImagePolicyCmd = &cobra.Command{
|
||||
|
||||
# List image policies from all namespaces
|
||||
flux get image policy --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: imagePolicyType,
|
||||
|
||||
@@ -37,6 +37,7 @@ var getImageRepositoryCmd = &cobra.Command{
|
||||
|
||||
# List image repositories from all namespaces
|
||||
flux get image repository --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: imageRepositoryType,
|
||||
|
||||
@@ -37,6 +37,7 @@ var getImageUpdateCmd = &cobra.Command{
|
||||
|
||||
# List image update automations from all namespaces
|
||||
flux get image update --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: imageUpdateAutomationType,
|
||||
|
||||
@@ -34,6 +34,7 @@ var getKsCmd = &cobra.Command{
|
||||
Long: "The get kustomizations command prints the statuses of the resources.",
|
||||
Example: ` # List all kustomizations and their status
|
||||
flux get kustomizations`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: kustomizationType,
|
||||
|
||||
@@ -34,6 +34,7 @@ var getReceiverCmd = &cobra.Command{
|
||||
Long: "The get receiver command prints the statuses of the resources.",
|
||||
Example: ` # List all Receiver and their status
|
||||
flux get receivers`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: receiverType,
|
||||
|
||||
@@ -36,6 +36,7 @@ var getSourceBucketCmd = &cobra.Command{
|
||||
|
||||
# List buckets from all namespaces
|
||||
flux get sources helm --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: bucketType,
|
||||
|
||||
@@ -36,6 +36,7 @@ var getSourceHelmChartCmd = &cobra.Command{
|
||||
|
||||
# List Helm charts from all namespaces
|
||||
flux get sources chart --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: helmChartType,
|
||||
|
||||
@@ -36,6 +36,7 @@ var getSourceGitCmd = &cobra.Command{
|
||||
|
||||
# List Git repositories from all namespaces
|
||||
flux get sources git --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: gitRepositoryType,
|
||||
|
||||
@@ -36,6 +36,7 @@ var getSourceHelmCmd = &cobra.Command{
|
||||
|
||||
# List Helm repositories from all namespaces
|
||||
flux get sources helm --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: helmRepositoryType,
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
@@ -42,16 +44,19 @@ var logsCmd = &cobra.Command{
|
||||
Short: "Display formatted logs for 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
|
||||
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
|
||||
flux logs --follow --level=error --all-namespaces
|
||||
# Stream logs for a particular log level
|
||||
flux logs --follow --level=error --all-namespaces
|
||||
|
||||
# Filter logs by kind, name and namespace
|
||||
flux logs --kind=Kustomization --name=podinfo --namespace=default
|
||||
# Filter logs by kind, name and namespace
|
||||
flux logs --kind=Kustomization --name=podinfo --namespace=default
|
||||
|
||||
# Print logs when Flux is installed in a different namespace than flux-system
|
||||
flux logs --flux-namespace=my-namespace
|
||||
# Print logs when Flux is installed in a different namespace than flux-system
|
||||
flux logs --flux-namespace=my-namespace
|
||||
`,
|
||||
RunE: logsCmdRun,
|
||||
}
|
||||
@@ -64,6 +69,8 @@ type logsFlags struct {
|
||||
name string
|
||||
fluxNamespace string
|
||||
allNamespaces bool
|
||||
sinceTime string
|
||||
sinceSeconds time.Duration
|
||||
}
|
||||
|
||||
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().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running")
|
||||
logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces")
|
||||
logsCmd.Flags().DurationVar(&logsArgs.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)
|
||||
}
|
||||
|
||||
@@ -87,7 +96,6 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
var pods []corev1.Pod
|
||||
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -102,7 +110,7 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("no argument required")
|
||||
}
|
||||
|
||||
pods, err = getPods(ctx, clientset, fluxSelector)
|
||||
pods, err := getPods(ctx, clientset, fluxSelector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -115,6 +123,24 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
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
|
||||
for _, pod := range pods {
|
||||
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,9 +23,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
)
|
||||
|
||||
@@ -109,11 +109,15 @@ var rootArgs = NewRootFlags()
|
||||
|
||||
func init() {
|
||||
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().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
|
||||
rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "",
|
||||
"absolute path to the kubeconfig file")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", "kubernetes context to use")
|
||||
rootCmd.RegisterFlagCompletionFunc("context", contextsCompletionFunc)
|
||||
|
||||
rootCmd.DisableAutoGenTag = true
|
||||
}
|
||||
@@ -127,18 +131,6 @@ func NewRootFlags() rootFlags {
|
||||
return rf
|
||||
}
|
||||
|
||||
type rootContext struct {
|
||||
kubeManager utils.KubeManager
|
||||
}
|
||||
|
||||
var rootCtx = NewRootContext()
|
||||
|
||||
func NewRootContext() rootContext {
|
||||
var rc rootContext
|
||||
rc.kubeManager = utils.DefaultKubeManager()
|
||||
return rc
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
configureKubeconfig()
|
||||
|
||||
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
|
||||
}
|
||||
@@ -4,32 +4,38 @@ 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"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Ensure tests print consistent timestamps regardless of timezone
|
||||
os.Setenv("TZ", "UTC")
|
||||
os.Exit(m.Run())
|
||||
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(objectFile string) ([]client.Object, error) {
|
||||
obj, err := os.ReadFile(objectFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects := []client.Object{}
|
||||
reader := k8syaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(obj)))
|
||||
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 {
|
||||
@@ -43,23 +49,49 @@ func readYamlObjects(objectFile string) ([]client.Object, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, unstructuredObj)
|
||||
objects = append(objects, *unstructuredObj)
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// A KubeManager that can create objects that are subject to a test.
|
||||
type fakeKubeManager struct {
|
||||
fakeClient client.WithWatch
|
||||
type testEnvKubeManager struct {
|
||||
client client.WithWatch
|
||||
testEnv *envtest.Environment
|
||||
kubeConfigPath string
|
||||
}
|
||||
|
||||
func (m *fakeKubeManager) NewClient(kubeconfig string, kubecontext string) (client.WithWatch, error) {
|
||||
return m.fakeClient, nil
|
||||
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 *fakeKubeManager) CreateObjects(clientObjects []client.Object) error {
|
||||
func (m *testEnvKubeManager) CreateObjects(clientObjects []unstructured.Unstructured, t *testing.T) error {
|
||||
for _, obj := range clientObjects {
|
||||
err := m.fakeClient.Create(context.Background(), obj)
|
||||
// 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
|
||||
}
|
||||
@@ -67,11 +99,198 @@ func (m *fakeKubeManager) CreateObjects(clientObjects []client.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFakeKubeManager() *fakeKubeManager {
|
||||
c := fakeclient.NewClientBuilder().WithScheme(utils.NewScheme()).Build()
|
||||
return &fakeKubeManager{
|
||||
fakeClient: c,
|
||||
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.
|
||||
@@ -87,72 +306,10 @@ func executeCommand(cmd string) (string, error) {
|
||||
rootCmd.SetErr(buf)
|
||||
rootCmd.SetArgs(args)
|
||||
|
||||
logger.stderr = rootCmd.ErrOrStderr()
|
||||
|
||||
_, err = rootCmd.ExecuteC()
|
||||
result := buf.String()
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// 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
|
||||
// When true, the test expects the command to fail.
|
||||
wantError bool
|
||||
// String literal that contains the expected test output.
|
||||
goldenValue string
|
||||
// Filename that contains the expected test output.
|
||||
goldenFile string
|
||||
// Filename that contains yaml objects to load into Kubernetes
|
||||
objectFile string
|
||||
}
|
||||
|
||||
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
||||
km := NewFakeKubeManager()
|
||||
rootCtx.kubeManager = km
|
||||
|
||||
if cmd.objectFile != "" {
|
||||
clientObjects, err := readYamlObjects(cmd.objectFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Error loading yaml: '%v'", err)
|
||||
}
|
||||
err = km.CreateObjects(clientObjects)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test objects: '%v'", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual, err := executeCommand(cmd.args)
|
||||
if (err != nil) != cmd.wantError {
|
||||
t.Fatalf("Expected error='%v', Got: %v", cmd.wantError, err)
|
||||
}
|
||||
if err != nil {
|
||||
actual = err.Error()
|
||||
}
|
||||
|
||||
var expected string
|
||||
if cmd.goldenValue != "" {
|
||||
expected = cmd.goldenValue
|
||||
}
|
||||
if cmd.goldenFile != "" {
|
||||
expectedOutput, err := os.ReadFile(cmd.goldenFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading golden file: '%s'", err)
|
||||
}
|
||||
expected = string(expectedOutput)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(expected, actual)
|
||||
if diff != "" {
|
||||
t.Errorf("Mismatch from '%s' (-want +got):\n%s", cmd.goldenFile, diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: "--version",
|
||||
goldenValue: "flux version 0.0.0-dev.0\n",
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -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.`,
|
||||
Example: ` # Trigger a reconciliation for an existing alert
|
||||
flux reconcile alert main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: alertType,
|
||||
object: alertAdapter{¬ificationv1.Alert{}},
|
||||
|
||||
@@ -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.`,
|
||||
Example: ` # Trigger a reconciliation for an existing provider
|
||||
flux reconcile alert-provider slack`,
|
||||
RunE: reconcileAlertProviderCmdRun,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||
RunE: reconcileAlertProviderCmdRun,
|
||||
}
|
||||
|
||||
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
|
||||
flux reconcile hr podinfo --with-source`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
RunE: reconcileWithSourceCommand{
|
||||
apiType: helmReleaseType,
|
||||
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.`,
|
||||
Example: ` # Trigger an scan for an existing image repository
|
||||
flux reconcile image repository alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: imageRepositoryType,
|
||||
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.`,
|
||||
Example: ` # Trigger an automation run for an existing image update automation
|
||||
flux reconcile image update latest-images`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: imageUpdateAutomationType,
|
||||
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
|
||||
flux reconcile kustomization podinfo --with-source`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: reconcileWithSourceCommand{
|
||||
apiType: kustomizationType,
|
||||
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.`,
|
||||
Example: ` # Trigger a reconciliation for an existing receiver
|
||||
flux reconcile receiver main`,
|
||||
RunE: reconcileReceiverCmdRun,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||
RunE: reconcileReceiverCmdRun,
|
||||
}
|
||||
|
||||
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.`,
|
||||
Example: ` # Trigger a reconciliation for an existing source
|
||||
flux reconcile source bucket podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: bucketType,
|
||||
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.`,
|
||||
Example: ` # Trigger a git pull for an existing source
|
||||
flux reconcile source git podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: gitRepositoryType,
|
||||
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.`,
|
||||
Example: ` # Trigger a reconciliation for an existing source
|
||||
flux reconcile source helm podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: helmRepositoryType,
|
||||
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||
|
||||
@@ -29,6 +29,7 @@ var resumeAlertCmd = &cobra.Command{
|
||||
finish the apply.`,
|
||||
Example: ` # Resume reconciliation for an existing Alert
|
||||
flux resume alert main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: alertType,
|
||||
object: alertAdapter{¬ificationv1.Alert{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var resumeHrCmd = &cobra.Command{
|
||||
finish the apply.`,
|
||||
Example: ` # Resume reconciliation for an existing Helm release
|
||||
flux resume hr podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: helmReleaseType,
|
||||
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.`,
|
||||
Example: ` # Resume reconciliation for an existing ImageRepository
|
||||
flux resume image repository alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: imageRepositoryType,
|
||||
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.`,
|
||||
Example: ` # Resume reconciliation for an existing ImageUpdateAutomation
|
||||
flux resume image update latest-images`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: imageUpdateAutomationType,
|
||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||
|
||||
@@ -32,6 +32,7 @@ var resumeKsCmd = &cobra.Command{
|
||||
finish the apply.`,
|
||||
Example: ` # Resume reconciliation for an existing Kustomization
|
||||
flux resume ks podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: kustomizationType,
|
||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||
|
||||
@@ -29,6 +29,7 @@ var resumeReceiverCmd = &cobra.Command{
|
||||
finish the apply.`,
|
||||
Example: ` # Resume reconciliation for an existing Receiver
|
||||
flux resume receiver main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: receiverType,
|
||||
object: receiverAdapter{¬ificationv1.Receiver{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var resumeSourceBucketCmd = &cobra.Command{
|
||||
Long: `The resume command marks a previously suspended Bucket resource for reconciliation and waits for it to finish.`,
|
||||
Example: ` # Resume reconciliation for an existing Bucket
|
||||
flux resume source bucket podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: bucketType,
|
||||
object: &bucketAdapter{&sourcev1.Bucket{}},
|
||||
|
||||
@@ -30,6 +30,7 @@ var resumeSourceHelmChartCmd = &cobra.Command{
|
||||
Long: `The resume command marks a previously suspended HelmChart resource for reconciliation and waits for it to finish.`,
|
||||
Example: ` # Resume reconciliation for an existing HelmChart
|
||||
flux resume source chart podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: helmChartType,
|
||||
object: &helmChartAdapter{&sourcev1.HelmChart{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var resumeSourceGitCmd = &cobra.Command{
|
||||
Long: `The resume command marks a previously suspended GitRepository resource for reconciliation and waits for it to finish.`,
|
||||
Example: ` # Resume reconciliation for an existing GitRepository
|
||||
flux resume source git podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: gitRepositoryType,
|
||||
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var resumeSourceHelmCmd = &cobra.Command{
|
||||
Long: `The resume command marks a previously suspended HelmRepository resource for reconciliation and waits for it to finish.`,
|
||||
Example: ` # Resume reconciliation for an existing HelmRepository
|
||||
flux resume source helm bitnami`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: helmRepositoryType,
|
||||
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendAlertCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a Alert resource.",
|
||||
Example: ` # Suspend reconciliation for an existing Alert
|
||||
flux suspend alert main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: alertType,
|
||||
object: &alertAdapter{¬ificationv1.Alert{}},
|
||||
|
||||
@@ -29,6 +29,7 @@ var suspendHrCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a HelmRelease resource.",
|
||||
Example: ` # Suspend reconciliation for an existing Helm release
|
||||
flux suspend hr podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: helmReleaseType,
|
||||
object: &helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendImageRepositoryCmd = &cobra.Command{
|
||||
Long: "The suspend image repository command disables the reconciliation of a ImageRepository resource.",
|
||||
Example: ` # Suspend reconciliation for an existing ImageRepository
|
||||
flux suspend image repository alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: imageRepositoryType,
|
||||
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendImageUpdateCmd = &cobra.Command{
|
||||
Long: "The suspend image update command disables the reconciliation of a ImageUpdateAutomation resource.",
|
||||
Example: ` # Suspend reconciliation for an existing ImageUpdateAutomation
|
||||
flux suspend image update latest-images`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: imageUpdateAutomationType,
|
||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||
|
||||
@@ -29,6 +29,7 @@ var suspendKsCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a Kustomization resource.",
|
||||
Example: ` # Suspend reconciliation for an existing Kustomization
|
||||
flux suspend ks podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: kustomizationType,
|
||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendReceiverCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a Receiver resource.",
|
||||
Example: ` # Suspend reconciliation for an existing Receiver
|
||||
flux suspend receiver main`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: receiverType,
|
||||
object: &receiverAdapter{¬ificationv1.Receiver{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendSourceBucketCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a Bucket resource.",
|
||||
Example: ` # Suspend reconciliation for an existing Bucket
|
||||
flux suspend source bucket podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: bucketType,
|
||||
object: bucketAdapter{&sourcev1.Bucket{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendSourceHelmChartCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a HelmChart resource.",
|
||||
Example: ` # Suspend reconciliation for an existing HelmChart
|
||||
flux suspend source chart podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: helmChartType,
|
||||
object: helmChartAdapter{&sourcev1.HelmChart{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendSourceGitCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a GitRepository resource.",
|
||||
Example: ` # Suspend reconciliation for an existing GitRepository
|
||||
flux suspend source git podinfo`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: gitRepositoryType,
|
||||
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||
|
||||
@@ -28,6 +28,7 @@ var suspendSourceHelmCmd = &cobra.Command{
|
||||
Long: "The suspend command disables the reconciliation of a HelmRepository resource.",
|
||||
Example: ` # Suspend reconciliation for an existing HelmRepository
|
||||
flux suspend source helm bitnami`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: helmRepositoryType,
|
||||
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||
|
||||
4
cmd/flux/testdata/check/check_pre.golden
vendored
Normal file
4
cmd/flux/testdata/check/check_pre.golden
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
► checking prerequisites
|
||||
✔ kubectl {{ .clientVersion }} >=1.18.0-0
|
||||
✔ Kubernetes {{ .serverVersion }} >=1.16.0-0
|
||||
✔ prerequisites checks passed
|
||||
6
cmd/flux/testdata/create_source_git/success.golden
vendored
Normal file
6
cmd/flux/testdata/create_source_git/success.golden
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
✚ generating GitRepository source
|
||||
► applying GitRepository source
|
||||
✔ GitRepository source created
|
||||
◎ waiting for GitRepository source reconciliation
|
||||
✔ GitRepository source reconciliation completed
|
||||
✔ fetched revision: v1
|
||||
6
cmd/flux/testdata/helmrelease/create_helmrelease_from_git.golden
vendored
Normal file
6
cmd/flux/testdata/helmrelease/create_helmrelease_from_git.golden
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
✚ generating HelmRelease
|
||||
► applying HelmRelease
|
||||
✔ HelmRelease created
|
||||
◎ waiting for HelmRelease reconciliation
|
||||
✔ HelmRelease thrfg is ready
|
||||
✔ applied revision 6.0.0
|
||||
6
cmd/flux/testdata/helmrelease/create_source_git.golden
vendored
Normal file
6
cmd/flux/testdata/helmrelease/create_source_git.golden
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
✚ generating GitRepository source
|
||||
► applying GitRepository source
|
||||
✔ GitRepository source created
|
||||
◎ waiting for GitRepository source reconciliation
|
||||
✔ GitRepository source reconciliation completed
|
||||
✔ fetched revision: 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951
|
||||
2
cmd/flux/testdata/helmrelease/delete_helmrelease_from_git.golden
vendored
Normal file
2
cmd/flux/testdata/helmrelease/delete_helmrelease_from_git.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
► deleting helmreleases thrfg in {{ .ns }} namespace
|
||||
✔ helmreleases deleted
|
||||
2
cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden
vendored
Normal file
2
cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
NAME READY MESSAGE REVISION SUSPENDED
|
||||
thrfg True Release reconciliation succeeded 6.0.0 False
|
||||
10
cmd/flux/testdata/helmrelease/reconcile_helmrelease_from_git.golden
vendored
Normal file
10
cmd/flux/testdata/helmrelease/reconcile_helmrelease_from_git.golden
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
► annotating GitRepository thrfg in {{ .ns }} namespace
|
||||
✔ GitRepository annotated
|
||||
◎ waiting for GitRepository reconciliation
|
||||
✔ GitRepository reconciliation completed
|
||||
✔ fetched revision 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951
|
||||
► annotating HelmRelease thrfg in {{ .ns }} namespace
|
||||
✔ HelmRelease annotated
|
||||
◎ waiting for HelmRelease reconciliation
|
||||
✔ HelmRelease reconciliation completed
|
||||
✔ applied revision 6.0.0
|
||||
5
cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden
vendored
Normal file
5
cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
► resuming helmreleases thrfg in {{ .ns }} namespace
|
||||
✔ helmreleases resumed
|
||||
◎ waiting for HelmRelease reconciliation
|
||||
✔ HelmRelease reconciliation completed
|
||||
✔ applied revision 6.0.0
|
||||
2
cmd/flux/testdata/helmrelease/suspend_helmrelease_from_git.golden
vendored
Normal file
2
cmd/flux/testdata/helmrelease/suspend_helmrelease_from_git.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
► suspending helmreleases thrfg in {{ .ns }} namespace
|
||||
✔ helmreleases suspended
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user