f79395458d2f367d836333101fa058373afd2355 — Louis Solofrizzo a month ago 2517568
api, sdk, csc: Add instance list command and route

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>
M api/CMakeLists.txt => api/CMakeLists.txt +2 -0
@@ 9,4 9,6 @@ quotas.go
      admin.go
      ssh.go
+     instance.go
+     cron.go
  )

M api/admin.go => api/admin.go +1 -1
@@ 198,7 198,7 @@ result.Instances = append(result.Instances, cisco.Instance{
  			Uuid:         instance.Uuid,
  			Name:         instance.Name,
- 			Ipv4:         instance.Ipv4,
+ 			Ipv4:         "instance.Ipv4",
  			Size:         instance.Size,
  			OS:           instance.OS,
  			Architecture: instance.Architecture,

M api/config.go => api/config.go +31 -0
@@ 1,6 1,7 @@ package main
  
  import (
+ 	"github.com/lxc/lxd/client"
  	"gopkg.in/yaml.v2"
  	"io/ioutil"
  )


@@ 14,10 15,15 @@ SMTPPassword string `yaml:"smtp_password"`
  	Host         string `yaml:"host"`
  	Salt         string `yaml:"salt"`
+ 	LXDCert      string `yaml:"lxd_cert"`
+ 	LXDKey       string `yaml:"lxd_key"`
+ 	LXDCA        string `yaml:"lxd_ca"`
+ 	LXDUrl       string `yaml:"lxd_url"`
  	HTTPHost     string
  }
  
  type CiscoContext struct {
+ 	LXD lxd.InstanceServer
  }
  
  var Config CiscoConfig


@@ 35,4 41,29 @@ }
  
  	Config.HTTPHost = Config.Host
+ 
+ 	crt, err := ioutil.ReadFile(Config.LXDCert)
+ 	if err != nil {
+ 		panic("Cannot read client certificate: " + err.Error())
+ 	}
+ 
+ 	key, err := ioutil.ReadFile(Config.LXDKey)
+ 	if err != nil {
+ 		panic("Cannot read client key: " + err.Error())
+ 	}
+ 
+ 	ca, err := ioutil.ReadFile(Config.LXDCA)
+ 	if err != nil {
+ 		panic("Cannot read cluster certificate: " + err.Error())
+ 	}
+ 
+ 	Context.LXD, err = lxd.ConnectLXD(Config.LXDUrl, &lxd.ConnectionArgs{
+ 		TLSServerCert: string(ca[:]),
+ 		TLSClientCert: string(crt[:]),
+ 		TLSClientKey:  string(key[:]),
+ 	})
+ 
+ 	if err != nil {
+ 		panic("Cannot connect to the LXD cluster: " + err.Error())
+ 	}
  }

A api/cron.go => api/cron.go +59 -0
@@ 0,0 1,59 @@
+ package main
+ 
+ import (
+ 	"fmt"
+ 	"github.com/lxc/lxd/shared/api"
+ )
+ 
+ func AddIpv4ToInstances() {
+ 	var entries []DbIpv4
+ 
+ 	DB.Find(&entries)
+ 	for _, ip := range entries {
+ 		found := false
+ 		inst, _, _ := Context.LXD.GetInstanceState("LF" + ip.Instance)
+ 		for _, net := range inst.Network {
+ 			for _, addr := range net.Addresses {
+ 				if addr.Address == ip.IP {
+ 					found = true
+ 				}
+ 			}
+ 		}
+ 
+ 		if !found {
+ 			op, err := Context.LXD.ExecContainer("LF"+ip.Instance, api.ContainerExecPost{
+ 				Command: []string{"/usr/bin/ip", "addr", "add", ip.IP + "/32", "dev", "eth0"},
+ 			}, nil)
+ 
+ 			if err != nil {
+ 				fmt.Printf("Error: %s", err.Error())
+ 			}
+ 
+ 			err = op.Wait()
+ 			if err != nil {
+ 				fmt.Printf("Error: %s", err.Error())
+ 			}
+ 		}
+ 	}
+ }
+ 
+ func SetHostnameToInstance() {
+ 	var entries []DbInstance
+ 
+ 	DB.Find(&entries)
+ 	for _, instance := range entries {
+ 		op, err := Context.LXD.ExecContainer("LF"+instance.Uuid, api.ContainerExecPost{
+ 			Command: []string{"/usr/bin/hostname", instance.Name},
+ 		}, nil)
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s", err.Error())
+ 		}
+ 
+ 		err = op.Wait()
+ 		if err != nil {
+ 			fmt.Printf("Error: %s", err.Error())
+ 		}
+ 
+ 	}
+ }

