Skip to content

Commit

Permalink
Merge pull request #4 from konstructio/improve-current-flow
Browse files Browse the repository at this point in the history
Improve and allow extendability of current CLI.
  • Loading branch information
patrickdappollonio authored Aug 12, 2024
2 parents cd4f936 + 389de66 commit 28170c5
Show file tree
Hide file tree
Showing 23 changed files with 1,421 additions and 234 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/testing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Go unit tests

on: [push, workflow_dispatch]

permissions:
checks: write
contents: read

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Run GolangCI-Lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.59
- name: Test application
run: go test -short -v ./...
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ tmp/
.vscode
.DS_STORE
dist/
.env
115 changes: 115 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
run:
tests: false
concurrency: 5
timeout: 3m

linters:
disable-all: true
enable:
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- asasalint
- asciicheck
- bidichk
- bodyclose
- contextcheck
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errchkjson
- errname
- errorlint
- exhaustive
- exportloopref
- forcetypeassert
- ginkgolinter
- gocheckcompilerdirectives
- gochecksumtype
- gocritic
- gocyclo
- gofmt
- gofumpt
- goheader
- goimports
- gomodguard
- goprintffuncname
- gosec
- gosmopolitan
- grouper
- importas
- inamedparam
- interfacebloat
- ireturn
- loggercheck
- makezero
- mirror
- misspell
- musttag
- nakedret
- nilerr
- nilnil
- noctx
- nolintlint
- nonamedreturns
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- revive
- rowserrcheck
- sloglint
- spancheck
- sqlclosecheck
- stylecheck
- tenv
- testableexamples
- testifylint
- testpackage
- thelper
- tparallel
- unconvert
- unparam
- usestdlibvars
- wastedassign
- whitespace
- wrapcheck
- zerologlint

linters-settings:
perfsprint:
int-conversion: false
err-error: false
errorf: true
sprintf1: true
strconcat: false

ireturn:
allow:
- ssh.PublicKey
- tea.Model
- error

gosec:
confidence: medium
excludes:
- G107 # Potential HTTP request made with variable url: these are often false positives or intentional
- G110 # Decompression bombs: we can check these manually when submitting code
- G306 # Poor file permissions used when creating a directory: we can check these manually when submitting code
- G404 # Use of weak random number generator (math/rand instead of crypto/rand): we can live with these

stylecheck:
checks:
- "all"
- "-ST1003" # this is covered by a different linter

gocyclo:
min-complexity: 60
107 changes: 65 additions & 42 deletions cmd/civo.go
Original file line number Diff line number Diff line change
@@ -1,65 +1,88 @@
package cmd

import (
"context"
"errors"
"fmt"
"io"
"os"

"github.com/konstructio/dropkick/internal/civo"
"github.com/konstructio/dropkick/internal/logger"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var CivoCmdOptions *civo.CivoCmdOptions = &civo.CivoCmdOptions{}
func getCivoCommand() *cobra.Command {
var (
nuke bool
region string
)

var civoCmd = &cobra.Command{
Use: "civo",
Short: "clean civo resources",
Long: `clean civo resources`,
Run: func(cmd *cobra.Command, args []string) {
civoCmd := &cobra.Command{
Use: "civo",
Short: "clean civo resources",
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)
},
}

if os.Getenv("CIVO_TOKEN") == "" {
log.Fatal("no civoCmd token present")
}
civoCmd.Flags().BoolVar(&nuke, "nuke", false, "required to confirm deletion of resources")
civoCmd.Flags().StringVar(&region, "region", "", "the civo region to clean")
err := civoCmd.MarkFlagRequired("region")
if err != nil {
log.Fatal(err)
}

civoConf := civo.CivoConfiguration{
Client: civo.NewClient(os.Getenv("CIVO_TOKEN"), CivoCmdOptions.Region),
Context: context.Background(),
}
return civoCmd
}

err := civoConf.NukeKubernetesClusters(CivoCmdOptions)
if err != nil {
log.Fatal(err)
}
func runCivo(output io.Writer, region, token string, nuke, quiet bool) error {
if token == "" {
return errors.New("required environment variable $CIVO_TOKEN not found")
}

err = civoConf.NukeObjectStores(CivoCmdOptions)
if err != nil {
log.Fatal(err)
}
// Create a logger and make it quiet
var log *logger.Logger
if quiet {
log = logger.New(io.Discard)
} else {
log = logger.New(output)
}

err = civoConf.NukeObjectStoreCredentials(CivoCmdOptions)
if err != nil {
log.Fatal(err)
}
client, err := civo.New(
civo.WithToken(token),
civo.WithRegion(region),
civo.WithNuke(nuke),
civo.WithLogger(log),
)
if err != nil {
return fmt.Errorf("unable to create new client: %w", err)
}

err = civoConf.NukeVolumes(CivoCmdOptions)
if err != nil {
log.Fatal(err)
}
if err := client.NukeKubernetesClusters(); err != nil {
return fmt.Errorf("unable to cleanup Kubernetes clusters: %w", err)
}

err = civoConf.NukeNetworks(CivoCmdOptions)
if err != nil {
log.Fatal(err)
}
},
}
if err := client.NukeObjectStores(); err != nil {
return fmt.Errorf("unable to cleanup object stores: %w", err)
}

