Skip to content

Commit

Permalink
Refactor code to make it testable
Browse files Browse the repository at this point in the history
Pull out command/deviations to separate packages

Move prevalidation checks to package cmd

Restructure registring flags to sub-command run/all
  • Loading branch information
nikhilsbhat committed Apr 3, 2023
1 parent ab60f9f commit 9fc8734
Show file tree
Hide file tree
Showing 18 changed files with 502 additions and 229 deletions.
43 changes: 41 additions & 2 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"strings"

"github.com/nikhilsbhat/helm-drift/pkg/errors"
"github.com/nikhilsbhat/helm-drift/version"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -40,7 +41,7 @@ func getVersionCommand() *cobra.Command {
}
}

func getDriftCommand() *cobra.Command {
func getRunCommand() *cobra.Command {
driftCommand := &cobra.Command{
Use: "run [RELEASE] [CHART] [flags]",
Short: "Identifies drifts from a selected chart/release",
Expand All @@ -58,11 +59,49 @@ func getDriftCommand() *cobra.Command {
drifts.SetChart(args[1])
}

if !drifts.SkipValidation {
if !drifts.ValidatePrerequisite() {
return &errors.PreValidationError{Message: "validation failed, please install prerequisites to identify drifts"}
}
}

return drifts.GetDrift()
},
}

registerRunFlags(driftCommand)
registerCommonFlags(driftCommand)
registerDriftFlags(driftCommand)

return driftCommand
}

func getAllCommand() *cobra.Command {
driftCommand := &cobra.Command{
Use: "all",
Short: "Identifies drifts from all release from the cluster",
Long: "Lists all configuration drifts that are part of various releases present in the cluster.",
Example: ` helm drift all --kube-context k3d-sample
helm drift all --kube-context k3d-sample -n sample`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
drifts.SetLogger(drifts.LogLevel)
drifts.SetWriter(os.Stdout)
cmd.SilenceUsage = true

if !drifts.SkipValidation {
if !drifts.ValidatePrerequisite() {
return &errors.PreValidationError{Message: "validation failed, please install prerequisites to identify drifts"}
}
}

drifts.All = true

return drifts.GetAllDrift()
},
}

registerCommonFlags(driftCommand)
registerDriftAllFlags(driftCommand)

return driftCommand
}
Expand Down
18 changes: 14 additions & 4 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ func registerFlags(cmd *cobra.Command) {
"enabling this would render summary with no color")
}

// Registers all flags to command, get.
func registerRunFlags(cmd *cobra.Command) {
// Registers flags to support command run/all.
func registerCommonFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&drifts.Regex, "regex", "", pkg.TemplateRegex,
"regex used to split helm template rendered")
cmd.PersistentFlags().StringVarP(&drifts.TempPath, "temp-path", "", filepath.Join(homedir.HomeDir(), ".helm-drift", "templates"),
"path on disk where the helm templates would be rendered on to (the same would be used be used by 'kubectl diff')")
cmd.PersistentFlags().BoolVarP(&drifts.FromRelease, "from-release", "", false,
"enable the flag to identify drifts from a release instead (disabled by default)")
cmd.PersistentFlags().BoolVarP(&drifts.SkipValidation, "skip-validation", "", false,
"enable the flag if prerequisite validation needs to be skipped")
cmd.PersistentFlags().BoolVarP(&drifts.SkipClean, "skip-cleaning", "", false,
Expand All @@ -52,3 +50,15 @@ func registerRunFlags(cmd *cobra.Command) {
"custom diff command to use instead of default, the command passed here would be set under `KUBECTL_EXTERNAL_DIFF`."+
"More information can be found here https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#diff")
}

// Registers flags specific to command, run.
func registerDriftFlags(cmd *cobra.Command) {
cmd.PersistentFlags().BoolVarP(&drifts.FromRelease, "from-release", "", false,
"enable the flag to identify drifts from a release instead (disabled by default, works with command 'run' not with 'all')")
}

