This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git

commit a6fb80da71d30ea5fefb735bcff34290066a534c
Author: Rohit Yadav <ro...@apache.org>
AuthorDate: Wed Apr 11 04:06:56 2018 +0530

    cloudmonkey: initial Go port (cmk)
    
    - Basic command handling and shell
    - API verb, resource autocompletion
    - PoC API arg autocompletion with interactive selector
    
    Signed-off-by: Rohit Yadav <ro...@apache.org>
---
 .gitignore       |  23 ++++++++
 CHANGES.md       |   7 +++
 Makefile         |  55 +++++++++++++++++++
 NOTICE           |   2 +-
 README.md        | 102 ++++++++++++----------------------
 cli/completer.go | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 cli/exec.go      |  38 +++++++++++++
 cli/selector.go  |  90 ++++++++++++++++++++++++++++++
 cli/shell.go     |  87 +++++++++++++++++++++++++++++
 cmd/api.go       |  64 +++++++++++++++++++++
 cmd/command.go   |  64 +++++++++++++++++++++
 cmd/exit.go      |  42 ++++++++++++++
 cmd/help.go      |  37 +++++++++++++
 cmd/login.go     |  74 +++++++++++++++++++++++++
 cmd/network.go   | 106 +++++++++++++++++++++++++++++++++++
 cmd/request.go   |  39 +++++++++++++
 cmd/set.go       |  45 +++++++++++++++
 cmd/shell.go     |  44 +++++++++++++++
 cmd/sync.go      |  39 +++++++++++++
 cmd/version.go   |  31 +++++++++++
 cmk.go           |  35 ++++++++++++
 config/cache.go  | 117 +++++++++++++++++++++++++++++++++++++++
 config/config.go | 100 +++++++++++++++++++++++++++++++++
 config/util.go   |  67 ++++++++++++++++++++++
 24 files changed, 1404 insertions(+), 69 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bdce524
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+cmk
+dist
+*.exe
+*.test
+*.out
+.idea
diff --git a/CHANGES.md b/CHANGES.md
index 013d837..761a768 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,13 @@
 Apache CloudStack CloudMonkey Changelog
 ---------------------------------------
 
+Version 6.0.0 (alpha, in progress)
+=============
+This release includes
+- Rewrite cloudmonkey in golang
+- Interactive parameter completion
+- Simplification of configuration
+
 Version 5.3.3
 =============
 This release includes
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fda6cce
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+build:
+       go build -ldflags='-s -w' -o cmk cmk.go
+
+run:
+       go run cmk.go
+
+test:
+       go test
+
+install: build
+       @echo Copied to ~/bin
+       @cp cmk ~/bin
+
+debug:
+       go build -gcflags='-N -l' -o cmk cmk.go &&  dlv --listen=:2345 
--headless=true --api-version=2 exec ./cmk
+
+dist:
+       rm -fr dist
+       mkdir -p dist
+       GOOS=linux   GOARCH=amd64 go build -ldflags='-s -w' -o 
dist/cmk-linux-amd64 cmk.go
+       GOOS=linux   GOARCH=386   go build -ldflags='-s -w' -o 
dist/cmk-linux-i386 cmk.go
+       GOOS=linux   GOARCH=arm64 go build -ldflags='-s -w' -o 
dist/cmk-linux-arm64 cmk.go
+       GOOS=linux   GOARCH=arm   go build -ldflags='-s -w' -o 
dist/cmk-linux-arm cmk.go
+       GOOS=windows GOARCH=amd64 go build -ldflags='-s -w' -o dist/cmk-x64.exe 
cmk.go
+       GOOS=windows GOARCH=386   go build -ldflags='-s -w' -o dist/cmk-x32.exe 
cmk.go
+       GOOS=darwin  GOARCH=amd64 go build -ldflags='-s -w' -o 
dist/cmk-mac64.bin cmk.go
+       GOOS=darwin  GOARCH=386   go build -ldflags='-s -w' -o 
dist/cmk-mac32.bin cmk.go
+
+clean:
+       @rm -f cmk
+       @rm -fr dist
+
+deps:
+       go get -u github.com/rhtyd/readline
+       go get -u github.com/mitchellh/go-homedir
+       go get -u github.com/mattn/go-shellwords
+       go get -u github.com/manifoldco/promptui
+
diff --git a/NOTICE b/NOTICE
index 9a34d61..2fcb349 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
 Apache CloudStack CloudMonkey
-Copyright 2014 The Apache Software Foundation
+Copyright 2018 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
index f710222..9f76029 100644
--- a/README.md
+++ b/README.md
@@ -1,74 +1,36 @@
 ## CloudMonkey
 
