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

Compare commits

...

77 Commits

Author SHA1 Message Date
Stefan Prodan
b4f13e10d3 Merge pull request #351 from fluxcd/gitlab-https-auth
Add GitLab HTTPS auth to bootstrap options
2020-10-17 13:15:26 +03:00
Stefan Prodan
d0eb55fb86 Merge branch 'gitlab-https-auth' of https://github.com/fluxcd/toolkit into gitlab-https-auth 2020-10-17 13:08:38 +03:00
Stefan Prodan
b041dbd14f Add GitLab HTTPS auth to bootstrap options
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-17 09:27:47 +00:00
Stefan Prodan
0ab814fbc4 Merge pull request #348 from fluxcd/update-components
Update toolkit components
2020-10-17 12:26:46 +03:00
fluxcdbot
a2144b1759 Update toolkit components 2020-10-17 07:21:01 +00:00
Stefan Prodan
82eb3b5ccf Merge pull request #353 from fluxcd/build/remove-docs-hotfix
Remove documentation version hotfix
2020-10-17 10:05:00 +03:00
Hidde Beydals
7515080c63 Remove documentation version hotfix
And re-use the same logic to properly compare current and next release
versions in update workflow.
2020-10-16 22:19:58 +02:00
Hidde Beydals
07015c5172 Merge pull request #352 from fluxcd/refactor-create-reconcile-resume
Refactor create, reconcile and resume commands
2020-10-16 20:47:39 +02:00
Hidde Beydals
19918cd342 Refactor create, reconcile and resume cmds
* Take ObservedGeneration into account in readiness checks where
  applicable
* Reduce amount of code (and duplicate GETs) by working with pointers
  where possible
* Improve logged messages to properly take resource names into account
  and better describe processes
2020-10-16 20:17:39 +02:00
Stefan Prodan
16f0ed548e Add GitLab HTTPS auth to bootstrap options
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-16 20:57:21 +03:00
Stefan Prodan
0d1600275d Merge pull request #349 from fluxcd/windows-os-cli
Publish CLI binary for Windows
2020-10-16 17:30:59 +03:00
Stefan Prodan
768f32bd2d Publish CLI binary for Windows
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-16 16:32:11 +03:00
Stefan Prodan
d86798a7c6 Merge pull request #345 from staceypotter/patch-2
updated featured talks + upcoming meetups
2020-10-16 10:50:49 +03:00
Stefan Prodan
7718c6a2a5 Merge pull request #340 from fluxcd/bootstrap-validate-manifests
Enable client side validation on bootstrap
2020-10-16 10:41:47 +03:00
Stefan Prodan
a0616ac2cb Merge pull request #334 from StupidScience/windows-support
Get rid of shell-out and invoke command directly via exec
2020-10-16 10:41:31 +03:00
Stefan Prodan
23bbe0d52b Merge pull request #346 from staceypotter/patch-3
updated with featured talks + upcoming meetups
2020-10-16 10:41:07 +03:00
Stefan Prodan
6d9e7758a2 Merge pull request #347 from fluxcd/rebase-action
Add rebase action
2020-10-16 10:39:48 +03:00
Stefan Prodan
f81a80c2da Add rebase action
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-16 10:31:50 +03:00
Stefan Prodan
ab64619a41 Merge pull request #344 from smorimoto/fossa-action
Use fossa-contrib/fossa-action instead
2020-10-16 10:23:33 +03:00
Stacey Potter
18bdd26be8 updated with featured talks + upcoming meetups 2020-10-15 19:57:21 -07:00
Stacey Potter
c5f3a674d6 updated featured talks + upcoming meetups 2020-10-15 19:52:07 -07:00
Sora Morimoto
f95e17b144 Use fossa-contrib/fossa-action instead
Signed-off-by: Sora Morimoto <sora@morimoto.io>
2020-10-16 08:16:19 +09:00
“Anton
4989826124 Get rid of shell-out and invoke kubetcl directly via exec 2020-10-15 20:38:44 +03:00
Stefan Prodan
f23a551ae3 Enable client side validation on bootstrap
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-15 18:04:35 +03:00
Stefan Prodan
bd2df01b12 Merge pull request #343 from fluxcd/alert-provider-fix
Set alert provider secretRef only if specified
2020-10-15 18:03:23 +03:00
Stefan Prodan
880ef303e8 Set alert provider secretRef only if specified
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-15 17:51:43 +03:00
Michael Bridgen
b4ed1a0bdc Merge pull request #341 from fluxcd/update-image-auto-progress
Mark the design tasks for image automation as done
2020-10-15 12:23:33 +01:00
Michael Bridgen
7a2eae0699 Mark the design tasks for image automation as done
This leaves the implementation tasks as not done -- a bit of a
simplification, since there's some implementation done, and some
design left to do, but it's close enough at this level.

I estimated that having the design basics figured out is worth 30%
overall.
2020-10-15 12:12:04 +01:00
Stefan Prodan
4464252cae Merge pull request #337 from fluxcd/monitoring
Add cluster stats dashboard to monitoring stack
2020-10-14 17:12:30 +03:00
Stefan Prodan
ca8cf44200 Add cluster stats dashboard to docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-14 16:34:46 +03:00
Stefan Prodan
4565165579 Add cluster stats dashboard
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-14 16:16:56 +03:00
Stefan Prodan
a37be432a9 Merge pull request #333 from fluxcd/docs-update-v0.1.6
Update docs website
2020-10-14 12:11:30 +03:00
Stefan Prodan
90591e852d Update docs website
- add notification-controller/api commands to index
- move diagrams to docs website
- update CRDs docs

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-14 10:35:29 +03:00
Stefan Prodan
13f7d44a06 Merge pull request #327 from nanikjava/f-fix-306
Add ARM instruction to get started guide
2020-10-13 23:55:58 +03:00
Nanik
7dc8aa66a6 Add ARM instruction under staging and production bootstrap 2020-10-14 07:44:16 +11:00
Stefan Prodan
6cf28ab718 Merge pull request #331 from fluxcd/update-components
Update toolkit components
2020-10-13 23:41:39 +03:00
fluxcdbot
f461c5e8b7 Update toolkit components 2020-10-13 17:47:46 +00:00
Stefan Prodan
9433bdf4ad Merge pull request #332 from fluxcd/uninstall-fix
Uninstall improvements
2020-10-13 20:43:48 +03:00
Stefan Prodan
d2d494e079 Uninstall improvements
- ignore not found errors when deleting objects
- remove the CR/CRDs before deleting the cluster role binding
- capture kubectl exist code

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-13 20:30:17 +03:00
Stefan Prodan
e64b35cde8 Merge pull request #302 from phillebaba/feature/notification-controller
Add notification controller CLI commands
2020-10-13 18:31:16 +03:00
Philip Laine
d32e8c6e98 Remove aliases 2020-10-13 12:21:45 +02:00
Philip Laine
55cee488bc Update docs 2020-10-13 11:11:55 +02:00
Philip Laine
65b8942416 Fix minor issues 2020-10-13 11:11:55 +02:00
Philip Laine
94cf7c329c Update docs 2020-10-13 11:11:55 +02:00
Philip Laine
8eac7d6b4d Implement table output 2020-10-13 11:11:55 +02:00
Philip Laine
7ebb34de80 Add receiver command 2020-10-13 11:11:55 +02:00
Philip Laine
6ea84906ac Add alert commands 2020-10-13 11:11:54 +02:00
Philip Laine
f7971a871a Add alert provider commands 2020-10-13 11:11:54 +02:00
Stefan Prodan
54b35b7c2b Merge pull request #323 from fluxcd/monitoring
docs: Install the monitoring stack with gotk
2020-10-12 13:04:43 +03:00
Stefan Prodan
ca970b4ffb docs: Install the monitoring stack with gotk
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-12 09:50:43 +03:00
Stefan Prodan
8a96e32679 Update Prometheus and Grafana
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-11 15:13:16 +03:00
Stefan Prodan
fc4d01b3e5 Allow scraping and webhooks
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-11 13:48:06 +03:00
Stefan Prodan
b6afc8f6ce Merge pull request #317 from staceypotter/patch-1
Add featured talks and meetups section
2020-10-09 09:03:14 +03:00
Stefan Prodan
c481a431be Merge branch 'main' into patch-1 2020-10-09 08:54:45 +03:00
Stacey Potter
0a7b82793e removed header link
Removed "(Check out our [Upcoming Meetups](#upcoming-meetups)!)" from the top of the page.
2020-10-08 22:54:04 -07:00
Stacey Potter
3653236bcb added "featured talks" section
+ upcoming meetups & header link
2020-10-08 15:31:10 -07:00
Stefan Prodan
a2eee72015 Merge pull request #318 from fluxcd/install-pkg
Introduce install package
2020-10-08 14:01:25 +03:00
Stefan Prodan
5672646278 Use install pkg in CLI
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-08 12:47:25 +03:00
Stefan Prodan
c4d3fa7a48 init install pkg
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-10-08 02:31:33 +03:00
Stacey Potter
32b0267b9f Added GOTK guide walk-through
Added invite for Leigh's GOTK guide walk-through on Oct 19 (10am PT/18:00 BST) to the Community Section. LMK if this should be somewhere else - or condensed (wasn't sure how much info to include). Thanks! :)
2020-10-07 11:43:14 -07:00
Stefan Prodan
a7b41a4b8c Merge pull request #315 from rieger-jared/bug/update-notification-api-in-docs
Update api versions in guides
2020-10-07 18:10:38 +03:00
Jared Rieger
bfd6d14bf3 Update api versions in guides 2020-10-07 17:00:43 +02:00
Daniel Holbach
469de31218 Merge pull request #313 from dholbach/update-frontpage
close parentheses, make link to guide more obvious
2020-10-06 14:51:18 +02:00
Daniel Holbach
2ce9823b3a close parentheses, make link to guide more obvious 2020-10-06 14:34:21 +02:00
Stefan Prodan
e8b3d09ddf Merge pull request #310 from fluxcd/mod-cleanup
Use GitHub actions from fluxcd/pkg@main
2020-10-06 11:09:46 +03:00
stefanprodan
f613c01803 Update blang/semver to v4
Signed-off-by: stefanprodan <stefan.prodan@gmail.com>
2020-10-06 10:50:32 +03:00
stefanprodan
a5a5908fb5 Use GitHub actions from fluxcd/pkg@main 2020-10-06 10:50:02 +03:00
Hidde Beydals
5313a0ed47 Merge pull request #305 from fluxcd/docs/get-cmds-columns
Update guide to include column output commands
2020-10-05 18:26:51 +02:00
Hidde Beydals
1c15eebd7c Merge pull request #307 from fluxcd/bugfix-get-source
Display proper revision for sources
2020-10-05 18:26:28 +02:00
Hidde Beydals
f3cab6e177 Display proper revision for sources
Includes a change to an empty revision string if the reconciler has not
produced an artifact yet, as this will otherwise result in a nil
pointer dereference.
2020-10-05 18:16:22 +02:00
Hidde Beydals
c0623334ee Update guide to include column output commands 2020-10-05 18:11:01 +02:00
Hidde Beydals
d41bd6b6b1 Merge pull request #299 from circa10a/main
Switch get commands to use tables for output
2020-10-05 12:34:49 +02:00
Hidde Beydals
c9b4a8eef5 Merge pull request #301 from fluxcd/multi-context-kubeconfig
Support multi-path KUBECONFIG
2020-10-05 08:45:20 +02:00
circa10a
3619cb8bd1 Switch get commands to use tables for output
Signed-off-by: circa10a <caleblemoine@gmail.com>
2020-10-04 16:34:04 -05:00
Hidde Beydals
8e2316ba62 Support multi-path KUBECONFIG
Ref:
https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable
2020-10-04 18:22:03 +02:00
Stefan Prodan
495abf42ef Merge pull request #298 from yiannistri/sealed-secrets-docs
Update api versions in docs
2020-10-03 21:54:30 +03:00
Yiannis
5372dd633e Update api versions in docs 2020-10-03 19:08:16 +01:00
129 changed files with 5457 additions and 1045 deletions

View File

