Skip to content

Commit

Permalink
Delete cmd (#295)
Browse files Browse the repository at this point in the history
* Command outline
* Add RBAC
* Delete things
* Namespace bugfixes
* Handle errors we can handle, pass on errors that aren't expected
* Add --all flag for deleting e2e tests

Also break delete() up with helper functions

Signed-off-by: liz <liz@heptio.com>
  • Loading branch information
liztio authored Feb 28, 2018
1 parent 0fa9777 commit b1fc2fb
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
8 changes: 8 additions & 0 deletions cmd/sonobuoy/app/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,11 @@ func AddSkipPreflightFlag(flag *bool, cmd *cobra.Command) {
"If true, skip all checks before kicking off the sonobuoy run.",
)
}

// AddDeleteAllFlag adds a boolean flag for deleting everything (including E2E tests).
func AddDeleteAllFlag(flag *bool, cmd *cobra.Command) {
cmd.PersistentFlags().BoolVar(
flag, "all", false,
"In addition to deleting Sonobuoy namespaces, also clean up dangling e2e- namespaces.",
)
}
77 changes: 77 additions & 0 deletions cmd/sonobuoy/app/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright 2018 Heptio Inc.
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 app

import (
"os"

"github.com/heptio/sonobuoy/pkg/client"
"github.com/heptio/sonobuoy/pkg/errlog"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
)

var deleteopts client.DeleteConfig

var deleteFlags struct {
kubeconfig Kubeconfig
rbacMode RBACMode
}

func init() {
cmd := &cobra.Command{
Use: "delete",
Short: "cleans up a sonobuoy run",
Run: deleteSonobuoyRun,
Args: cobra.ExactArgs(0),
}

AddKubeconfigFlag(&deleteFlags.kubeconfig, cmd)
AddNamespaceFlag(&deleteopts.Namespace, cmd)
AddRBACModeFlags(&deleteFlags.rbacMode, cmd, DetectRBACMode)
AddDeleteAllFlag(&deleteopts.DeleteAll, cmd)

RootCmd.AddCommand(cmd)
}

func deleteSonobuoyRun(cmd *cobra.Command, args []string) {
cfg, err := deleteFlags.kubeconfig.Get()
if err != nil {
errlog.LogError(errors.Wrap(err, "couldn't get kubernetes config"))
os.Exit(1)
}

kubeclient, err := kubernetes.NewForConfig(cfg)
if err != nil {
errlog.LogError(errors.Wrap(err, "couldn't get kubernetes client"))
os.Exit(1)
}

rbacEnabled, err := deleteFlags.rbacMode.Enabled(kubeclient)
if err != nil {
errlog.LogError(errors.Wrap(err, "couldn't detect RBAC status"))
os.Exit(1)
}
deleteopts.EnableRBAC = rbacEnabled

if err := client.NewSonobuoyClient().Delete(&deleteopts, kubeclient); err != nil {
errlog.LogError(errors.Wrap(err, "failed to delete sonobuoy resources"))
os.Exit(1)
}

}
132 changes: 132 additions & 0 deletions pkg/client/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright 2018 Heptio Inc.
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 client

import (
"strings"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kubeerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

const (
clusterRoleFieldName = "component"
clusterRoleFieldValue = "sonobuoy"

e2eNamespacePrefix = "e2e-"
)

func (c *SonobuoyClient) Delete(cfg *DeleteConfig, client kubernetes.Interface) error {
if err := cleanupNamespace(cfg.Namespace, client); err != nil {
return err
}

if cfg.EnableRBAC {
if err := deleteRBAC(client); err != nil {
return err
}
}

if cfg.DeleteAll {
if err := cleanupE2E(client); err != nil {
return err
}
}
return nil
}

func cleanupNamespace(namespace string, client kubernetes.Interface) error {
// Delete the namespace
log := logrus.WithFields(logrus.Fields{
"kind": "namespace",
"namespace": namespace,
})

err := client.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{})
if err := logDelete(log, err); err != nil {
return errors.Wrap(err, "couldn't delete namespace")
}

return nil
}

func deleteRBAC(client kubernetes.Interface) error {
// ClusterRole and ClusterRoleBindings aren't namespaced, so delete them seperately
selector := metav1.AddLabelToSelector(
&metav1.LabelSelector{},
clusterRoleFieldName,
clusterRoleFieldValue,
)

deleteOpts := &metav1.DeleteOptions{}
listOpts := metav1.ListOptions{
LabelSelector: metav1.FormatLabelSelector(selector),
}

err := client.RbacV1().ClusterRoleBindings().DeleteCollection(deleteOpts, listOpts)
if err := logDelete(logrus.WithField("kind", "clusterrolebindings"), err); err != nil {
return errors.Wrap(err, "failed to delete cluster role binding")
}

// ClusterRole and ClusterRole bindings aren't namespaced, so delete them manually
err = client.RbacV1().ClusterRoles().DeleteCollection(deleteOpts, listOpts)
if err := logDelete(logrus.WithField("kind", "clusterroles"), err); err != nil {
return errors.Wrap(err, "failed to delete cluster role")
}

return nil
}

func cleanupE2E(client kubernetes.Interface) error {
// Delete any dangling E2E namespaces
namespaces, err := client.CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {
return errors.Wrap(err, "failed to list namespaces")
}

for _, namespace := range namespaces.Items {
if strings.HasPrefix(namespace.Name, e2eNamespacePrefix) {

log := logrus.WithFields(logrus.Fields{
"kind": "namespace",
"namespace": namespace.Name,
})
err := client.CoreV1().Namespaces().Delete(namespace.Name, &metav1.DeleteOptions{})
if err := logDelete(log, err); err != nil {
return errors.Wrap(err, "couldn't delete namespace")
}
}
}
return nil
}

func logDelete(log logrus.FieldLogger, err error) error {
switch {
case err == nil:
log.Info("deleted")
case kubeerror.IsNotFound(err):
log.Info("already deleted")
case kubeerror.IsConflict(err):
log.WithError(err).Info("delete in progress")
case err != nil:
return err
}
return nil
}
9 changes: 9 additions & 0 deletions pkg/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ type RunConfig struct {
SkipPreflight bool
}

// DeleteConfig are the input options for cleaning up a Sonobuoy run.
type DeleteConfig struct {
Namespace string
EnableRBAC bool
DeleteAll bool
}

// RetrieveConfig are the options passed to RetrieveResults.
type RetrieveConfig struct {
// CmdErr is the place to write errors to.
Expand Down Expand Up @@ -91,4 +98,6 @@ type Interface interface {
GetStatus(namespace string, client kubernetes.Interface) (*aggregation.Status, error)
// GetLogs streams logs from the sonobuoy pod by default to stdout.
GetLogs(cfg *LogConfig, client kubernetes.Interface) error
// Delete removes a sonobuoy run, namespace, and all associated resources.
Delete(cfg *DeleteConfig, client kubernetes.Interface) error
}

0 comments on commit b1fc2fb

Please sign in to comment.