Skip to content

Commit

Permalink
Refactoring of "get instances" CLI command (#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
fabianbaier authored and kensipe committed Jul 11, 2019
1 parent 5b683b9 commit f05d5a8
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 123 deletions.
21 changes: 14 additions & 7 deletions pkg/kudoctl/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import (
"github.com/spf13/cobra"
)

// newGetCmd creates a new command that lists instances
// newGetCmd creates a command that lists the instances in the cluster
func newGetCmd() *cobra.Command {
newCmd := &cobra.Command{
Use: "get",
Short: "-> Show all available instances.",
Long: `The get command has subcommands to show all available instances.`,
options := get.DefaultOptions
getCmd := &cobra.Command{
Use: "get instances",
Short: "Gets all available instances.",
Long: `
# Get all available instances
kudoctl get instances`,
RunE: func(cmd *cobra.Command, args []string) error {
return get.Run(args, options)
},
}

newCmd.AddCommand(get.NewGetInstancesCmd())
getCmd.Flags().StringVar(&options.KubeConfigPath, "kubeconfig", "", "The file path to kubernetes configuration file; defaults to $HOME/.kube/config")
getCmd.Flags().StringVar(&options.Namespace, "namespace", "default", "The namespace where the operator watches for changes.")

return newCmd
return getCmd
}
94 changes: 94 additions & 0 deletions pkg/kudoctl/cmd/get/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package get

import (
"fmt"
"log"
"os"

"github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo"
"github.com/pkg/errors"

"github.com/xlab/treeprint"
"k8s.io/client-go/tools/clientcmd"

"github.com/kudobuilder/kudo/pkg/kudoctl/util/check"
)

// Options defines configuration options for the get command
type Options struct {
KubeConfigPath string
Namespace string
}

// DefaultOptions initializes the get command options to its defaults
var DefaultOptions = &Options{
Namespace: "default",
}

// Run returns the errors associated with cmd env
func Run(args []string, options *Options) error {

err := validate(args, options)
if err != nil {
return err
}

kc, err := kudo.NewClient(options.Namespace, options.KubeConfigPath)
if err != nil {
return errors.Wrap(err, "creating kudo client")
}

p, err := getInstances(kc, options)
if err != nil {
log.Printf("Error: %v", err)
}
tree := treeprint.New()

for _, plan := range p {
tree.AddBranch(plan)
}
fmt.Printf("List of current installed instances in namespace \"%s\":\n", options.Namespace)
fmt.Println(tree.String())
return err
}

func validate(args []string, options *Options) error {
if len(args) != 1 {
return fmt.Errorf("expecting exactly one argument - \"instances\"")
}

if args[0] != "instances" {
return fmt.Errorf("expecting \"instances\" and not \"%s\"", args[0])
}

// If the $KUBECONFIG environment variable is set, use that
if len(os.Getenv("KUBECONFIG")) > 0 {
options.KubeConfigPath = os.Getenv("KUBECONFIG")
}

configPath, err := check.KubeConfigLocationOrDefault(options.KubeConfigPath)
if err != nil {
return fmt.Errorf("error when getting default kubeconfig path: %+v", err)
}
options.KubeConfigPath = configPath
if err := check.ValidateKubeConfigPath(options.KubeConfigPath); err != nil {
return errors.WithMessage(err, "could not check kubeconfig path")
}
_, err = clientcmd.BuildConfigFromFlags("", options.KubeConfigPath)
if err != nil {
return errors.Wrap(err, "getting config failed")
}

return nil

}

func getInstances(kc *kudo.Client, options *Options) ([]string, error) {

instanceList, err := kc.ListInstances(options.Namespace)
if err != nil {
return nil, errors.Wrap(err, "getting instances")
}

return instanceList, nil
}
109 changes: 109 additions & 0 deletions pkg/kudoctl/cmd/get/get_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package get

import (
"testing"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1"
"github.com/kudobuilder/kudo/pkg/client/clientset/versioned/fake"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestValidate(t *testing.T) {
tests := []struct {
arg []string
opt Options
err string
}{
{nil, *DefaultOptions, "expecting exactly one argument - \"instances\""}, // 1
{[]string{"arg", "arg2"}, *DefaultOptions, "expecting exactly one argument - \"instances\""}, // 2
{[]string{}, *DefaultOptions, "expecting exactly one argument - \"instances\""}, // 3
{[]string{"somethingelse"}, *DefaultOptions, "expecting \"instances\" and not \"somethingelse\""}, // 4
}

for _, tt := range tests {
err := validate(tt.arg, DefaultOptions)
if err != nil {
if err.Error() != tt.err {
t.Errorf("Expecting error message '%s' but got '%s'", tt.err, err)
}
}
}
}

func newTestClient() *kudo.Client {
return kudo.NewClientFromK8s(fake.NewSimpleClientset())
}

func TestGetInstances(t *testing.T) {
testInstance := &v1alpha1.Instance{
TypeMeta: metav1.TypeMeta{
APIVersion: "kudo.k8s.io/v1alpha1",
Kind: "Instance",
},
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"controller-tools.k8s.io": "1.0",
"operator": "test",
},
Name: "test",
},
Spec: v1alpha1.InstanceSpec{
OperatorVersion: v1.ObjectReference{
Name: "test-1.0",
},
},
}
tests := []struct {
arg []string
opt Options
err string
instances []string
}{
{nil, *DefaultOptions, "expecting exactly one argument - \"instances\"", nil}, // 1
{[]string{"arg", "arg2"}, *DefaultOptions, "expecting exactly one argument - \"instances\"", nil}, // 2
{[]string{}, *DefaultOptions, "expecting exactly one argument - \"instances\"", nil}, // 3
{[]string{"somethingelse"}, *DefaultOptions, "expecting \"instances\" and not \"somethingelse\"", nil}, // 4
{[]string{"instances"}, *DefaultOptions, "expecting \"instances\" and not \"somethingelse\"", []string{"test"}}, // 5
}

for i, tt := range tests {
kc := newTestClient()
kc.InstallInstanceObjToCluster(testInstance, "default")
instanceList, err := getInstances(kc, DefaultOptions)
if err != nil {
if err.Error() != tt.err {
t.Errorf("%d: Expecting error message '%s' but got '%s'", i+1, tt.err, err)
}
}
missing := compareSlice(tt.instances, instanceList)
for _, m := range missing {
t.Errorf("%d: Missed expected instance \"%v\"", i+1, m)
}
}
}

func compareSlice(real, mock []string) []string {
lm := len(mock)

var diff []string

for _, rv := range real {
i := 0
j := 0
for _, mv := range mock {
i++
if rv == mv {
continue
}
if rv != mv {
j++
}
if lm <= j {
diff = append(diff, rv)
}
}
}
return diff
}
116 changes: 0 additions & 116 deletions pkg/kudoctl/cmd/get/instances.go

This file was deleted.

21 changes: 21 additions & 0 deletions pkg/kudoctl/util/kudo/kudo.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,27 @@ func (c *Client) AnyOperatorVersionExistsInCluster(operator string, namespace st
return true
}

// ListInstances lists all instances of given operator installed in the cluster in a given ns
func (c *Client) ListInstances(namespace string) ([]string, error) {
instances, err := c.clientset.KudoV1alpha1().Instances(namespace).List(v1.ListOptions{})
if err != nil {
return nil, err
}
existingInstances := []string{}

for _, v := range instances.Items {
existingInstances = append(existingInstances, v.Name)
}
return existingInstances, nil
}

// NewClientFromK8s creates KUDO client from kubernetes client interface
func NewClientFromK8s(client versioned.Interface) *Client {
result := Client{}
result.clientset = client
return &result
}

// InstanceExistsInCluster checks if any OperatorVersion object matches to the given Operator name
// in the cluster.
// An Instance has two identifiers:
Expand Down
Loading

0 comments on commit f05d5a8

Please sign in to comment.