b9c28140a42cd9c29742d5bcc1af71436e5aac0c — Louis Solofrizzo 16 days ago 8af6cd6
sdk: Add go sdk for cisco

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>
A sdk/CMakeLists.txt => sdk/CMakeLists.txt +5 -0
@@ 0,0 1,5 @@
+ add_go_component(cisco-sdk
+     config.go
+     request.go
+     invite.go
+ )

A sdk/config.go => sdk/config.go +26 -0
@@ 0,0 1,26 @@
+ package cisco
+ 
+ type Config struct {
+ 	Endpoint string
+ 	Key      string
+ 	Secret   string
+ 	Debug    bool
+ }
+ 
+ var GlobalConfig Config
+ 
+ func SetConfig(conf *Config) {
+ 	if conf.Endpoint != "" {
+ 		GlobalConfig.Endpoint = conf.Endpoint
+ 	}
+ 
+ 	if conf.Key != "" {
+ 		GlobalConfig.Key = conf.Key
+ 	}
+ 
+ 	if conf.Secret != "" {
+ 		GlobalConfig.Secret = conf.Secret
+ 	}
+ 
+ 	GlobalConfig.Debug = conf.Debug
+ }

A sdk/instance.go => sdk/instance.go +16 -0
@@ 0,0 1,16 @@
+ package cisco
+ 
+ import (
+ 	"time"
+ )
+ 
+ type Instance struct {
+ 	Uuid         string    `json:"uuid"`
+ 	Name         string    `json:"name"`
+ 	Ipv4         uint      `json:"ipv4"`
+ 	Size         uint      `json:"size"`
+ 	OS           string    `json:"os"`
+ 	Architecture string    `json:"architecture"`
+ 	Type         string    `json:"type"`
+ 	Created      time.Time `json:"created"`
+ }

A sdk/invite.go => sdk/invite.go +66 -0
@@ 0,0 1,66 @@
+ package cisco
+ 
+ import (
+ 	"crypto/sha256"
+ 	"encoding/hex"
+ 	"encoding/json"
+ 	"time"
+ )
+ 
+ type Invite struct {
+ 	Email   string    `json:"email"`
+ 	Claimed bool      `json:"claimed"`
+ 	Created time.Time `json:"created"`
+ }
+ 
+ type InviteClaim struct {
+ 	Password string `json:"password"`
+ 	Username string `json:"username"`
+ }
+ 
+ func ClaimInvite(hash string, username string, password string) error {
+ 	hashedPassword := sha256.Sum256([]byte(password))
+ 
+ 	_, err := SendRequest(InviteClaimRoute, Request{
+ 		Arg: hash,
+ 		Data: &InviteClaim{
+ 			Username: username,
+ 			Password: hex.EncodeToString(hashedPassword[:]),
+ 		},
+ 	})
+ 
+ 	return err
+ }
+ 
+ type InviteGet struct {
+ 	Email   string `json:"email"`
+ 	Claimed bool   `json:"claimed"`
+ }
+ 
+ func GetInvite(hash string) (*InviteGet, error) {
+ 	var invite InviteGet
+ 
+ 	data, err := SendRequest(InviteGetRoute, Request{
+ 		Arg: hash,
+ 	})
+ 
+ 	if err != nil {
+ 		return nil, err
+ 	}
+ 
+ 	err = json.Unmarshal(data.([]byte), &invite)
+ 	return &invite, err
+ }
+ 
+ type InviteNew struct {
+ 	Email string `json:"email"`
+ }
+ 
+ func NewInvite(email string) error {
+ 	_, err := SendRequest(InviteNewRoute, Request{
+ 		Data: &InviteNew{
+ 			Email: email,
+ 		},
+ 	})
+ 	return err
+ }