@@ -15,18 +15,14 @@ jobs:
- name: Copy assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SOURCE_VER: ${{ 'v0.1.0' }}
KUSTOMIZE_VER: ${{ 'v0.1.0' }}
HELM_VER: ${{ 'v0.1.0' }}
NOTIFICATION_VER: ${{ 'v0.1.0' }}
run: |
controller_version() {
sed -n "s/\(.*$1\/.*?ref=\)//p;n" "manifests/bases/$1/kustomization.yaml"
sed -n "s/.*$1\/archive\/\(.*\).zip.*/\1/p;n" manifests/bases/$1/kustomization.yaml
}
{
# source-controller CRDs
# SOURCE_VER=$(controller_version source-controller)
SOURCE_VER=$(controller_version source-controller)
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/api/source.md" > docs/components/source/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1beta1/gitrepositories.md" > docs/components/source/gitrepositories.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1beta1/helmrepositories.md" > docs/components/source/helmrepositories.md
@@ -36,21 +32,21 @@ jobs:
{
# kustomize-controller CRDs
# KUSTOMIZE_VER=$(controller_version kustomize-controller)
KUSTOMIZE_VER=$(controller_version kustomize-controller)
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/kustomize-controller/$KUSTOMIZE_VER/docs/api/kustomize.md" > docs/components/kustomize/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/kustomize-controller/$KUSTOMIZE_VER/docs/spec/v1beta1/kustomization.md" > docs/components/kustomize/kustomization.md
}
{
# helm-controller CRDs
# HELM_VER=$(controller_version helm-controller)
HELM_VER=$(controller_version helm-controller)
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/helm-controller/$HELM_VER/docs/api/helmrelease.md" > docs/components/helm/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/helm-controller/$HELM_VER/docs/spec/v2beta1/helmreleases.md" > docs/components/helm/helmreleases.md
}
{
# notification-controller CRDs
# NOTIFICATION_VER=$(controller_version notification-controller)
NOTIFICATION_VER=$(controller_version notification-controller)
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/api/notification.md" > docs/components/notification/api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/event.md" > docs/components/notification/event.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/alert.md" > docs/components/notification/alert.md

View File

@@ -20,9 +20,9 @@ jobs:
restore-keys: |
${{ runner.os }}-go-
- name: Setup Go
uses: actions/setup-go@v2-beta
uses: actions/setup-go@v2
with:
go-version: 1.14.x
go-version: 1.15.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.4.0
with:
@@ -59,6 +59,9 @@ jobs:
- name: gotk get sources git
run: |
./bin/gotk get sources git
- name: gotk get sources git --all-namespaces
run: |
./bin/gotk get sources git --all-namespaces
- name: gotk create kustomization
run: |
./bin/gotk create kustomization podinfo \
@@ -76,6 +79,9 @@ jobs:
- name: gotk get kustomizations
run: |
./bin/gotk get kustomizations
- name: gotk get kustomizations --all-namespaces
run: |
./bin/gotk get kustomizations --all-namespaces
- name: gotk suspend kustomization
run: |
./bin/gotk suspend kustomization podinfo
@@ -112,6 +118,9 @@ jobs:
- name: gotk get helmreleases
run: |
./bin/gotk get helmreleases
- name: gotk get helmreleases --all-namespaces
run: |
./bin/gotk get helmreleases --all-namespaces
- name: gotk export helmrelease
run: |
./bin/gotk export hr --all

View File

@@ -13,15 +13,13 @@ jobs:
- uses: actions/setup-go@v2
with:
go-version: "^1.14.x"
- run: go version
# Runs a set of commands to initialize and analyze with FOSSA
- name: run FOSSA analysis
env:
- name: Add GOPATH to GITHUB_ENV
run: echo "GOPATH=$(go env GOPATH)" >>"$GITHUB_ENV"
- name: Add GOPATH to GITHUB_PATH
run: echo "$GOPATH/bin" >>"$GITHUB_PATH"
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@v1
with:
# FOSSA Push-Only API Token
FOSSA_API_KEY: '5ee8bf422db1471e0bcf2bcb289185de'
run: |
export GOPATH=$HOME/go
export PATH=$PATH:$(go env GOPATH)/bin
curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash
fossa init
fossa analyze
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}

21
.github/workflows/rebase.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: rebase
on:
pull_request:
types: [opened]
issue_comment:
types: [created]
jobs:
rebase:
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') && (github.event.comment.author_association == 'CONTRIBUTOR' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.3.1
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@@ -28,7 +28,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@master
uses: fluxcd/pkg//actions/kustomize@main
- name: Generate manifests tarball
run: |
mkdir -p ./output

View File

@@ -19,7 +19,7 @@ jobs:
bump_version() {
local RELEASE_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
local CURRENT_VERSION=$(sed -n "s/\(.*$1\/.*?ref=\)//p;n" "manifests/bases/$1/kustomization.yaml")
local CURRENT_VERSION=$(sed -n "s/.*$1\/archive\/\(.*\).zip.*/\1/p;n" manifests/bases/$1/kustomization.yaml)
if [[ "${RELEASE_VERSION}" != "${CURRENT_VERSION}" ]]; then
# bump kustomize

View File

@@ -6,6 +6,7 @@ builds:
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64

View File

@@ -2,10 +2,9 @@
[![e2e](https://github.com/fluxcd/toolkit/workflows/e2e/badge.svg)](https://github.com/fluxcd/toolkit/actions)
[![report](https://goreportcard.com/badge/github.com/fluxcd/toolkit)](https://goreportcard.com/report/github.com/fluxcd/toolkit)
[![license](https://img.shields.io/github/license/fluxcd/toolkit.svg)](https://github.com/fluxcd/toolkit/blob/master/LICENSE)
[![license](https://img.shields.io/github/license/fluxcd/toolkit.svg)](https://github.com/fluxcd/toolkit/blob/main/LICENSE)
[![release](https://img.shields.io/github/release/fluxcd/toolkit/all.svg)](https://github.com/fluxcd/toolkit/releases)
![overview](docs/diagrams/gotk-feature.png)
![overview](docs/diagrams/gitops-toolkit.png)
The GitOps Toolkit is a set of composable APIs and specialized tools
that can be used to build a Continuous Delivery platform on top of Kubernetes.
@@ -31,7 +30,7 @@ With Bash:
curl -s https://toolkit.fluxcd.io/install.sh | sudo bash
# enable completions in ~/.bash_profile
. <(gotk completion)
. <(gotk completion bash)
```
Binaries for macOS and Linux AMD64/ARM64 are available to download on the
@@ -82,4 +81,14 @@ The GitOps Toolkit is always looking for new contributors and there are a multit
- To be part of the conversation about Flux's development, [join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
- Check out [how to contribute](CONTRIBUTING.md) to the project
## Featured Talks
- 12 Oct 2020 - [Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY)
- 4 Sep 2020 - [KubeCon/CloudNativeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU)
- 25 June 2020 - [Cloud Native Nordics: Introduction to GitOps & GitOps Toolkit with Alexis Richardson & Stefan Prodan](https://youtu.be/qQBtSkgl7tI)
- 7 May 2020 - [GitOps Days - Community Special: GitOps Toolkit Experimentation with Stefan Prodan](https://youtu.be/WHzxunv4DKk?t=6521)
### Upcoming Meetups
- 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1](https://www.meetup.com/GitOps-Community/events/273640196/)
- 2 Nov 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2](https://www.meetup.com/GitOps-Community/events/273934676/)
We are looking forward to seeing you with us!

View File

@@ -19,6 +19,7 @@ package main
import (
"context"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
@@ -36,6 +37,8 @@ import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/toolkit/pkg/install"
)
var bootstrapCmd = &cobra.Command{
@@ -111,46 +114,50 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
if err := os.MkdirAll(manifestsDir, os.ModePerm); err != nil {
return "", fmt.Errorf("creating manifests dir failed: %w", err)
}
manifest := path.Join(manifestsDir, bootstrapInstallManifest)
if localManifests != "" {
if err := buildKustomization(localManifests, manifest); err != nil {
return "", fmt.Errorf("build kustomization failed: %w", err)
}
return manifest, nil
opts := install.Options{
BaseURL: localManifests,
Version: bootstrapVersion,
Namespace: namespace,
Components: bootstrapComponents,
Registry: bootstrapRegistry,
ImagePullSecret: bootstrapImagePullSecret,
Arch: bootstrapArch,
WatchAllNamespaces: bootstrapWatchAllNamespaces,
NetworkPolicy: bootstrapNetworkPolicy,
LogLevel: bootstrapLogLevel,
NotificationController: defaultNotification,
ManifestsFile: fmt.Sprintf("%s.yaml", namespace),
Timeout: timeout,
}
gotkDir := path.Join(tmpDir, ".gotk")
defer os.RemoveAll(gotkDir)
if err := os.MkdirAll(gotkDir, os.ModePerm); err != nil {
return "", fmt.Errorf("generating manifests failed: %w", err)
if localManifests == "" {
opts.BaseURL = install.MakeDefaultOptions().BaseURL
}
if err := genInstallManifests(bootstrapVersion, namespace, bootstrapComponents,
bootstrapWatchAllNamespaces, bootstrapNetworkPolicy, bootstrapRegistry, bootstrapImagePullSecret,
bootstrapArch, bootstrapLogLevel, gotkDir); err != nil {
return "", fmt.Errorf("generating manifests failed: %w", err)
output, err := install.Generate(opts)
if err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
if err := buildKustomization(gotkDir, manifest); err != nil {
return "", fmt.Errorf("build kustomization failed: %w", err)
if err := ioutil.WriteFile(manifest, output, os.ModePerm); err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
return manifest, nil
}
func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
command := fmt.Sprintf("kubectl apply -f %s", manifestPath)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
kubectlArgs := []string{"apply", "-f", manifestPath}
if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
}
for _, deployment := range components {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String())
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
}
}
@@ -211,6 +218,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
Kind: sourcev1.GitRepositoryKind,
Name: name,
},
Validation: "client",
},
}
@@ -231,20 +239,22 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
}
func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, targetPath, tmpDir string) error {
command := fmt.Sprintf("kubectl apply -k %s", filepath.Join(tmpDir, targetPath, namespace))
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
kubectlArgs := []string{"apply", "-k", filepath.Join(tmpDir, targetPath, namespace)}
if _, err := utils.execKubectlCommand(ctx, ModeStderrOS, kubectlArgs...); err != nil {
return err
}
logger.Waitingf("waiting for cluster sync")
var gitRepository sourcev1.GitRepository
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isGitRepositoryReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &gitRepository)); err != nil {
return err
}
var kustomization kustomizev1.Kustomization
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
isKustomizationReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &kustomization)); err != nil {
return err
}

View File

@@ -26,6 +26,8 @@ import (
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/git"
)
@@ -41,14 +43,17 @@ the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token>
# Run bootstrap for a private repo owned by a GitLab group
# Run bootstrap for a private repo using HTTPS token authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name>
# Run bootstrap for a private repo using SSH authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --ssh-hostname=gitlab.com
# Run bootstrap for a repository path
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
# Run bootstrap for a private repo hosted on a GitLab server
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
@@ -77,7 +82,7 @@ func init() {
bootstrapGitLabCmd.Flags().BoolVar(&glPrivate, "private", true, "is private repository")
bootstrapGitLabCmd.Flags().DurationVar(&glInterval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&glHostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname")
bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, defaults to hostname if not specified")
bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, when specified a deploy key will be added to the repository")
bootstrapGitLabCmd.Flags().StringVar(&glPath, "path", "", "repository path, when specified the cluster sync will be scoped to this path")
bootstrapCmd.AddCommand(bootstrapGitLabCmd)
@@ -172,34 +177,54 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("install completed")
}
// setup SSH deploy key
if shouldCreateDeployKey(ctx, kubeClient, namespace) {
logger.Actionf("configuring deploy key")
u, err := url.Parse(repository.GetSSH())
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
}
repoURL := repository.GetURL()
key, err := generateDeployKey(ctx, kubeClient, u, namespace)
if err != nil {
return fmt.Errorf("generating deploy key failed: %w", err)
}
if glSSHHostname != "" {
// setup SSH deploy key
repoURL = repository.GetSSH()
if shouldCreateDeployKey(ctx, kubeClient, namespace) {
logger.Actionf("configuring deploy key")
u, err := url.Parse(repoURL)
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
}
keyName := "gotk"
if glPath != "" {
keyName = fmt.Sprintf("gotk-%s", glPath)
}
key, err := generateDeployKey(ctx, kubeClient, u, namespace)
if err != nil {
return fmt.Errorf("generating deploy key failed: %w", err)
}
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
keyName := "gotk"
if glPath != "" {
keyName = fmt.Sprintf("gotk-%s", glPath)
}
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
return err
} else if changed {
logger.Successf("deploy key configured")
}
}
} else {
// setup HTTPS token auth
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
Namespace: namespace,
},
StringData: map[string]string{
"username": "git",
"password": glToken,
},
}
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
} else if changed {
logger.Successf("deploy key configured")
}
}
// configure repo synchronization
logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repository.GetSSH(), bootstrapBranch, namespace, namespace, glPath, tmpDir, glInterval); err != nil {
if err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, glPath, tmpDir, glInterval); err != nil {
return err
}

View File

@@ -18,13 +18,14 @@ package main
import (
"context"
"fmt"
"encoding/json"
"os"
"os/exec"
"strings"
"github.com/blang/semver"
"github.com/blang/semver/v4"
"github.com/spf13/cobra"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
@@ -48,6 +49,10 @@ var (
checkComponents []string
)
type kubectlVersion struct {
ClientVersion *apimachineryversion.Info `json:"clientVersion"`
}
func init() {
checkCmd.Flags().BoolVarP(&checkPre, "pre", "", false,
"only run pre-installation checks")
@@ -97,14 +102,20 @@ func kubectlCheck(ctx context.Context, version string) bool {
return false
}
command := "kubectl version --client --short | awk '{ print $3 }'"
output, err := utils.execCommand(ctx, ModeCapture, command)
kubectlArgs := []string{"version", "--client", "--output", "json"}
output, err := utils.execKubectlCommand(ctx, ModeCapture, kubectlArgs...)
if err != nil {
logger.Failuref("kubectl version can't be determined")
return false
}
v, err := semver.ParseTolerant(output)
kv := &kubectlVersion{}
if err = json.Unmarshal([]byte(output), kv); err != nil {
logger.Failuref("kubectl version output can't be unmarshaled")
return false
}
v, err := semver.ParseTolerant(kv.ClientVersion.GitVersion)
if err != nil {
logger.Failuref("kubectl version can't be parsed")
return false
@@ -161,9 +172,8 @@ func componentsCheck() bool {
ok := true
for _, deployment := range checkComponents {
command := fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String())
if output, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
kubectlArgs := []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
if output, err := utils.execKubectlCommand(ctx, ModeCapture, kubectlArgs...); err != nil {
logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n"))
ok = false
} else {

190
cmd/gotk/create_alert.go Normal file
View File

@@ -0,0 +1,190 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var createAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Create or update a Alert resource",
Long: "The create alert command generates a Alert resource.",
Example: ` # Create an Alert for kustomization events
gotk create alert \
--event-severity info \
--event-source Kustomization/gotk-system \
--provider-ref slack \
gotk-system
`,
RunE: createAlertCmdRun,
}
var (
aProviderRef string
aEventSeverity string
aEventSources []string
)
func init() {
createAlertCmd.Flags().StringVar(&aProviderRef, "provider-ref", "", "reference to provider")
createAlertCmd.Flags().StringVar(&aEventSeverity, "event-severity", "", "severity of events to send alerts for")
createAlertCmd.Flags().StringArrayVar(&aEventSources, "event-source", []string{}, "sources that should generate alerts (<kind>/<name>)")
createCmd.AddCommand(createAlertCmd)
}
func createAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
if aProviderRef == "" {
return fmt.Errorf("provider ref is required")
}
eventSources := []notificationv1.CrossNamespaceObjectReference{}
for _, eventSource := range aEventSources {
kind, name := utils.parseObjectKindName(eventSource)
if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", eventSource)
}
eventSources = append(eventSources, notificationv1.CrossNamespaceObjectReference{
Kind: kind,
Name: name,
})
}
if len(eventSources) == 0 {
return fmt.Errorf("at least one event source is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating Alert")
}
alert := notificationv1.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: notificationv1.AlertSpec{
ProviderRef: corev1.LocalObjectReference{
Name: aProviderRef,
},
EventSeverity: aEventSeverity,
EventSources: eventSources,
Suspend: false,
},
}
if export {
return exportAlert(alert)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying Alert")
namespacedName, err := upsertAlert(ctx, kubeClient, &alert)
if err != nil {
return err
}
logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert %s is ready", name)
return nil
}
func upsertAlert(ctx context.Context, kubeClient client.Client,
alert *notificationv1.Alert) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: alert.GetNamespace(),
Name: alert.GetName(),
}
var existing notificationv1.Alert
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, alert); err != nil {
return namespacedName, err
} else {
logger.Successf("Alert created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = alert.Labels
existing.Spec = alert.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
alert = &existing
logger.Successf("Alert updated")
return namespacedName, nil
}
func isAlertReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil {
return false, err
}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -0,0 +1,189 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
)
var createAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Create or update a Provider resource",
Long: "The create alert-provider command generates a Provider resource.",
Example: ` # Create a Provider for a Slack channel
gotk create alert-provider slack \
--type slack \
--channel general \
--address https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
--secret-ref webhook-url
# Create a Provider for a Github repository
gotk create alert-provider github-podinfo \
--type github \
--address https://github.com/stefanprodan/podinfo \
--secret-ref github-token
`,
RunE: createAlertProviderCmdRun,
}
var (
apType string
apChannel string
apUsername string
apAddress string
apSecretRef string
)
func init() {
createAlertProviderCmd.Flags().StringVar(&apType, "type", "", "type of provider")
createAlertProviderCmd.Flags().StringVar(&apChannel, "channel", "", "channel to send messages to in the case of a chat provider")
createAlertProviderCmd.Flags().StringVar(&apUsername, "username", "", "bot username used by the provider")
createAlertProviderCmd.Flags().StringVar(&apAddress, "address", "", "path to either the git repository, chat provider or webhook")
createAlertProviderCmd.Flags().StringVar(&apSecretRef, "secret-ref", "", "name of secret containing authentication token")
createCmd.AddCommand(createAlertProviderCmd)
}
func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Provider name is required")
}
name := args[0]
if apType == "" {
return fmt.Errorf("Provider type is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating Provider")
}
provider := notificationv1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: notificationv1.ProviderSpec{
Type: apType,
Channel: apChannel,
Username: apUsername,
Address: apAddress,
},
}
if apSecretRef != "" {
provider.Spec.SecretRef = &corev1.LocalObjectReference{
Name: apSecretRef,
}
}
if export {
return exportAlertProvider(provider)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying Provider")
namespacedName, err := upsertAlertProvider(ctx, kubeClient, &provider)
if err != nil {
return err
}
logger.Waitingf("waiting for Provider reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
return err
}
logger.Successf("Provider %s is ready", name)
return nil
}
func upsertAlertProvider(ctx context.Context, kubeClient client.Client,
provider *notificationv1.Provider) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: provider.GetNamespace(),
Name: provider.GetName(),
}
var existing notificationv1.Provider
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, provider); err != nil {
return namespacedName, err
} else {
logger.Successf("Provider created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = provider.Labels
existing.Spec = provider.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
provider = &existing
logger.Successf("Provider updated")
return namespacedName, nil
}
func isAlertProviderReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, provider)
if err != nil {
return false, err
}
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -19,14 +19,13 @@ package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"io/ioutil"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
apierrors "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"
@@ -34,7 +33,6 @@ import (
"sigs.k8s.io/yaml"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var createHelmReleaseCmd = &cobra.Command{
@@ -100,7 +98,7 @@ var (
)
func init() {
createHelmReleaseCmd.Flags().StringVar(&hrName, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<hr-name>'")
createHelmReleaseCmd.Flags().StringVar(&hrName, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
createHelmReleaseCmd.Flags().StringVar(&hrSource, "source", "", "source that contains the chart (<kind>/<name>)")
createHelmReleaseCmd.Flags().StringVar(&hrChart, "chart", "", "Helm chart name or path")
createHelmReleaseCmd.Flags().StringVar(&hrChartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)")
@@ -112,7 +110,7 @@ func init() {
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("release name is required")
return fmt.Errorf("HelmRelease name is required")
}
name := args[0]
@@ -137,7 +135,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
if !export {
logger.Generatef("generating release")
logger.Generatef("generating HelmRelease")
}
helmRelease := helmv2.HelmRelease{
@@ -193,43 +191,25 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Actionf("applying release")
if err := upsertHelmRelease(ctx, kubeClient, helmRelease); err != nil {
return err
}
logger.Waitingf("waiting for reconciliation")
chartName := fmt.Sprintf("%s-%s", namespace, name)
if err := wait.PollImmediate(pollInterval, timeout,
isHelmChartReady(ctx, kubeClient, chartName, namespace)); err != nil {
return err
}
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("release %s is ready", name)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
logger.Actionf("applying HelmRelease")
namespacedName, err := upsertHelmRelease(ctx, kubeClient, &helmRelease)
if err != nil {
return fmt.Errorf("release failed: %w", err)
return err
}
if helmRelease.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
} else {
return fmt.Errorf("reconciliation failed")
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
return err
}
logger.Successf("HelmRelease %s is ready", name)
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
return nil
}
func upsertHelmRelease(ctx context.Context, kubeClient client.Client, helmRelease helmv2.HelmRelease) error {
func upsertHelmRelease(ctx context.Context, kubeClient client.Client,
helmRelease *helmv2.HelmRelease) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: helmRelease.GetNamespace(),
Name: helmRelease.GetName(),
@@ -239,75 +219,39 @@ func upsertHelmRelease(ctx context.Context, kubeClient client.Client, helmReleas
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &helmRelease); err != nil {
return err
if err := kubeClient.Create(ctx, helmRelease); err != nil {
return namespacedName, err
} else {
logger.Successf("release created")
return nil
logger.Successf("HelmRelease created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = helmRelease.Labels
existing.Spec = helmRelease.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("release updated")
return nil
helmRelease = &existing
logger.Successf("HelmRelease updated")
return namespacedName, nil
}
func isHelmChartReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
return func() (bool, error) {
var helmChart sourcev1.HelmChart
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmChart)
if err != nil {
if apierrors.IsNotFound(err) {
return false, nil
}
return false, err
}
if c := meta.GetCondition(helmChart.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}
func isHelmReleaseReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
return func() (bool, error) {
var helmRelease helmv2.HelmRelease
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRelease)
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
// Confirm the state we are observing is for the current generation
if helmRelease.Generation != helmRelease.Status.ObservedGeneration {
return false, nil
}
return false, nil
return meta.HasReadyCondition(helmRelease.Status.Conditions), nil
}
}

View File

@@ -102,7 +102,7 @@ func init() {
func createKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("kustomization name is required")
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
@@ -127,7 +127,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
}
if !export {
logger.Generatef("generating kustomization")
logger.Generatef("generating Kustomization")
}
ksLabels, err := parseLabels()
@@ -232,38 +232,25 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Actionf("applying kustomization")
if err := upsertKustomization(ctx, kubeClient, kustomization); err != nil {
return err
}
logger.Waitingf("waiting for kustomization sync")
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("kustomization %s is ready", name)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &kustomization)
logger.Actionf("applying Kustomization")
namespacedName, err := upsertKustomization(ctx, kubeClient, &kustomization)
if err != nil {
return fmt.Errorf("kustomization sync failed: %w", err)
return err
}
if kustomization.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else {
return fmt.Errorf("kustomization sync failed")
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err
}
logger.Successf("Kustomization %s is ready", name)
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
return nil
}
func upsertKustomization(ctx context.Context, kubeClient client.Client, kustomization kustomizev1.Kustomization) error {
func upsertKustomization(ctx context.Context, kubeClient client.Client,
kustomization *kustomizev1.Kustomization) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: kustomization.GetNamespace(),
Name: kustomization.GetName(),
@@ -273,39 +260,39 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client, kustomiz
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &kustomization); err != nil {
return err
if err := kubeClient.Create(ctx, kustomization); err != nil {
return namespacedName, err
} else {
logger.Successf("kustomization created")
return nil
logger.Successf("Kustomization created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = kustomization.Labels
existing.Spec = kustomization.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("kustomization updated")
return nil
kustomization = &existing
logger.Successf("Kustomization updated")
return namespacedName, nil
}
func isKustomizationReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isKustomizationReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
return func() (bool, error) {
var kustomization kustomizev1.Kustomization
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &kustomization)
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if kustomization.Generation != kustomization.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:

201
cmd/gotk/create_receiver.go Normal file
View File

@@ -0,0 +1,201 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"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"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
)
var createReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Create or update a Receiver resource",
Long: "The create receiver command generates a Receiver resource.",
Example: ` # Create a Receiver
gotk create receiver github-receiver \
--type github \
--event ping \
--event push \
--secret-ref webhook-token \
--resource GitRepository/webapp \
--resource HelmRepository/webapp
`,
RunE: createReceiverCmdRun,
}
var (
rcvType string
rcvSecretRef string
rcvEvents []string
rcvResources []string
)
func init() {
createReceiverCmd.Flags().StringVar(&rcvType, "type", "", "")
createReceiverCmd.Flags().StringVar(&rcvSecretRef, "secret-ref", "", "")
createReceiverCmd.Flags().StringArrayVar(&rcvEvents, "event", []string{}, "")
createReceiverCmd.Flags().StringArrayVar(&rcvResources, "resource", []string{}, "")
createCmd.AddCommand(createReceiverCmd)
}
func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Receiver name is required")
}
name := args[0]
if rcvType == "" {
return fmt.Errorf("Receiver type is required")
}
if rcvSecretRef == "" {
return fmt.Errorf("secret ref is required")
}
resources := []notificationv1.CrossNamespaceObjectReference{}
for _, resource := range rcvResources {
kind, name := utils.parseObjectKindName(resource)
if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", resource)
}
resources = append(resources, notificationv1.CrossNamespaceObjectReference{
Kind: kind,
Name: name,
})
}
if len(resources) == 0 {
return fmt.Errorf("atleast one resource is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
if !export {
logger.Generatef("generating Receiver")
}
receiver := notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: sourceLabels,
},
Spec: notificationv1.ReceiverSpec{
Type: rcvType,
Events: rcvEvents,
Resources: resources,
SecretRef: corev1.LocalObjectReference{
Name: rcvSecretRef,
},
Suspend: false,
},
}
if export {
return exportReceiver(receiver)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
logger.Actionf("applying Receiver")
namespacedName, err := upsertReceiver(ctx, kubeClient, &receiver)
if err != nil {
return err
}
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver %s is ready", name)
logger.Successf("generated webhook URL %s", receiver.Status.URL)
return nil
}
func upsertReceiver(ctx context.Context, kubeClient client.Client,
receiver *notificationv1.Receiver) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: receiver.GetNamespace(),
Name: receiver.GetName(),
}
var existing notificationv1.Receiver
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, receiver); err != nil {
return namespacedName, err
} else {
logger.Successf("Receiver created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = receiver.Labels
existing.Spec = receiver.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
receiver = &existing
logger.Successf("Receiver updated")
return namespacedName, nil
}
func isReceiverReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, receiver)
if err != nil {
return false, err
}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -83,13 +83,13 @@ func init() {
func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("Bucket source name is required")
}
name := args[0]
secretName := fmt.Sprintf("bucket-%s", name)
if !utils.containsItemString(supportedSourceBucketProviders, sourceBucketProvider) {
return fmt.Errorf("bucket provider %s is not supported, can be %v",
return fmt.Errorf("Bucket provider %s is not supported, can be %v",
sourceBucketProvider, supportedSourceBucketProviders)
}
@@ -112,7 +112,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
}
defer os.RemoveAll(tmpDir)
bucket := sourcev1.Bucket{
bucket := &sourcev1.Bucket{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
@@ -131,7 +131,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
}
if export {
return exportBucket(bucket)
return exportBucket(*bucket)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
@@ -142,7 +142,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Generatef("generating source")
logger.Generatef("generating Bucket source")
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
@@ -168,38 +168,28 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("authentication configured")
}
logger.Actionf("applying source")
if err := upsertBucket(ctx, kubeClient, bucket); err != nil {
return err
}
logger.Waitingf("waiting for download")
if err := wait.PollImmediate(pollInterval, timeout,
isBucketReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("download completed")
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &bucket)
logger.Actionf("applying Bucket source")
namespacedName, err := upsertBucket(ctx, kubeClient, bucket)
if err != nil {
return fmt.Errorf("could not retrieve bucket: %w", err)
return err
}
if bucket.Status.Artifact != nil {
logger.Successf("fetched revision: %s", bucket.Status.Artifact.Revision)
} else {
return fmt.Errorf("download failed, artifact not found")
logger.Waitingf("waiting for Bucket source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil {
return err
}
logger.Successf("Bucket source reconciliation completed")
if bucket.Status.Artifact == nil {
return fmt.Errorf("Bucket source reconciliation but no artifact was found")
}
logger.Successf("fetched revision: %s", bucket.Status.Artifact.Revision)
return nil
}
func upsertBucket(ctx context.Context, kubeClient client.Client, bucket sourcev1.Bucket) error {
func upsertBucket(ctx context.Context, kubeClient client.Client,
bucket *sourcev1.Bucket) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: bucket.GetNamespace(),
Name: bucket.GetName(),
@@ -209,22 +199,22 @@ func upsertBucket(ctx context.Context, kubeClient client.Client, bucket sourcev1
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &bucket); err != nil {
return err
if err := kubeClient.Create(ctx, bucket); err != nil {
return namespacedName, err
} else {
logger.Successf("source created")
return nil
logger.Successf("Bucket source created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = bucket.Labels
existing.Spec = bucket.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("source updated")
return nil
bucket = &existing
logger.Successf("Bucket source updated")
return namespacedName, nil
}

View File

@@ -111,7 +111,7 @@ func init() {
func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("GitRepository source name is required")
}
name := args[0]
@@ -234,7 +234,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("authentication configured")
}
logger.Generatef("generating source")
logger.Generatef("generating GitRepository source")
if withAuth {
gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{
@@ -242,34 +242,23 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
}
}
logger.Actionf("applying source")
if err := upsertGitRepository(ctx, kubeClient, gitRepository); err != nil {
return err
}
logger.Waitingf("waiting for git sync")
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("git sync completed")
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &gitRepository)
logger.Actionf("applying GitRepository source")
namespacedName, err := upsertGitRepository(ctx, kubeClient, &gitRepository)
if err != nil {
return fmt.Errorf("git sync failed: %w", err)
return err
}
if gitRepository.Status.Artifact != nil {
logger.Successf("fetched revision: %s", gitRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("git sync failed, artifact not found")
logger.Waitingf("waiting for GitRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
return err
}
logger.Successf("GitRepository source reconciliation completed")
if gitRepository.Status.Artifact == nil {
return fmt.Errorf("GitRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision: %s", gitRepository.Status.Artifact.Revision)
return nil
}
@@ -330,7 +319,8 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
return nil
}
func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepository sourcev1.GitRepository) error {
func upsertGitRepository(ctx context.Context, kubeClient client.Client,
gitRepository *sourcev1.GitRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: gitRepository.GetNamespace(),
Name: gitRepository.GetName(),
@@ -340,35 +330,30 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepos
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &gitRepository); err != nil {
return err
if err := kubeClient.Create(ctx, gitRepository); err != nil {
return namespacedName, err
} else {
logger.Successf("source created")
return nil
logger.Successf("GitRepository source created")
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = gitRepository.Labels
existing.Spec = gitRepository.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
logger.Successf("source updated")
return nil
gitRepository = &existing
logger.Successf("GitRepository source updated")
return namespacedName, nil
}
func isGitRepositoryReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, gitRepository *sourcev1.GitRepository) wait.ConditionFunc {
return func() (bool, error) {
var gitRepository sourcev1.GitRepository
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &gitRepository)
err := kubeClient.Get(ctx, namespacedName, gitRepository)
if err != nil {
return false, err
}

View File

@@ -83,7 +83,7 @@ func init() {
func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("HelmRepository source name is required")
}
name := args[0]
secretName := fmt.Sprintf("helm-%s", name)
@@ -107,7 +107,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("url parse failed: %w", err)
}
helmRepository := sourcev1.HelmRepository{
helmRepository := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
@@ -122,7 +122,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
}
if export {
return exportHelmRepository(helmRepository)
return exportHelmRepository(*helmRepository)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
@@ -133,7 +133,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Generatef("generating source")
logger.Generatef("generating HelmRepository source")
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
@@ -181,38 +181,28 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("authentication configured")
}
logger.Actionf("applying source")
if err := upsertHelmRepository(ctx, kubeClient, helmRepository); err != nil {
return err
}
logger.Waitingf("waiting for index download")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("index download completed")
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &helmRepository)
logger.Actionf("applying HelmRepository source")
namespacedName, err := upsertHelmRepository(ctx, kubeClient, helmRepository)
if err != nil {
return fmt.Errorf("helm index failed: %w", err)
return err
}
if helmRepository.Status.Artifact != nil {
logger.Successf("fetched revision: %s", helmRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("index download failed, artifact not found")
logger.Waitingf("waiting for HelmRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil {
return err
}
logger.Successf("HelmRepository source reconciliation completed")
if helmRepository.Status.Artifact == nil {
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision: %s", helmRepository.Status.Artifact.Revision)
return nil
}
func upsertHelmRepository(ctx context.Context, kubeClient client.Client, helmRepository sourcev1.HelmRepository) error {
func upsertHelmRepository(ctx context.Context, kubeClient client.Client,
helmRepository *sourcev1.HelmRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: helmRepository.GetNamespace(),
Name: helmRepository.GetName(),
@@ -222,22 +212,22 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client, helmRep
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &helmRepository); err != nil {
return err
if err := kubeClient.Create(ctx, helmRepository); err != nil {
return namespacedName, err
} else {
logger.Successf("source created")
return nil
return namespacedName, nil
}
}
return err
return namespacedName, err
}
existing.Labels = helmRepository.Labels
existing.Spec = helmRepository.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
return namespacedName, err
}
helmRepository = &existing
logger.Successf("source updated")
return nil
return namespacedName, nil
}

87
cmd/gotk/delete_alert.go Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var deleteAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Delete a Alert resource",
Long: "The delete alert command removes the given Alert from the cluster.",
Example: ` # Delete an Alert and the Kubernetes resources created by it
gotk delete alert main
`,
RunE: deleteAlertCmdRun,
}
func init() {
deleteCmd.AddCommand(deleteAlertCmd)
}
func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Alert",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting alert %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &alert)
if err != nil {
return err
}
logger.Successf("alert deleted")
return nil
}

View File

@@ -0,0 +1,87 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var deleteAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Delete a Provider resource",
Long: "The delete alert-provider command removes the given Provider from the cluster.",
Example: ` # Delete a Provider and the Kubernetes resources created by it
gotk delete alert-provider slack
`,
RunE: deleteAlertProviderCmdRun,
}
func init() {
deleteCmd.AddCommand(deleteAlertProviderCmd)
}
func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("provider name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Provider",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting provider %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &alertProvider)
if err != nil {
return err
}
logger.Successf("provider deleted")
return nil
}

View File

@@ -0,0 +1,87 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var deleteReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Delete a Receiver resource",
Long: "The delete receiver command removes the given Receiver from the cluster.",
Example: ` # Delete an Receiver and the Kubernetes resources created by it
gotk delete receiver main
`,
RunE: deleteReceiverCmdRun,
}
func init() {
deleteCmd.AddCommand(deleteReceiverCmd)
}
func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Receiver",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting receiver %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &receiver)
if err != nil {
return err
}
logger.Successf("receiver deleted")
return nil
}

119
cmd/gotk/export_alert.go Normal file
View File

@@ -0,0 +1,119 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var exportAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Export Alert resources in YAML format",
Long: "The export alert command exports one or all Alert resources in YAML format.",
Example: ` # Export all Alert resources
gotk export alert --all > alerts.yaml
# Export a Alert
gotk export alert main > main.yaml
`,
RunE: exportAlertCmdRun,
}
func init() {
exportCmd.AddCommand(exportAlertCmd)
}
func exportAlertCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list notificationv1.AlertList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no alerts found in %s namespace", namespace)
return nil
}
for _, alert := range list.Items {
if err := exportAlert(alert); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
return exportAlert(alert)
}
return nil
}
func exportAlert(alert notificationv1.Alert) error {
gvk := notificationv1.GroupVersion.WithKind("Alert")
export := notificationv1.Alert{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: alert.Name,
Namespace: alert.Namespace,
Labels: alert.Labels,
Annotations: alert.Annotations,
},
Spec: alert.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -0,0 +1,119 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var exportAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Export Provider resources in YAML format",
Long: "The export alert-provider command exports one or all Provider resources in YAML format.",
Example: ` # Export all Provider resources
gotk export alert-provider --all > alert-providers.yaml
# Export a Provider
gotk export alert-provider slack > slack.yaml
`,
RunE: exportAlertProviderCmdRun,
}
func init() {
exportCmd.AddCommand(exportAlertProviderCmd)
}
func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no alertproviders found in %s namespace", namespace)
return nil
}
for _, alertProvider := range list.Items {
if err := exportAlertProvider(alertProvider); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
return exportAlertProvider(alertProvider)
}
return nil
}
func exportAlertProvider(alertProvider notificationv1.Provider) error {
gvk := notificationv1.GroupVersion.WithKind("Provider")
export := notificationv1.Provider{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: alertProvider.Name,
Namespace: alertProvider.Namespace,
Labels: alertProvider.Labels,
Annotations: alertProvider.Annotations,
},
Spec: alertProvider.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

119
cmd/gotk/export_receiver.go Normal file
View File

@@ -0,0 +1,119 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var exportReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Export Receiver resources in YAML format",
Long: "The export receiver command exports one or all Receiver resources in YAML format.",
Example: ` # Export all Receiver resources
gotk export receiver --all > receivers.yaml
# Export a Receiver
gotk export receiver main > main.yaml
`,
RunE: exportReceiverCmdRun,
}
func init() {
exportCmd.AddCommand(exportReceiverCmd)
}
func exportReceiverCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
if exportAll {
var list notificationv1.ReceiverList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no receivers found in %s namespace", namespace)
return nil
}
for _, receiver := range list.Items {
if err := exportReceiver(receiver); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
return exportReceiver(receiver)
}
return nil
}
func exportReceiver(receiver notificationv1.Receiver) error {
gvk := notificationv1.GroupVersion.WithKind("Receiver")
export := notificationv1.Receiver{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: receiver.Name,
Namespace: receiver.Namespace,
Labels: receiver.Labels,
Annotations: receiver.Annotations,
},
Spec: receiver.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -26,6 +26,10 @@ var getCmd = &cobra.Command{
Long: "The get sub-commands print the statuses of sources and resources.",
}
var allNamespaces bool
func init() {
getCmd.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false,
"list the requested object(s) across all namespaces")
rootCmd.AddCommand(getCmd)
}

102
cmd/gotk/get_alert.go Normal file
View File

@@ -0,0 +1,102 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
)
var getAlertCmd = &cobra.Command{
Use: "alerts",
Short: "Get Alert statuses",
Long: "The get alert command prints the statuses of the resources.",
Example: ` # List all Alerts and their status
gotk get alerts
`,
RunE: getAlertCmdRun,
}
func init() {
getCmd.AddCommand(getAlertCmd)
}
func getAlertCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list notificationv1.AlertList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no alerts found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, alert := range list.Items {
row := []string{}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
alert.GetName(),
//alert.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
alert.GetName(),
//alert.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{alert.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -0,0 +1,96 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"os"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
)
var getAlertProviderCmd = &cobra.Command{
Use: "alert-providers",
Short: "Get Provider statuses",
Long: "The get alert-provider command prints the statuses of the resources.",
Example: ` # List all Providers and their status
gotk get alert-providers
`,
RunE: getAlertProviderCmdRun,
}
func init() {
getCmd.AddCommand(getAlertProviderCmd)
}
func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no providers found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, provider := range list.Items {
row := []string{}
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
provider.GetName(),
string(c.Status),
c.Message,
}
} else {
row = []string{
provider.GetName(),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{provider.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,6 +18,10 @@ package main
import (
"context"
"os"
"strconv"
"strings"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
@@ -51,8 +55,12 @@ func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list helmv2.HelmReleaseList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
@@ -62,26 +70,35 @@ func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
for _, helmRelease := range list.Items {
if helmRelease.Spec.Suspend {
logger.Successf("%s is suspended", helmRelease.GetName())
continue
}
isInitialized := false
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
logger.Successf("%s last applied revision %s", helmRelease.GetName(), helmRelease.Status.LastAppliedRevision)
case corev1.ConditionUnknown:
logger.Successf("%s reconciling", helmRelease.GetName())
default:
logger.Failuref("%s %s", helmRelease.GetName(), c.Message)
}
isInitialized = true
}
if !isInitialized {
logger.Failuref("%s is not ready", helmRelease.GetName())
}
header := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, helmRelease := range list.Items {
row := []string{}
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
helmRelease.GetName(),
helmRelease.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(helmRelease.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
helmRelease.GetName(),
helmRelease.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(helmRelease.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{helmRelease.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,6 +18,10 @@ package main
import (
"context"
"os"
"strconv"
"strings"
"github.com/fluxcd/pkg/apis/meta"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
@@ -50,8 +54,12 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list kustomizev1.KustomizationList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
@@ -61,26 +69,35 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
for _, kustomization := range list.Items {
if kustomization.Spec.Suspend {
logger.Successf("%s is suspended", kustomization.GetName())
continue
}
isInitialized := false
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
logger.Successf("%s last applied revision %s", kustomization.GetName(), kustomization.Status.LastAppliedRevision)
case corev1.ConditionUnknown:
logger.Successf("%s reconciling", kustomization.GetName())
default:
logger.Failuref("%s %s", kustomization.GetName(), c.Message)
}
isInitialized = true
}
if !isInitialized {
logger.Failuref("%s is not ready", kustomization.GetName())
}
header := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, kustomization := range list.Items {
row := []string{}
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
kustomization.GetName(),
kustomization.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(kustomization.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
kustomization.GetName(),
kustomization.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(kustomization.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{kustomization.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

97
cmd/gotk/get_receiver.go Normal file
View File

@@ -0,0 +1,97 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
)
var getReceiverCmd = &cobra.Command{
Use: "receivers",
Short: "Get Receiver statuses",
Long: "The get receiver command prints the statuses of the resources.",
Example: ` # List all Receiver and their status
gotk get receivers
`,
RunE: getReceiverCmdRun,
}
func init() {
getCmd.AddCommand(getReceiverCmd)
}
func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list notificationv1.ReceiverList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no receivers found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, receiver := range list.Items {
row := []string{}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
receiver.GetName(),
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
receiver.GetName(),
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,6 +18,8 @@ package main
import (
"context"
"os"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@@ -49,36 +51,52 @@ func getSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list sourcev1.BucketList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no sources found in %s namespace", namespace)
logger.Failuref("no bucket sources found in %s namespace", namespace)
return nil
}
// TODO(hidde): this should print a table, and should produce better output
// for items that have an artifact attached while they are in a reconciling
// 'Unknown' state.
for _, source := range list.Items {
isInitialized := false
if c := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
logger.Successf("%s last fetched revision: %s", source.GetName(), source.GetArtifact().Revision)
case corev1.ConditionUnknown:
logger.Successf("%s reconciling", source.GetName())
default:
logger.Failuref("%s %s", source.GetName(), c.Message)
}
isInitialized = true
}
if !isInitialized {
logger.Failuref("%s is not ready", source.GetName())
}
header := []string{"Name", "Revision", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
revision,
string(c.Status),
c.Message,
}
} else {
row = []string{
source.GetName(),
revision,
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,6 +18,8 @@ package main
import (
"context"
"os"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@@ -49,36 +51,52 @@ func getSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list sourcev1.GitRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no sources found in %s namespace", namespace)
logger.Failuref("no git sources found in %s namespace", namespace)
return nil
}
// TODO(hidde): this should print a table, and should produce better output
// for items that have an artifact attached while they are in a reconciling
// 'Unknown' state.
for _, source := range list.Items {
isInitialized := false
if c := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
logger.Successf("%s last fetched revision: %s", source.GetName(), source.GetArtifact().Revision)
case corev1.ConditionUnknown:
logger.Successf("%s reconciling", source.GetName())
default:
logger.Failuref("%s %s", source.GetName(), c.Message)
}
isInitialized = true
}
if !isInitialized {
logger.Failuref("%s is not ready", source.GetName())
}
header := []string{"Name", "Revision", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
revision,
string(c.Status),
c.Message,
}
} else {
row = []string{
source.GetName(),
revision,
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -18,6 +18,8 @@ package main
import (
"context"
"os"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@@ -49,36 +51,52 @@ func getSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list sourcev1.HelmRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no sources found in %s namespace", namespace)
logger.Failuref("no helm sources found in %s namespace", namespace)
return nil
}
// TODO(hidde): this should print a table, and should produce better output
// for items that have an artifact attached while they are in a reconciling
// 'Unknown' state.
for _, source := range list.Items {
isInitialized := false
if c := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
logger.Successf("%s last fetched revision: %s", source.GetName(), source.GetArtifact().Revision)
case corev1.ConditionUnknown:
logger.Successf("%s reconciling", source.GetName())
default:
logger.Failuref("%s %s", source.GetName(), c.Message)
}
isInitialized = true
}
if !isInitialized {
logger.Failuref("%s is not ready", source.GetName())
}
header := []string{"Name", "Revision", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
revision,
string(c.Status),
c.Message,
}
} else {
row = []string{
source.GetName(),
revision,
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.printTable(os.Stdout, header, rows)
return nil
}

View File

@@ -20,18 +20,13 @@ import (
"context"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/krusty"
"github.com/fluxcd/pkg/untar"
"github.com/fluxcd/toolkit/pkg/install"
)
var installCmd = &cobra.Command{
@@ -114,50 +109,62 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
if !installExport {
logger.Generatef("generating manifests")
}
opts := install.Options{
BaseURL: installManifestsPath,
Version: installVersion,
Namespace: namespace,
Components: installComponents,
Registry: installRegistry,
ImagePullSecret: installImagePullSecret,
Arch: installArch,
WatchAllNamespaces: installWatchAllNamespaces,
NetworkPolicy: installNetworkPolicy,
LogLevel: installLogLevel,
NotificationController: defaultNotification,
ManifestsFile: fmt.Sprintf("%s.yaml", namespace),
Timeout: timeout,
}
if installManifestsPath == "" {
err = genInstallManifests(installVersion, namespace, installComponents,
installWatchAllNamespaces, installNetworkPolicy, installRegistry, installImagePullSecret,
installArch, installLogLevel, tmpDir)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
installManifestsPath = tmpDir
opts.BaseURL = install.MakeDefaultOptions().BaseURL
}
output, err := install.Generate(opts)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
manifest := path.Join(tmpDir, fmt.Sprintf("%s.yaml", namespace))
if err := buildKustomization(installManifestsPath, manifest); err != nil {
if err := ioutil.WriteFile(manifest, output, os.ModePerm); err != nil {
return fmt.Errorf("install failed: %w", err)
}
command := fmt.Sprintf("cat %s", manifest)
if yaml, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
return fmt.Errorf("install failed: %w", err)
} else {
if verbose {
fmt.Print(yaml)
} else if installExport {
fmt.Println("---")
fmt.Println("# GitOps Toolkit revision", installVersion, time.Now().Format(time.RFC3339))
fmt.Println("# Components:", strings.Join(installComponents, ","))
fmt.Print(yaml)
fmt.Println("---")
return nil
}
yaml := string(output)
if verbose {
fmt.Print(yaml)
} else if installExport {
fmt.Println("---")
fmt.Println("# GitOps Toolkit revision", installVersion)
fmt.Println("# Components:", strings.Join(installComponents, ","))
fmt.Print(yaml)
fmt.Println("---")
return nil
}
logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", namespace)
applyOutput := ModeStderrOS
if verbose {
applyOutput = ModeOS
}
dryRun := ""
kubectlArgs := []string{"apply", "-f", manifest}
if installDryRun {
dryRun = "--dry-run=client"
args = append(args, "--dry-run=client")
applyOutput = ModeOS
}
command = fmt.Sprintf("cat %s | kubectl apply -f- %s", manifest, dryRun)
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
if _, err := utils.execKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
}
@@ -170,9 +177,8 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("verifying installation")
for _, deployment := range installComponents {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String())
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
if _, err := utils.execKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
} else {
logger.Successf("%s ready", deployment)
@@ -182,250 +188,3 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("install finished")
return nil
}
var namespaceTmpl = `---
apiVersion: v1
kind: Namespace
metadata:
name: {{.Namespace}}
`
var labelsTmpl = `---
apiVersion: builtin
kind: LabelTransformer
metadata:
name: labels
labels:
app.kubernetes.io/instance: {{.Namespace}}
app.kubernetes.io/version: "{{.Version}}"
fieldSpecs:
- path: metadata/labels
create: true
`
var kustomizationTmpl = `---
{{- $eventsAddr := .EventsAddr }}
{{- $watchAllNamespaces := .WatchAllNamespaces }}
{{- $registry := .Registry }}
{{- $arch := .Arch }}
{{- $logLevel := .LogLevel }}
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: {{.Namespace}}
transformers:
- labels.yaml
resources:
- namespace.yaml
{{- if .NetworkPolicy }}
- policies.yaml
{{- end }}
- roles
{{- range .Components }}
- {{.}}.yaml
{{- end }}
patches:
- path: node-selector.yaml
target:
kind: Deployment
patchesJson6902:
{{- range $i, $component := .Components }}
{{- if eq $component "notification-controller" }}
- target:
group: apps
version: v1
kind: Deployment
name: {{$component}}
patch: |-
- op: replace
path: /spec/template/spec/containers/0/args/0
value: --watch-all-namespaces={{$watchAllNamespaces}}
- op: replace
path: /spec/template/spec/containers/0/args/1
value: --log-level={{$logLevel}}
{{- else }}
- target:
group: apps
version: v1
kind: Deployment
name: {{$component}}
patch: |-
- op: replace
path: /spec/template/spec/containers/0/args/0
value: --events-addr={{$eventsAddr}}
- op: replace
path: /spec/template/spec/containers/0/args/1
value: --watch-all-namespaces={{$watchAllNamespaces}}
- op: replace
path: /spec/template/spec/containers/0/args/2
value: --log-level={{$logLevel}}
{{- end }}
{{- end }}
{{- if $registry }}
images:
{{- range $i, $component := .Components }}
- name: fluxcd/{{$component}}
{{- if eq $arch "amd64" }}
newName: {{$registry}}/{{$component}}
{{- else }}
newName: {{$registry}}/{{$component}}-arm64
{{- end }}
{{- end }}
{{- end }}
`
var kustomizationRolesTmpl = `---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac.yaml
nameSuffix: -{{.Namespace}}
`
var nodeSelectorTmpl = `---
apiVersion: apps/v1
kind: Deployment
metadata:
name: all
spec:
template:
spec:
nodeSelector:
kubernetes.io/arch: {{.Arch}}
kubernetes.io/os: linux
{{- if .ImagePullSecret }}
imagePullSecrets:
- name: {{.ImagePullSecret}}
{{- end }}
`
func downloadManifests(version string, tmpDir string) error {
ghURL := "https://github.com/fluxcd/toolkit/releases/latest/download/manifests.tar.gz"
if strings.HasPrefix(version, "v") {
ghURL = fmt.Sprintf("https://github.com/fluxcd/toolkit/releases/download/%s/manifests.tar.gz", version)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
req, err := http.NewRequest("GET", ghURL, nil)
if err != nil {
return fmt.Errorf("failed to create HTTP request for %s, error: %w", ghURL, err)
}
// download
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return fmt.Errorf("failed to download artifact from %s, error: %w", ghURL, err)
}
defer resp.Body.Close()
// check response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("faild to download artifact from %s, status: %s", ghURL, resp.Status)
}
// extract
if _, err = untar.Untar(resp.Body, tmpDir); err != nil {
return fmt.Errorf("faild to untar manifests from %s, error: %w", ghURL, err)
}
return nil
}
func genInstallManifests(version string, namespace string, components []string,
watchAllNamespaces, networkPolicy bool, registry, imagePullSecret, arch, logLevel, tmpDir string) error {
eventsAddr := ""
if utils.containsItemString(components, defaultNotification) {
eventsAddr = fmt.Sprintf("http://%s/", defaultNotification)
}
model := struct {
Version string
Namespace string
Components []string
EventsAddr string
Registry string
ImagePullSecret string
Arch string
WatchAllNamespaces bool
NetworkPolicy bool
LogLevel string
}{
Version: version,
Namespace: namespace,
Components: components,
EventsAddr: eventsAddr,
Registry: registry,
ImagePullSecret: imagePullSecret,
Arch: arch,
WatchAllNamespaces: watchAllNamespaces,
NetworkPolicy: networkPolicy,
LogLevel: logLevel,
}
if err := downloadManifests(version, tmpDir); err != nil {
return err
}
if err := utils.execTemplate(model, namespaceTmpl, path.Join(tmpDir, "namespace.yaml")); err != nil {
return fmt.Errorf("generate namespace failed: %w", err)
}
if err := utils.execTemplate(model, labelsTmpl, path.Join(tmpDir, "labels.yaml")); err != nil {
return fmt.Errorf("generate labels failed: %w", err)
}
if err := utils.execTemplate(model, nodeSelectorTmpl, path.Join(tmpDir, "node-selector.yaml")); err != nil {
return fmt.Errorf("generate node selector failed: %w", err)
}
if err := utils.execTemplate(model, kustomizationTmpl, path.Join(tmpDir, "kustomization.yaml")); err != nil {
return fmt.Errorf("generate kustomization failed: %w", err)
}
if err := os.MkdirAll(path.Join(tmpDir, "roles"), os.ModePerm); err != nil {
return fmt.Errorf("generate roles failed: %w", err)
}
if err := utils.execTemplate(model, kustomizationRolesTmpl, path.Join(tmpDir, "roles/kustomization.yaml")); err != nil {
return fmt.Errorf("generate roles failed: %w", err)
}
if err := utils.copyFile(filepath.Join(tmpDir, "rbac.yaml"), filepath.Join(tmpDir, "roles/rbac.yaml")); err != nil {
return fmt.Errorf("generate rbac failed: %w", err)
}
return nil
}
func buildKustomization(base, manifests string) error {
kfile := filepath.Join(base, "kustomization.yaml")
fs := filesys.MakeFsOnDisk()
if !fs.Exists(kfile) {
return fmt.Errorf("%s not found", kfile)
}
opt := krusty.MakeDefaultOptions()
k := krusty.MakeKustomizer(fs, opt)
m, err := k.Run(base)
if err != nil {
return err
}
resources, err := m.AsYaml()
if err != nil {
return err
}
if err := fs.WriteFile(manifests, resources); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,91 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var reconcileAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Reconcile an Alert",
Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing alert
gotk reconcile alert main
`,
RunE: reconcileAlertCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileAlertCmd)
}
func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Alert %s in %s namespace", name, namespace)
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
if alert.Annotations == nil {
alert.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
alert.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert annotated")
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert reconciliation completed")
return nil
}

View File

@@ -0,0 +1,91 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var reconcileAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]",
Short: "Reconcile a Provider",
Long: `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing provider
gotk reconcile alert-provider slack
`,
RunE: reconcileAlertProviderCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileAlertProviderCmd)
}
func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Provider name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Provider %s in %s namespace", name, namespace)
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {
return err
}
if alertProvider.Annotations == nil {
alertProvider.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
alertProvider.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &alertProvider); err != nil {
return err
}
logger.Successf("Provider annotated")
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil {
return err
}
logger.Successf("Provider reconciliation completed")
return nil
}

View File

@@ -98,19 +98,19 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
}
}
lastHandledReconcileAt := helmRelease.Status.LastHandledReconcileAt
logger.Actionf("annotating HelmRelease %s in %s namespace", name, namespace)
if err := requestHelmReleaseReconciliation(ctx, kubeClient, namespacedName); err != nil {
if err := requestHelmReleaseReconciliation(ctx, kubeClient, namespacedName, &helmRelease); err != nil {
return err
}
logger.Successf("HelmRelease annotated")
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
helmReleaseReconciliationHandled(ctx, kubeClient, name, namespace, helmRelease.Status.LastHandledReconcileAt),
helmReleaseReconciliationHandled(ctx, kubeClient, namespacedName, &helmRelease, lastHandledReconcileAt),
); err != nil {
return err
}
logger.Successf("HelmRelease reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
@@ -120,7 +120,7 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionFalse:
return fmt.Errorf("HelmRelease reconciliation failed")
return fmt.Errorf("HelmRelease reconciliation failed: %s", c.Message)
default:
logger.Successf("reconciled revision %s", helmRelease.Status.LastAppliedRevision)
}
@@ -129,39 +129,29 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
}
func helmReleaseReconciliationHandled(ctx context.Context, kubeClient client.Client,
name, namespace, lastHandledReconcileAt string) wait.ConditionFunc {
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
var helmRelease helmv2.HelmRelease
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRelease)
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
return helmRelease.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
}
func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Client, namespacedName types.NamespacedName) error {
var release helmv2.HelmRelease
func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, &release); err != nil {
if err := kubeClient.Get(ctx, namespacedName, helmRelease); err != nil {
return err
}
if release.Annotations == nil {
release.Annotations = map[string]string{
if helmRelease.Annotations == nil {
helmRelease.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
release.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
helmRelease.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
err = kubeClient.Update(ctx, &release)
return
return kubeClient.Update(ctx, helmRelease)
})
}

View File

@@ -54,14 +54,14 @@ var (
)
func init() {
reconcileKsCmd.Flags().BoolVar(&syncKsWithSource, "with-source", false, "reconcile kustomization source")
reconcileKsCmd.Flags().BoolVar(&syncKsWithSource, "with-source", false, "reconcile Kustomization source")
reconcileCmd.AddCommand(reconcileKsCmd)
}
func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("kustomization name is required")
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
@@ -77,7 +77,6 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
Namespace: namespace,
Name: name,
}
var kustomization kustomizev1.Kustomization
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
@@ -96,30 +95,26 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
}
}
logger.Actionf("annotating kustomization %s in %s namespace", name, namespace)
if err := requestKustomizeReconciliation(ctx, kubeClient, namespacedName); err != nil {
lastHandledReconcileAt := kustomization.Status.LastHandledReconcileAt
logger.Actionf("annotating Kustomization %s in %s namespace", name, namespace)
if err := requestKustomizeReconciliation(ctx, kubeClient, namespacedName, &kustomization); err != nil {
return err
}
logger.Successf("kustomization annotated")
logger.Successf("Kustomization annotated")
logger.Waitingf("waiting for kustomization reconciliation")
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(
pollInterval, timeout,
kustomizeReconciliationHandled(ctx, kubeClient, name, namespace, kustomization.Status.LastHandledReconcileAt),
kustomizeReconciliationHandled(ctx, kubeClient, namespacedName, &kustomization, lastHandledReconcileAt),
); err != nil {
return err
}
logger.Successf("Kustomization reconciliation completed")
logger.Successf("kustomization reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
return err
}
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionFalse:
return fmt.Errorf("kustomization reconciliation failed")
return fmt.Errorf("Kustomization reconciliation failed")
default:
logger.Successf("reconciled revision %s", kustomization.Status.LastAppliedRevision)
}
@@ -128,30 +123,22 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
}
func kustomizeReconciliationHandled(ctx context.Context, kubeClient client.Client,
name, namespace, lastHandledReconcileAt string) wait.ConditionFunc {
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
var kustomize kustomizev1.Kustomization
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &kustomize)
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
return kustomize.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
return kustomization.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
}
func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Client, namespacedName types.NamespacedName) error {
var kustomization kustomizev1.Kustomization
func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, &kustomization); err != nil {
if err := kubeClient.Get(ctx, namespacedName, kustomization); err != nil {
return err
}
if kustomization.Annotations == nil {
kustomization.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
@@ -159,8 +146,6 @@ func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Clien
} else {
kustomization.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
err = kubeClient.Update(ctx, &kustomization)
return
return kubeClient.Update(ctx, kustomization)
})
}