A api/instance.go => api/instance.go +49 -0
@@ 0,0 1,49 @@
+ package main
+ 
+ import (
+ 	"cisco/sdk"
+ 	"github.com/kataras/iris"
+ 	"strings"
+ )
+ 
+ func instance_list(ctx iris.Context) {
+ 	var entries []DbInstance
+ 	var result []cisco.Instance
+ 	var ipv6 string
+ 
+ 	user := ctx.Values().Get("User")
+ 	DB.Find(&entries, "user = ?", user)
+ 
+ 	for _, instance := range entries {
+ 		var ipv4 DbIpv4
+ 
+ 		DB.Find(&ipv4, "instance = ?", instance.Uuid)
+ 		inst, _, _ := Context.LXD.GetInstanceState("LF" + instance.Uuid)
+ 
+ 		for _, net := range inst.Network {
+ 			for _, addr := range net.Addresses {
+ 				if addr.Family == "inet6" && !strings.HasPrefix(addr.Address, "fe80") && addr.Scope != "local" {
+ 					ipv6 = addr.Address
+ 					break
+ 				}
+ 			}
+ 		}
+ 
+ 		result = append(result, cisco.Instance{
+ 			Uuid:         instance.Uuid,
+ 			Name:         instance.Name,
+ 			Size:         instance.Size,
+ 			OS:           instance.OS,
+ 			Architecture: instance.Architecture,
+ 			Ipv4:         ipv4.IP,
+ 			Status:       inst.Status,
+ 			Ipv6:         ipv6,
+ 		})
+ 	}
+ 
+ 	ctx.JSON(&result)
+ }
+ 
+ func InitInstanceRoutes(app *iris.Application) {
+ 	app.Get("/instance", APIAuth, instance_list)
+ }

M api/main.go => api/main.go +7 -0
@@ 9,6 9,7 @@ "flag"
  	"fmt"
  	"github.com/kataras/iris"
+ 	"github.com/robfig/cron"
  	"github.com/satori/go.uuid"
  	"os"
  )


@@ 146,6 147,12 @@ InitTokenRoutes(app)
  	InitAdminRoutes(app)
  	InitSSHRoutes(app)
+ 	InitInstanceRoutes(app)
+ 
+ 	c := cron.New()
+ 	c.AddFunc("@every 1m", AddIpv4ToInstances)
+ 	c.AddFunc("@every 1m", SetHostnameToInstance)
+ 	c.Start()
  
  	app.Run(iris.Addr(Config.Address))
  }

M csc/cmd/admin/user/get.go => csc/cmd/admin/user/get.go +2 -2
@@ 51,7 51,7 @@ if quota.Storage == -1 {
  				fmt.Printf("    Storage   : %d / Infinite\n", storage)
  			} else {
- 				fmt.Printf("    Storage   : %d / %dGb\n", storage, quota.Storage)
+ 				fmt.Printf("    Storage   : %d / %dGB\n", storage, quota.Storage)
  			}
  
  			if quota.Invites == -1 {


@@ 73,7 73,7 @@ for _, instance := range userinfo.Instances {
  			fmt.Printf("    %s:\n", instance.Uuid)
  			fmt.Printf("        Name         : %s\n", instance.Name)
- 			fmt.Printf("        Size         : %dGb\n", instance.Size)
+ 			fmt.Printf("        Size         : %dGB\n", instance.Size)
  			fmt.Printf("        OS           : %s\n", instance.OS)
  			fmt.Printf("        Architecture : %s\n", instance.Architecture)
  			fmt.Printf("        Type         : %s\n", instance.Type)

A csc/cmd/instance.go => csc/cmd/instance.go +14 -0
@@ 0,0 1,14 @@
+ package cmd
+ 
+ import (
+ 	"github.com/spf13/cobra"
+ )
+ 
+ var InstanceRootCmd = &cobra.Command{
+ 	Use:   "instance",
+ 	Short: "Utilities for instance",
+ }
+ 
+ func init() {
+ 	RootCmd.AddCommand(InstanceRootCmd)
+ }

A csc/cmd/instance/list.go => csc/cmd/instance/list.go +36 -0
@@ 0,0 1,36 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/olekukonko/tablewriter"
+ 	"github.com/spf13/cobra"
+ 	"os"
+ 	"strconv"
+ )
+ 
+ var instanceList = &cobra.Command{
+ 	Use:   "list",
+ 	Short: "List Instances",
+ 	Args:  cobra.ExactArgs(0),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		instances, err := cisco.ListInstance()
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 			return
+ 		}
+ 
+ 		table := tablewriter.NewWriter(os.Stdout)
+ 		table.SetHeader([]string{"Name", "Status", "Ipv4", "Ipv6", "Size"})
+ 		for _, instance := range instances {
+ 			table.Append([]string{instance.Name, instance.Status, instance.Ipv4, instance.Ipv6, strconv.FormatInt(int64(instance.Size), 10) + "GB"})
+ 		}
+ 		table.Render()
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceList)
+ }