A sdk/ipv4.go => sdk/ipv4.go +11 -0
@@ 0,0 1,11 @@
+ package cisco
+ 
+ import (
+ 	"time"
+ )
+ 
+ type Ipv4 struct {
+ 	IP       string    `json:"ip"`
+ 	Instance string    `json:"instance"`
+ 	Created  time.Time `json:"created"`
+ }

A sdk/quota.go => sdk/quota.go +102 -0
@@ 0,0 1,102 @@
+ package cisco
+ 
+ import (
+ 	"encoding/json"
+ )
+ 
+ type Quota struct {
+ 	Role      string `json:"role"`
+ 	Instances int    `json:"instances"`
+ 	Ipv4      int    `json:"ipv4"`
+ 	Storage   int    `json:"storage"`
+ 	Invites   int    `json:"invites"`
+ }
+ 
+ func ListQuota() ([]Quota, error) {
+ 	var result []Quota
+ 
+ 	data, err := SendRequest(AdminQuotaListRoute, Request{})
+ 
+ 	if err != nil {
+ 		return nil, err
+ 	}
+ 
+ 	err = json.Unmarshal(data.([]byte), &result)
+ 	return result, err
+ }
+ 
+ func CreateQuota(name string) error {
+ 	_, err := SendRequest(AdminQuotaCreateRoute, Request{
+ 		Arg: name,
+ 	})
+ 
+ 	return err
+ }
+ 
+ func DeleteQuota(name string) error {
+ 	_, err := SendRequest(AdminQuotaDeleteRoute, Request{
+ 		Arg: name,
+ 	})
+ 
+ 	return err
+ }
+ 
+ type QuotaInstanceUpdate struct {
+ 	Instance int `json:"instance"`
+ }
+ 
+ func UpdateInstanceQuota(name string, instance int) error {
+ 	_, err := SendRequest(AdminQuotaUpdateInstanceRoute, Request{
+ 		Arg: name,
+ 		Data: &QuotaInstanceUpdate{
+ 			Instance: instance,
+ 		},
+ 	})
+ 
+ 	return err
+ }
+ 
+ type QuotaIpv4Update struct {
+ 	Ipv4 int `json:"ipv4"`
+ }
+ 
+ func UpdateIpv4Quota(name string, ipv4 int) error {
+ 	_, err := SendRequest(AdminQuotaUpdateIpv4Route, Request{
+ 		Arg: name,
+ 		Data: &QuotaIpv4Update{
+ 			Ipv4: ipv4,
+ 		},
+ 	})
+ 
+ 	return err
+ }
+ 
+ type QuotaStorageUpdate struct {
+ 	Storage int `json:"storage"`
+ }
+ 
+ func UpdateStorageQuota(name string, storage int) error {
+ 	_, err := SendRequest(AdminQuotaUpdateStorageRoute, Request{
+ 		Arg: name,
+ 		Data: &QuotaStorageUpdate{
+ 			Storage: storage,
+ 		},
+ 	})
+ 
+ 	return err
+ }
+ 
+ type QuotaInvitesUpdate struct {
+ 	Invites int `json:"invites"`
+ }
+ 
+ func UpdateInvitesQuota(name string, invites int) error {
+ 	_, err := SendRequest(AdminQuotaUpdateInvitesRoute, Request{
+ 		Arg: name,
+ 		Data: &QuotaInvitesUpdate{
+ 			Invites: invites,
+ 		},
+ 	})
+ 
+ 	return err
+ }