View File

@@ -0,0 +1,93 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var reconcileReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Reconcile a Receiver",
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
gotk reconcile receiver main
`,
RunE: reconcileReceiverCmdRun,
}
func init() {
reconcileCmd.AddCommand(reconcileReceiverCmd)
}
func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
logger.Actionf("annotating Receiver %s in %s namespace", name, namespace)
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
if receiver.Annotations == nil {
receiver.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
receiver.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver annotated")
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver reconciliation completed")
return nil
}

View File

@@ -64,7 +64,7 @@ func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
Name: name,
}
logger.Actionf("annotating source %s in %s namespace", name, namespace)
logger.Actionf("annotating Bucket source %s in %s namespace", name, namespace)
var bucket sourcev1.Bucket
err = kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil {
@@ -81,42 +81,35 @@ func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &bucket); err != nil {
return err
}
logger.Successf("source annotated")
logger.Successf("Bucket source annotated")
logger.Waitingf("waiting for reconciliation")
logger.Waitingf("waiting for Bucket source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isBucketReady(ctx, kubeClient, name, namespace)); err != nil {
isBucketReady(ctx, kubeClient, namespacedName, &bucket)); err != nil {
return err
}
logger.Successf("Bucket source reconciliation completed")
logger.Successf("bucket reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil {
return err
}
if bucket.Status.Artifact != nil {
logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
} else {
return fmt.Errorf("bucket reconciliation failed, artifact not found")
if bucket.Status.Artifact == nil {
return fmt.Errorf("Bucket source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
return nil
}
func isBucketReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isBucketReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
return func() (bool, error) {
var bucket sourcev1.Bucket
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &bucket)
err := kubeClient.Get(ctx, namespacedName, bucket)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if bucket.Generation != bucket.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(bucket.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:

View File

@@ -62,7 +62,7 @@ func reconcileSourceGitCmdRun(cmd *cobra.Command, args []string) error {
Name: name,
}
logger.Actionf("annotating source %s in %s namespace", name, namespace)
logger.Actionf("annotating GitRepository source %s in %s namespace", name, namespace)
var gitRepository sourcev1.GitRepository
err = kubeClient.Get(ctx, namespacedName, &gitRepository)
if err != nil {
@@ -79,25 +79,18 @@ func reconcileSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &gitRepository); err != nil {
return err
}
logger.Successf("source annotated")
logger.Successf("GitRepository source annotated")
logger.Waitingf("waiting for reconciliation")
logger.Waitingf("waiting for GitRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
return err
}
logger.Successf("GitRepository source reconciliation completed")
logger.Successf("git reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &gitRepository)
if err != nil {
return err
}
if gitRepository.Status.Artifact != nil {
logger.Successf("fetched revision %s", gitRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("git reconciliation failed, artifact not found")
if gitRepository.Status.Artifact == nil {
return fmt.Errorf("GitRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision %s", gitRepository.Status.Artifact.Revision)
return nil
}

View File

@@ -47,7 +47,7 @@ func init() {
func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
return fmt.Errorf("HelmRepository source name is required")
}
name := args[0]
@@ -64,7 +64,7 @@ func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
Name: name,
}
logger.Actionf("annotating source %s in %s namespace", name, namespace)
logger.Actionf("annotating HelmRepository source %s in %s namespace", name, namespace)
var helmRepository sourcev1.HelmRepository
err = kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil {
@@ -81,42 +81,35 @@ func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &helmRepository); err != nil {
return err
}
logger.Successf("source annotated")
logger.Successf("HelmRepository source annotated")
logger.Waitingf("waiting for reconciliation")
logger.Waitingf("waiting for HelmRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
isHelmRepositoryReady(ctx, kubeClient, namespacedName, &helmRepository)); err != nil {
return err
}
logger.Successf("HelmRepository source reconciliation completed")
logger.Successf("helm reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil {
return err
}
if helmRepository.Status.Artifact != nil {
logger.Successf("fetched revision %s", helmRepository.Status.Artifact.Revision)
} else {
return fmt.Errorf("helm reconciliation failed, artifact not found")
if helmRepository.Status.Artifact == nil {
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
}
logger.Successf("fetched revision %s", helmRepository.Status.Artifact.Revision)
return nil
}
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionFunc {
return func() (bool, error) {
var helmRepository sourcev1.HelmRepository
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRepository)
err := kubeClient.Get(ctx, namespacedName, helmRepository)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if helmRepository.Generation != helmRepository.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(helmRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:

109
cmd/gotk/resume_alert.go Normal file
View File

@@ -0,0 +1,109 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var resumeAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Resume a suspended Alert",
Long: `The resume command marks a previously suspended Alert resource for reconciliation and waits for it to
finish the apply.`,
Example: ` # Resume reconciliation for an existing Alert
gotk resume alert main
`,
RunE: resumeAlertCmdRun,
}
func init() {
resumeCmd.AddCommand(resumeAlertCmd)
}
func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
logger.Actionf("resuming Alert %s in %s namespace", name, namespace)
alert.Spec.Suspend = false
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert resumed")
logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isAlertResumed(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert reconciliation completed")
return nil
}
func isAlertResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil {
return false, err
}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -79,36 +79,25 @@ func resumeHrCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isHelmReleaseResumed(ctx, kubeClient, name, namespace)); err != nil {
isHelmReleaseResumed(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
return err
}
logger.Successf("HelmRelease reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
return err
}
if helmRelease.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
} else {
return fmt.Errorf("HelmRelease reconciliation failed")
}
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
return nil
}
func isHelmReleaseResumed(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isHelmReleaseResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
return func() (bool, error) {
var helmRelease helmv2.HelmRelease
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
err := kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
// Confirm the state we are observing is for the current generation
if helmRelease.Generation != helmRelease.Status.ObservedGeneration {
return false, err
}

View File

@@ -47,7 +47,7 @@ func init() {
func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("kustomization name is required")
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
@@ -69,48 +69,37 @@ func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Actionf("resuming kustomization %s in %s namespace", name, namespace)
logger.Actionf("resuming Kustomization %s in %s namespace", name, namespace)
kustomization.Spec.Suspend = false
if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err
}
logger.Successf("kustomization resumed")
logger.Successf("Kustomization resumed")
logger.Waitingf("waiting for kustomization sync")
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationResumed(ctx, kubeClient, name, namespace)); err != nil {
isKustomizationResumed(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err
}
logger.Successf("Kustomization reconciliation completed")
logger.Successf("kustomization sync completed")
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
return err
}
if kustomization.Status.LastAppliedRevision != "" {
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else {
return fmt.Errorf("kustomization sync failed")
}
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
return nil
}
func isKustomizationResumed(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
func isKustomizationResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
return func() (bool, error) {
var kustomization kustomizev1.Kustomization
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &kustomization)
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if kustomization.Generation != kustomization.Status.ObservedGeneration {
return false, nil
}
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:

110
cmd/gotk/resume_receiver.go Normal file
View File

@@ -0,0 +1,110 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var resumeReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Resume a suspended Receiver",
Long: `The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to
finish the apply.`,
Example: ` # Resume reconciliation for an existing Receiver
gotk resume receiver main
`,
RunE: resumeReceiverCmdRun,
}
func init() {
resumeCmd.AddCommand(resumeReceiverCmd)
}
func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
logger.Actionf("resuming Receiver %s in %s namespace", name, namespace)
receiver.Spec.Suspend = false
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver resumed")
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverResumed(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver reconciliation completed")
return nil
}
func isReceiverResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, receiver)
if err != nil {
return false, err
}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
return true, nil
case corev1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

75
cmd/gotk/suspend_alert.go Normal file
View File

@@ -0,0 +1,75 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var suspendAlertCmd = &cobra.Command{
Use: "alert [name]",
Short: "Suspend reconciliation of Alert",
Long: "The suspend command disables the reconciliation of a Alert resource.",
Example: ` # Suspend reconciliation for an existing Alert
gotk suspend alert main
`,
RunE: suspendAlertCmdRun,
}
func init() {
suspendCmd.AddCommand(suspendAlertCmd)
}
func suspendAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
logger.Actionf("suspending Alert %s in %s namespace", name, namespace)
alert.Spec.Suspend = true
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert suspended")
return nil
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)
var suspendReceiverCmd = &cobra.Command{
Use: "receiver [name]",
Short: "Suspend reconciliation of Receiver",
Long: "The suspend command disables the reconciliation of a Receiver resource.",
Example: ` # Suspend reconciliation for an existing Receiver
gotk suspend receiver main
`,
RunE: suspendReceiverCmdRun,
}
func init() {
suspendCmd.AddCommand(suspendReceiverCmd)
}
func suspendReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
logger.Actionf("suspending Receiver %s in %s namespace", name, namespace)
receiver.Spec.Suspend = true
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver suspended")
return nil
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
@@ -70,10 +71,8 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
return err
}
dryRun := ""
if uninstallDryRun {
dryRun = "--dry-run=client"
} else if !uninstallSilent {
dryRun := "--dry-run=server"
if !uninstallDryRun && !uninstallSilent {
prompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace),
IsConfirm: true,
@@ -102,25 +101,43 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
kustomizev1.KustomizationKind,
sourcev1.GitRepositoryKind,
sourcev1.HelmRepositoryKind,
helmv2.HelmReleaseKind,
} {
command := fmt.Sprintf("kubectl -n %s delete %s --all --timeout=%s %s",
namespace, kind, timeout.String(), dryRun)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
return fmt.Errorf("uninstall failed")
kubectlArgs := []string{
"-n", namespace,
"delete", kind, "--all", "--ignore-not-found",
"--timeout", timeout.String(),
}
if uninstallDryRun {
kubectlArgs = append(kubectlArgs, dryRun)
}
if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("uninstall failed: %w", err)
}
}
}
kinds := "namespace,clusterroles,clusterrolebindings"
var kinds []string
if uninstallCRDs {
kinds += ",crds"
kinds = append(kinds, "crds")
}
kinds = append(kinds, "clusterroles,clusterrolebindings", "namespace")
logger.Actionf("uninstalling components")
command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --timeout=%s %s",
kinds, namespace, timeout.String(), dryRun)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
return fmt.Errorf("uninstall failed")
for _, kind := range kinds {
kubectlArgs := []string{
"delete", kind,
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", namespace),
"--ignore-not-found", "--timeout", timeout.String(),
}
if uninstallDryRun {
kubectlArgs = append(kubectlArgs, dryRun)
}
if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("uninstall failed: %w", err)
}
}
logger.Successf("uninstall finished")

View File

@@ -26,12 +26,13 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
apiruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kustomize/api/filesys"
@@ -42,8 +43,10 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/runtime/dependency"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/olekukonko/tablewriter"
)
type Utils struct {
@@ -57,9 +60,10 @@ const (
ModeCapture ExecMode = "capture.stderr|stdout"
)
func (*Utils) execCommand(ctx context.Context, mode ExecMode, command string) (string, error) {
func (*Utils) execKubectlCommand(ctx context.Context, mode ExecMode, args ...string) (string, error) {
var stdoutBuf, stderrBuf bytes.Buffer
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
c := exec.CommandContext(ctx, "kubectl", args...)
if mode == ModeStderrOS {
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
@@ -120,29 +124,48 @@ func (*Utils) execTemplate(obj interface{}, tmpl, filename string) error {
return file.Sync()
}
func (*Utils) kubeClient(config string) (client.Client, error) {
cfg, err := clientcmd.BuildConfigFromFlags("", config)
func (*Utils) kubeClient(kubeConfigPath string) (client.Client, error) {
configFiles := utils.splitKubeConfigPath(kubeConfigPath)
cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{Precedence: configFiles},
&clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("Kubernetes client initialization failed: %w", err)
return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
}
scheme := runtime.NewScheme()
scheme := apiruntime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = rbacv1.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme)
_ = kustomizev1.AddToScheme(scheme)
_ = helmv2.AddToScheme(scheme)
_ = notificationv1.AddToScheme(scheme)
kubeClient, err := client.New(cfg, client.Options{
Scheme: scheme,
})
if err != nil {
return nil, fmt.Errorf("Kubernetes client initialization failed: %w", err)
return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
}
return kubeClient, nil
}
// splitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS
// target.
//
// Ref: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable
func (*Utils) splitKubeConfigPath(path string) []string {
var sep string
switch runtime.GOOS {
case "windows":
sep = ";"
default:
sep = ":"
}
return strings.Split(path, sep)
}
func (*Utils) writeFile(content, filename string) error {
file, err := os.Create(filename)
if err != nil {
@@ -297,3 +320,21 @@ func (*Utils) generateKustomizationYaml(dirPath string) error {
}
return nil
}
func (*Utils) printTable(writer io.Writer, header []string, rows [][]string) {
table := tablewriter.NewWriter(writer)
table.SetHeader(header)
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t")
table.SetNoWhiteSpace(true)
table.AppendBulk(rows)
table.Render()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -20,14 +20,17 @@ gotk bootstrap gitlab [flags]
# Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token>
# Run bootstrap for a private repo owned by a GitLab group
# Run bootstrap for a private repo using HTTPS token authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name>
# Run bootstrap for a private repo using SSH authentication
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --ssh-hostname=gitlab.com
# Run bootstrap for a repository path
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
gotk bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
# Run bootstrap for a private repo hosted on a GitLab server
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
@@ -48,7 +51,7 @@ gotk bootstrap gitlab [flags]
--personal is personal repository
--private is private repository (default true)
--repository string GitLab repository name
--ssh-hostname string GitLab SSH hostname, defaults to hostname if not specified
--ssh-hostname string GitLab SSH hostname, when specified a deploy key will be added to the repository
```
### Options inherited from parent commands

