From 8dc5db17ac8031aa287cd3d229f7cef56cc3ee80 Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Tue, 14 Jul 2020 14:00:59 +0300 Subject: [PATCH] Add helm suspend/resume commands --- cmd/tk/reconcile_helmrelease.go | 30 ++++++- cmd/tk/reconcile_source_helm.go | 36 ++++++++- cmd/tk/resume_helmrelease.go | 126 +++++++++++++++++++++++++++++ cmd/tk/suspend_helmrelease.go | 73 +++++++++++++++++ docs/cmd/tk_resume.md | 1 + docs/cmd/tk_resume_helmrelease.md | 32 ++++++++ docs/cmd/tk_suspend.md | 1 + docs/cmd/tk_suspend_helmrelease.md | 31 +++++++ mkdocs.yml | 2 + 9 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 cmd/tk/resume_helmrelease.go create mode 100644 cmd/tk/suspend_helmrelease.go create mode 100644 docs/cmd/tk_resume_helmrelease.md create mode 100644 docs/cmd/tk_suspend_helmrelease.md diff --git a/cmd/tk/reconcile_helmrelease.go b/cmd/tk/reconcile_helmrelease.go index d8129500..6ac4769b 100644 --- a/cmd/tk/reconcile_helmrelease.go +++ b/cmd/tk/reconcile_helmrelease.go @@ -22,8 +22,10 @@ import ( "time" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1" ) @@ -100,7 +102,7 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error { logger.Waitingf("waiting for HelmRelease reconciliation") if err := wait.PollImmediate(pollInterval, timeout, - isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil { + isHelmReleaseReady(ctx, kubeClient, name, namespace)); err != nil { return err } @@ -118,3 +120,29 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error { } return 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 { + return false, err + } + + for _, condition := range helmRelease.Status.Conditions { + if condition.Type == helmv2.ReadyCondition { + if condition.Status == corev1.ConditionTrue { + return true, nil + } else if condition.Status == corev1.ConditionFalse { + return false, fmt.Errorf(condition.Message) + } + } + } + return false, nil + } +} diff --git a/cmd/tk/reconcile_source_helm.go b/cmd/tk/reconcile_source_helm.go index 21ef3123..e5fa9662 100644 --- a/cmd/tk/reconcile_source_helm.go +++ b/cmd/tk/reconcile_source_helm.go @@ -19,11 +19,15 @@ package main import ( "context" "fmt" - sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1" + "time" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" - "time" + "sigs.k8s.io/controller-runtime/pkg/client" + + sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1" ) var reconcileSourceHelmCmd = &cobra.Command{ @@ -80,7 +84,7 @@ func syncSourceHelmCmdRun(cmd *cobra.Command, args []string) error { logger.Waitingf("waiting for reconciliation") if err := wait.PollImmediate(pollInterval, timeout, - isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil { + isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil { return err } @@ -98,3 +102,29 @@ func syncSourceHelmCmdRun(cmd *cobra.Command, args []string) error { } return nil } + +func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc { + return func() (bool, error) { + var helmRepository sourcev1.HelmRepository + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + + err := kubeClient.Get(ctx, namespacedName, &helmRepository) + if err != nil { + return false, err + } + + for _, condition := range helmRepository.Status.Conditions { + if condition.Type == sourcev1.ReadyCondition { + if condition.Status == corev1.ConditionTrue { + return true, nil + } else if condition.Status == corev1.ConditionFalse { + return false, fmt.Errorf(condition.Message) + } + } + } + return false, nil + } +} diff --git a/cmd/tk/resume_helmrelease.go b/cmd/tk/resume_helmrelease.go new file mode 100644 index 00000000..ce588b5e --- /dev/null +++ b/cmd/tk/resume_helmrelease.go @@ -0,0 +1,126 @@ +/* +Copyright 2020 The Flux CD contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1" +) + +var resumeHrCmd = &cobra.Command{ + Use: "helmrelease [name]", + Aliases: []string{"hr"}, + Short: "Resume a suspended HelmRelease", + Long: `The resume command marks a previously suspended HelmRelease resource for reconciliation and waits for it to +finish the apply.`, + RunE: resumeHrCmdRun, +} + +func init() { + resumeCmd.AddCommand(resumeHrCmd) +} + +func resumeHrCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("HelmRelease name is required") + } + name := args[0] + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.kubeClient(kubeconfig) + if err != nil { + return err + } + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + var helmRelease helmv2.HelmRelease + err = kubeClient.Get(ctx, namespacedName, &helmRelease) + if err != nil { + return err + } + + logger.Actionf("resuming HelmRelease %s in %s namespace", name, namespace) + helmRelease.Spec.Suspend = false + if err := kubeClient.Update(ctx, &helmRelease); err != nil { + return err + } + logger.Successf("HelmRelease resumed") + + logger.Waitingf("waiting for HelmRelease reconciliation") + if err := wait.PollImmediate(pollInterval, timeout, + isHelmReleaseResumed(ctx, kubeClient, name, namespace)); err != nil { + return err + } + + logger.Successf("HelmRelease reconciliation completed") + + err = kubeClient.Get(ctx, namespacedName, &helmRelease) + if err != nil { + return err + } + + if helmRelease.Status.LastAppliedRevision != "" { + logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision) + } else { + return fmt.Errorf("HelmRelease reconciliation failed") + } + + return nil +} + +func isHelmReleaseResumed(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 { + return false, err + } + + for _, condition := range helmRelease.Status.Conditions { + if condition.Type == helmv2.ReadyCondition { + if condition.Status == corev1.ConditionTrue { + return true, nil + } else if condition.Status == corev1.ConditionFalse { + if condition.Reason == helmv2.SuspendedReason { + return false, nil + } + + return false, fmt.Errorf(condition.Message) + } + } + } + return false, nil + } +} diff --git a/cmd/tk/suspend_helmrelease.go b/cmd/tk/suspend_helmrelease.go new file mode 100644 index 00000000..eafeab62 --- /dev/null +++ b/cmd/tk/suspend_helmrelease.go @@ -0,0 +1,73 @@ +/* +Copyright 2020 The Flux CD contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/types" + + helmv2 "github.com/fluxcd/helm-controller/api/v2alpha1" +) + +var suspendHrCmd = &cobra.Command{ + Use: "helmrelease [name]", + Aliases: []string{"hr"}, + Short: "Suspend reconciliation of HelmRelease", + Long: "The suspend command disables the reconciliation of a HelmRelease resource.", + RunE: suspendHrCmdRun, +} + +func init() { + suspendCmd.AddCommand(suspendHrCmd) +} + +func suspendHrCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("HelmRelease name is required") + } + name := args[0] + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.kubeClient(kubeconfig) + if err != nil { + return err + } + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + var helmRelease helmv2.HelmRelease + err = kubeClient.Get(ctx, namespacedName, &helmRelease) + if err != nil { + return err + } + + logger.Actionf("suspending HelmRelease %s in %s namespace", name, namespace) + helmRelease.Spec.Suspend = true + if err := kubeClient.Update(ctx, &helmRelease); err != nil { + return err + } + logger.Successf("HelmRelease suspended") + + return nil +} diff --git a/docs/cmd/tk_resume.md b/docs/cmd/tk_resume.md index e6aac38f..f7567b2f 100644 --- a/docs/cmd/tk_resume.md +++ b/docs/cmd/tk_resume.md @@ -24,5 +24,6 @@ The resume sub-commands resume a suspended resource. ### SEE ALSO * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines +* [tk resume helmrelease](tk_resume_helmrelease.md) - Resume a suspended HelmRelease * [tk resume kustomization](tk_resume_kustomization.md) - Resume a suspended Kustomization diff --git a/docs/cmd/tk_resume_helmrelease.md b/docs/cmd/tk_resume_helmrelease.md new file mode 100644 index 00000000..7d526984 --- /dev/null +++ b/docs/cmd/tk_resume_helmrelease.md @@ -0,0 +1,32 @@ +## tk resume helmrelease + +Resume a suspended HelmRelease + +### Synopsis + +The resume command marks a previously suspended HelmRelease resource for reconciliation and waits for it to +finish the apply. + +``` +tk resume helmrelease [name] [flags] +``` + +### Options + +``` + -h, --help help for helmrelease +``` + +### Options inherited from parent commands + +``` + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + --namespace string the namespace scope for this operation (default "gitops-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [tk resume](tk_resume.md) - Resume suspended resources + diff --git a/docs/cmd/tk_suspend.md b/docs/cmd/tk_suspend.md index 1cfd236a..b192ef0e 100644 --- a/docs/cmd/tk_suspend.md +++ b/docs/cmd/tk_suspend.md @@ -24,5 +24,6 @@ The suspend sub-commands suspend the reconciliation of a resource. ### SEE ALSO * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines +* [tk suspend helmrelease](tk_suspend_helmrelease.md) - Suspend reconciliation of HelmRelease * [tk suspend kustomization](tk_suspend_kustomization.md) - Suspend reconciliation of Kustomization diff --git a/docs/cmd/tk_suspend_helmrelease.md b/docs/cmd/tk_suspend_helmrelease.md new file mode 100644 index 00000000..45a2f11a --- /dev/null +++ b/docs/cmd/tk_suspend_helmrelease.md @@ -0,0 +1,31 @@ +## tk suspend helmrelease + +Suspend reconciliation of HelmRelease + +### Synopsis + +The suspend command disables the reconciliation of a HelmRelease resource. + +``` +tk suspend helmrelease [name] [flags] +``` + +### Options + +``` + -h, --help help for helmrelease +``` + +### Options inherited from parent commands + +``` + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + --namespace string the namespace scope for this operation (default "gitops-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [tk suspend](tk_suspend.md) - Suspend resources + diff --git a/mkdocs.yml b/mkdocs.yml index 916dabcb..a536311c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -88,8 +88,10 @@ nav: - Install: cmd/tk_install.md - Resume: cmd/tk_resume.md - Resume kustomization: cmd/tk_resume_kustomization.md + - Resume helmrelease: cmd/tk_resume_helmrelease.md - Suspend: cmd/tk_suspend.md - Suspend kustomization: cmd/tk_suspend_kustomization.md + - Suspend helmrelease: cmd/tk_suspend_helmrelease.md - Reconcile: cmd/tk_reconcile.md - Reconcile kustomization: cmd/tk_reconcile_kustomization.md - Reconcile helmrelease: cmd/tk_reconcile_helmrelease.md