Import of https://github.com/ciena/voltctl at commit 40d61fbf3f910ed4017cf67c9c79e8e1f82a33a5

Change-Id: I8464c59e60d76cb8612891db3303878975b5416c
diff --git a/internal/pkg/commands/version.go b/internal/pkg/commands/version.go
new file mode 100644
index 0000000..0d1f280
--- /dev/null
+++ b/internal/pkg/commands/version.go
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"context"
+	"encoding/json"
+	"github.com/ciena/voltctl/internal/pkg/cli/version"
+	"github.com/ciena/voltctl/pkg/format"
+	"github.com/fullstorydev/grpcurl"
+	flags "github.com/jessevdk/go-flags"
+	"github.com/jhump/protoreflect/dynamic"
+	"strings"
+)
+
+type VersionDetails struct {
+	Version   string `json:"version"`
+	GoVersion string `json:"goversion"`
+	VcsRef    string `json:"gitcommit"`
+	VcsDirty  string `json:"gitdirty"`
+	BuildTime string `json:"buildtime"`
+	Os        string `json:"os"`
+	Arch      string `json:"arch"`
+}
+
+type VersionOutput struct {
+	Client  VersionDetails `json:"client"`
+	Cluster VersionDetails `json:"cluster"`
+}
+
+type VersionOpts struct {
+	OutputOptions
+	ClientOnly bool `long:"clientonly" description:"Display only client version information"`
+}
+
+var versionOpts = VersionOpts{}
+
+var versionInfo = VersionOutput{
+	Client: VersionDetails{
+		Version:   version.Version,
+		GoVersion: version.GoVersion,
+		VcsRef:    version.VcsRef,
+		VcsDirty:  version.VcsDirty,
+		Os:        version.Os,
+		Arch:      version.Arch,
+		BuildTime: version.BuildTime,
+	},
+	Cluster: VersionDetails{
+		Version:   "unknown-version",
+		GoVersion: "unknown-goversion",
+		VcsRef:    "unknown-vcsref",
+		VcsDirty:  "unknown-vcsdirty",
+		Os:        "unknown-os",
+		Arch:      "unknown-arch",
+		BuildTime: "unknown-buildtime",
+	},
+}
+
+func RegisterVersionCommands(parent *flags.Parser) {
+	_, err := parent.AddCommand("version", "display version", "Display client and server version", &versionOpts)
+	if err != nil {
+		panic(err)
+	}
+}
+
+const ClientOnlyFormat = `Client:
+ Version        {{.Version}}
+ Go version:    {{.GoVersion}}
+ Vcs reference: {{.VcsRef}}
+ Vcs dirty:     {{.VcsDirty}}
+ Built:         {{.BuildTime}}
+ OS/Arch:       {{.Os}}/{{.Arch}}
+`
+
+const DefaultFormat = `Client:
+ Version        {{.Client.Version}}
+ Go version:    {{.Client.GoVersion}}
+ Vcs reference: {{.Client.VcsRef}}
+ Vcs dirty:     {{.Client.VcsDirty}}
+ Built:         {{.Client.BuildTime}}
+ OS/Arch:       {{.Client.Os}}/{{.Client.Arch}}
+
+Cluster:
+ Version        {{.Cluster.Version}}
+ Go version:    {{.Cluster.GoVersion}}
+ Vcs feference: {{.Cluster.VcsRef}}
+ Vcs dirty:     {{.Cluster.VcsDirty}}
+ Built:         {{.Cluster.BuildTime}}
+ OS/Arch:       {{.Cluster.Os}}/{{.Cluster.Arch}}
+`
+
+func (options *VersionOpts) clientOnlyVersion(args []string) error {
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = ClientOnlyFormat
+	}
+	if options.Quiet {
+		outputFormat = "{{.Version}}"
+	}
+
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      versionInfo.Client,
+	}
+
+	GenerateOutput(&result)
+	return nil
+}
+
+func (options *VersionOpts) Execute(args []string) error {
+	if options.ClientOnly {
+		return options.clientOnlyVersion(args)
+	}
+
+	conn, err := NewConnection()
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+	descriptor, method, err := GetMethod("version")
+	if err != nil {
+		return err
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	defer cancel()
+
+	h := &RpcEventHandler{}
+	err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
+	if err != nil {
+		return err
+	}
+
+	if h.Status != nil && h.Status.Err() != nil {
+		return h.Status.Err()
+	}
+
+	d, err := dynamic.AsDynamicMessage(h.Response)
+	if err != nil {
+		return err
+	}
+	version, err := d.TryGetFieldByName("version")
+	if err != nil {
+		return err
+	}
+
+	info := make(map[string]interface{})
+	err = json.Unmarshal([]byte(version.(string)), &info)
+	if err != nil {
+		versionInfo.Cluster.Version = strings.ReplaceAll(version.(string), "\n", "")
+	} else {
+		var ok bool
+		if _, ok = info["version"]; ok {
+			versionInfo.Cluster.Version = info["version"].(string)
+		} else {
+			versionInfo.Cluster.Version = "unknown-version"
+		}
+
+		if _, ok = info["goversion"]; ok {
+			versionInfo.Cluster.GoVersion = info["goversion"].(string)
+		} else {
+			versionInfo.Cluster.GoVersion = "unknown-goversion"
+		}
+
+		if _, ok = info["vcsref"]; ok {
+			versionInfo.Cluster.VcsRef = info["vcsref"].(string)
+		} else {
+			versionInfo.Cluster.VcsRef = "unknown-vcsref"
+		}
+
+		if _, ok = info["vcsdirty"]; ok {
+			versionInfo.Cluster.VcsDirty = info["vcsdirty"].(string)
+		} else {
+			versionInfo.Cluster.VcsDirty = "unknown-vcsdirty"
+		}
+
+		if _, ok = info["buildtime"]; ok {
+			versionInfo.Cluster.BuildTime = info["buildtime"].(string)
+		} else {
+			versionInfo.Cluster.BuildTime = "unknown-buildtime"
+		}
+
+		if _, ok = info["os"]; ok {
+			versionInfo.Cluster.Os = info["os"].(string)
+		} else {
+			versionInfo.Cluster.Os = "unknown-os"
+		}
+
+		if _, ok = info["arch"]; ok {
+			versionInfo.Cluster.Arch = info["arch"].(string)
+		} else {
+			versionInfo.Cluster.Arch = "unknown-arch"
+		}
+	}
+
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = DefaultFormat
+	}
+	if options.Quiet {
+		outputFormat = "{{.Client.Version}}"
+	}
+
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      versionInfo,
+	}
+
+	GenerateOutput(&result)
+	return nil
+}