1
0
mirror of synced 2026-03-02 11:36:56 +00:00

Compare commits

..

31 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
45 changed files with 1454 additions and 664 deletions

View File

@@ -15,18 +15,14 @@ jobs:
- name: Copy assets - name: Copy assets
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SOURCE_VER: ${{ 'v0.1.1' }}
KUSTOMIZE_VER: ${{ 'v0.1.1' }}
HELM_VER: ${{ 'v0.1.2' }}
NOTIFICATION_VER: ${{ 'v0.1.1' }}
run: | run: |
controller_version() { 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-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/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/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 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-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/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 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-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/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 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-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/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/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 curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/alert.md" > docs/components/notification/alert.md

View File

@@ -13,15 +13,13 @@ jobs:
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: "^1.14.x" go-version: "^1.14.x"
- run: go version - name: Add GOPATH to GITHUB_ENV
# Runs a set of commands to initialize and analyze with FOSSA run: echo "GOPATH=$(go env GOPATH)" >>"$GITHUB_ENV"
- name: run FOSSA analysis - name: Add GOPATH to GITHUB_PATH
env: 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 Push-Only API Token
FOSSA_API_KEY: '5ee8bf422db1471e0bcf2bcb289185de' fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
run: | github-token: ${{ github.token }}
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

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

@@ -19,7 +19,7 @@ jobs:
bump_version() { 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 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 if [[ "${RELEASE_VERSION}" != "${CURRENT_VERSION}" ]]; then
# bump kustomize # bump kustomize

View File

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

View File

@@ -82,17 +82,13 @@ The GitOps Toolkit is always looking for new contributors and there are a multit
- Check out [how to contribute](CONTRIBUTING.md) to the project - Check out [how to contribute](CONTRIBUTING.md) to the project
## Featured Talks ## Featured Talks
- [12 Oct 2020 - Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY) - 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) - 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) - 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) - 7 May 2020 - [GitOps Days - Community Special: GitOps Toolkit Experimentation with Stefan Prodan](https://youtu.be/WHzxunv4DKk?t=6521)
### Upcoming Meetups ### Upcoming Meetups
- [19 October 2020 - GitOps Toolkit Guide Walk-through](https://www.meetup.com/GitOps-Community/events/273640196/) - 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1](https://www.meetup.com/GitOps-Community/events/273640196/)
Join us 10am PT / 18:00 BST) for to this special walk-through of the GitOps Toolkit! - 2 Nov 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2](https://www.meetup.com/GitOps-Community/events/273934676/)
Through this talk you'll be able to see how the upcoming Flux v2 and GitOps Toolkit will bring
great improvements to the Flux that you love! Watch or follow along as Leigh Capili shares some
highlights and then goes through Getting Started with GitOps Toolkit.
- 2 November 2020 - GitOps Toolkit Guide Walk-through - Part 2 (TBD)
We are looking forward to seeing you with us! We are looking forward to seeing you with us!

View File

@@ -150,15 +150,14 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
} }
func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error { func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
command := fmt.Sprintf("kubectl apply -f %s", manifestPath) kubectlArgs := []string{"apply", "-f", manifestPath}
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil { if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} }
for _, deployment := range components { for _, deployment := range components {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s", kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
namespace, deployment, timeout.String()) if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} }
} }
@@ -219,6 +218,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
Kind: sourcev1.GitRepositoryKind, Kind: sourcev1.GitRepositoryKind,
Name: name, Name: name,
}, },
Validation: "client",
}, },
} }
@@ -239,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 { 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)) kubectlArgs := []string{"apply", "-k", filepath.Join(tmpDir, targetPath, namespace)}
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil { if _, err := utils.execKubectlCommand(ctx, ModeStderrOS, kubectlArgs...); err != nil {
return err return err
} }
logger.Waitingf("waiting for cluster sync") logger.Waitingf("waiting for cluster sync")
var gitRepository sourcev1.GitRepository
if err := wait.PollImmediate(pollInterval, timeout, 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 return err
} }
var kustomization kustomizev1.Kustomization
if err := wait.PollImmediate(pollInterval, timeout, 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 return err
} }

View File