View File

@@ -27,7 +27,10 @@ The create sub-commands generate sources and resources.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk create alert](gotk_create_alert.md) - Create or update a Alert resource
* [gotk create alert-provider](gotk_create_alert-provider.md) - Create or update a Provider resource
* [gotk create helmrelease](gotk_create_helmrelease.md) - Create or update a HelmRelease resource
* [gotk create kustomization](gotk_create_kustomization.md) - Create or update a Kustomization resource
* [gotk create receiver](gotk_create_receiver.md) - Create or update a Receiver resource
* [gotk create source](gotk_create_source.md) - Create or update sources

View File

@@ -0,0 +1,57 @@
## gotk create alert-provider
Create or update a Provider resource
### Synopsis
The create alert-provider command generates a Provider resource.
```
gotk create alert-provider [name] [flags]
```
### Examples
```
# Create a Provider for a Slack channel
gotk create alert-provider slack \
--type slack \
--channel general \
--address https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
--secret-ref webhook-url
# Create a Provider for a Github repository
gotk create alert-provider github-podinfo \
--type github \
--address https://github.com/stefanprodan/podinfo \
--secret-ref github-token
```
### Options
```
--address string path to either the git repository, chat provider or webhook
--channel string channel to send messages to in the case of a chat provider
-h, --help help for alert-provider
--secret-ref string name of secret containing authentication token
--type string type of provider
--username string bot username used by the provider
```
### Options inherited from parent commands
```
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources

View File

@@ -0,0 +1,49 @@
## gotk create alert
Create or update a Alert resource
### Synopsis
The create alert command generates a Alert resource.
```
gotk create alert [name] [flags]
```
### Examples
```
# Create an Alert for kustomization events
gotk create alert \
--event-severity info \
--event-source Kustomization/gotk-system \
--provider-ref slack \
gotk-system
```
### Options
```
--event-severity string severity of events to send alerts for
--event-source stringArray sources that should generate alerts (<kind>/<name>)
-h, --help help for alert
--provider-ref string reference to provider
```
### Options inherited from parent commands
```
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources

