-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from appuio/cleanup-ns
Cleanup empty namespaces
- Loading branch information
Showing
21 changed files
with
1,120 additions
and
100 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package cmd | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/appuio/seiso/cfg" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func Test_validateNamespaceCommandInput(t *testing.T) { | ||
type args struct { | ||
args []string | ||
config cfg.Configuration | ||
} | ||
tests := map[string]struct { | ||
name string | ||
input args | ||
wantErr bool | ||
}{ | ||
"ShouldThrowError_IfNoLabelSelector": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
DeleteAfter: "1s", | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
"ShouldThrowError_InvalidLabelSelector": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
DeleteAfter: "1s", | ||
Labels: []string{"invalid"}, | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
"ShouldThrowError_IfInvalidDeleteAfterFlag": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
Labels: []string{"some=label"}, | ||
DeleteAfter: "invalid", | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
"ShouldThrowError_IfNegativeDeleteAfterFlag": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
Labels: []string{"some=label"}, | ||
DeleteAfter: "-1s", | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
"Success_IfValidDeleteAfterFlag1d": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
Labels: []string{"some=label"}, | ||
DeleteAfter: "1d", | ||
}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
"Success_IfValidDeleteAfterFlag1d1y": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
Labels: []string{"some=label"}, | ||
DeleteAfter: "1d1y", | ||
}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
"Success_IfValidDeleteAfterFlag1w1m": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
Labels: []string{"some=label"}, | ||
DeleteAfter: "1w1m", | ||
}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
"Success_IfValidDeleteAfterFlag1h1s": { | ||
input: args{ | ||
config: cfg.Configuration{ | ||
Resource: cfg.ResourceConfig{ | ||
Labels: []string{"some=label"}, | ||
DeleteAfter: "1h1s", | ||
}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
config = &tt.input.config | ||
err := validateNsCommandInput(&cobra.Command{}, tt.input.args) | ||
if tt.wantErr { | ||
assert.Error(t, err) | ||
return | ||
} | ||
assert.NoError(t, err) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/appuio/seiso/cfg" | ||
"github.com/appuio/seiso/pkg/kubernetes" | ||
"github.com/appuio/seiso/pkg/namespace" | ||
log "github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
const ( | ||
nsCommandLongDescription = `Sometimes Namespaces are left empty in a Kubernetes cluster. | ||
This command deletes Namespaces that are not being used anymore. | ||
A Namespace is deemed empty if no Helm releases, Pods, Deployments, StatefulSets or DaemonSets can be found.` | ||
) | ||
|
||
var ( | ||
nsCmd = &cobra.Command{ | ||
Use: "namespaces", | ||
Short: "Cleans up your empty Namespaces", | ||
Long: nsCommandLongDescription, | ||
Aliases: []string{"namespace", "ns"}, | ||
SilenceUsage: true, | ||
PreRunE: validateNsCommandInput, | ||
RunE: executeNsCleanupCommand, | ||
} | ||
) | ||
|
||
func init() { | ||
rootCmd.AddCommand(nsCmd) | ||
defaults := cfg.NewDefaultConfig() | ||
|
||
nsCmd.PersistentFlags().BoolP("delete", "d", defaults.Delete, "Effectively delete Namespaces found") | ||
nsCmd.PersistentFlags().StringSliceP("label", "l", defaults.Resource.Labels, | ||
"Identify the Namespaces by these \"key=value\" labels") | ||
nsCmd.PersistentFlags().String("delete-after", defaults.Resource.DeleteAfter, | ||
"Only delete Namespaces after they were empty for this duration, e.g. [1y2mo3w4d5h6m7s]") | ||
} | ||
|
||
func validateNsCommandInput(cmd *cobra.Command, _ []string) (returnErr error) { | ||
defer showUsageOnError(cmd, returnErr) | ||
if len(config.Resource.Labels) == 0 { | ||
return missingLabelSelectorError(config.Namespace, "namespaces") | ||
} | ||
for _, label := range config.Resource.Labels { | ||
if !strings.Contains(label, "=") { | ||
return fmt.Errorf("incorrect label format does not match expected \"key=value\" format: %s", label) | ||
} | ||
} | ||
if _, err := parseCutOffDateTime(config.Resource.DeleteAfter); err != nil { | ||
return fmt.Errorf("could not parse delete-after flag %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
func executeNsCleanupCommand(_ *cobra.Command, _ []string) error { | ||
coreClient, err := kubernetes.NewCoreV1Client() | ||
if err != nil { | ||
return fmt.Errorf("cannot initiate kubernetes client: %w", err) | ||
} | ||
|
||
dynamicClient, err := kubernetes.NewDynamicClient() | ||
if err != nil { | ||
return fmt.Errorf("cannot initiate kubernetes dynamic client: %w", err) | ||
} | ||
|
||
ctx := context.Background() | ||
c := config.Resource | ||
service := namespace.NewNamespacesService( | ||
coreClient.Namespaces(), | ||
dynamicClient, | ||
namespace.ServiceConfiguration{ | ||
Batch: config.Log.Batch, | ||
}) | ||
|
||
log.Debug("Getting Namespaces") | ||
allNamespaces, err := service.List(ctx, toListOptions(c.Labels)) | ||
if err != nil { | ||
return fmt.Errorf("could not retrieve Namespaces with labels %q: %w", c.Labels, err) | ||
} | ||
|
||
emptyNamespaces, err := service.GetEmptyFor(ctx, allNamespaces, c.DeleteAfter) | ||
if err != nil { | ||
return fmt.Errorf("could not retrieve empty namespaces %w", err) | ||
} | ||
|
||
if config.Delete { | ||
err := service.Delete(ctx, emptyNamespaces) | ||
if err != nil { | ||
return fmt.Errorf("could not delete Namespaces %w", err) | ||
} | ||
} else { | ||
log.WithFields(log.Fields{ | ||
"delete_after": c.DeleteAfter, | ||
}).Info("Showing results") | ||
service.Print(emptyNamespaces) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.