Add alert provider commands

pull/302/head
Philip Laine 4 years ago committed by Philip Laine
parent 54b35b7c2b
commit f7971a871a

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

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

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

@ -0,0 +1,83 @@
/*
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"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
)
var getAlertProviderCmd = &cobra.Command{
Use: "alert-provider",
Aliases: []string{"ap"},
Short: "Get Provider statuses",
Long: "The get alert-provider command prints the statuses of the resources.",
Example: ` # List all Providers and their status
gotk get alert-provider
`,
RunE: getAlertProviderCmdRun,
}
func init() {
getCmd.AddCommand(getAlertProviderCmd)
}
func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.kubeClient(kubeconfig)
if err != nil {
return err
}
var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no providers found in %s namespace", namespace)
return nil
}
for _, provider := range list.Items {
isInitialized := false
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case corev1.ConditionTrue:
logger.Successf("%s is ready", provider.GetName())
case corev1.ConditionUnknown:
logger.Successf("%s reconciling", provider.GetName())
default:
logger.Failuref("%s %s", provider.GetName(), c.Message)
}
isInitialized = true
}
if !isInitialized {
logger.Failuref("%s is not ready", provider.GetName())
}
}
return nil
}

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

@ -43,6 +43,7 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/runtime/dependency"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/olekukonko/tablewriter"
@ -137,6 +138,7 @@ func (*Utils) kubeClient(kubeConfigPath string) (client.Client, error) {
_ = sourcev1.AddToScheme(scheme)
_ = kustomizev1.AddToScheme(scheme)
_ = helmv2.AddToScheme(scheme)
_ = notificationv1.AddToScheme(scheme)
kubeClient, err := client.New(cfg, client.Options{
Scheme: scheme,

@ -6,6 +6,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/fluxcd/helm-controller/api v0.1.1
github.com/fluxcd/kustomize-controller/api v0.1.0
github.com/fluxcd/notification-controller/api v0.1.0
github.com/fluxcd/pkg/apis/meta v0.0.2
github.com/fluxcd/pkg/git v0.0.7
github.com/fluxcd/pkg/runtime v0.0.6

@ -115,6 +115,8 @@ github.com/fluxcd/helm-controller/api v0.1.1 h1:iKskkLGRYRi5hiZg/+Rn+rpneGPayGQP
github.com/fluxcd/helm-controller/api v0.1.1/go.mod h1:orwdS+iYGcM8BReUQfIb5CJ+jiFdlKmnLnzp6K3FK2U=
github.com/fluxcd/kustomize-controller/api v0.1.0 h1:dPowX408q0jO7wnWBj5Dglc22euAQBLxDhPS8XHlLM0=
github.com/fluxcd/kustomize-controller/api v0.1.0/go.mod h1:upR7/OzX/wXJlKgiBLUn7ez4XG4Lo5edep2WKSx0u7c=
github.com/fluxcd/notification-controller/api v0.1.0 h1:+gJ0CFFg3OkjLGl48gBCVgqNbKNy54xzfjYVlPp8064=
github.com/fluxcd/notification-controller/api v0.1.0/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/go.mod h1:nCNps5JJOcEQr3MNDmZqI4o0chjePSUYL6Q2ktDtotU=
github.com/fluxcd/pkg/git v0.0.7 h1:tFSYPy7tcIYfOt8H5EUERXIRz7fk0id302oQZde1NtU=

Loading…
Cancel
Save