M csc/main.go => csc/main.go +1 -0
@@ 6,6 6,7 @@ _ "cisco/csc/cmd/admin/quotas"
  	_ "cisco/csc/cmd/admin/quotas/update"
  	_ "cisco/csc/cmd/admin/user"
+ 	_ "cisco/csc/cmd/instance"
  	_ "cisco/csc/cmd/invite"
  	_ "cisco/csc/cmd/ssh"
  	_ "cisco/csc/cmd/token"

M go.mod => go.mod +10 -0
@@ 13,17 13,20 @@ github.com/fatih/structs v1.1.0 // indirect
  	github.com/flosch/pongo2 v0.0.0-20181225140029-79872a7b2769 // indirect
  	github.com/go-xorm/xorm v0.7.1
+ 	github.com/gorilla/websocket v1.4.1 // indirect
  	github.com/iris-contrib/blackfriday v2.0.0+incompatible // indirect
  	github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1 // indirect
  	github.com/iris-contrib/go.uuid v2.0.0+incompatible // indirect
  	github.com/iris-contrib/middleware v0.0.0-20181021162728-8bd5d51b3c2e
  	github.com/jinzhu/gorm v1.9.10
  	github.com/json-iterator/go v1.1.5 // indirect
+ 	github.com/juju/webbrowser v0.0.0-20180907093207-efb9432b2bcb // indirect
  	github.com/kataras/golog v0.0.0-20180321173939-03be10146386 // indirect
  	github.com/kataras/iris v11.1.1+incompatible
  	github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
  	github.com/klauspost/compress v1.4.1 // indirect
  	github.com/klauspost/cpuid v1.2.0 // indirect
+ 	github.com/lxc/lxd v0.0.0-20191012201401-49395865679a
  	github.com/mattn/go-runewidth v0.0.4 // indirect
  	github.com/mattn/go-sqlite3 v1.10.0
  	github.com/microcosm-cc/bluemonday v1.0.2 // indirect


@@ 31,6 34,8 @@ github.com/modern-go/reflect2 v1.0.1 // indirect
  	github.com/olekukonko/tablewriter v0.0.1
  	github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
+ 	github.com/robfig/cron v1.2.0 // indirect
+ 	github.com/rogpeppe/fastuuid v1.2.0 // indirect
  	github.com/ryanuber/columnize v2.1.0+incompatible // indirect
  	github.com/satori/go.uuid v1.2.0
  	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect


@@ 38,5 43,10 @@ github.com/spf13/cobra v0.0.5
  	github.com/spf13/viper v1.3.2
  	golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
+ 	gopkg.in/errgo.v1 v1.0.1 // indirect
+ 	gopkg.in/httprequest.v1 v1.2.0 // indirect
+ 	gopkg.in/macaroon-bakery.v2 v2.1.0 // indirect
+ 	gopkg.in/macaroon.v2 v2.1.0 // indirect
+ 	gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 // indirect
  	gopkg.in/yaml.v2 v2.2.2
  )

M go.sum => go.sum +33 -0
@@ 43,6 43,9 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
  github.com/flosch/pongo2 v0.0.0-20181225140029-79872a7b2769 h1:XToLChWPMXLomJ2InnkrmUkddaXfevrmomMTFL+MaKU=
  github.com/flosch/pongo2 v0.0.0-20181225140029-79872a7b2769/go.mod h1:tbAXHifHQWNSpWbiJHpJTZH5fi3XHhDMdP//vuz9WS4=
+ github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
+ github.com/frankban/quicktest v1.1.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
+ github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
  github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
  github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
  github.com/go-check/check v1.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=


@@ 63,15 66,19 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
  github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
  github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
  github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
  github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
  github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
  github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+ github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
  github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
  github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
  github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
  github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
  github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
  github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
  github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
  github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=


@@ 98,8 105,13 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
  github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok=
  github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+ github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8=
  github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+ github.com/juju/qthttptest v0.0.1/go.mod h1://LCf/Ls22/rPw2u1yWukUJvYtfPY4nYpWUl2uZhryo=
  github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