func init() {
rootCmd.AddCommand(civoCmd)
civoCmd.Flags().BoolVar(&CivoCmdOptions.Nuke, "nuke", CivoCmdOptions.Nuke, "required to confirm deletion of resources")
err = client.NukeObjectStoreCredentials()
if err != nil {
return fmt.Errorf("unable to cleanup object store credentials: %w", err)
}

civoCmd.Flags().StringVar(&CivoCmdOptions.Region, "region", CivoCmdOptions.Region, "the civo region to clean")
err := civoCmd.MarkFlagRequired("region")
err = client.NukeVolumes()
if err != nil {
log.Fatal(err)
return fmt.Errorf("unable to cleanup volumes: %w", err)
}

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

return nil
}
95 changes: 95 additions & 0 deletions cmd/digitalocean.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"context"
"errors"
"fmt"
"io"

"github.com/konstructio/dropkick/internal/digitalocean"
"github.com/konstructio/dropkick/internal/logger"
"github.com/konstructio/dropkick/pkg/env"
"github.com/spf13/cobra"
)

type doOptions struct {
nuke bool
token string
spacesAccessKey string
spacesSecretKey string
spacesRegion string
}

func getDigitalOceanCommand() *cobra.Command {
var opts doOptions

cmd := &cobra.Command{
Use: "digitalocean",
Short: "clean digitalocean resources",
Long: `clean digitalocean resources`,
RunE: func(cmd *cobra.Command, _ []string) error {
opts.token = env.GetFirstNotEmpty("DIGITALOCEAN_TOKEN")
opts.spacesAccessKey = env.GetFirstNotEmpty("DIGITALOCEAN_SPACES_ACCESS_KEY", "SPACES_KEY")
opts.spacesSecretKey = env.GetFirstNotEmpty("DIGITALOCEAN_SPACES_SECRET_KEY", "SPACES_SECRET")
opts.spacesRegion = env.GetFirstNotEmpty("DIGITALOCEAN_SPACES_REGION", "SPACES_REGION")
quiet := cmd.Flags().Lookup("quiet").Value.String() == "true"
return runDigitalOcean(cmd.OutOrStderr(), opts, quiet)
},
}

cmd.Flags().BoolVar(&opts.nuke, "nuke", false, "required to confirm deletion of resources")
return cmd
}

func runDigitalOcean(output io.Writer, opts doOptions, quiet bool) error {
// Check token
if opts.token == "" {
return errors.New("required environment variable $DIGITALOCEAN_TOKEN not set")
}

// Check spaces credentials
if opts.spacesAccessKey == "" {
return errors.New("required environment variable $DIGITALOCEAN_SPACES_ACCESS_KEY or $SPACES_KEY not set")
}
if opts.spacesSecretKey == "" {
return errors.New("required environment variable $DIGITALOCEAN_SPACES_SECRET_KEY or $SPACES_SECRET not set")
}
if opts.spacesRegion == "" {
return errors.New("required environment variable $DIGITALOCEAN_SPACES_REGION or $SPACES_REGION not set")
}

// Create a logger and make it quiet
var log *logger.Logger
if quiet {
log = logger.New(io.Discard)
} else {
log = logger.New(output)
}

// Create DigitalOcean client
client, err := digitalocean.New(
digitalocean.WithToken(opts.token),
digitalocean.WithS3Storage(opts.spacesAccessKey, opts.spacesSecretKey, opts.spacesRegion),
digitalocean.WithNuke(opts.nuke),
digitalocean.WithContext(context.Background()),
digitalocean.WithLogger(log),
)
if err != nil {
return fmt.Errorf("unable to create new client: %w", err)
}

// Cleanup resources
if err := client.NukeKubernetesClusters(); err != nil {
return fmt.Errorf("unable to cleanup Kubernetes clusters: %w", err)
}

if err := client.NukeS3Storage(); err != nil {
return fmt.Errorf("unable to cleanup spaces storage: %w", err)
}

if err := client.NukeVolumes(); err != nil {
return fmt.Errorf("unable to cleanup volumes: %w", err)
}

return nil
}
Loading

0 comments on commit 28170c5

Please sign in to comment.