View File

@@ -66,7 +66,7 @@ gotk create helmrelease [name] [flags]
--chart-version string Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)
--depends-on stringArray HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'
-h, --help help for helmrelease
--release-name string name used for the Helm release, defaults to a composition of '[<target-namespace>-]<hr-name>'
--release-name string name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'
--source string source that contains the chart (<kind>/<name>)
--target-namespace string namespace to install this release, defaults to the HelmRelease namespace
--values string local path to the values.yaml file

View File

@@ -0,0 +1,52 @@
## gotk create receiver
Create or update a Receiver resource
### Synopsis
The create receiver command generates a Receiver resource.
```
gotk create receiver [name] [flags]
```
### Examples
```
# Create a Receiver
gotk create receiver github-receiver \
--type github \
--event ping \
--event push \
--secret-ref webhook-token \
--resource GitRepository/webapp \
--resource HelmRepository/webapp
```
### Options
```
--event stringArray
-h, --help help for receiver
--resource stringArray
--secret-ref string
--type string
```
### Options inherited from parent commands
```
--export export in YAML format to stdout
--interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk create](gotk_create.md) - Create or update sources and resources

View File

@@ -25,7 +25,10 @@ The delete sub-commands delete sources and resources.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk delete alert](gotk_delete_alert.md) - Delete a Alert resource
* [gotk delete alert-provider](gotk_delete_alert-provider.md) - Delete a Provider resource
* [gotk delete helmrelease](gotk_delete_helmrelease.md) - Delete a HelmRelease resource
* [gotk delete kustomization](gotk_delete_kustomization.md) - Delete a Kustomization resource
* [gotk delete receiver](gotk_delete_receiver.md) - Delete a Receiver resource
* [gotk delete source](gotk_delete_source.md) - Delete sources

View File

@@ -0,0 +1,40 @@
## gotk delete alert-provider
Delete a Provider resource
### Synopsis
The delete alert-provider command removes the given Provider from the cluster.
```
gotk delete alert-provider [name] [flags]
```
### Examples
```
# Delete a Provider and the Kubernetes resources created by it
gotk delete alert-provider slack
```
### Options
```
-h, --help help for alert-provider
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
-s, --silent delete resource without asking for confirmation
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk delete](gotk_delete.md) - Delete sources and resources