-`cloudmonkey` :cloud::monkey_face: is a command line interface for
+`cloudmonkey` :cloud::monkey_face: is a command line interface (CLI) for
 [Apache CloudStack](http://cloudstack.apache.org).
 CloudMonkey can be use both as an interactive shell and as a command line tool
-which simplifies Apache CloudStack configuration and management. It can be used
-with Apache CloudStack 4.0-incubating and above.
+which simplifies Apache CloudStack configuration and management.
 
-![version badge](https://badge.fury.io/py/cloudmonkey.png) ![download 
badge](http://img.shields.io/pypi/dm/cloudmonkey.png)
+The modern cloudmonkey is a re-written and simplified port in Go and can be 
used
+with Apache CloudStack 4.9 and above. The legacy cloudmonkey written in Python
+can be used with Apache CloudStack 4.0-incubating and above.
 
+### Usage
 
-### For users
+Work in progress: 6.0.0-alpha1 (TO BE UPDATED soon)
 
-Install:
-
-    $ pip install cloudmonkey
-
-Upgrade:
-
-    $ pip install --upgrade cloudmonkey
-
-Install/Upgrade latest using git repository:
-
-    $ pip install --upgrade 
git+https://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey.git
-
-Install/upgrade using the Github git mirror:
-
-    $ pip install --upgrade 
git+https://github.com/apache/cloudstack-cloudmonkey.git
-
-Please see the [CloudMonkey 
Wiki](https://cwiki.apache.org/confluence/display/CLOUDSTACK/CloudStack+cloudmonkey+CLI)
 for usage.
-
-
-### Using Docker image
-
-The default configuration provided connect to CloudStack managemenent server 
as container:
-
-Enter the CLI:
-
-    $ docker run -ti --rm --link cloudstack:8080 cloudstack/cloudmonkey
+For modern cloudmonkey usage, please see the [usage 
page](https://github.com/apache/cloudstack-cloudmonkey/wiki/Usage).
 
-To execute single api command:
+Legacy cloudmonkey can be installed using `pip install cloudmonkey`.
+For legacy cloudmonkey, please see the [cwiki usage 
page](https://cwiki.apache.org/confluence/display/CLOUDSTACK/CloudStack+cloudmonkey+CLI).
 
-    $ docker run -ti --rm --link cloudstack:8080 cloudstack/cloudmonkey list 
accounts
-
-Use your own CloudMonkey configuration file:
-
-    $ docker run -ti --rm -v `pwd`/.cloudmonkey:/cloudmonkey 
cloudstack/cloudmonkey
-
-
-### Build
-
-All:
-
-    Cleans and then builds with precache
-    $ make all
+### Development
 
 Build:
 
     $ make build
 
-Build Precache:
-
-    $ make buildcache
+Run:
 
-Build with Precache:
+    $ ./cmk
 
-    $ make buildwithcache
+Build and run:
 
-Check changes, code styles:
-
-    $ make check
+    $ make run
 
 Clean:
 
@@ -78,32 +40,36 @@ Install:
 
     $ make install
 
+### Community
 
-### Mailing lists
+You may join the relevant mailing list(s) for cloudmonkey related discussion:
 
 [Development Mailing List](mailto:dev-subscr...@cloudstack.apache.org)
 
 [Users Mailing List](mailto:users-subscr...@cloudstack.apache.org)
 
-[Commits Mailing List](mailto:commits-subscr...@cloudstack.apache.org)
-
-[Issues Mailing List](mailto:issues-subscr...@cloudstack.apache.org)
-
-[Marketing Mailing List](mailto:marketing-subscr...@cloudstack.apache.org)
-
+### Contribution
 
-### Contributing
-
-Discuss features development on the [Development Mailing 
List](mailto:dev-subscr...@cloudstack.apache.org).
-Report issues on the `User` mailing list and open issue on 
[JIRA](http://issues.apache.org/jira/browse/CLOUDSTACK).
+Discuss issue(s) and feature(s) on CloudStack [development mailing 
list](mailto:dev-subscr...@cloudstack.apache.org).
+Report issue(s) on the `user` mailing list and/or open a Github 
[issue](https://github.com/apache/cloudstack-cloudmonkey/issues).
 
 1. Fork the repository on Github
 2. Create a named feature branch (like `add_component_x`)
-3. Write your change
-4. Write tests for your change (if applicable)
+3. Commit your change
+4. Write tests for your change if applicable
 5. Run the tests, ensuring they all pass
-6. Submit a Pull Request using Github
+6. Submit a [Pull 
Request](https://github.com/apache/cloudstack-cloudmonkey/pull/new/master) 
using Github
+
+### History
+
+`cloudmonkey` was originally written in Python and contributed to Apache 
CloudStack
+project by [Rohit Yadav](http://rohityadav.cloud) on 31 Oct 2012 under the 
Apache
+License 2.0. The original tool is also referred to as legacy cloudmonkey can
+be installed using `pip install cloudmonkey`.
 
+Starting version 6.0.0, referred to as the modern cloudmonkey, is a simplified
+Go port of the original tool and ships as a standalone executable for several
+targets such as Linux, Mac and Windows.
 
 ### License
 
@@ -122,4 +88,4 @@ 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.
\ No newline at end of file
+under the License.
diff --git a/cli/completer.go b/cli/completer.go
new file mode 100644
index 0000000..ced72f4
--- /dev/null
+++ b/cli/completer.go
@@ -0,0 +1,165 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cli
+
+import (
+       "fmt"
+       "sort"
+       "strings"
+       "unicode"
+
+       "../cmd"
+       "../config"
+
+       "github.com/rhtyd/readline/runes"
+)
+
+type CliCompleter struct {
+       Config *config.Config
+}
+
+var completer *CliCompleter
+
+func TrimSpaceLeft(in []rune) []rune {
+       firstIndex := len(in)
+       for i, r := range in {
+               if unicode.IsSpace(r) == false {
+                       firstIndex = i
+                       break
+               }
+       }
+       return in[firstIndex:]
+}
+
+func doInternal(line []rune, pos int, lineLen int, argName []rune) (newLine 
[][]rune, offset int) {
+       offset = lineLen
+       if lineLen >= len(argName) {
+               if runes.HasPrefix(line, argName) {
+                       if lineLen == len(argName) {
+                               newLine = append(newLine, []rune{' '})
+                       } else {
+                               newLine = append(newLine, argName)
+                       }
+                       offset = offset - len(argName) - 1
+               }
+       } else {
+               if runes.HasPrefix(argName, line) {
+                       newLine = append(newLine, argName[offset:])
+               }
+       }
+       return
+}
+
+func (t *CliCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) 
{
+
+       line = TrimSpaceLeft(line[:pos])
+       lineLen := len(line)
+
+       apiCache := t.Config.GetCache()
+       apiMap := make(map[string][]*config.Api)
+       for api := range apiCache {
+               verb := apiCache[api].Verb
+               apiMap[verb] = append(apiMap[verb], apiCache[api])
+       }
+
+       for _, cmd := range cmd.AllCommands() {
+               verb := cmd.Name
+               if cmd.SubCommands != nil && len(cmd.SubCommands) > 0 {
+                       for _, scmd := range cmd.SubCommands {
+                               dummyApi := &config.Api{
+                                       Name: scmd,
+                                       Verb: verb,
+                               }
+                               apiMap[verb] = append(apiMap[verb], dummyApi)
+                       }
+               } else {
+                       dummyApi := &config.Api{
+                               Name: "",
+                               Verb: verb,
+                       }
+                       apiMap[verb] = append(apiMap[verb], dummyApi)
+               }
+       }
+
+       var verbs []string
+       for verb := range apiMap {
+               verbs = append(verbs, verb)
+               sort.Slice(apiMap[verb], func(i, j int) bool {
+                       return apiMap[verb][i].Name < apiMap[verb][j].Name
+               })
+       }
+       sort.Strings(verbs)
+
+       var verbsFound []string
+       for _, verb := range verbs {
+               search := verb + " "
+               if !runes.HasPrefix(line, []rune(search)) {
+                       sLine, sOffset := doInternal(line, pos, lineLen, 
[]rune(search))
+                       newLine = append(newLine, sLine...)
+                       offset = sOffset
+               } else {
+                       verbsFound = append(verbsFound, verb)
+               }
+       }
+
+       apiArg := false
+       for _, verbFound := range verbsFound {
+               search := verbFound + " "
+
+               nLine := TrimSpaceLeft(line[len(search):])
+               offset = lineLen - len(verbFound) - 1
+
+               for _, api := range apiMap[verbFound] {
+                       resource := 
strings.TrimPrefix(strings.ToLower(api.Name), verbFound)
+                       search = resource + " "
+
+                       if runes.HasPrefix(nLine, []rune(search)) {
+                               // FIXME: handle params to API here with = stuff
+                               for _, arg := range api.Args {
+                                       opt := arg.Name + "="
+                                       newLine = append(newLine, []rune(opt))
+                               }
+                               if string(nLine[len(nLine)-1]) == "=" {
+                                       apiArg = true
+                               }
+                               offset = lineLen - len(verbFound) - 
len(resource) - 1
+                       } else {
+                               sLine, _ := doInternal(nLine, pos, len(nLine), 
[]rune(search))
+                               newLine = append(newLine, sLine...)
+                       }
+               }
+       }
+
+       // FIXME: pass selector uuid options
+       if apiArg {
+               fmt.Println()
+               option := ShowSelector()
+               // show only one option in autocompletion
+               newLine = [][]rune{[]rune(option)}
+               offset = 0
+       }
+
+       return newLine, offset
+}
+
+func NewCompleter(cfg *config.Config) *CliCompleter {
+       completer = &CliCompleter{
+               Config: cfg,
+       }
+       return completer
+}
diff --git a/cli/exec.go b/cli/exec.go
new file mode 100644
index 0000000..99b1735
--- /dev/null
+++ b/cli/exec.go
@@ -0,0 +1,38 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cli
+
+import (
+       "../cmd"
+       "../config"
+       "github.com/rhtyd/readline"
+)
+
+func ExecCmd(cfg *config.Config, args []string, shell *readline.Instance) 
error {
+       if len(args) < 1 {
+               return nil
+       }
+
+       command := cmd.FindCommand(args[0])
+       if command != nil {
+               return command.Handle(cmd.NewRequest(command, cfg, shell, 
args[1:]))
+       }
+
+       catchAllHandler := cmd.GetAPIHandler()
+       return catchAllHandler.Handle(cmd.NewRequest(catchAllHandler, cfg, 
shell, args))
+}
diff --git a/cli/selector.go b/cli/selector.go
new file mode 100644
index 0000000..209cb56
--- /dev/null
+++ b/cli/selector.go
@@ -0,0 +1,90 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cli
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/manifoldco/promptui"
+       "github.com/rhtyd/readline"
+)
+
+type SelectOptions struct {
+       Name   string
+       Id     string
+       Detail string
+}
+
+func ShowSelector() string {
+       options := []SelectOptions{
+               {Name: "Option1", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option2", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option3", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option4", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option5", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option6", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option7", Id: "some-uuid", Detail: "Some Detail"},
+               {Name: "Option8", Id: "some-uuid", Detail: "Some Detail"},
+       }
+
+       templates := &promptui.SelectTemplates{
+               Label:    "{{ . }}?",
+               Active:   "🐵 {{ .Name | cyan }} ({{ .Id | red }})",
+               Inactive: "  {{ .Name | cyan }} ({{ .Id | red }})",
+               Selected: "Selected: {{ .Name | cyan }} ({{ .Id | red }})",
+               Details: `
+--------- Current Selection ----------
+{{ "Name:" | faint }} {{ .Name }}
+{{ "Id:" | faint }}  {{ .Id }}
+{{ "Detail:" | faint }}  {{ .Detail }}`,
+       }
+
+       searcher := func(input string, index int) bool {
+               pepper := options[index]
+               name := strings.Replace(strings.ToLower(pepper.Name), " ", "", 
-1)
+               input = strings.Replace(strings.ToLower(input), " ", "", -1)
+
+               return strings.Contains(name, input)
+       }
+
+       prompt := promptui.Select{
+               Label:     "Use the arrow keys to navigate: ↓ ↑ → ←  and / 
toggles search",
+               Items:     options,
+               Templates: templates,
+               Size:      5,
+               Searcher:  searcher,
+               StartInSearchMode: true,
+               Keys: &promptui.SelectKeys{
+                       Prev:     promptui.Key{Code: readline.CharPrev, 
Display: "↑"},
+                       Next:     promptui.Key{Code: readline.CharNext, 
Display: "↓"},
+                       PageUp:   promptui.Key{Code: readline.CharBackward, 
Display: "←"},
+                       PageDown: promptui.Key{Code: readline.CharForward, 
Display: "→"},
+                       Search:   promptui.Key{Code: '/', Display: "/"},
+               },
+       }
+
+       i, _, err := prompt.Run()
+
+       if err != nil {
+               fmt.Printf("Prompt failed %v\n", err)
+               return ""
+       }
+
+       return options[i].Id
+}
\ No newline at end of file
diff --git a/cli/shell.go b/cli/shell.go
new file mode 100644
index 0000000..9477db6
--- /dev/null
+++ b/cli/shell.go
@@ -0,0 +1,87 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cli
+
+import (
+       "fmt"
+       "io"
+       "strings"
+
+       "../config"
+       "github.com/mattn/go-shellwords"
+
+       "github.com/rhtyd/readline"
+)
+
+func ExecShell(cfg *config.Config) {
+       shell, err := readline.NewEx(&readline.Config{
+               Prompt:            cfg.GetPrompt(),
+               HistoryFile:       cfg.HistoryFile,
+               AutoComplete:      NewCompleter(cfg),
+               InterruptPrompt:   "^C",
+               EOFPrompt:         "exit",
+               VimMode:           false,
+               HistorySearchFold: true,
+               FuncFilterInputRune: func(r rune) (rune, bool) {
+                       switch r {
+                       case readline.CharCtrlZ:
+                               return r, false
+                       }
+                       return r, true
+               },
+       })
+
+       if err != nil {
+               panic(err)
+       }
+       defer shell.Close()
+
+       cfg.PrintHeader()
+
+       for {
+               line, err := shell.Readline()
+               if err == readline.ErrInterrupt {
+                       continue
+               } else if err == io.EOF {
+                       break
+               }
+
+               line = strings.TrimSpace(line)
+               if len(line) < 1 {
+                       continue
+               }
+
+               shellwords.ParseEnv = true
+               parser := shellwords.NewParser()
+               args, err := parser.Parse(line)
+               if err != nil {
+                       fmt.Println("Failed to parse line:", err)
+                       continue
+               }
+
+               if parser.Position > 0 {
+                       line = fmt.Sprintf("shell %s %v", cfg.Name(), line)
+                       args = strings.Split(line, " ")
+               }
+
+               err = ExecCmd(cfg, args, shell)
+               if err != nil {
+                       fmt.Println("Error:", err)
+               }
+       }
+}
diff --git a/cmd/api.go b/cmd/api.go
new file mode 100644
index 0000000..57a34e0
--- /dev/null
+++ b/cmd/api.go
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "encoding/json"
+       "errors"
+       "fmt"
+       "strings"
+
+)
+
+var apiCommand *Command
+
+func GetAPIHandler() *Command {
+       return apiCommand
+}
+
+func init() {
+       apiCommand = &Command{
+               Name: "api",
+               Help: "Runs a provided API",
+               Handle: func(r *Request) error {
+                       if len(r.Args) == 0 {
+                               return errors.New("please provide an API to 
execute")
+                       }
+
+                       apiName := strings.ToLower(r.Args[0])
+                       apiArgs := r.Args[1:]
+                       if r.Config.GetCache()[apiName] == nil && len(r.Args) > 
1 {
+                               apiName = 
strings.ToLower(strings.Join(r.Args[:2], ""))
+                               apiArgs = r.Args[2:]
+                       }
+
+                       api := r.Config.GetCache()[apiName]
+                       if api == nil {
+                               return errors.New("unknown or unauthorized API: 
" + apiName)
+                       }
+
+                       b, _ := NewAPIRequest(r, api.Name, apiArgs)
+                       response, _ := json.MarshalIndent(b, "", "  ")
+
+                       // Implement various output formats
+                       fmt.Println(string(response))
+                       return nil
+               },
+       }
+       AddCommand(apiCommand)
+}
diff --git a/cmd/command.go b/cmd/command.go
new file mode 100644
index 0000000..b631d6c
--- /dev/null
+++ b/cmd/command.go
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "fmt"
+)
+
+type Command struct {
+       Name            string
+       Help            string
+       SubCommands     []string
+       CustomCompleter func(input string, position int)
+       Handle          func(*Request) error
+}
+
+var commands []*Command
+var commandMap map[string]*Command
+
+func FindCommand(name string) *Command {
+       return commandMap[name]
+}
+
+func AllCommands() []*Command {
+       return commands
+}
+
+func AddCommand(cmd *Command) {
+       commands = append(commands, cmd)
+       if commandMap == nil {
+               commandMap = make(map[string]*Command)
+       }
+       commandMap[cmd.Name] = cmd
+}
+
+func PrintUsage() {
+       commandHelp := ""
+       for _, cmd := range commands {
+               commandHelp += fmt.Sprintf("%s\t\t%s\n", cmd.Name, cmd.Help)
+       }
+       fmt.Printf(`usage: cmk [options] [commands]
+
+Command Line Interface for Apache CloudStack
+
+default commands:
+%s
+
+Try cmk [help]`, commandHelp)
+}
diff --git a/cmd/exit.go b/cmd/exit.go
new file mode 100644
index 0000000..104c5b2
--- /dev/null
+++ b/cmd/exit.go
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "os"
+
+       "github.com/manifoldco/promptui"
+)
+
+func init() {
+       AddCommand(&Command{
+               Name: "exit",
+               Help: "Exits",
+               Handle: func(r *Request) error {
+                       prompt := promptui.Prompt{
+                               Label:     "Do you really want to exit 
([y]/n)?",
+                               IsConfirm: true,
+                       }
+
+                       if result, _ := prompt.Run(); result == "y" {
+                               os.Exit(0)
+                       }
+                       return nil
+               },
+       })
+}
diff --git a/cmd/help.go b/cmd/help.go
new file mode 100644
index 0000000..e26dc79
--- /dev/null
+++ b/cmd/help.go
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "fmt"
+)
+
+func init() {
+       AddCommand(&Command{
+               Name: "help",
+               Help: "Help",
+               Handle: func(r *Request) error {
+                       if len(r.Args) < 1 {
+                               PrintUsage()
+                               return nil
+                       }
+                       fmt.Println("FIXME: add cmd help docs")
+                       return nil
+               },
+       })
+}
diff --git a/cmd/login.go b/cmd/login.go
new file mode 100644
index 0000000..a2ace48
--- /dev/null
+++ b/cmd/login.go
@@ -0,0 +1,74 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "errors"
+       "fmt"
+
+       "github.com/manifoldco/promptui"
+)
+
+func init() {
+       AddCommand(&Command{
+               Name: "login",
+               Help: "Log in to your account",
+               Handle: func(r *Request) error {
+                       if len(r.Args) > 0 {
+                               return errors.New("this does not accept any 
additional arguments")
+                       }
+
+                       validate := func(input string) error {
+                               if len(input) < 1 {
+                                       return errors.New("You have not entered 
anything")
+                               }
+                               return nil
+                       }
+
+                       prompt := promptui.Prompt{
+                               Label:    "Username",
+                               Validate: validate,
+                               Default:  "",
+                       }
+
+                       username, err := prompt.Run()
+                       if err != nil {
+                               fmt.Printf("Prompt failed %v\n", err)
+                               return nil
+                       }
+
+                       prompt = promptui.Prompt{
+                               Label:    "Password",
+                               Validate: validate,
+                               Mask:     '*',
+                       }
+
+                       password, err := prompt.Run()
+
+                       if err != nil {
+                               fmt.Printf("Prompt failed %v\n", err)
+                               return nil
+                       }
+
+                       // TODO: implement login based key setup workflow
+                       fmt.Println("Trying to log in using", username, 
password)
+
+                       return nil
+               },
+       })
+}
diff --git a/cmd/network.go b/cmd/network.go
new file mode 100644
index 0000000..97ccc42
--- /dev/null
+++ b/cmd/network.go
@@ -0,0 +1,106 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "bytes"
+       "crypto/hmac"
+       "crypto/sha1"
+       "encoding/base64"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "net/url"
+       "sort"
+       "strings"
+
+)
+
+func encodeRequestParams(params url.Values) string {
+       if params == nil {
+               return ""
+       }
+
+       keys := make([]string, 0, len(params))
+       for key := range params {
+               keys = append(keys, key)
+       }
+       sort.Strings(keys)
+
+       var buf bytes.Buffer
+       for _, key := range keys {
+               value := params.Get(key)
+               if buf.Len() > 0 {
+                       buf.WriteByte('&')
+               }
+               buf.WriteString(key)
+               buf.WriteString("=")
+               buf.WriteString(url.QueryEscape(value))
+       }
+       return buf.String()
+}
+
+func NewAPIRequest(r *Request, api string, args []string) 
(map[string]interface{}, error) {
+       fmt.Println("[debug] Running api:", api, args)
+
+       params := make(url.Values)
+       params.Add("command", api)
+       for _, arg := range args {
+               parts := strings.Split(arg, "=")
+               if len(parts) == 2 {
+                       params.Add(parts[0], parts[1])
+               }
+       }
+
+       apiKey := r.Config.ActiveProfile.ApiKey
+       secretKey := r.Config.ActiveProfile.SecretKey
+
+       if len(apiKey) > 0 {
+               params.Add("apiKey", apiKey)
+       }
+
+       params.Add("response", "json")
+       encodedParams := encodeRequestParams(params)
+
+       mac := hmac.New(sha1.New, []byte(secretKey))
+       mac.Write([]byte(strings.Replace(strings.ToLower(encodedParams), "+", 
"%20", -1)))
+       signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
+       encodedParams = encodedParams + fmt.Sprintf("&signature=%s", 
url.QueryEscape(signature))
+
+       apiUrl := fmt.Sprintf("%s?%s", r.Config.ActiveProfile.Url, 
encodedParams)
+
+       fmt.Println("[debug] Requesting: ", apiUrl)
+       response, err := http.Get(apiUrl)
+       if err != nil {
+               fmt.Println("Error:", err)
+               return nil, err
+       }
+       body, _ := ioutil.ReadAll(response.Body)
+
+       var data map[string]interface{}
+       _ = json.Unmarshal([]byte(body), &data)
+
+       for k := range data {
+               if strings.HasSuffix(k, "response") {
+                       return data[k].(map[string]interface{}), nil
+               }
+       }
+       return nil, errors.New("failed to decode response")
+}
diff --git a/cmd/request.go b/cmd/request.go
new file mode 100644
index 0000000..294dfd7
--- /dev/null
+++ b/cmd/request.go
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "../config"
+       "github.com/rhtyd/readline"
+)
+
+type Request struct {
+       Command *Command
+       Config  *config.Config
+       Shell   *readline.Instance
+       Args    []string
+}
+
+func NewRequest(cmd *Command, cfg *config.Config, shell *readline.Instance, 
args []string) *Request {
+       return &Request{
+               Command: cmd,
+               Config:  cfg,
+               Shell:   shell,
+               Args:    args,
+       }
+}
diff --git a/cmd/set.go b/cmd/set.go
new file mode 100644
index 0000000..2e716e9
--- /dev/null
+++ b/cmd/set.go
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "fmt"
+       "strings"
+)
+
+func init() {
+       AddCommand(&Command{
+               Name:        "set",
+               Help:        "Configures options for cmk",
+               SubCommands: []string{"block", "output", "profile"},
+               Handle: func(r *Request) error {
+                       if len(r.Args) < 1 {
+                               fmt.Println("Please select one of the 
sub-commands: ", strings.Join(r.Command.SubCommands, ", "))
+                               return nil
+                       }
+                       subCommand := r.Args[0]
+                       value := strings.Join(r.Args[1:], " ")
+                       r.Config.UpdateGlobalConfig(subCommand, value)
+
+                       if subCommand == "profile" && r.Shell != nil {
+                               r.Shell.SetPrompt(r.Config.GetPrompt())
+                       }
+                       return nil
+               },
+       })
+}
diff --git a/cmd/shell.go b/cmd/shell.go
new file mode 100644
index 0000000..7bbbfcd
--- /dev/null
+++ b/cmd/shell.go
@@ -0,0 +1,44 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "errors"
+       "fmt"
+       "os/exec"
+       "strings"
+)
+
+func init() {
+       AddCommand(&Command{
+               Name: "shell",
+               Help: "Drops into a shell",
+               Handle: func(r *Request) error {
+                       cmd := strings.TrimSpace(strings.Join(r.Args, " "))
+                       if len(cmd) < 1 {
+                               return errors.New("no shell command provided")
+                       }
+                       out, err := exec.Command("bash", "-c", cmd).Output()
+                       if err == nil {
+                               fmt.Println(string(out))
+                               return nil
+                       }
+                       return errors.New("failed to execute command, " + 
err.Error())
+               },
+       })
+}
diff --git a/cmd/sync.go b/cmd/sync.go
new file mode 100644
index 0000000..599d304
--- /dev/null
+++ b/cmd/sync.go
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import (
+       "fmt"
+
+)
+
+func init() {
+       AddCommand(&Command{
+               Name: "sync",
+               Help: "Discovers and updates APIs",
+               Handle: func(r *Request) error {
+                       response, err := NewAPIRequest(r, "listApis", 
[]string{"listall=true"})
+                       if err != nil {
+                               return err
+                       }
+                       fmt.Printf("Discovered %v APIs\n", 
r.Config.UpdateCache(response))
+                       r.Config.SaveCache(response)
+                       return nil
+               },
+       })
+}
diff --git a/cmd/version.go b/cmd/version.go
new file mode 100644
index 0000000..72fde90
--- /dev/null
+++ b/cmd/version.go
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 cmd
+
+import "fmt"
+
+func init() {
+       AddCommand(&Command{
+               Name: "version",
+               Help: "Version info",
+               Handle: func(r *Request) error {
+                       fmt.Println("Apache CloudStack 🐵 cloudmonkey", 
r.Config.Version())
+                       return nil
+               },
+       })
+}
diff --git a/cmk.go b/cmk.go
new file mode 100644
index 0000000..0febd0f
--- /dev/null
+++ b/cmk.go
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 main
+
+import (
+       "os"
+
+       "./config"
+       "./cli"
+)
+
+func main() {
+       args := os.Args[1:]
+       cfg := config.NewConfig()
+       if len(args) > 0 {
+               cli.ExecCmd(cfg, args, nil)
+       } else {
+               cli.ExecShell(cfg)
+       }
+}
diff --git a/config/cache.go b/config/cache.go
new file mode 100644
index 0000000..3814669
--- /dev/null
+++ b/config/cache.go
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 config
+
+import (
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "strings"
+       "unicode"
+)
+
+type ApiArg struct {
+       Name        string
+       Description string
+       Required    bool
+       Length      int
+       Type        string
+       Related     []string
+}
+
+type Api struct {
+       Name         string
+       ResponseName string
+       Description  string
+       Async        bool
+       Related      []string
+       Args         []*ApiArg
+       RequiredArgs []*ApiArg
+       Verb         string
+}
+
+var apiCache map[string]*Api
+
+func (c *Config) GetCache() map[string]*Api {
+       if apiCache == nil {
+               // read from disk?
+               return make(map[string]*Api)
+       }
+       return apiCache
+}
+
+func LoadCache(c *Config) {
+       cache, err := ioutil.ReadFile(c.CacheFile)
+       if err != nil {
+               fmt.Println("Please run sync. Failed to read the cache file: " 
+ c.CacheFile)
+               return
+       }
+       var data map[string]interface{}
+       _ = json.Unmarshal(cache, &data)
+       c.UpdateCache(data)
+}
+
+func (c *Config) SaveCache(response map[string]interface{}) {
+       output, _ := json.Marshal(response)
+       ioutil.WriteFile(c.CacheFile, output, 0600)
+}
+
+func (c *Config) UpdateCache(response map[string]interface{}) interface{} {
+       apiCache = make(map[string]*Api)
+
+       count := response["count"]
+       apiList := response["api"].([]interface{})
+
+       for _, node := range apiList {
+               api, valid := node.(map[string]interface{})
+               if !valid {
+                       //fmt.Println("Errro, moving on")
+                       continue
+               }
+               apiName := api["name"].(string)
+               isAsync := api["isasync"].(bool)
+
+               idx := 0
+               for _, chr := range apiName {
+                       if unicode.IsLower(chr) {
+                               idx++
+                       } else {
+                               break
+                       }
+               }
+               verb := apiName[:idx]
+
+               var apiArgs []*ApiArg
+               for _, argNode := range api["params"].([]interface{}) {
+                       apiArg, _ := argNode.(map[string]interface{})
+                       apiArgs = append(apiArgs, &ApiArg{
+                               Name:     apiArg["name"].(string),
+                               Type:     apiArg["type"].(string),
+                               Required: apiArg["required"].(bool),
+                       })
+               }
+
+               apiCache[strings.ToLower(apiName)] = &Api{
+                       Name:  apiName,
+                       Async: isAsync,
+                       Args:  apiArgs,
+                       Verb:  verb,
+               }
+       }
+       return count
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..06c5393
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,100 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 config
+
+import (
+       "os"
+       "path"
+)
+
+type OutputFormat string
+
+const (
+       Json  OutputFormat = "json"
+       Xml   OutputFormat = "xml"
+       Table OutputFormat = "table"
+       Text  OutputFormat = "text"
+)
+
+type Profile struct {
+       Name       string
+       Url        string
+       VerifyCert bool
+       Username   string
+       Password   string
+       Domain     string
+       ApiKey     string
+       SecretKey  string
+}
+
+type Config struct {
+       Dir           string
+       ConfigFile    string
+       HistoryFile   string
+       CacheFile     string
+       LogFile       string
+       Output        OutputFormat
+       AsyncBlock    bool
+       ActiveProfile Profile
+}
+
+func NewConfig() *Config {
+       return loadConfig()
+}
+
+func defaultConfig() *Config {
+       configDir := getDefaultConfigDir()
+       return &Config{
+               Dir:         configDir,
+               ConfigFile:  path.Join(configDir, "config"),
+               HistoryFile: path.Join(configDir, "history"),
+               CacheFile:   path.Join(configDir, "cache"),
+               LogFile:     path.Join(configDir, "log"),
+               Output:      Json,
+               AsyncBlock:  true,
+               ActiveProfile: Profile{
+                       Name:       "local",
+                       Url:        "http://192.168.1.10:8080/client/api";,
+                       VerifyCert: false,
+                       Username:   "admin",
+                       Password:   "password",
+                       Domain:     "/",
+                       // TODO: remove test data
+                       ApiKey:    
"IgrUOA_46IVoBNzAR_Th2JbdbgIs2lMW1kGe9A80F9X0uOnfGO0Su23IqOSqbdzZW3To95PNrcdWsk60ieXYBQ",
+                       SecretKey: 
"E7NRSv5d_1VhqXUHJEqvAsm7htR_V_vtPJZsCPkgPKSgkiS3sh4SOrIqMm_eWhSFoL6RHRIlxtA_viQAt7EDVA",
+               },
+       }
+}
+
+func loadConfig() *Config {
+       cfg := defaultConfig()
+
+       if _, err := os.Stat(cfg.Dir); err != nil {
+               os.Mkdir(cfg.Dir, 0700)
+       }
+
+       if _, err := os.Stat(cfg.ConfigFile); err != nil {
+               // FIXME: write default cfg
+       } else {
+               //load config?
+       }
+
+       LoadCache(cfg)
+
+       return cfg
+}
diff --git a/config/util.go b/config/util.go
new file mode 100644
index 0000000..7421e5b
--- /dev/null
+++ b/config/util.go
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 config
+
+import (
+       "fmt"
+       "os"
+       "path"
+
+       "github.com/mitchellh/go-homedir"
+)
+
+var name = "cloudmonkey"
+var version = "6.0.0-alpha1"
+
+func getDefaultConfigDir() string {
+       home, err := homedir.Dir()
+       if err != nil {
+               fmt.Println(err)
+               os.Exit(1)
+       }
+       return path.Join(home, ".cmk")
+}
+
+func (c *Config) Name() string {
+       return name
+}
+
+func (c *Config) Version() string {
+       return version
+}
+
+func (c *Config) PrintHeader() {
+       fmt.Printf("Apache CloudStack 🐵 cloudmonkey %s.\n", version)
+       fmt.Printf("Type \"help\" for details, \"sync\" to update API cache or 
press tab to list commands.\n\n")
+}
+
+func (c *Config) GetPrompt() string {
+       return fmt.Sprintf("(%s) \033[34m🐵\033[0m > ", c.ActiveProfile.Name)
+}
+
+func (c *Config) UpdateGlobalConfig(key string, value string) {
+       c.UpdateConfig("", key, value)
+}
+
+func (c *Config) UpdateConfig(namespace string, key string, value string) {
+       fmt.Println("Updating for key", key, ", value=", value, ", in ns=", 
namespace)
+       if key == "profile" {
+               //FIXME
+               c.ActiveProfile.Name = value
+       }
+}

-- 
To stop receiving notification emails like this one, please contact
ro...@apache.org.

Reply via email to