Skip to content

Commit

Permalink
test: add test cases for readState
Browse files Browse the repository at this point in the history
Signed-off-by: Luis Davim <[email protected]>
  • Loading branch information
Luis Davim committed Nov 24, 2020
1 parent 21c0a08 commit 5d420ba
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 58 deletions.
4 changes: 2 additions & 2 deletions examples/minimal-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md

[helmRepos]
jenkins = https://charts.jenkins.io
center = https://repo.chartcenter.io
jenkins = "https://charts.jenkins.io"
center = "https://repo.chartcenter.io"

[namespaces]
[namespaces.staging]
Expand Down
4 changes: 2 additions & 2 deletions examples/minimal-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
## It will use your current kube context and will deploy Tiller without RBAC service account.
## For the full config spec and options, check https://github.com/Praqma/helmsman/blob/master/docs/desired_state_specification.md
helmRepos:
jenkins = https://charts.jenkins.io
center = https://repo.chartcenter.io
jenkins: https://charts.jenkins.io
center: https://repo.chartcenter.io

namespaces:
staging:
Expand Down
32 changes: 11 additions & 21 deletions internal/app/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (c *cli) parse() {
os.Setenv("KUBECONFIG", c.kubeconfig)
}

if !ToolExists("kubectl") {
if !ToolExists(kubectlBin) {
log.Fatal("kubectl is not installed/configured correctly. Aborting!")
}

Expand All @@ -191,21 +191,21 @@ func (c *cli) parse() {
}

// readState gets the desired state from files
func (c *cli) readState(s *state) {
func (c *cli) readState(s *state) error {
// read the env file
if len(c.envFiles) == 0 {
if _, err := os.Stat(".env"); err == nil {
err = godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
return fmt.Errorf("error loading .env file: %w", err)
}
}
}

for _, e := range c.envFiles {
err := godotenv.Load(e)
if err != nil {
log.Fatal("Error loading " + e + " env file")
return fmt.Errorf("error loading %s env file: %w", e, err)
}
}

Expand All @@ -221,49 +221,38 @@ func (c *cli) readState(s *state) {
if result {
log.Info(msg)
} else {
log.Fatal(msg)
return fmt.Errorf(msg)
}
// Merge Apps that already existed in the state
for appName, app := range fileState.Apps {
if _, ok := s.Apps[appName]; ok {
if err := mergo.Merge(s.Apps[appName], app, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
log.Fatal("Failed to merge " + appName + " from desired state file" + f)
return fmt.Errorf("failed to merge %s from desired state file %s: %w", appName, f, err)
}
}
}

// Merge the remaining Apps
if err := mergo.Merge(&s.Apps, &fileState.Apps); err != nil {
log.Fatal("Failed to merge desired state file" + f)
return fmt.Errorf("failed to merge desired state file %s: %w", f, err)
}
// All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge
fileState.Apps = make(map[string]*release)

if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
log.Fatal("Failed to merge desired state file" + f)
return fmt.Errorf("failed to merge desired state file %s: %w", f, err)
}
}

s.setDefaults()
s.initializeNamespaces()
s.init() // Set defaults
s.disableUntargetedApps(c.group, c.target)

if len(c.target) > 0 && len(s.TargetMap) == 0 {
log.Info("No apps defined with -target flag were found, exiting")
os.Exit(0)
}

if len(c.group) > 0 && len(s.TargetMap) == 0 {
log.Info("No apps defined with -group flag were found, exiting")
os.Exit(0)
}

if !c.skipValidation {
// validate the desired state content
if len(c.files) > 0 {
log.Info("Validating desired state definition")
if err := s.validate(); err != nil { // syntax validation
log.Fatal(err.Error())
return err
}
}
} else {
Expand All @@ -273,6 +262,7 @@ func (c *cli) readState(s *state) {
if c.debug {
s.print()
}
return nil
}

// getDryRunFlags returns dry-run flag
Expand Down
126 changes: 101 additions & 25 deletions internal/app/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,115 @@ var _ = func() bool {
return true
}()

func Test_toolExists(t *testing.T) {
type args struct {
tool string
func Test_readState(t *testing.T) {
type result struct {
numApps int
numNSs int
numEnabledApps int
numEnabledNSs int
}
tests := []struct {
name string
args args
want bool
name string
flags cli
want result
}{
{
name: "test case 1 -- checking helm exists.",
args: args{
tool: helmBin,
},
want: true,
}, {
name: "test case 2 -- checking kubectl exists.",
args: args{
tool: "kubectl",
},
want: true,
}, {
name: "test case 3 -- checking nonExistingTool exists.",
args: args{
tool: "nonExistingTool",
},
want: false,
name: "yaml minimal example; no validation",
flags: cli{
files: stringArray([]string{"../../examples/minimal-example.yaml"}),
skipValidation: true,
},
want: result{
numApps: 2,
numNSs: 1,
numEnabledApps: 2,
numEnabledNSs: 1,
},
},
{
name: "toml minimal example; no validation",
flags: cli{
files: stringArray([]string{"../../examples/minimal-example.toml"}),
skipValidation: true,
},
want: result{
numApps: 2,
numNSs: 1,
numEnabledApps: 2,
numEnabledNSs: 1,
},
},
{
name: "yaml minimal example; no validation with bad target",
flags: cli{
target: stringArray([]string{"foo"}),
files: stringArray([]string{"../../examples/minimal-example.yaml"}),
skipValidation: true,
},
want: result{
numApps: 2,
numNSs: 1,
numEnabledApps: 0,
numEnabledNSs: 0,
},
},
{
name: "yaml minimal example; no validation; target jenkins",
flags: cli{
target: stringArray([]string{"jenkins"}),
files: stringArray([]string{"../../examples/minimal-example.yaml"}),
skipValidation: true,
},
want: result{
numApps: 2,
numNSs: 1,
numEnabledApps: 1,
numEnabledNSs: 1,
},
},
{
name: "yaml and toml minimal examples merged; no validation",
flags: cli{
files: stringArray([]string{"../../examples/minimal-example.yaml", "../../examples/minimal-example.toml"}),
skipValidation: true,
},
want: result{
numApps: 2,
numNSs: 1,
numEnabledApps: 2,
numEnabledNSs: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToolExists(tt.args.tool); got != tt.want {
t.Errorf("toolExists() = %v, want %v", got, tt.want)
s := state{}
if err := tt.flags.readState(&s); err != nil {
t.Errorf("readState() = Unexpected error: %v", err)
}
if len(s.Apps) != tt.want.numApps {
t.Errorf("readState() = app count mismatch: want: %d, got: %d", tt.want.numApps, len(s.Apps))
}
if len(s.Namespaces) != tt.want.numNSs {
t.Errorf("readState() = NS count mismatch: want: %d, got: %d", tt.want.numNSs, len(s.Namespaces))
}

var enabledApps, enabledNSs int
for _, a := range s.Apps {
if !a.disabled {
enabledApps++
}
}
if enabledApps != tt.want.numEnabledApps {
t.Errorf("readState() = app count mismatch: want: %d, got: %d", tt.want.numEnabledApps, enabledApps)
}
for _, n := range s.Namespaces {
if !n.disabled {
enabledNSs++
}
}
if enabledNSs != tt.want.numEnabledNSs {
t.Errorf("readState() = app count mismatch: want: %d, got: %d", tt.want.numEnabledNSs, enabledNSs)
}
})
}
Expand Down
38 changes: 38 additions & 0 deletions internal/app/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ import (
"testing"
)

func Test_toolExists(t *testing.T) {
type args struct {
tool string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "test case 1 -- checking helm exists.",
args: args{
tool: helmBin,
},
want: true,
}, {
name: "test case 2 -- checking kubectl exists.",
args: args{
tool: kubectlBin,
},
want: true,
}, {
name: "test case 3 -- checking nonExistingTool exists.",
args: args{
tool: "nonExistingTool",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToolExists(tt.args.tool); got != tt.want {
t.Errorf("toolExists() = %v, want %v", got, tt.want)
}
})
}
}

func Test_command_exec(t *testing.T) {
type fields struct {
Cmd string
Expand Down
2 changes: 1 addition & 1 deletion internal/app/kube_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func addNamespaces(s *state) {
// kubectl prepares a kubectl command to be executed
func kubectl(args []string, desc string) Command {
return Command{
Cmd: "kubectl",
Cmd: kubectlBin,
Args: args,
Description: desc,
}
Expand Down
16 changes: 15 additions & 1 deletion internal/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

const (
helmBin = "helm"
kubectlBin = "kubectl"
appVersion = "v3.6.1"
tempFilesDir = ".helmsman-tmp"
defaultContextName = "default"
Expand Down Expand Up @@ -35,7 +36,20 @@ func Main() {
defer s.cleanup()
}

flags.readState(&s)
if err := flags.readState(&s); err != nil {
log.Fatal(err.Error())
}

if len(flags.target) > 0 && len(s.TargetMap) == 0 {
log.Info("No apps defined with -target flag were found, exiting")
os.Exit(0)
}

if len(flags.group) > 0 && len(s.TargetMap) == 0 {
log.Info("No apps defined with -group flag were found, exiting")
os.Exit(0)
}

log.SlackWebhook = s.Settings.SlackWebhook

settings = &s.Settings
Expand Down
10 changes: 6 additions & 4 deletions internal/app/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ func (n *namespace) Disable() {

// print prints the namespace
func (n *namespace) print() {
fmt.Println("")
fmt.Println("\tprotected : ", n.Protected)
fmt.Println("\tlabels : ")
fmt.Println("\tprotected: ", n.Protected)
fmt.Println("\tdisabled: ", n.disabled)
fmt.Println("\tlabels:")
printMap(n.Labels, 2)
fmt.Println("------------------- ")
fmt.Println("\tannotations:")
printMap(n.Annotations, 2)
fmt.Println("-------------------")
}
5 changes: 5 additions & 0 deletions internal/app/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ type state struct {
ChartInfo map[string]map[string]*chartInfo
}

func (s *state) init() {
s.setDefaults()
s.initializeNamespaces()
}

func (s *state) setDefaults() {
if s.Settings.StorageBackend != "" {
os.Setenv("HELM_DRIVER", s.Settings.StorageBackend)
Expand Down
5 changes: 3 additions & 2 deletions internal/app/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ func printMap(m map[string]string, indent int) {

// printObjectMap prints to the console any map of string keys and object values.
func printNamespacesMap(m map[string]*namespace) {
for key, value := range m {
fmt.Println(key, " : protected = ", value)
for name, ns := range m {
fmt.Println(name, ":")
ns.print()
}
}

Expand Down

0 comments on commit 5d420ba

Please sign in to comment.