A sdk/request.go => sdk/request.go +250 -0
@@ 0,0 1,250 @@
+ package cisco
+ 
+ import (
+ 	"crypto/hmac"
+ 	"crypto/md5"
+ 	"crypto/sha256"
+ 	"encoding/hex"
+ 	"encoding/json"
+ 	"errors"
+ 	"fmt"
+ 	"io/ioutil"
+ 	"net/http"
+ 	"strings"
+ 	"time"
+ )
+ 
+ type Route struct {
+ 	Path      string
+ 	Method    string
+ 	Protected bool
+ }
+ 
+ var InviteGetRoute Route = Route{
+ 	Path:      "/invite",
+ 	Method:    "GET",
+ 	Protected: false,
+ }
+ 
+ var InviteClaimRoute Route = Route{
+ 	Path:      "/invite/claim",
+ 	Method:    "POST",
+ 	Protected: false,
+ }
+ 
+ var InviteNewRoute Route = Route{
+ 	Path:      "/invite",
+ 	Method:    "POST",
+ 	Protected: true,
+ }
+ 
+ var TokenNewRoute Route = Route{
+ 	Path:      "/token/new",
+ 	Method:    "POST",
+ 	Protected: false,
+ }
+ 
+ var TokenListRoute Route = Route{
+ 	Path:      "/token/list",
+ 	Method:    "GET",
+ 	Protected: true,
+ }
+ 
+ var TokenRevokeRoute Route = Route{
+ 	Path:      "/token/revoke",
+ 	Method:    "DELETE",
+ 	Protected: true,
+ }
+ 
+ var AdminUserListRoute Route = Route{
+ 	Path:      "/admin/user/list",
+ 	Method:    "GET",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaListRoute Route = Route{
+ 	Path:      "/admin/quota/list",
+ 	Method:    "GET",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaCreateRoute Route = Route{
+ 	Path:      "/admin/quota",
+ 	Method:    "PUT",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaDeleteRoute Route = Route{
+ 	Path:      "/admin/quota",
+ 	Method:    "DELETE",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaUpdateInstanceRoute Route = Route{
+ 	Path:      "/admin/quota/instance",
+ 	Method:    "POST",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaUpdateIpv4Route Route = Route{
+ 	Path:      "/admin/quota/ipv4",
+ 	Method:    "POST",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaUpdateStorageRoute Route = Route{
+ 	Path:      "/admin/quota/storage",
+ 	Method:    "POST",
+ 	Protected: true,
+ }
+ 
+ var AdminQuotaUpdateInvitesRoute Route = Route{
+ 	Path:      "/admin/quota/invites",
+ 	Method:    "POST",
+ 	Protected: true,
+ }
+ 
+ var AdminUserGetRoute Route = Route{
+ 	Path:      "/admin/user",
+ 	Method:    "GET",
+ 	Protected: true,
+ }
+ 
+ type Request struct {
+ 	Arg  string
+ 	Data interface{}
+ }
+ 
+ type ApiError struct {
+ 	Code    int    `json:"code"`
+ 	Error   string `json:"error"`
+ 	Details string `json:"details"`
+ 	Doc     string `json:"documentation"`
+ }
+ 
+ func SendRequest(route Route, req Request) (interface{}, error) {
+ 	var apiError ApiError
+ 	var err error
+ 	var httpReq *http.Request
+ 	var bytes []byte
+ 
+ 	client := &http.Client{}
+ 	uri := GlobalConfig.Endpoint + route.Path
+ 
+ 	if req.Arg != "" {
+ 		uri += "/" + req.Arg
+ 	}
+ 
+ 	if GlobalConfig.Debug == true {
+ 		fmt.Printf("DEBUG: --- New request (%s)\n", time.Now().String())
+ 		fmt.Printf("DEBUG: -> Sending request to endpoint %s\n", GlobalConfig.Endpoint)
+ 		fmt.Printf("DEBUG: -> %s %s\n", route.Method, uri)
+ 	}
+ 
+ 	if req.Data == nil {
+ 		httpReq, err = http.NewRequest(route.Method, uri, nil)
+ 	} else {
+ 		bytes, err = json.Marshal(req.Data)
+ 		if err != nil {
+ 			return nil, err
+ 		}
+ 
+ 		httpReq, err = http.NewRequest(route.Method, uri, strings.NewReader(string(bytes)))
+ 		sum := md5.Sum(bytes)
+ 
+ 		httpReq.Header.Add("Content-type", "application/json")
+ 		httpReq.Header.Add("Content-MD5", hex.EncodeToString(sum[:]))
+ 	}
+ 
+ 	httpReq.Header.Add("Date", time.Now().String())
+ 
+ 	if route.Protected == true {
+ 		var toSign string
+ 
+ 		httpReq.Header.Add("X-Csc-Key", GlobalConfig.Key)
+ 		if GlobalConfig.Debug == true {
+ 			fmt.Printf("DEBUG: -> Route is marked as protected, signing request\n")
+ 		}
+ 
+ 		toSign = route.Method + uri + httpReq.Header.Get("Date") + GlobalConfig.Key
+ 		if req.Data != nil {
+ 			toSign += string(bytes) + httpReq.Header.Get("Content-MD5")
+ 		}
+ 
+ 		secretHex, err := hex.DecodeString(GlobalConfig.Secret)
+ 		if err != nil {
+ 			return nil, errors.New("The provided secret is not valid: " + err.Error())
+ 		}
+ 
+ 		h := hmac.New(sha256.New, secretHex)
+ 		h.Write([]byte(toSign))
+ 		signature := hex.EncodeToString(h.Sum(nil))
+ 		httpReq.Header.Add("X-Csc-Signature", signature)
+ 
+ 		if GlobalConfig.Debug == true {
+ 			fmt.Printf("DEBUG: -> String to sign: '%s'\n", toSign)
+ 			fmt.Printf("DEBUG: -> Signature '%s'\n", signature)
+ 		}
+ 	}
+ 
+ 	if GlobalConfig.Debug == true {
+ 		if len(httpReq.Header) > 0 {
+ 			fmt.Printf("DEBUG: -> Headers:\n")
+ 		}
+ 
+ 		for key, val := range httpReq.Header {
+ 			fmt.Printf("DEBUG: -> \t%s: %s\n", key, val)
+ 		}
+ 
+ 		if req.Data != nil {
+ 			fmt.Printf("DEBUG: -> Body\n")
+ 			fmt.Printf("DEBUG: -> %s\n", req.Data)
+ 		}
+ 
+ 	}
+ 
+ 	if err != nil {
+ 		if GlobalConfig.Debug == true {
+ 			fmt.Printf("DEBUG: -> Could not create HTTP request: %s\n", err.Error())
+ 			fmt.Printf("DEBUG: --- Request end (%s)\n", time.Now().String())
+ 		}
+ 		return nil, err
+ 	}
+ 
+ 	resp, err := client.Do(httpReq)
+ 	if err != nil {
+ 		if GlobalConfig.Debug == true {
+ 			fmt.Printf("DEBUG: -> Could not execute HTTP Request: %s\n", err.Error())
+ 			fmt.Printf("DEBUG: --- Request end (%s)\n", time.Now().String())
+ 		}
+ 		return nil, err
+ 	}
+ 
+ 	data, err := ioutil.ReadAll(resp.Body)
+ 	if err != nil {
+ 		if GlobalConfig.Debug == true {
+ 			fmt.Printf("DEBUG: <- Could not read body of response: %s\n", err.Error())
+ 			fmt.Printf("DEBUG: --- Request end (%s)\n", time.Now().String())
+ 		}
+ 		return nil, err
+ 	}
+ 
+ 	if GlobalConfig.Debug == true {
+ 		fmt.Printf("DEBUG: <- Received response from %s\n", GlobalConfig.Endpoint)
+ 		fmt.Printf("DEBUG: <- %s\n", data)
+ 	}
+ 
+ 	err = json.Unmarshal(data, &apiError)
+ 	if err == nil && apiError.Code != 0 {
+ 		if GlobalConfig.Debug == true {
+ 			fmt.Printf("DEBUG: <- API error detected, parsing JSON\n")
+ 			fmt.Printf("DEBUG: --- Request end (%s)\n", time.Now().String())
+ 		}
+ 		return nil, errors.New(apiError.Error + ": " + apiError.Details)
+ 	}
+ 
+ 	if GlobalConfig.Debug == true {
+ 		fmt.Printf("DEBUG: --- Request end (%s)\n", time.Now().String())
+ 	}
+ 	return data, nil
+ }

A sdk/ssh.go => sdk/ssh.go +10 -0
@@ 0,0 1,10 @@
+ package cisco
+ 
+ import (
+ 	"time"
+ )
+ 
+ type SSHKey struct {
+ 	Key     string    `json:"key"`
+ 	Created time.Time `json:"created"`
+ }

A sdk/token.go => sdk/token.go +61 -0
@@ 0,0 1,61 @@
+ package cisco
+ 
+ import (
+ 	"crypto/sha256"
+ 	"encoding/hex"
+ 	"encoding/json"
+ 	"time"
+ )
+ 
+ type TokenNew struct {
+ 	Username    string `json:"username"`
+ 	Password    string `json:"password"`
+ 	Description string `json:"description"`
+ }
+ 
+ type Token struct {
+ 	Key         string    `json:"key"`
+ 	Secret      string    `json:"secret"`
+ 	Description string    `json:"description"`
+ 	Created     time.Time `json:"created"`
+ }
+ 
+ func NewToken(username string, password string, description string) (*Token, error) {
+ 	var token Token
+ 
+ 	hashedPassword := sha256.Sum256([]byte(password))
+ 
+ 	data, err := SendRequest(TokenNewRoute, Request{
+ 		Data: &TokenNew{
+ 			Username:    username,
+ 			Password:    hex.EncodeToString(hashedPassword[:]),
+ 			Description: description,
+ 		},
+ 	})
+ 
+ 	if err != nil {
+ 		return nil, err
+ 	}
+ 
+ 	err = json.Unmarshal(data.([]byte), &token)
+ 	return &token, err
+ }
+ 
+ func ListToken() ([]Token, error) {
+ 	var ret []Token
+ 
+ 	data, err := SendRequest(TokenListRoute, Request{})
+ 	if err != nil {
+ 		return nil, err
+ 	}
+ 	err = json.Unmarshal(data.([]byte), &ret)
+ 	return ret, err
+ }
+ 
+ func RevokeToken(token string) error {
+ 	_, err := SendRequest(TokenRevokeRoute, Request{
+ 		Arg: token,
+ 	})
+ 
+ 	return err
+ }

A sdk/user.go => sdk/user.go +49 -0
@@ 0,0 1,49 @@
+ package cisco
+ 
+ import (
+ 	"encoding/json"
+ 	"time"
+ )
+ 
+ type User struct {
+ 	Username string    `json:"username"`
+ 	Email    string    `json:"email"`
+ 	Role     string    `json:"role"`
+ 	Created  time.Time `json:"created"`
+ }
+ 
+ func ListUser() ([]User, error) {
+ 	var result []User
+ 
+ 	data, err := SendRequest(AdminUserListRoute, Request{})
+ 	if err != nil {
+ 		return nil, err
+ 	}
+ 
+ 	err = json.Unmarshal(data.([]byte), &result)
+ 	return result, err
+ }
+ 
+ type UserInfo struct {
+ 	User      User       `json:"user"`
+ 	Tokens    []Token    `json:"tokens"`
+ 	Keys      []SSHKey   `json:"ssh_keys"`
+ 	Instances []Instance `json:"instances"`
+ 	Ipv4s     []Ipv4     `json:"ipv4s"`
+ 	Invites   []Invite   `json:"invites"`
+ }
+ 
+ func GetUserInfoAdmin(name string) (UserInfo, error) {
+ 	var result UserInfo
+ 
+ 	data, err := SendRequest(AdminUserGetRoute, Request{
+ 		Arg: name,
+ 	})
+ 
+ 	if err != nil {
+ 		return result, err
+ 	}
+ 
+ 	err = json.Unmarshal(data.([]byte), &result)
+ 	return result, err
+ }