// Registers flags specific to command, all.
func registerDriftAllFlags(cmd *cobra.Command) {
cmd.PersistentFlags().BoolVarP(&drifts.IsDefaultNamespace, "is-default-namespace", "", false,
"set this flag if drifts have to be checked specifically in 'default' namespace")
}
3 changes: 2 additions & 1 deletion cmd/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func SetDriftCommands() *cobra.Command {
// Add an entry in below function to register new command.
func getDriftCommands() *cobra.Command {
command := new(driftCommands)
command.commands = append(command.commands, getDriftCommand())
command.commands = append(command.commands, getRunCommand())
command.commands = append(command.commands, getAllCommand())
command.commands = append(command.commands, getVersionCommand())

return command.prepareCommands()
Expand Down
22 changes: 22 additions & 0 deletions example/chart/sample/crds/crd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: "apiextensions.k8s.io/v1beta1"
kind: "CustomResourceDefinition"
metadata:
name: "projects.example.martin-helmich.de"
spec:
group: "example.martin-helmich.de"
version: "v1alpha1"
scope: "Namespaced"
names:
plural: "projects"
singular: "project"
kind: "Project"
validation:
openAPIV3Schema:
required: ["spec"]
properties:
spec:
required: ["replicas"]
properties:
replicas:
type: "integer"
minimum: 1
34 changes: 34 additions & 0 deletions pkg/command/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package command

import (
"context"
"os/exec"

"github.com/nikhilsbhat/helm-drift/pkg/deviation"
log "github.com/sirupsen/logrus"
)

const (
HelmContext = "HELM_KUBECONTEXT"
HelmNamespace = "HELM_NAMESPACE"
KubeConfig = "KUBECONFIG"
)

type Exec interface {
SetKubeCmd(namespace string, args ...string)
RunKubeCmd(deviation deviation.Deviation) (deviation.Deviation, error)
}

type command struct {
baseCmd *exec.Cmd
log *log.Logger
}

func NewCommand(cmd string, logger *log.Logger) Exec {
commandClient := command{
baseCmd: exec.CommandContext(context.Background(), cmd),
log: logger,
}

return &commandClient
}
32 changes: 32 additions & 0 deletions pkg/command/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package command

import (
"errors"
"fmt"
"os/exec"

"github.com/nikhilsbhat/helm-drift/pkg/deviation"
)

func (cmd *command) RunKubeCmd(deviation deviation.Deviation) (deviation.Deviation, error) {
cmd.log.Debugf("envionment variables that would be used: %v", cmd.baseCmd.Environ())

out, err := cmd.baseCmd.CombinedOutput()
if err != nil {
var exerr *exec.ExitError
if errors.As(err, &exerr) {
switch exerr.ExitCode() {
case 1:
deviation.HasDrift = true
deviation.Deviations = string(out)
cmd.log.Debugf("found diffs for '%s' with name '%s'", deviation.Kind, deviation.Kind)
default:
return deviation, fmt.Errorf("running kubectl diff errored with exit code: %w ,with message: %s", err, string(out))
}
}
} else {
cmd.log.Debugf("no diffs found for '%s' with name '%s'", deviation.Kind, deviation.Kind)
}

return deviation, nil
}
56 changes: 56 additions & 0 deletions pkg/command/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package command

import (
"fmt"
"os"
"path/filepath"

"k8s.io/client-go/util/homedir"
)

func (cmd *command) SetKubeCmd(namespace string, args ...string) {
cmd.baseCmd.Env = cmd.prepareKubeEnvironments()
cmd.baseCmd.Args = append(cmd.baseCmd.Args, "diff")
cmd.baseCmd.Args = append(cmd.baseCmd.Args, args...)
cmd.baseCmd.Args = append(cmd.baseCmd.Args, cmd.getNamespace(namespace))

if len(setContext()) != 0 {
cmd.baseCmd.Args = append(cmd.baseCmd.Args, setContext())
}

cmd.log.Debugf("running command '%s' to find diff", cmd.baseCmd.String())
}

func (cmd *command) prepareKubeEnvironments() []string {
config := os.Getenv(KubeConfig)

os.Environ()
var envs []string

if len(config) != 0 {
envs = append(envs, constructEnv(KubeConfig, config))
} else {
envs = append(envs, constructEnv(KubeConfig, filepath.Join(homedir.HomeDir(), ".kube", "config")))
}

envs = append(envs, os.Environ()...)

return envs
}

func (cmd *command) getNamespace(nameSpace string) string {
return fmt.Sprintf("-n=%s", nameSpace)
}

func constructEnv(key, value string) string {
return fmt.Sprintf("%s=%s", key, value)
}

func setContext() string {
kubeContext := os.Getenv(HelmContext)
if len(kubeContext) != 0 {
return fmt.Sprintf("--context=%s", kubeContext)
}

return kubeContext
}
68 changes: 68 additions & 0 deletions pkg/deviation/deviation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package deviation

import (
"github.com/thoas/go-funk"
)

const (
Failed = "FAILED"
Success = "SUCCESS"
)

type DriftedReleases struct {
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Deviations []Deviation `json:"deviations,omitempty" yaml:"deviations,omitempty"`
}

type Deviation struct {
Deviations string `json:"deviations,omitempty" yaml:"deviations,omitempty"`
HasDrift bool `json:"has_drift,omitempty" yaml:"has_drift,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
Resource string `json:"resource,omitempty" yaml:"resource,omitempty"`
TemplatePath string `json:"template_path,omitempty" yaml:"template_path,omitempty"`
ManifestPath string `json:"manifest_path,omitempty" yaml:"manifest_path,omitempty"`
}

type Deviations []Deviation

func (dvn *Deviation) Drifted() string {
if dvn.HasDrift {
return "YES"
}

return "NO"
}

func (dvn Deviations) GetDriftAsMap(chart, release, time string) map[string]interface{} {
return map[string]interface{}{
"drifts": dvn,
"total_drifts": dvn.Count(),
"time": time,
"release": release,
"chart": chart,
"status": dvn.Status(),
}
}

func (dvn Deviations) Status() string {
hasDrift := funk.Contains(dvn, func(dft Deviation) bool {
return dft.HasDrift
})

if hasDrift {
return Failed
}

return Success
}

func (dvn Deviations) Count() int {
var count int
for _, dft := range dvn {
if dft.HasDrift {
count++
}
}

return count
}
Loading

0 comments on commit 9fc8734

Please sign in to comment.