Skip to content

Commit

Permalink
Add support for filtering names using regexps. Add colors.
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickdappollonio committed Sep 3, 2024
1 parent 2eead2a commit 1187fe9
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 95 deletions.
13 changes: 8 additions & 5 deletions cmd/civo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (

func getCivoCommand() *cobra.Command {
var (
nuke bool
region string
nuke bool
region string
nameFilter string
)

civoCmd := &cobra.Command{
Expand All @@ -24,12 +25,13 @@ func getCivoCommand() *cobra.Command {
Long: `clean civo resources`,
RunE: func(cmd *cobra.Command, _ []string) error {
quiet := cmd.Flags().Lookup("quiet").Value.String() == "true"
return runCivo(cmd.OutOrStderr(), region, os.Getenv("CIVO_TOKEN"), nuke, quiet)
return runCivo(cmd.OutOrStderr(), region, os.Getenv("CIVO_TOKEN"), nameFilter, nuke, quiet)
},
}

civoCmd.Flags().BoolVar(&nuke, "nuke", false, "required to confirm deletion of resources")
civoCmd.Flags().StringVar(&region, "region", "", "the civo region to clean")
civoCmd.Flags().StringVar(&nameFilter, "filter", "", "(regexp) if set, only resources with a name matching the regexp will be deleted")
err := civoCmd.MarkFlagRequired("region")
if err != nil {
log.Fatal(err)
Expand All @@ -38,9 +40,9 @@ func getCivoCommand() *cobra.Command {
return civoCmd
}

func runCivo(output io.Writer, region, token string, nuke, quiet bool) error {
func runCivo(output io.Writer, region, token, nameFilter string, nuke, quiet bool) error {
if token == "" {
return errors.New("required environment variable $CIVO_TOKEN not found")
return errors.New("required environment variable $CIVO_TOKEN not found: get one at https://dashboard.civo.com/security")
}

// Create a logger and make it quiet
Expand All @@ -54,6 +56,7 @@ func runCivo(output io.Writer, region, token string, nuke, quiet bool) error {
client, err := civo.New(
civo.WithToken(token),
civo.WithRegion(region),
civo.WithNameFilter(nameFilter),
civo.WithNuke(nuke),
civo.WithLogger(log),
)
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
require (
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/goccy/go-json v0.10.3 // indirect
Expand All @@ -27,6 +28,8 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.74 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down Expand Up @@ -162,6 +164,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
Expand Down Expand Up @@ -324,7 +327,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
Expand Down
27 changes: 21 additions & 6 deletions internal/civo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import (
"context"
"errors"
"fmt"
"regexp"

"github.com/civo/civogo"
"github.com/konstructio/dropkick/internal/logger"
)

// Civo is a client for the Civo API.
type Civo struct {
client *civogo.Client // The underlying Civo API client.
context context.Context // The context for API requests.
nuke bool // Whether to nuke resources.
region string // The region for API requests.
token string // The API token.
logger *logger.Logger // The logger instance.
client *civogo.Client // The underlying Civo API client.
context context.Context // The context for API requests.
nuke bool // Whether to nuke resources.
region string // The region for API requests.
nameFilter *regexp.Regexp // If set, only resources with a name matching the regexp will be deleted.
token string // The API token.
logger *logger.Logger // The logger instance.
}

// Option is a function that configures a Civo.
Expand Down Expand Up @@ -62,6 +64,19 @@ func WithContext(ctx context.Context) Option {
}
}

// WithNameFilter sets the name filter for a Civo.
func WithNameFilter(nameFilter string) Option {
return func(c *Civo) error {
reFilter, err := regexp.Compile(nameFilter)
if err != nil {
return fmt.Errorf("unable to compile name filter regexp %q: %w", nameFilter, err)
}

c.nameFilter = reFilter
return nil
}
}

// New creates a new Civo with the given options.
// It returns an error if the token or region is not set, or if it fails to create the underlying Civo API client.
func New(opts ...Option) (*Civo, error) {
Expand Down
66 changes: 45 additions & 21 deletions internal/civo/kubernetes.go
Original file line number Diff line number Diff line change
@@ -1,84 +1,108 @@
package civo

import (
"errors"
"fmt"

"github.com/civo/civogo"
"github.com/konstructio/dropkick/internal/outputwriter"
)

// NukeKubernetesClusters deletes all Kubernetes clusters associated with the Civo client.
// It returns an error if the deletion process encounters any issues.
func (c *Civo) NukeKubernetesClusters() error {
c.logger.Printf("listing Kubernetes clusters")
c.logger.Infof("listing Kubernetes clusters")

clusters, err := c.client.ListKubernetesClusters()
if err != nil {
return fmt.Errorf("unable to list Kubernetes clusters: %w", err)
}

c.logger.Printf("found %d clusters", len(clusters.Items))
c.logger.Infof("found %d clusters", len(clusters.Items))

for _, cluster := range clusters.Items {
c.logger.Printf("found cluster: name: %q - ID: %q", cluster.Name, cluster.ID)
c.logger.Infof("found cluster: name: %q - ID: %q", cluster.Name, cluster.ID)

if c.nameFilter != nil && !c.nameFilter.MatchString(cluster.Name) {
c.logger.Warnf("skipping cluster %q: name does not match filter", cluster.Name)
continue
}

clusterVolumes, err := c.client.ListVolumesForCluster(cluster.ID)
if err != nil {
return fmt.Errorf("unable to list volumes for cluster %q: %w", cluster.ID, err)
return fmt.Errorf("unable to list volumes for cluster %q: %w", cluster.Name, err)
}

for _, volume := range clusterVolumes {
c.logger.Infof("found volume: name: %q - ID: %q", volume.Name, volume.ID)

if c.nameFilter != nil && !c.nameFilter.MatchString(volume.Name) {
c.logger.Warnf("skipping volume %q: name does not match filter", volume.Name)
continue
}

if c.nuke {
c.logger.Printf("deleting volume %q for cluster %q", volume.ID, cluster.ID)
c.logger.Infof("deleting volume %q for cluster %q", volume.Name, cluster.Name)
res, err := c.client.DeleteVolume(volume.ID)
if err != nil {
return fmt.Errorf("unable to delete cluster %q volume %q: %w", cluster.ID, volume.ID, err)
return fmt.Errorf("unable to delete cluster %q volume %q: %w", cluster.Name, volume.Name, err)
}

if res.ErrorCode != "200" {
return fmt.Errorf("Civo returned an error code %s when deleting volume %q: %s", res.ErrorCode, volume.ID, res.ErrorDetails)
return fmt.Errorf("Civo returned an error code %q when deleting volume %q: %s", res.ErrorCode, volume.Name, res.ErrorDetails)
}

outputwriter.WriteStdoutf("deleted volume %q for cluster %q", volume.ID, cluster.ID)
outputwriter.WriteStdoutf("deleted volume %q for cluster %q", volume.Name, cluster.Name)
} else {
c.logger.Printf("refusing to delete volume %q for cluster %q: nuke is not enabled", volume.ID, cluster.ID)
c.logger.Warnf("refusing to delete volume %q for cluster %q: nuke is not enabled", volume.Name, cluster.Name)
}
}

if c.nuke {
c.logger.Printf("deleting cluster %q", cluster.ID)
c.logger.Infof("deleting cluster %q", cluster.Name)
res, err := c.client.DeleteKubernetesCluster(cluster.ID)
if err != nil {
return fmt.Errorf("unable to delete cluster %q: %w", cluster.ID, err)
return fmt.Errorf("unable to delete cluster %q: %w", cluster.Name, err)
}

if res.ErrorCode != "200" {
return fmt.Errorf("Civo returned an error code %s when deleting cluster %q: %s", res.ErrorCode, cluster.ID, res.ErrorDetails)
return fmt.Errorf("Civo returned an error code %q when deleting cluster %q: %s", res.ErrorCode, cluster.Name, res.ErrorDetails)
}

outputwriter.WriteStdoutf("deleted cluster %q", cluster.ID)
outputwriter.WriteStdoutf("deleted cluster %q", cluster.Name)
} else {
c.logger.Printf("refusing to delete cluster %q: nuke is not enabled", cluster.ID)
c.logger.Warnf("refusing to delete cluster %q: nuke is not enabled", cluster.Name)
}

network, err := c.client.FindNetwork(cluster.Name)
network, err := c.client.FindNetwork(cluster.ID)
if err != nil {
return fmt.Errorf("unable to find network for cluster %q: %w", cluster.ID, err)
if errors.Is(err, civogo.ZeroMatchesError) {
c.logger.Warnf("no network found for cluster %q", cluster.Name)
continue
}

return fmt.Errorf("unable to find network for cluster %q: %w", cluster.Name, err)
}

if c.nameFilter != nil && !c.nameFilter.MatchString(network.Name) {
c.logger.Warnf("skipping network %q: name does not match filter", network.Name)
continue
}

if c.nuke {
c.logger.Printf("deleting network %q", network.ID)
c.logger.Infof("deleting network %q", network.Name)
res, err := c.client.DeleteNetwork(network.ID)
if err != nil {
return fmt.Errorf("unable to delete cluster network %q: %w", cluster.ID, err)
return fmt.Errorf("unable to delete cluster network %q: %w", cluster.Name, err)
}

if res.ErrorCode != "200" {
return fmt.Errorf("Civo returned an error code %s when deleting network %q: %s", res.ErrorCode, network.ID, res.ErrorDetails)
return fmt.Errorf("Civo returned an error code %q when deleting network %q: %s", res.ErrorCode, network.Name, res.ErrorDetails)
}

outputwriter.WriteStdoutf("deleted network %q", network.ID)
outputwriter.WriteStdoutf("deleted network %q", network.Name)
} else {
c.logger.Printf("refusing to delete network %q: nuke is not enabled", network.ID)
c.logger.Warnf("refusing to delete network %q: nuke is not enabled", network.Name)
}
}

Expand Down
21 changes: 13 additions & 8 deletions internal/civo/networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,37 @@ import (
// NukeNetworks deletes all networks associated with the Civo client.
// It returns an error if the deletion process encounters any issues.
func (c *Civo) NukeNetworks() error {
c.logger.Printf("listing networks")
c.logger.Infof("listing networks")

networks, err := c.client.ListNetworks()
if err != nil {
return fmt.Errorf("unable to list networks: %w", err)
}

c.logger.Printf("found %d networks", len(networks))
c.logger.Infof("found %d networks", len(networks))

for _, network := range networks {
c.logger.Printf("found network: name: %q - ID: %q", network.Name, network.ID)
c.logger.Infof("found network: name: %q - ID: %q", network.Name, network.ID)

if c.nameFilter != nil && !c.nameFilter.MatchString(network.Name) {
c.logger.Warnf("skipping network %q: name does not match filter", network.Name)
continue
}

if c.nuke {
c.logger.Printf("deleting network %q", network.ID)
c.logger.Infof("deleting network %q", network.Name)
res, err := c.client.DeleteNetwork(network.ID)
if err != nil {
return fmt.Errorf("unable to delete network %q: %w", network.ID, err)
return fmt.Errorf("unable to delete network %q: %w", network.Name, err)
}

if res.ErrorCode != "200" {
return fmt.Errorf("Civo returned an error code %s when deleting network %q: %s", res.ErrorCode, network.ID, res.ErrorDetails)
return fmt.Errorf("Civo returned an error code %q when deleting network %q: %s", res.ErrorCode, network.Name, res.ErrorDetails)
}

outputwriter.WriteStdoutf("deleted network %q", network.ID)
outputwriter.WriteStdoutf("deleted network %q", network.Name)
} else {
c.logger.Printf("refusing to delete network %q: nuke is not enabled", network.ID)
c.logger.Warnf("refusing to delete network %q: nuke is not enabled", network.Name)
}
}

Expand Down
Loading

0 comments on commit 1187fe9

Please sign in to comment.