+ github.com/juju/webbrowser v0.0.0-20180907093207-efb9432b2bcb h1:iffwDjS1xyg1Owlh8OZV4kd4P6pVHNZOxIja+ia2nKE=
+ github.com/juju/webbrowser v0.0.0-20180907093207-efb9432b2bcb/go.mod h1:G6PCelgkM6cuvyD10iYJsjLBsSadVXtJ+nBxFAxE2BU=
+ github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
  github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
  github.com/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA=
  github.com/kataras/golog v0.0.0-20180321173939-03be10146386/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M=


@@ 119,6 131,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
  github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
  github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+ github.com/lxc/lxd v0.0.0-20191012201401-49395865679a h1:PtKJLO/8S+V36I7ujX9muhddOQAusClRgERMed2fioY=
+ github.com/lxc/lxd v0.0.0-20191012201401-49395865679a/go.mod h1:2BaZflfwsv8a3uy3/Vw+de4Avn4DSrAiqaHJjCIXMV4=
  github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
  github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
  github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=


@@ 151,6 165,7 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
  github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
  github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
  github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
  github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=


@@ 161,6 176,10 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
  github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
  github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+ github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+ github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+ github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
+ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
  github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
  github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
  github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=


@@ 190,6 209,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
  github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
  go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+ golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
  golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
  golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
  golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=


@@ 200,6 220,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
  golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
  golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+ golang.org/x/net v0.0.0-20150829230318-ea47fc708ee3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
  golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
  golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
  golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=


@@ 232,6 253,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
  golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
  golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+ golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
  golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
  golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
  golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=


@@ 247,10 269,21 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
  gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+ gopkg.in/errgo.v1 v1.0.0/go.mod h1:CxwszS/Xz1C49Ucd2i6Zil5UToP1EmyrFhKaMVbg1mk=
+ gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
+ gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
  gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+ gopkg.in/httprequest.v1 v1.2.0 h1:YTGV1oXzaoKI6oPzQ0knoIPcrrVzeRG3amkoxoP7Xng=
+ gopkg.in/httprequest.v1 v1.2.0/go.mod h1:T61ZUaJLpMnzvoJDO03ZD8yRXD4nZzBeDoW5e9sffjg=
  gopkg.in/lxc/go-lxc.v2 v2.0.0-20181227225324-7c910f8a5edc h1:VlshHw11jO8RWbTpyKhKq9TkfqDscZGtWZ3k/ffnih4=
  gopkg.in/lxc/go-lxc.v2 v2.0.0-20181227225324-7c910f8a5edc/go.mod h1:4K0lbUXeslpmjwJZyW1lI6s5j97mrsj4+kpYwwvuLXo=
+ gopkg.in/macaroon-bakery.v2 v2.1.0 h1:9Jw/+9XHBSutkaeVpWhDx38IcSNLJwWUICkOK98DHls=
+ gopkg.in/macaroon-bakery.v2 v2.1.0/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
+ gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI=
+ gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o=
  gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+ gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY=
+ gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
  gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
  gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
  gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

M sdk/instance.go => sdk/instance.go +17 -1
@@ 1,16 1,32 @@ package cisco
  
  import (
+ 	"encoding/json"
  	"time"
  )
  
  type Instance struct {
  	Uuid         string    `json:"uuid"`
  	Name         string    `json:"name"`
- 	Ipv4         uint      `json:"ipv4"`
+ 	Status       string    `json:"status"`
+ 	Ipv4         string    `json:"ipv4"`
+ 	Ipv6         string    `json:"ipv6"`
  	Size         uint      `json:"size"`
  	OS           string    `json:"os"`
  	Architecture string    `json:"architecture"`
  	Type         string    `json:"type"`
  	Created      time.Time `json:"created"`
  }
+ 
+ func ListInstance() ([]Instance, error) {
+ 	var result []Instance
+ 
+ 	data, err := SendRequest(InstanceListRoute, Request{})
+ 
+ 	if err != nil {
+ 		return nil, err
+ 	}
+ 
+ 	err = json.Unmarshal(data.([]byte), &result)
+ 	return result, err
+ }

M sdk/request.go => sdk/request.go +6 -0
@@ 134,6 134,12 @@ Protected: true,
  }
  
+ var InstanceListRoute Route = Route{
+ 	Path:      "/instance",
+ 	Method:    "GET",
+ 	Protected: true,
+ }
+ 
  type Request struct {
  	Arg  string
  	Data interface{}