View File

@@ -0,0 +1,40 @@
## gotk delete alert
Delete a Alert resource
### Synopsis
The delete alert command removes the given Alert from the cluster.
```
gotk delete alert [name] [flags]
```
### Examples
```
# Delete an Alert and the Kubernetes resources created by it
gotk delete alert main
```
### Options
```
-h, --help help for alert
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
-s, --silent delete resource without asking for confirmation
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk delete](gotk_delete.md) - Delete sources and resources

View File

@@ -0,0 +1,40 @@
## gotk delete receiver
Delete a Receiver resource
### Synopsis
The delete receiver command removes the given Receiver from the cluster.
```
gotk delete receiver [name] [flags]
```
### Examples
```
# Delete an Receiver and the Kubernetes resources created by it
gotk delete receiver main
```
### Options
```
-h, --help help for receiver
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
-s, --silent delete resource without asking for confirmation
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk delete](gotk_delete.md) - Delete sources and resources

View File

@@ -25,7 +25,10 @@ The export sub-commands export resources in YAML format.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk export alert](gotk_export_alert.md) - Export Alert resources in YAML format
* [gotk export alert-provider](gotk_export_alert-provider.md) - Export Provider resources in YAML format
* [gotk export helmrelease](gotk_export_helmrelease.md) - Export HelmRelease resources in YAML format
* [gotk export kustomization](gotk_export_kustomization.md) - Export Kustomization resources in YAML format
* [gotk export receiver](gotk_export_receiver.md) - Export Receiver resources in YAML format
* [gotk export source](gotk_export_source.md) - Export sources

