VOL-3943 - multiple stack configuration support

Change-Id: I28cb26b6cd6fbc5b3f0e8d406a68bf5c964a57fd
diff --git a/internal/pkg/commands/command.go b/internal/pkg/commands/command.go
index 3e1beb4..93944a8 100644
--- a/internal/pkg/commands/command.go
+++ b/internal/pkg/commands/command.go
@@ -35,6 +35,7 @@
 	"github.com/golang/protobuf/proto"
 	configv1 "github.com/opencord/voltctl/internal/pkg/apis/config/v1"
 	configv2 "github.com/opencord/voltctl/internal/pkg/apis/config/v2"
+	configv3 "github.com/opencord/voltctl/internal/pkg/apis/config/v3"
 	"github.com/opencord/voltctl/pkg/filter"
 	"github.com/opencord/voltctl/pkg/format"
 	"github.com/opencord/voltctl/pkg/order"
@@ -70,12 +71,13 @@
 
 	CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
 
-	GlobalConfig = configv2.NewDefaultConfig()
+	GlobalConfig = configv3.NewDefaultConfig()
 
 	GlobalCommandOptions = make(map[string]map[string]string)
 
 	GlobalOptions struct {
 		Config  string `short:"c" long:"config" env:"VOLTCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
+		Stack   string `short:"v" long:"stack" env:"STACK" value-name:"STACK" default:"" description:"Name of stack to use in multistack deployment"`
 		Server  string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of VOLTHA"`
 		Kafka   string `short:"k" long:"kafka" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of Kafka"`
 		KvStore string `short:"e" long:"kvstore" env:"KVSTORE" value-name:"SERVER:PORT" description:"IP/Host and port of KV store (etcd)"`
@@ -194,75 +196,18 @@
 }
 
 func ProcessGlobalOptions() {
-	if len(GlobalOptions.Config) == 0 {
-		home, err := os.UserHomeDir()
-		if err != nil {
-			Warn.Printf("Unable to discover the user's home directory: %s", err)
-			home = "~"
+	ReadConfig()
+
+	// If a stack is selected via command line set it
+	if GlobalOptions.Stack != "" {
+		if GlobalConfig.StackByName(GlobalOptions.Stack) == nil {
+			Error.Fatalf("stack specified, '%s', not found in configuration",
+				GlobalOptions.Stack)
 		}
-		GlobalOptions.Config = filepath.Join(home, ".volt", "config")
+		GlobalConfig.CurrentStack = GlobalOptions.Stack
 	}
 
-	if info, err := os.Stat(GlobalOptions.Config); err == nil && !info.IsDir() {
-		configFile, err := ioutil.ReadFile(GlobalOptions.Config)
-		if err != nil {
-			Error.Fatalf("Unable to read the configuration file '%s': %s",
-				GlobalOptions.Config, err.Error())
-		}
-		// First try the latest version of the config api then work
-		// backwards
-		if err = yaml.Unmarshal(configFile, &GlobalConfig); err != nil {
-			GlobalConfigV1 := configv1.NewDefaultConfig()
-			if err = yaml.Unmarshal(configFile, &GlobalConfigV1); err != nil {
-				Error.Fatalf("Unable to parse the configuration file '%s': %s",
-					GlobalOptions.Config, err.Error())
-			}
-			GlobalConfig = configv2.FromConfigV1(GlobalConfigV1)
-		}
-	}
-
-	// Override from command line
-	if GlobalOptions.Server != "" {
-		GlobalConfig.Server = GlobalOptions.Server
-	}
-
-	if GlobalOptions.UseTLS {
-		GlobalConfig.Tls.UseTls = true
-	}
-
-	if GlobalOptions.Verify {
-		GlobalConfig.Tls.Verify = true
-	}
-
-	if GlobalOptions.Kafka != "" {
-		GlobalConfig.Kafka = GlobalOptions.Kafka
-	}
-
-	if GlobalOptions.KvStore != "" {
-		GlobalConfig.KvStore = GlobalOptions.KvStore
-	}
-
-	if GlobalOptions.KvStoreTimeout != "" {
-		timeout, err := time.ParseDuration(GlobalOptions.KvStoreTimeout)
-		if err != nil {
-			Error.Fatalf("Unable to parse specified KV strore timeout duration '%s': %s",
-				GlobalOptions.KvStoreTimeout, err.Error())
-		}
-		GlobalConfig.KvStoreConfig.Timeout = timeout
-	}
-
-	if GlobalOptions.Timeout != "" {
-		timeout, err := time.ParseDuration(GlobalOptions.Timeout)
-		if err != nil {
-			Error.Fatalf("Unable to parse specified timeout duration '%s': %s",
-				GlobalOptions.Timeout, err.Error())
-		}
-		GlobalConfig.Grpc.Timeout = timeout
-	}
-
-	if GlobalOptions.MaxCallRecvMsgSize != "" {
-		GlobalConfig.Grpc.MaxCallRecvMsgSize = GlobalOptions.MaxCallRecvMsgSize
-	}
+	ApplyOptionOverrides(GlobalConfig.Current())
 
 	// If a k8s cert/key were not specified, then attempt to read it from
 	// any $HOME/.kube/config if it exists
@@ -297,13 +242,98 @@
 	}
 }
 
+func ApplyOptionOverrides(stack *configv3.StackConfigSpec) {
+
+	if stack == nil {
+		// nothing to do
+		return
+	}
+	// Override from command line
+	if GlobalOptions.Server != "" {
+		stack.Server = GlobalOptions.Server
+	}
+
+	if GlobalOptions.UseTLS {
+		stack.Tls.UseTls = true
+	}
+
+	if GlobalOptions.Verify {
+		stack.Tls.Verify = true
+	}
+
+	if GlobalOptions.Kafka != "" {
+		stack.Kafka = GlobalOptions.Kafka
+	}
+
+	if GlobalOptions.KvStore != "" {
+		stack.KvStore = GlobalOptions.KvStore
+	}
+
+	if GlobalOptions.KvStoreTimeout != "" {
+		timeout, err := time.ParseDuration(GlobalOptions.KvStoreTimeout)
+		if err != nil {
+			Error.Fatalf("Unable to parse specified KV strore timeout duration '%s': %s",
+				GlobalOptions.KvStoreTimeout, err.Error())
+		}
+		stack.KvStoreConfig.Timeout = timeout
+	}
+
+	if GlobalOptions.Timeout != "" {
+		timeout, err := time.ParseDuration(GlobalOptions.Timeout)
+		if err != nil {
+			Error.Fatalf("Unable to parse specified timeout duration '%s': %s",
+				GlobalOptions.Timeout, err.Error())
+		}
+		stack.Grpc.Timeout = timeout
+	}
+
+	if GlobalOptions.MaxCallRecvMsgSize != "" {
+		stack.Grpc.MaxCallRecvMsgSize = GlobalOptions.MaxCallRecvMsgSize
+	}
+}
+
+func ReadConfig() {
+	if len(GlobalOptions.Config) == 0 {
+		home, err := os.UserHomeDir()
+		if err != nil {
+			Warn.Printf("Unable to discover the user's home directory: %s", err)
+			home = "~"
+		}
+		GlobalOptions.Config = filepath.Join(home, ".volt", "config")
+	}
+
+	if info, err := os.Stat(GlobalOptions.Config); err == nil && !info.IsDir() {
+		configFile, err := ioutil.ReadFile(GlobalOptions.Config)
+		if err != nil {
+			Error.Fatalf("Unable to read the configuration file '%s': %s",
+				GlobalOptions.Config, err.Error())
+		}
+		// First try the latest version of the config api then work
+		// backwards
+		if err = yaml.Unmarshal(configFile, &GlobalConfig); err != nil {
+			GlobalConfigV2 := configv2.NewDefaultConfig()
+			if err = yaml.Unmarshal(configFile, &GlobalConfigV2); err != nil {
+				GlobalConfigV1 := configv1.NewDefaultConfig()
+				if err = yaml.Unmarshal(configFile, &GlobalConfigV1); err != nil {
+					Error.Fatalf("Unable to parse the configuration file '%s': %s",
+						GlobalOptions.Config, err.Error())
+				}
+				GlobalConfig = configv3.FromConfigV1(GlobalConfigV1)
+			} else {
+				GlobalConfig = configv3.FromConfigV2(GlobalConfigV2)
+			}
+		}
+	}
+
+}
+
 func NewConnection() (*grpc.ClientConn, error) {
 	ProcessGlobalOptions()
 
 	// convert grpc.msgSize into bytes
-	n, err := parseSize(GlobalConfig.Grpc.MaxCallRecvMsgSize)
+	n, err := parseSize(GlobalConfig.Current().Grpc.MaxCallRecvMsgSize)
 	if err != nil {
-		Error.Fatalf("Cannot convert msgSize %s to bytes", GlobalConfig.Grpc.MaxCallRecvMsgSize)
+		Error.Fatalf("Cannot convert msgSize %s to bytes", GlobalConfig.Current().Grpc.MaxCallRecvMsgSize)
 	}
 
 	var opts []grpc.DialOption
@@ -313,17 +343,17 @@
 		grpc.WithBlock(),
 		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(n))))
 
-	if GlobalConfig.Tls.UseTls {
+	if GlobalConfig.Current().Tls.UseTls {
 		creds := credentials.NewTLS(&tls.Config{
-			InsecureSkipVerify: !GlobalConfig.Tls.Verify})
+			InsecureSkipVerify: !GlobalConfig.Current().Tls.Verify})
 		opts = append(opts, grpc.WithTransportCredentials(creds))
 	} else {
 		opts = append(opts, grpc.WithInsecure())
 	}
 	ctx, cancel := context.WithTimeout(context.TODO(),
-		GlobalConfig.Grpc.ConnectTimeout)
+		GlobalConfig.Current().Grpc.ConnectTimeout)
 	defer cancel()
-	return grpc.DialContext(ctx, GlobalConfig.Server, opts...)
+	return grpc.DialContext(ctx, GlobalConfig.Current().Server, opts...)
 }
 
 func ConvertJsonProtobufArray(data_in interface{}) (string, error) {