@@ -26,6 +26,8 @@ import (
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/git" "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 Example: ` # Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token> 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> 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 # Run bootstrap for a repository path
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account # 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 # Run bootstrap for a private repo hosted on a GitLab server
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain> 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().BoolVar(&glPrivate, "private", true, "is private repository")
bootstrapGitLabCmd.Flags().DurationVar(&glInterval, "interval", time.Minute, "sync interval") bootstrapGitLabCmd.Flags().DurationVar(&glInterval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&glHostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname") 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") bootstrapGitLabCmd.Flags().StringVar(&glPath, "path", "", "repository path, when specified the cluster sync will be scoped to this path")
bootstrapCmd.AddCommand(bootstrapGitLabCmd) bootstrapCmd.AddCommand(bootstrapGitLabCmd)
@@ -172,34 +177,54 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("install completed") logger.Successf("install completed")
} }
// setup SSH deploy key repoURL := repository.GetURL()
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)
}
key, err := generateDeployKey(ctx, kubeClient, u, namespace) if glSSHHostname != "" {
if err != nil { // setup SSH deploy key
return fmt.Errorf("generating deploy key failed: %w", err) 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" key, err := generateDeployKey(ctx, kubeClient, u, namespace)
if glPath != "" { if err != nil {
keyName = fmt.Sprintf("gotk-%s", glPath) 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 return err
} else if changed {
logger.Successf("deploy key configured")
} }
} }
// configure repo synchronization // configure repo synchronization
logger.Actionf("generating sync manifests") 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 return err
} }

View File

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

View File

@@ -61,7 +61,7 @@ func init() {
func createAlertCmdRun(cmd *cobra.Command, args []string) error { func createAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("alert name is required") return fmt.Errorf("Alert name is required")
} }
name := args[0] name := args[0]
@@ -92,7 +92,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
if !export { if !export {
logger.Generatef("generating alert") logger.Generatef("generating Alert")
} }
alert := notificationv1.Alert{ alert := notificationv1.Alert{
@@ -123,23 +123,23 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("applying alert") logger.Actionf("applying Alert")
if err := upsertAlert(ctx, kubeClient, alert); err != nil { namespacedName, err := upsertAlert(ctx, kubeClient, &alert)
if err != nil {
return err return err
} }
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isAlertReady(ctx, kubeClient, name, namespace)); err != nil { isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err return err
} }
logger.Successf("Alert %s is ready", name)
logger.Successf("alert %s is ready", name)
return nil return nil
} }
func upsertAlert(ctx context.Context, kubeClient client.Client, alert notificationv1.Alert) error { func upsertAlert(ctx context.Context, kubeClient client.Client,
alert *notificationv1.Alert) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: alert.GetNamespace(), Namespace: alert.GetNamespace(),
Name: alert.GetName(), Name: alert.GetName(),
@@ -149,35 +149,30 @@ func upsertAlert(ctx context.Context, kubeClient client.Client, alert notificati
err := kubeClient.Get(ctx, namespacedName, &existing) err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &alert); err != nil { if err := kubeClient.Create(ctx, alert); err != nil {
return err return namespacedName, err
} else { } else {
logger.Successf("alert created") logger.Successf("Alert created")
return nil return namespacedName, nil
} }
} }
return err return namespacedName, err
} }
existing.Labels = alert.Labels existing.Labels = alert.Labels
existing.Spec = alert.Spec existing.Spec = alert.Spec
if err := kubeClient.Update(ctx, &existing); err != nil { if err := kubeClient.Update(ctx, &existing); err != nil {
return err return namespacedName, err
} }
alert = &existing
logger.Successf("alert updated") logger.Successf("Alert updated")
return nil return namespacedName, nil
} }
func isAlertReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc { func isAlertReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
var alert notificationv1.Alert err := kubeClient.Get(ctx, namespacedName, alert)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &alert)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@@ -19,7 +19,6 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -30,6 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
) )
var createAlertProviderCmd = &cobra.Command{ var createAlertProviderCmd = &cobra.Command{
@@ -71,12 +71,12 @@ func init() {
func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error { func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("provider name is required") return fmt.Errorf("Provider name is required")
} }
name := args[0] name := args[0]
if apType == "" { if apType == "" {
return fmt.Errorf("type is required") return fmt.Errorf("Provider type is required")
} }
sourceLabels, err := parseLabels() sourceLabels, err := parseLabels()
@@ -85,10 +85,10 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
if !export { if !export {
logger.Generatef("generating provider") logger.Generatef("generating Provider")
} }
alertProvider := notificationv1.Provider{ provider := notificationv1.Provider{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: namespace,
@@ -99,14 +99,17 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
Channel: apChannel, Channel: apChannel,
Username: apUsername, Username: apUsername,
Address: apAddress, Address: apAddress,
SecretRef: &corev1.LocalObjectReference{
Name: apSecretRef,
},
}, },
} }
if apSecretRef != "" {
provider.Spec.SecretRef = &corev1.LocalObjectReference{
Name: apSecretRef,
}
}
if export { if export {
return exportAlertProvider(alertProvider) return exportAlertProvider(provider)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
@@ -117,66 +120,63 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("applying provider") logger.Actionf("applying Provider")
if err := upsertAlertProvider(ctx, kubeClient, alertProvider); err != nil { namespacedName, err := upsertAlertProvider(ctx, kubeClient, &provider)
if err != nil {
return err return err
} }
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for Provider reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isAlertProviderReady(ctx, kubeClient, name, namespace)); err != nil { isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
return err return err
} }
logger.Successf("provider %s is ready", name) logger.Successf("Provider %s is ready", name)
return nil return nil
} }
func upsertAlertProvider(ctx context.Context, kubeClient client.Client, alertProvider notificationv1.Provider) error { func upsertAlertProvider(ctx context.Context, kubeClient client.Client,
provider *notificationv1.Provider) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: alertProvider.GetNamespace(), Namespace: provider.GetNamespace(),
Name: alertProvider.GetName(), Name: provider.GetName(),
} }
var existing notificationv1.Provider var existing notificationv1.Provider
err := kubeClient.Get(ctx, namespacedName, &existing) err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &alertProvider); err != nil { if err := kubeClient.Create(ctx, provider); err != nil {
return err return namespacedName, err
} else { } else {
logger.Successf("provider created") logger.Successf("Provider created")
return nil return namespacedName, nil
} }
} }
return err return namespacedName, err
} }
existing.Labels = alertProvider.Labels existing.Labels = provider.Labels
existing.Spec = alertProvider.Spec existing.Spec = provider.Spec
if err := kubeClient.Update(ctx, &existing); err != nil { if err := kubeClient.Update(ctx, &existing); err != nil {
return err return namespacedName, err
} }
provider = &existing
logger.Successf("provider updated") logger.Successf("Provider updated")
return nil return namespacedName, nil
} }
func isAlertProviderReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc { func isAlertProviderReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
var alertProvider notificationv1.Provider err := kubeClient.Get(ctx, namespacedName, provider)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil { if err != nil {
return false, err return false, err
} }
if c := meta.GetCondition(alertProvider.Status.Conditions, meta.ReadyCondition); c != nil { if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case corev1.ConditionTrue:
return true, nil return true, nil

View File

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

View File

@@ -65,12 +65,12 @@ func init() {
func createReceiverCmdRun(cmd *cobra.Command, args []string) error { func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("receiver name is required") return fmt.Errorf("Receiver name is required")
} }
name := args[0] name := args[0]
if rcvType == "" { if rcvType == "" {
return fmt.Errorf("type is required") return fmt.Errorf("Receiver type is required")
} }
if rcvSecretRef == "" { if rcvSecretRef == "" {
@@ -100,7 +100,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
if !export { if !export {
logger.Generatef("generating receiver") logger.Generatef("generating Receiver")
} }
receiver := notificationv1.Receiver{ receiver := notificationv1.Receiver{
@@ -132,34 +132,25 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("applying receiver") logger.Actionf("applying Receiver")
if err := upsertReceiver(ctx, kubeClient, receiver); err != nil { namespacedName, err := upsertReceiver(ctx, kubeClient, &receiver)
return err
}
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isReceiverReady(ctx, kubeClient, name, namespace)); err != nil {
return err
}
logger.Successf("receiver %s is ready", name)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil { if err != nil {
return fmt.Errorf("receiver sync failed: %w", err) 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) logger.Successf("generated webhook URL %s", receiver.Status.URL)
return nil return nil
} }
func upsertReceiver(ctx context.Context, kubeClient client.Client, receiver notificationv1.Receiver) error { func upsertReceiver(ctx context.Context, kubeClient client.Client,
receiver *notificationv1.Receiver) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: receiver.GetNamespace(), Namespace: receiver.GetNamespace(),
Name: receiver.GetName(), Name: receiver.GetName(),
@@ -169,35 +160,30 @@ func upsertReceiver(ctx context.Context, kubeClient client.Client, receiver noti
err := kubeClient.Get(ctx, namespacedName, &existing) err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &receiver); err != nil { if err := kubeClient.Create(ctx, receiver); err != nil {
return err return namespacedName, err
} else { } else {
logger.Successf("receiver created") logger.Successf("Receiver created")
return nil return namespacedName, nil
} }
} }
return err return namespacedName, err
} }
existing.Labels = receiver.Labels existing.Labels = receiver.Labels
existing.Spec = receiver.Spec existing.Spec = receiver.Spec
if err := kubeClient.Update(ctx, &existing); err != nil { if err := kubeClient.Update(ctx, &existing); err != nil {
return err return namespacedName, err
} }
receiver = &existing
logger.Successf("receiver updated") logger.Successf("Receiver updated")
return nil return namespacedName, nil
} }
func isReceiverReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc { func isReceiverReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
var receiver notificationv1.Receiver err := kubeClient.Get(ctx, namespacedName, receiver)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

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

View File

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

View File

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

View File

@@ -158,14 +158,13 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
if verbose { if verbose {
applyOutput = ModeOS applyOutput = ModeOS
} }
dryRun := ""
kubectlArgs := []string{"apply", "-f", manifest}
if installDryRun { if installDryRun {
dryRun = "--dry-run=client" args = append(args, "--dry-run=client")
applyOutput = ModeOS applyOutput = ModeOS
} }
if _, err := utils.execKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
command := fmt.Sprintf("kubectl apply -f %s %s", manifest, dryRun)
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} }
@@ -178,9 +177,8 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("verifying installation") logger.Waitingf("verifying installation")
for _, deployment := range installComponents { for _, deployment := range installComponents {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s", kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
namespace, deployment, timeout.String()) if _, err := utils.execKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} else { } else {
logger.Successf("%s ready", deployment) logger.Successf("%s ready", deployment)

View File

@@ -45,7 +45,7 @@ func init() {
func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error { func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("alert name is required") return fmt.Errorf("Alert name is required")
} }
name := args[0] name := args[0]
@@ -62,7 +62,7 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
Name: name, Name: name,
} }
logger.Actionf("annotating alert %s in %s namespace", name, namespace) logger.Actionf("annotating Alert %s in %s namespace", name, namespace)
var alert notificationv1.Alert var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert) err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil { if err != nil {
@@ -79,15 +79,13 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &alert); err != nil { if err := kubeClient.Update(ctx, &alert); err != nil {
return err return err
} }
logger.Successf("alert annotated") logger.Successf("Alert annotated")
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isAlertReady(ctx, kubeClient, name, namespace)); err != nil { isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err return err
} }
logger.Successf("Alert reconciliation completed")
logger.Successf("alert reconciliation completed")
return nil return nil
} }

View File

@@ -45,7 +45,7 @@ func init() {
func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error { func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("provider name is required") return fmt.Errorf("Provider name is required")
} }
name := args[0] name := args[0]
@@ -62,7 +62,7 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
Name: name, Name: name,
} }
logger.Actionf("annotating provider %s in %s namespace", name, namespace) logger.Actionf("annotating Provider %s in %s namespace", name, namespace)
var alertProvider notificationv1.Provider var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider) err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil { if err != nil {
@@ -79,15 +79,13 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &alertProvider); err != nil { if err := kubeClient.Update(ctx, &alertProvider); err != nil {
return err return err
} }
logger.Successf("provider annotated") logger.Successf("Provider annotated")
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isAlertProviderReady(ctx, kubeClient, name, namespace)); err != nil { isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil {
return err return err
} }
logger.Successf("Provider reconciliation completed")
logger.Successf("provider reconciliation completed")
return nil 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) 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 return err
} }
logger.Successf("HelmRelease annotated") logger.Successf("HelmRelease annotated")
logger.Waitingf("waiting for HelmRelease reconciliation") logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
helmReleaseReconciliationHandled(ctx, kubeClient, name, namespace, helmRelease.Status.LastHandledReconcileAt), helmReleaseReconciliationHandled(ctx, kubeClient, namespacedName, &helmRelease, lastHandledReconcileAt),
); err != nil { ); err != nil {
return err return err
} }
logger.Successf("HelmRelease reconciliation completed") logger.Successf("HelmRelease reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRelease) 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 { if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionFalse: case corev1.ConditionFalse:
return fmt.Errorf("HelmRelease reconciliation failed") return fmt.Errorf("HelmRelease reconciliation failed: %s", c.Message)
default: default:
logger.Successf("reconciled revision %s", helmRelease.Status.LastAppliedRevision) 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, 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) { return func() (bool, error) {
var helmRelease helmv2.HelmRelease err := kubeClient.Get(ctx, namespacedName, helmRelease)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil { if err != nil {
return false, err return false, err
} }
return helmRelease.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil return helmRelease.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
} }
} }
func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Client, namespacedName types.NamespacedName) error { func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Client,
var release helmv2.HelmRelease namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err 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 return err
} }
if helmRelease.Annotations == nil {
if release.Annotations == nil { helmRelease.Annotations = map[string]string{
release.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
} }
} else { } else {
release.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) helmRelease.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
return kubeClient.Update(ctx, helmRelease)
err = kubeClient.Update(ctx, &release)
return
}) })
} }

View File

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

View File

@@ -62,7 +62,7 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
Name: name, Name: name,
} }
logger.Actionf("annotating receiver %s in %s namespace", name, namespace) logger.Actionf("annotating Receiver %s in %s namespace", name, namespace)
var receiver notificationv1.Receiver var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver) err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil { if err != nil {
@@ -79,15 +79,15 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &receiver); err != nil { if err := kubeClient.Update(ctx, &receiver); err != nil {
return err return err
} }
logger.Successf("receiver annotated") logger.Successf("Receiver annotated")
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isReceiverReady(ctx, kubeClient, name, namespace)); err != nil { isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err return err
} }
logger.Successf("receiver reconciliation completed") logger.Successf("Receiver reconciliation completed")
return nil return nil
} }

View File

@@ -64,7 +64,7 @@ func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
Name: name, 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 var bucket sourcev1.Bucket
err = kubeClient.Get(ctx, namespacedName, &bucket) err = kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil { if err != nil {
@@ -81,42 +81,35 @@ func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &bucket); err != nil { if err := kubeClient.Update(ctx, &bucket); err != nil {
return err 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, if err := wait.PollImmediate(pollInterval, timeout,
isBucketReady(ctx, kubeClient, name, namespace)); err != nil { isBucketReady(ctx, kubeClient, namespacedName, &bucket)); err != nil {
return err return err
} }
logger.Successf("Bucket source reconciliation completed")
logger.Successf("bucket reconciliation completed") if bucket.Status.Artifact == nil {
return fmt.Errorf("Bucket source reconciliation completed but no artifact was found")
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")
} }
logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
return nil 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) { return func() (bool, error) {
var bucket sourcev1.Bucket err := kubeClient.Get(ctx, namespacedName, bucket)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil { if err != nil {
return false, err 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 { if c := meta.GetCondition(bucket.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case corev1.ConditionTrue:

View File

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

View File

@@ -47,7 +47,7 @@ func init() {
func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error { func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("source name is required") return fmt.Errorf("HelmRepository source name is required")
} }
name := args[0] name := args[0]
@@ -64,7 +64,7 @@ func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
Name: name, 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 var helmRepository sourcev1.HelmRepository
err = kubeClient.Get(ctx, namespacedName, &helmRepository) err = kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil { if err != nil {
@@ -81,42 +81,35 @@ func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &helmRepository); err != nil { if err := kubeClient.Update(ctx, &helmRepository); err != nil {
return err 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, if err := wait.PollImmediate(pollInterval, timeout,
isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil { isHelmRepositoryReady(ctx, kubeClient, namespacedName, &helmRepository)); err != nil {
return err return err
} }
logger.Successf("HelmRepository source reconciliation completed")
logger.Successf("helm reconciliation completed") if helmRepository.Status.Artifact == nil {
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
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")
} }
logger.Successf("fetched revision %s", helmRepository.Status.Artifact.Revision)
return nil 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) { return func() (bool, error) {
var helmRepository sourcev1.HelmRepository err := kubeClient.Get(ctx, namespacedName, helmRepository)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &helmRepository)
if err != nil { if err != nil {
return false, err 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 { if c := meta.GetCondition(helmRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case corev1.ConditionTrue:

View File

@@ -78,24 +78,17 @@ func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("waiting for Alert reconciliation") logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isAlertResumed(ctx, kubeClient, name, namespace)); err != nil { isAlertResumed(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err return err
} }
logger.Successf("Alert reconciliation completed") logger.Successf("Alert reconciliation completed")
return nil return nil
} }
func isAlertResumed(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc { func isAlertResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
var alert notificationv1.Alert err := kubeClient.Get(ctx, namespacedName, alert)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &alert)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

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

View File

@@ -47,7 +47,7 @@ func init() {
func resumeKsCmdRun(cmd *cobra.Command, args []string) error { func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("kustomization name is required") return fmt.Errorf("Kustomization name is required")
} }
name := args[0] name := args[0]
@@ -69,48 +69,37 @@ func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
return err 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 kustomization.Spec.Suspend = false
if err := kubeClient.Update(ctx, &kustomization); err != nil { if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err 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, if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationResumed(ctx, kubeClient, name, namespace)); err != nil { isKustomizationResumed(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err return err
} }
logger.Successf("Kustomization reconciliation completed")
logger.Successf("kustomization sync completed") logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
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")
}
return nil 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) { return func() (bool, error) {
var kustomization kustomizev1.Kustomization err := kubeClient.Get(ctx, namespacedName, kustomization)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil { if err != nil {
return false, err 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 { if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case corev1.ConditionTrue:

View File

@@ -78,24 +78,18 @@ func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("waiting for Receiver reconciliation") logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isReceiverResumed(ctx, kubeClient, name, namespace)); err != nil { isReceiverResumed(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err return err
} }
logger.Successf("Receiver reconciliation completed") logger.Successf("Receiver reconciliation completed")
return nil return nil
} }
func isReceiverResumed(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc { func isReceiverResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
var receiver notificationv1.Receiver err := kubeClient.Get(ctx, namespacedName, receiver)
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@@ -71,10 +71,8 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
dryRun := "" dryRun := "--dry-run=server"
if uninstallDryRun { if !uninstallDryRun && !uninstallSilent {
dryRun = "--dry-run=server"
} else if !uninstallSilent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace), Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace),
IsConfirm: true, IsConfirm: true,
@@ -105,9 +103,15 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
sourcev1.HelmRepositoryKind, sourcev1.HelmRepositoryKind,
helmv2.HelmReleaseKind, helmv2.HelmReleaseKind,
} { } {
command := fmt.Sprintf("kubectl -n %s delete %s --all --ignore-not-found --timeout=%s %s", kubectlArgs := []string{
namespace, kind, timeout.String(), dryRun) "-n", namespace,
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil { "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) return fmt.Errorf("uninstall failed: %w", err)
} }
} }
@@ -123,9 +127,15 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
logger.Actionf("uninstalling components") logger.Actionf("uninstalling components")
for _, kind := range kinds { for _, kind := range kinds {
command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --ignore-not-found --timeout=%s %s", kubectlArgs := []string{
kind, namespace, timeout.String(), dryRun) "delete", kind,
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil { "-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) return fmt.Errorf("uninstall failed: %w", err)
} }
} }

View File

@@ -60,9 +60,10 @@ const (
ModeCapture ExecMode = "capture.stderr|stdout" 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 var stdoutBuf, stderrBuf bytes.Buffer
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
c := exec.CommandContext(ctx, "kubectl", args...)
if mode == ModeStderrOS { if mode == ModeStderrOS {
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View File

@@ -20,14 +20,17 @@ gotk bootstrap gitlab [flags]
# Create a GitLab API token and export it as an env var # Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token> 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> 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 # Run bootstrap for a repository path
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster gotk bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account # 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 # Run bootstrap for a private repo hosted on a GitLab server
gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain> gotk bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
@@ -48,7 +51,7 @@ gotk bootstrap gitlab [flags]
--personal is personal repository --personal is personal repository
--private is private repository (default true) --private is private repository (default true)
--repository string GitLab repository name --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 ### Options inherited from parent commands

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) --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>' --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 -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>) --source string source that contains the chart (<kind>/<name>)
--target-namespace string namespace to install this release, defaults to the HelmRelease namespace --target-namespace string namespace to install this release, defaults to the HelmRelease namespace
--values string local path to the values.yaml file --values string local path to the values.yaml file

View File

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

View File

@@ -154,6 +154,22 @@ gotk bootstrap gitlab \
--personal --personal
``` ```
To run the bootstrap for a repository using deploy keys for authentication, you have to specify the SSH hostname:
```sh
gotk bootstrap gitlab \
--ssh-hostname=gitlab.com \
--owner=my-gitlab-username \
--repository=my-repository \
--branch=master \
--path=my-cluster
```
!!! hint "Authentication"
When providing the `--ssh-hostname`, a read-only (SSH) deploy key will be added
to your repository, otherwise your GitLab personal token will be used to
authenticate against the HTTPS endpoint instead.
Run the bootstrap for a repository owned by a GitLab group: Run the bootstrap for a repository owned by a GitLab group:
```sh ```sh
@@ -393,12 +409,17 @@ kubectl -n gotk-system port-forward svc/grafana 3000:3000
``` ```
Navigate to [http://localhost:3000/d/gitops-toolkit-control-plane](http://localhost:3000/d/gitops-toolkit-control-plane/gitops-toolkit-control-plane) Navigate to [http://localhost:3000/d/gitops-toolkit-control-plane](http://localhost:3000/d/gitops-toolkit-control-plane/gitops-toolkit-control-plane)
for the control plane dashboards: for the control plane dashboard:
![](../_files/cp-dashboard-p1.png) ![](../_files/cp-dashboard-p1.png)
![](../_files/cp-dashboard-p2.png) ![](../_files/cp-dashboard-p2.png)
Navigate to [http://localhost:3000/d/gitops-toolkit-cluster](http://localhost:3000/d/gitops-toolkit-cluster/gitops-toolkit-cluster-stats)
for the cluster reconciliation stats dashboard:
![](../_files/cluster-dashboard.png)
If you wish to use your own Prometheus and Grafana instances, then you can import the dashboards from If you wish to use your own Prometheus and Grafana instances, then you can import the dashboards from
[GitHub](https://github.com/fluxcd/toolkit/tree/main/manifests/monitoring/grafana/dashboards). [GitHub](https://github.com/fluxcd/toolkit/tree/main/manifests/monitoring/grafana/dashboards).

View File

@@ -65,4 +65,14 @@ The GitOps Toolkit is always looking for new contributors and there are a multit
- And if you are completely new to the GitOps Toolkit, take a look at our [Get Started guide](get-started/index.md) and give us feedback - And if you are completely new to the GitOps Toolkit, take a look at our [Get Started guide](get-started/index.md) and give us feedback
- Check out [how to contribute](contributing/index.md) to the project - Check out [how to contribute](contributing/index.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! We are looking forward to seeing you with us!

View File

@@ -43,7 +43,7 @@ Tasks
### Flux image update feature parity ### Flux image update feature parity
[= 0% "0%"] [= 30% "30%"]
Goals Goals
@@ -55,9 +55,9 @@ Non-Goals
Tasks Tasks
- [ ] [Design the image scanning and automation API](https://github.com/fluxcd/toolkit/discussions/107) - [x] <span style="color:grey">[Design the image scanning and automation API](https://github.com/fluxcd/toolkit/discussions/107)</span>
- [ ] Implement an image scanning controller - [ ] Implement an image scanning controller
- [ ] Design the automation component - [x] <span style="color:grey">Design the automation component</span>
- [ ] Implement the image scan/patch/push workflow - [ ] Implement the image scan/patch/push workflow
- [ ] Integrate the new components in the toolkit assembler - [ ] Integrate the new components in the toolkit assembler
- [ ] Create a migration guide from Flux annotations - [ ] Create a migration guide from Flux annotations

4
go.mod
View File

@@ -4,8 +4,8 @@ go 1.15
require ( require (
github.com/blang/semver/v4 v4.0.0 github.com/blang/semver/v4 v4.0.0
github.com/fluxcd/helm-controller/api v0.1.2 github.com/fluxcd/helm-controller/api v0.1.3
github.com/fluxcd/kustomize-controller/api v0.1.1 github.com/fluxcd/kustomize-controller/api v0.1.2
github.com/fluxcd/notification-controller/api v0.1.1 github.com/fluxcd/notification-controller/api v0.1.1
github.com/fluxcd/pkg/apis/meta v0.0.2 github.com/fluxcd/pkg/apis/meta v0.0.2
github.com/fluxcd/pkg/git v0.0.7 github.com/fluxcd/pkg/git v0.0.7

8
go.sum
View File

@@ -111,10 +111,10 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fluxcd/helm-controller/api v0.1.2 h1:gBky+nMpDaUT8mhLSaRkHEWczOvLR/JT6L5iRhu4CIs= github.com/fluxcd/helm-controller/api v0.1.3 h1:OztoSyxj5+2P38FRc9JXqnXP+f4eNQ0j199PRIqhnR4=
github.com/fluxcd/helm-controller/api v0.1.2/go.mod h1:eMkEzQrgDnOFa/iUey4VVjdqmPJFwcWb+3SFPDX9lJ0= github.com/fluxcd/helm-controller/api v0.1.3/go.mod h1:eMkEzQrgDnOFa/iUey4VVjdqmPJFwcWb+3SFPDX9lJ0=
github.com/fluxcd/kustomize-controller/api v0.1.1 h1:hg9koO2YD5VLetwT/Xsaq4MWJ5uXKdjKhx9xDFOJxmo= github.com/fluxcd/kustomize-controller/api v0.1.2 h1:gocCnhlqTjzZy7DbTdA2QezFMdudfIXv5RRAP4D5lfc=
github.com/fluxcd/kustomize-controller/api v0.1.1/go.mod h1:84YzQnJ2DShfIE842HYHqB48i0vhpZMJ9XQsdgOEkfM= github.com/fluxcd/kustomize-controller/api v0.1.2/go.mod h1:MztOqUKfKn/CBzRofBMq/DAOjzQSoDTmFdIKR32BEQg=
github.com/fluxcd/notification-controller/api v0.1.1 h1:tu6+bi28vfHoSp2MUD9h42SIvqY+YtEwS9toH9k7cRA= github.com/fluxcd/notification-controller/api v0.1.1 h1:tu6+bi28vfHoSp2MUD9h42SIvqY+YtEwS9toH9k7cRA=
github.com/fluxcd/notification-controller/api v0.1.1/go.mod h1:w1gILYTSqt3dFMYRmCihA/K84yDBfIkL5m5dcbaUyUY= github.com/fluxcd/notification-controller/api v0.1.1/go.mod h1:w1gILYTSqt3dFMYRmCihA/K84yDBfIkL5m5dcbaUyUY=
github.com/fluxcd/pkg/apis/meta v0.0.2 h1:kyA4Y0IzNjf1joBOnFqpWG7aNDHvtLExZcaHQM7qhRI= github.com/fluxcd/pkg/apis/meta v0.0.2 h1:kyA4Y0IzNjf1joBOnFqpWG7aNDHvtLExZcaHQM7qhRI=

View File

@@ -1,8 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- https://github.com/fluxcd/helm-controller/archive/v0.1.2.zip//helm-controller-0.1.2/config/crd - https://github.com/fluxcd/helm-controller/archive/v0.1.3.zip//helm-controller-0.1.3/config/crd
- https://github.com/fluxcd/helm-controller/archive/v0.1.2.zip//helm-controller-0.1.2/config/manager - https://github.com/fluxcd/helm-controller/archive/v0.1.3.zip//helm-controller-0.1.3/config/manager
patchesJson6902: patchesJson6902:
- target: - target:
group: apps group: apps

View File

@@ -1,8 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- https://github.com/fluxcd/kustomize-controller/archive/v0.1.1.zip//kustomize-controller-0.1.1/config/crd - https://github.com/fluxcd/kustomize-controller/archive/v0.1.2.zip//kustomize-controller-0.1.2/config/crd
- https://github.com/fluxcd/kustomize-controller/archive/v0.1.1.zip//kustomize-controller-0.1.1/config/manager - https://github.com/fluxcd/kustomize-controller/archive/v0.1.2.zip//kustomize-controller-0.1.2/config/manager
patchesJson6902: patchesJson6902:
- target: - target:
group: apps group: apps

View File

@@ -0,0 +1,905 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"iteration": 1602679512025,
"links": [],
"panels": [
{
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"decimals": 0,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "red",
"value": 100
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 0
},
"id": 24,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "value"
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": "count(gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"True\",kind=~\"Kustomization|HelmRelease\"})\n-\nsum(gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"Deleted\",kind=~\"Kustomization|HelmRelease\"})",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Cluster Reconcilers",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"decimals": 0,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 0
},
"id": 28,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "value"
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": "sum(gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"Kustomization|HelmRelease\"})",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Failing Reconcilers",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"decimals": 0,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "red",
"value": 100
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 0
},
"id": 29,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "value"
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": "count(gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"True\",kind=~\"GitRepository|HelmRepository|Bucket\"})\n-\nsum(gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"Deleted\",kind=~\"GitRepository|HelmRepository|Bucket\"})",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Kubernetes Manifests Sources",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"decimals": 0,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 0
},
"id": 30,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "value"
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": "sum(gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"GitRepository|HelmRepository|Bucket\"})",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Failing Sources",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "#EAB839",
"value": 1
},
{
"color": "red",
"value": 61
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 12,
"x": 0,
"y": 5
},
"id": 8,
"options": {
"displayMode": "gradient",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": " sum(rate(gotk_reconcile_duration_sum{namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_count{namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)",
"interval": "",
"legendFormat": "{{kind}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Reconciler ops avg. duration",
"type": "bargauge"
},
{
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "#EAB839",
"value": 1
},
{
"color": "red",
"value": 61
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 12,
"x": 12,
"y": 5
},
"id": 31,
"options": {
"displayMode": "gradient",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": " sum(rate(gotk_reconcile_duration_sum{namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_count{namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind)",
"interval": "",
"legendFormat": "{{kind}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Source ops avg. duration",
"type": "bargauge"
},
{
"collapsed": false,
"datasource": "${DS_PROMETHEUS}",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 9
},
"id": 15,
"panels": [],
"title": "Status",
"type": "row"
},
{
"datasource": null,
"description": "",
"fieldConfig": {
"defaults": {
"custom": {
"align": null,
"filterable": true
},
"mappings": [
{
"from": "",
"id": 1,
"text": "Ready",
"to": "",
"type": 1,
"value": "0"
},
{
"from": "",
"id": 2,
"text": "Not Ready",
"to": "",
"type": 1,
"value": "1"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "blue",
"value": 0
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Status"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 10
},
"id": 33,
"options": {
"showHeader": true
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": "gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"Kustomization|HelmRelease\"}",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Cluster reconciliation readiness ",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"__name__": true,
"app": true,
"instance": true,
"job": true,
"kubernetes_namespace": true,
"kubernetes_pod_name": true,
"namespace": true,
"pod_template_hash": true,
"status": true,
"type": true
},
"indexByName": {},
"renameByName": {
"Value": "Status",
"kind": "Kind",
"name": "Name"
}
}
}
],
"type": "table"
},
{
"datasource": null,
"description": "",
"fieldConfig": {
"defaults": {
"custom": {
"align": null,
"filterable": true
},
"mappings": [
{
"from": "",
"id": 1,
"text": "Ready",
"to": "",
"type": 1,
"value": "0"
},
{
"from": "",
"id": 2,
"text": "Not Ready",
"to": "",
"type": 1,
"value": "1"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "blue",
"value": 0
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Status"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 10
},
"id": 34,
"options": {
"showHeader": true
},
"pluginVersion": "7.2.1",
"targets": [
{
"expr": "gotk_reconcile_condition{namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"GitRepository|HelmRepository|Bucket\"}",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Source acquisition readiness ",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"__name__": true,
"app": true,
"instance": true,
"job": true,
"kubernetes_namespace": true,
"kubernetes_pod_name": true,
"namespace": true,
"pod_template_hash": true,
"status": true,
"type": true
},
"indexByName": {},
"renameByName": {
"Value": "Status",
"kind": "Kind",
"name": "Name"
}
}
}
],
"type": "table"
},
{
"collapsed": false,
"datasource": "${DS_PROMETHEUS}",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 18
},
"id": 17,
"panels": [],
"title": "Timing",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 19
},
"hiddenSeries": false,
"id": 27,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"hideEmpty": true,
"hideZero": true,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.1",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": " sum(rate(gotk_reconcile_duration_sum{namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_count{namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)",
"hide": false,
"interval": "",
"legendFormat": "{{kind}}/{{name}}",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Cluster reconciliation duration",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "s",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 27
},
"hiddenSeries": false,
"id": 35,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"hideEmpty": true,
"hideZero": true,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.1",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": " sum(rate(gotk_reconcile_duration_sum{namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_count{namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind, name)",
"hide": false,
"interval": "",
"legendFormat": "{{kind}}/{{name}}",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Source acquisition duration",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "s",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "10s",
"schemaVersion": 26,
"style": "light",
"tags": [
"gitops-toolkit"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "prometheus",
"value": "prometheus"
},
"hide": 2,
"includeAll": false,
"label": null,
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": "$DS_PROMETHEUS",
"definition": "gotk_reconcile_condition",
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "namespace",
"options": [],
"query": "gotk_reconcile_condition",
"refresh": 2,
"regex": "/.*namespace=\"([^\"]*).*/",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "GitOps Toolkit Cluster Stats",
"uid": "gitops-toolkit-cluster",
"version": 1
}

View File

@@ -10,4 +10,5 @@ configMapGenerator:
- name: grafana-dashboards - name: grafana-dashboards
files: files:
- dashboards/control-plane.json - dashboards/control-plane.json
- dashboards/cluster.json