View File

@@ -0,0 +1,43 @@
## gotk export alert-provider
Export Provider resources in YAML format
### Synopsis
The export alert-provider command exports one or all Provider resources in YAML format.
```
gotk export alert-provider [name] [flags]
```
### Examples
```
# Export all Provider resources
gotk export alert-provider --all > alert-providers.yaml
# Export a Provider
gotk export alert-provider slack > slack.yaml
```
### Options
```
-h, --help help for alert-provider
```
### Options inherited from parent commands
```
--all select all resources
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk export](gotk_export.md) - Export resources in YAML format

View File

@@ -0,0 +1,43 @@
## gotk export alert
Export Alert resources in YAML format
### Synopsis
The export alert command exports one or all Alert resources in YAML format.
```
gotk export alert [name] [flags]
```
### Examples
```
# Export all Alert resources
gotk export alert --all > alerts.yaml
# Export a Alert
gotk export alert main > main.yaml
```
### Options
```
-h, --help help for alert
```
### Options inherited from parent commands
```
--all select all resources
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk export](gotk_export.md) - Export resources in YAML format

View File

@@ -0,0 +1,43 @@
## gotk export receiver
Export Receiver resources in YAML format
### Synopsis
The export receiver command exports one or all Receiver resources in YAML format.
```
gotk export receiver [name] [flags]
```
### Examples
```
# Export all Receiver resources
gotk export receiver --all > receivers.yaml
# Export a Receiver
gotk export receiver main > main.yaml
```
### Options
```
-h, --help help for receiver
```
### Options inherited from parent commands
```
--all select all resources
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk export](gotk_export.md) - Export resources in YAML format

View File

@@ -9,7 +9,8 @@ The get sub-commands print the statuses of sources and resources.
### Options
```
-h, --help help for get
-A, --all-namespaces list the requested object(s) across all namespaces
-h, --help help for get
```
### Options inherited from parent commands
@@ -24,7 +25,10 @@ The get sub-commands print the statuses of sources and resources.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk get alert-providers](gotk_get_alert-providers.md) - Get Provider statuses
* [gotk get alerts](gotk_get_alerts.md) - Get Alert statuses
* [gotk get helmreleases](gotk_get_helmreleases.md) - Get HelmRelease statuses
* [gotk get kustomizations](gotk_get_kustomizations.md) - Get Kustomization statuses
* [gotk get receivers](gotk_get_receivers.md) - Get Receiver statuses
* [gotk get sources](gotk_get_sources.md) - Get source statuses

View File

@@ -0,0 +1,40 @@
## gotk get alert-provider
Get Provider statuses
### Synopsis
The get alert-provider command prints the statuses of the resources.
```
gotk get alert-provider [flags]
```
### Examples
```
# List all Providers and their status
gotk get alert-provider
```
### Options
```
-h, --help help for alert-provider
```
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk get](gotk_get.md) - Get sources and resources

View File

@@ -0,0 +1,40 @@
## gotk get alert-providers
Get Provider statuses
### Synopsis
The get alert-provider command prints the statuses of the resources.
```
gotk get alert-providers [flags]
```
### Examples
```
# List all Providers and their status
gotk get alert-providers
```
### Options
```
-h, --help help for alert-providers
```
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk get](gotk_get.md) - Get sources and resources

View File

@@ -0,0 +1,40 @@
## gotk get alert
Get Alert statuses
### Synopsis
The get alert command prints the statuses of the resources.
```
gotk get alert [flags]
```
### Examples
```
# List all Alerts and their status
gotk get alert
```
### Options
```
-h, --help help for alert
```
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk get](gotk_get.md) - Get sources and resources

View File

@@ -0,0 +1,40 @@
## gotk get alerts
Get Alert statuses
### Synopsis
The get alert command prints the statuses of the resources.
```
gotk get alerts [flags]
```
### Examples
```
# List all Alerts and their status
gotk get alerts
```
### Options
```
-h, --help help for alerts
```
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk get](gotk_get.md) - Get sources and resources

View File

@@ -27,6 +27,7 @@ gotk get helmreleases [flags]
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)

View File

@@ -27,6 +27,7 @@ gotk get kustomizations [flags]
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)

View File

@@ -0,0 +1,40 @@
## gotk get receiver
Get Receiver statuses
### Synopsis
The get receiver command prints the statuses of the resources.
```
gotk get receiver [flags]
```
### Examples
```
# List all Receiver and their status
gotk get receiver
```
### Options
```
-h, --help help for receiver
```
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk get](gotk_get.md) - Get sources and resources

View File

@@ -0,0 +1,40 @@
## gotk get receivers
Get Receiver statuses
### Synopsis
The get receiver command prints the statuses of the resources.
```
gotk get receivers [flags]
```
### Examples
```
# List all Receiver and their status
gotk get receivers
```
### Options
```
-h, --help help for receivers
```
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk get](gotk_get.md) - Get sources and resources

View File

@@ -15,6 +15,7 @@ The get source sub-commands print the statuses of the sources.
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)

View File

@@ -27,6 +27,7 @@ gotk get sources bucket [flags]
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)

View File

@@ -27,6 +27,7 @@ gotk get sources git [flags]
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)

View File

@@ -27,6 +27,7 @@ gotk get sources helm [flags]
### Options inherited from parent commands
```
-A, --all-namespaces list the requested object(s) across all namespaces
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)

View File

@@ -24,7 +24,10 @@ The reconcile sub-commands trigger a reconciliation of sources and resources.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk reconcile alert](gotk_reconcile_alert.md) - Reconcile an Alert
* [gotk reconcile alert-provider](gotk_reconcile_alert-provider.md) - Reconcile a Provider
* [gotk reconcile helmrelease](gotk_reconcile_helmrelease.md) - Reconcile a HelmRelease resource
* [gotk reconcile kustomization](gotk_reconcile_kustomization.md) - Reconcile a Kustomization resource
* [gotk reconcile receiver](gotk_reconcile_receiver.md) - Reconcile a Receiver
* [gotk reconcile source](gotk_reconcile_source.md) - Reconcile sources

View File

@@ -0,0 +1,39 @@
## gotk reconcile alert-provider
Reconcile a Provider
### Synopsis
The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.
```
gotk reconcile alert-provider [name] [flags]
```
### Examples
```
# Trigger a reconciliation for an existing provider
gotk reconcile alert-provider slack
```
### Options
```
-h, --help help for alert-provider
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk reconcile](gotk_reconcile.md) - Reconcile sources and resources

View File

@@ -0,0 +1,39 @@
## gotk reconcile alert
Reconcile an Alert
### Synopsis
The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.
```
gotk reconcile alert [name] [flags]
```
### Examples
```
# Trigger a reconciliation for an existing alert
gotk reconcile alert main
```
### Options
```
-h, --help help for alert
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk reconcile](gotk_reconcile.md) - Reconcile sources and resources

View File

@@ -26,7 +26,7 @@ gotk reconcile kustomization [name] [flags]
```
-h, --help help for kustomization
--with-source reconcile kustomization source
--with-source reconcile Kustomization source
```
### Options inherited from parent commands

View File

@@ -0,0 +1,39 @@
## gotk reconcile receiver
Reconcile a Receiver
### Synopsis
The reconcile receiver command triggers a reconciliation of a Receiver resource and waits for it to finish.
```
gotk reconcile receiver [name] [flags]
```
### Examples
```
# Trigger a reconciliation for an existing receiver
gotk reconcile receiver main
```
### Options
```
-h, --help help for receiver
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk reconcile](gotk_reconcile.md) - Reconcile sources and resources

View File

@@ -24,6 +24,8 @@ The resume sub-commands resume a suspended resource.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk resume alert](gotk_resume_alert.md) - Resume a suspended Alert
* [gotk resume helmrelease](gotk_resume_helmrelease.md) - Resume a suspended HelmRelease
* [gotk resume kustomization](gotk_resume_kustomization.md) - Resume a suspended Kustomization
* [gotk resume receiver](gotk_resume_receiver.md) - Resume a suspended Receiver

View File

@@ -0,0 +1,40 @@
## gotk resume alert
Resume a suspended Alert
### Synopsis
The resume command marks a previously suspended Alert resource for reconciliation and waits for it to
finish the apply.
```
gotk resume alert [name] [flags]
```
### Examples
```
# Resume reconciliation for an existing Alert
gotk resume alert main
```
### Options
```
-h, --help help for alert
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk resume](gotk_resume.md) - Resume suspended resources

View File

@@ -0,0 +1,40 @@
## gotk resume receiver
Resume a suspended Receiver
### Synopsis
The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to
finish the apply.
```
gotk resume receiver [name] [flags]
```
### Examples
```
# Resume reconciliation for an existing Receiver
gotk resume receiver main
```
### Options
```
-h, --help help for receiver
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk resume](gotk_resume.md) - Resume suspended resources

View File

@@ -24,6 +24,8 @@ The suspend sub-commands suspend the reconciliation of a resource.
### SEE ALSO
* [gotk](gotk.md) - Command line utility for assembling Kubernetes CD pipelines
* [gotk suspend alert](gotk_suspend_alert.md) - Suspend reconciliation of Alert
* [gotk suspend helmrelease](gotk_suspend_helmrelease.md) - Suspend reconciliation of HelmRelease
* [gotk suspend kustomization](gotk_suspend_kustomization.md) - Suspend reconciliation of Kustomization
* [gotk suspend receiver](gotk_suspend_receiver.md) - Suspend reconciliation of Receiver

View File

@@ -0,0 +1,39 @@
## gotk suspend alert
Suspend reconciliation of Alert
### Synopsis
The suspend command disables the reconciliation of a Alert resource.
```
gotk suspend alert [name] [flags]
```
### Examples
```
# Suspend reconciliation for an existing Alert
gotk suspend alert main
```
### Options
```
-h, --help help for alert
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk suspend](gotk_suspend.md) - Suspend resources

View File

@@ -0,0 +1,39 @@
## gotk suspend receiver
Suspend reconciliation of Receiver
### Synopsis
The suspend command disables the reconciliation of a Receiver resource.
```
gotk suspend receiver [name] [flags]
```
### Examples
```
# Suspend reconciliation for an existing Receiver
gotk suspend receiver main
```
### Options
```
-h, --help help for receiver
```
### Options inherited from parent commands
```
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
-n, --namespace string the namespace scope for this operation (default "gotk-system")
--timeout duration timeout for this operation (default 5m0s)
--verbose print generated objects
```
### SEE ALSO
* [gotk suspend](gotk_suspend.md) - Suspend resources

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -70,7 +70,6 @@ Verify that your staging cluster satisfies the prerequisites with:
```console
$ gotk check --pre
► checking prerequisites
✔ kubectl 1.18.3 >=1.18.0
✔ kubernetes 1.18.2 >=1.16.0
@@ -88,6 +87,11 @@ gotk bootstrap github \
--personal
```
!!! hint "ARM"
When deploying to a Kubernetes cluster with ARM architecture,
you can use `--arch=arm` for ARMv7 32-bit container images
and `--arch=arm64` for ARMv8 64-bit container images.
The bootstrap command creates a repository if one doesn't exist and
commits the toolkit components manifests to the default branch at the specified path.
Then it configures the target cluster to synchronize with the specified path inside the repository.
@@ -108,7 +112,6 @@ Example output:
```text
$ gotk bootstrap github --owner=gitopsrun --repository=fleet-infra --path=staging-cluster --team=devs
► connecting to github.com
✔ repository created
✔ devs team access granted
@@ -209,18 +212,17 @@ In about 30s the synchronization should start:
```console
$ watch gotk get kustomizations
gotk-system last applied revision master/35d5765a1acb9e9ce66cad7274c6fe03eee1e8eb
webapp-backend reconciling
webapp-common last applied revision master/f43f9b2eb6766e07f318d266a99d2ec7c940b0cf
webapp-frontend dependency 'gotk-system/webapp-backend' is not ready
NAME REVISION SUSPENDED READY MESSAGE
gotk-system main/6eea299fe9997c8561b826b67950afaf9a476cf8 False True Applied revision: main/6eea299fe9997c8561b826b67950afaf9a476cf8
webapp-backend False False dependency 'gotk-system/webapp-common' is not ready
webapp-common master/7411da595c25183daba255068814b83843fe3395 False True Applied revision: master/7411da595c25183daba255068814b83843fe3395
webapp-frontend False False dependency 'gotk-system/webapp-backend' is not ready
```
When the synchronization finishes you can check that the webapp services are running:
```console
$ kubectl -n webapp get deployments,services
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend 1/1 1 1 4m1s
deployment.apps/frontend 1/1 1 1 3m31s
@@ -264,7 +266,6 @@ gotk bootstrap github \
--path=prod-cluster \
--personal
```
Pull the changes locally:
```sh
@@ -308,9 +309,9 @@ List git sources:
```console
$ gotk get sources git
gotk-system last fetched revision master/99072ee132abdead8b7799d7891eae2f524eb73d
webapp last fetched revision 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27
NAME REVISION READY MESSAGE
gotk-system main/5ae055e24b2c8a78f981708b61507a97a30bd7a6 True Fetched revision: main/113360052b3153e439a0cf8de76b8e3d2a7bdf27
webapp 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27 True Fetched revision: 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27
```
The kubectl equivalent is `kubectl -n gotk-system get gitrepositories`.
@@ -319,9 +320,9 @@ List kustomization:
```console
$ gotk get kustomizations
gotk-system last applied revision master/99072ee132abdead8b7799d7891eae2f524eb73d
webapp last applied revision 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27
NAME REVISION SUSPENDED READY MESSAGE
gotk-system main/5ae055e24b2c8a78f981708b61507a97a30bd7a6 False True Applied revision: main/5ae055e24b2c8a78f981708b61507a97a30bd7a6
webapp 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27 False True Applied revision: 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27
```
The kubectl equivalent is `kubectl -n gotk-system get kustomizations`.
@@ -342,15 +343,14 @@ Trigger a git sync:
```console
$ gotk reconcile ks gotk-system --with-source
► annotating source gotk-system
✔ source annotated
◎ waiting for reconcilitation
✔ git reconciliation completed
✔ fetched revision master/d751ea264d48bf0db8b588d1d08184834ac8fec9
✔ fetched revision main/d751ea264d48bf0db8b588d1d08184834ac8fec9
◎ waiting for kustomization reconcilitation
✔ kustomization reconcilitation completed
✔ applied revision master/d751ea264d48bf0db8b588d1d08184834ac8fec9
✔ applied revision main/d751ea264d48bf0db8b588d1d08184834ac8fec9
```
The kubectl equivalent is `kubectl -n gotk-system annotate gitrepository/gotk-system fluxcd.io/reconcileAt="$(date +%s)"`.
@@ -359,7 +359,7 @@ Wait for the webapp to be upgraded:
```console
$ watch gotk get kustomizations
gotk-system last applied revision master/d751ea264d48bf0db8b588d1d08184834ac8fec9
webapp last applied revision 4.0.5/f43f9b2eb6766e07f318d266a99d2ec7c940b0cf
NAME REVISION SUSPENDED READY MESSAGE
gotk-system main/d751ea264d48bf0db8b588d1d08184834ac8fec9 False True Applied revision: main/d751ea264d48bf0db8b588d1d08184834ac8fec9
webapp 4.0.6/26a630c0b4b3452833d96c511d93f6f2d2e90a99 False True Applied revision: 4.0.6/26a630c0b4b3452833d96c511d93f6f2d2e90a99
```

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