├── .gitignore ├── README.md ├── account.go ├── account_test.go ├── background.go ├── background_test.go ├── configurations.go ├── configurations_test.go ├── notifications.go ├── notifications_test.go ├── scalet.go ├── scalet_test.go ├── sshkeys.go ├── sshkeys_test.go ├── strings.go ├── timestamp.go ├── todo.org ├── vscale.go └── vscale_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vscale API 2 | 3 | [![Vexor status](https://ci.vexor.io/projects/4089aaeb-e6f6-4400-a8ec-0d00c6db8c9f/status.svg)](https://ci.vexor.io/ui/projects/4089aaeb-e6f6-4400-a8ec-0d00c6db8c9f/builds) 4 | 5 | ## Installation and documentation 6 | 7 | To install `Vscale API`, simply run: 8 | 9 | ``` 10 | $ go get github.com/evrone/vscale_api 11 | ``` 12 | 13 | ## Getting Started 14 | 15 | ``` go 16 | package main 17 | import( 18 | "fmt" 19 | vscale "github.com/evrone/vscale_api" 20 | ) 21 | 22 | func GetAccountInfo() { 23 | client := vscale.New("API_SECRET_TOKEN") 24 | account, _, err := client.Account.Get() 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Printf("Account info: %v", account) 29 | } 30 | ``` 31 | 32 | ## Contribution Guidelines 33 | 34 | 01. Fork 35 | 02. Change 36 | 03. PR 37 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | type AccountService interface { 4 | Get() (*Account, *Response, error) 5 | } 6 | 7 | type AccountServiceOp struct { 8 | client *Client 9 | } 10 | 11 | var _ AccountService = &AccountServiceOp{} 12 | 13 | // https://developers.vscale.io/documentation/api/v1/#api-Account-GetAccount 14 | type Account struct { 15 | ActivateDate string `json:"actdate,omitempty"` 16 | Country string `json:"country,omitempty"` 17 | FaceID int `json:"face_id,string,omitempty"` 18 | ID int `json:"id,string,omitempty"` 19 | State int `json:"state,string,omitempty"` 20 | Email string `json:"email,omitempty"` 21 | Name string `json:"name,omitempty"` 22 | MiddleName string `json:"middlename,omitempty"` 23 | SurName string `json:"surname,omitempty"` 24 | } 25 | 26 | type accountInfo struct { 27 | Info *Account `json:"info,omitempty"` 28 | } 29 | 30 | func (r Account) String() string { 31 | return Stringify(r) 32 | } 33 | 34 | func (s *AccountServiceOp) Get() (*Account, *Response, error) { 35 | path := "/v1/account" 36 | req, err := s.client.NewRequest("GET", path, nil) 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | 41 | info := new(accountInfo) 42 | 43 | resp, err := s.client.Do(req, info) 44 | if err != nil { 45 | return nil, nil, err 46 | } 47 | 48 | return info.Info, resp, err 49 | } 50 | -------------------------------------------------------------------------------- /account_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestAccountGet(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | mux.HandleFunc("/v1/account", func(w http.ResponseWriter, r *http.Request) { 15 | testMethod(t, r, "GET") 16 | 17 | response := ` 18 | { 19 | "info": { 20 | "actdate": "2015-07-07 08:16:47.107987", 21 | "country": "", 22 | "email": "username@example.com", 23 | "face_id": "1", 24 | "id": "1001", 25 | "locale": "ru", 26 | "middlename": "MiddleName", 27 | "mobile": "+79901234567", 28 | "name": "UserName", 29 | "state": "1", 30 | "surname": "SurName" 31 | } 32 | }` 33 | 34 | fmt.Fprint(w, response) 35 | }) 36 | 37 | acct, _, err := client.Account.Get() 38 | if err != nil { 39 | t.Errorf("Account.Get returned error: %v", err) 40 | } 41 | 42 | expected := &Account{ 43 | ActivateDate: "2015-07-07 08:16:47.107987", 44 | Country: "", 45 | FaceID: 1, 46 | ID: 1001, 47 | State: 1, 48 | Email: "username@example.com", 49 | Name: "UserName", 50 | MiddleName: "MiddleName", 51 | SurName: "SurName", 52 | } 53 | 54 | if !reflect.DeepEqual(acct, expected) { 55 | t.Errorf("Account.Get returned %+v, expected %+v", acct, expected) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /background.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | type BackgroundService interface { 4 | Locations() (*[]Location, *Response, error) 5 | Images() (*[]Image, *Response, error) 6 | } 7 | 8 | type BackgroundServiceOp struct { 9 | client *Client 10 | } 11 | 12 | var _ BackgroundService = &BackgroundServiceOp{} 13 | 14 | type Location struct { 15 | ID string `json:"id,omitempty"` 16 | Active bool `json:"active,omitempty"` 17 | Description string `json:"description,omitempty"` 18 | Rplans []string `json:"rplans,omitempty"` 19 | Templates []string `json:"templates,omitempty"` 20 | PrivateNetworking bool `json:"private_networking,omitempty"` 21 | } 22 | 23 | type Image struct { 24 | ID string `json:"id,omitempty"` 25 | Active bool `json:"active,omitempty"` 26 | Description string `json:"description,omitempty"` 27 | Rplans []string `json:"rplans,omitempty"` 28 | Locations []string `json:"locations,omitempty"` 29 | Size int `json:"size,omitempty"` 30 | } 31 | 32 | func (s *BackgroundServiceOp) Locations() (*[]Location, *Response, error) { 33 | path := "/v1/locations" 34 | req, err := s.client.NewRequest("GET", path, nil) 35 | if err != nil { 36 | return nil, nil, err 37 | } 38 | 39 | locations := &[]Location{} 40 | 41 | resp, err := s.client.Do(req, locations) 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | 46 | return locations, resp, err 47 | } 48 | 49 | func (s *BackgroundServiceOp) Images() (*[]Image, *Response, error) { 50 | path := "/v1/images" 51 | req, err := s.client.NewRequest("GET", path, nil) 52 | if err != nil { 53 | return nil, nil, err 54 | } 55 | 56 | images := &[]Image{} 57 | resp, err := s.client.Do(req, images) 58 | if err != nil { 59 | return nil, nil, err 60 | } 61 | 62 | return images, resp, err 63 | } 64 | -------------------------------------------------------------------------------- /background_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestLocations(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | mux.HandleFunc("/v1/locations", func(w http.ResponseWriter, r *http.Request) { 15 | testMethod(t, r, "GET") 16 | 17 | response := ` 18 | [ 19 | { 20 | "rplans": [ 21 | "small", 22 | "medium", 23 | "large", 24 | "huge", 25 | "monster" 26 | ], 27 | "id": "spb0", 28 | "active": true, 29 | "description": "Цветочная 21", 30 | "private_networking": false, 31 | "templates": [ 32 | "debian_8.1_64_001_master", 33 | "centos_7.1_64_001_master", 34 | "ubuntu_14.04_64_002_master" 35 | ] 36 | } 37 | ]` 38 | 39 | fmt.Fprint(w, response) 40 | }) 41 | 42 | locations, _, err := client.Background.Locations() 43 | if err != nil { 44 | t.Errorf("Background.Locations returned error: %v", err) 45 | } 46 | 47 | expected := &[]Location{ 48 | { 49 | ID: "spb0", 50 | Active: true, 51 | Description: "\u0426\u0432\u0435\u0442\u043e\u0447\u043d\u0430\u044f 21", 52 | Rplans: []string{"small", "medium", "large", "huge", "monster"}, 53 | Templates: []string{"debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"}, 54 | PrivateNetworking: false, 55 | }, 56 | } 57 | 58 | if !reflect.DeepEqual(locations, expected) { 59 | t.Errorf("Background.Locations returned %+v, expected: %+v", locations, expected) 60 | } 61 | } 62 | 63 | func TestImages(t *testing.T) { 64 | setup() 65 | defer teardown() 66 | 67 | mux.HandleFunc("/v1/images", func(w http.ResponseWriter, r *http.Request) { 68 | testMethod(t, r, "GET") 69 | 70 | response := `[{"rplans": ["small", "medium", "large", "huge", "monster"], "active": true, "size": 2048, "locations": ["spb0"], "id": "debian_8.1_64_001_master", "description": "Debian_8.1_64_001_master"}, {"rplans": ["small", "medium", "large", "huge", "monster"], "active": true, "size": 2048, "locations": ["spb0"], "id": "centos_7.1_64_001_master", "description": "Centos_7.1_64_001_master"}, {"rplans": ["small", "medium", "large", "huge", "monster"], "active": true, "size": 2048, "locations": ["spb0"], "id": "ubuntu_14.04_64_002_master", "description": "Ubuntu_14.04_64_002_master"}]` 71 | fmt.Fprint(w, response) 72 | }) 73 | 74 | images, _, err := client.Background.Images() 75 | if err != nil { 76 | t.Errorf("Background.Images returned error: %v", err) 77 | } 78 | expected := &[]Image{ 79 | { 80 | ID: "debian_8.1_64_001_master", 81 | Active: true, 82 | Description: "Debian_8.1_64_001_master", 83 | Rplans: []string{"small", "medium", "large", "huge", "monster"}, 84 | Locations: []string{"spb0"}, 85 | Size: 2048, 86 | }, 87 | { 88 | ID: "centos_7.1_64_001_master", 89 | Active: true, 90 | Description: "Centos_7.1_64_001_master", 91 | Rplans: []string{"small", "medium", "large", "huge", "monster"}, 92 | Locations: []string{"spb0"}, 93 | Size: 2048, 94 | }, 95 | { 96 | ID: "ubuntu_14.04_64_002_master", 97 | Active: true, 98 | Description: "Ubuntu_14.04_64_002_master", 99 | Rplans: []string{"small", "medium", "large", "huge", "monster"}, 100 | Locations: []string{"spb0"}, 101 | Size: 2048, 102 | }, 103 | } 104 | 105 | if !reflect.DeepEqual(images, expected) { 106 | t.Errorf("Background.Images returned %+v, expected: %+v", images, expected) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /configurations.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | type ConfigurationsService interface { 4 | Rplans() (*[]Rplan, *Response, error) 5 | } 6 | 7 | type Rplan struct { 8 | ID string `json:"id,omitempty"` 9 | Memory int `json:"memory,omitempty"` 10 | Disk int `json:"disk,omitempty"` 11 | Locations []string `json:"locations,omitempty"` 12 | Network int `json:"network,omitempty"` 13 | Addresses int `json:"addresses,omitempty"` 14 | Cpus int `json:"cpus,omitempty"` 15 | Templates []string `json:"templates,omitempty"` 16 | } 17 | 18 | type ConfigurationsServiceOp struct { 19 | client *Client 20 | } 21 | 22 | var _ ConfigurationsService = &ConfigurationsServiceOp{} 23 | 24 | func (s *ConfigurationsServiceOp) Rplans() (*[]Rplan, *Response, error) { 25 | path := "/v1/rplans" 26 | req, err := s.client.NewRequest("GET", path, nil) 27 | if err != nil { 28 | return nil, nil, err 29 | } 30 | 31 | collection := &[]Rplan{} 32 | resp, err := s.client.Do(req, collection) 33 | if err != nil { 34 | return nil, nil, err 35 | } 36 | return collection, resp, err 37 | } 38 | -------------------------------------------------------------------------------- /configurations_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestRplans(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | mux.HandleFunc("/v1/rplans", func(w http.ResponseWriter, r *http.Request) { 15 | testMethod(t, r, "GET") 16 | 17 | response := `[{"memory": 512, "disk": 20480, "locations": ["spb0"], "network": 1024, "id": "small", "addresses": 1, "cpus": 1, "templates": ["debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"]}, {"memory": 1024, "disk": 30720, "locations": ["spb0"], "network": 2048, "id": "medium", "addresses": 1, "cpus": 1, "templates": ["debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"]}, {"memory": 2048, "disk": 40960, "locations": ["spb0"], "network": 3072, "id": "large", "addresses": 1, "cpus": 2, "templates": ["debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"]}, {"memory": 4096, "disk": 61440, "locations": ["spb0"], "network": 4096, "id": "huge", "addresses": 1, "cpus": 2, "templates": ["debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"]}, {"memory": 8192, "disk": 81920, "locations": ["spb0"], "network": 5120, "id": "monster", "addresses": 1, "cpus": 4, "templates": ["debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"]}]` 18 | fmt.Fprint(w, response) 19 | }) 20 | 21 | collection, _, err := client.Configurations.Rplans() 22 | if err != nil { 23 | t.Errorf("Configurations.Rplans returnes error: %v", err) 24 | } 25 | 26 | expected := &[]Rplan{ 27 | { 28 | ID: "small", 29 | Memory: 512, 30 | Disk: 20480, 31 | Locations: []string{"spb0"}, 32 | Network: 1024, 33 | Addresses: 1, 34 | Cpus: 1, 35 | Templates: []string{"debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"}, 36 | }, 37 | { 38 | ID: "medium", 39 | Memory: 1024, 40 | Disk: 30720, 41 | Locations: []string{"spb0"}, 42 | Network: 2048, 43 | Addresses: 1, 44 | Cpus: 1, 45 | Templates: []string{"debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"}, 46 | }, 47 | { 48 | ID: "large", 49 | Memory: 2048, 50 | Disk: 40960, 51 | Locations: []string{"spb0"}, 52 | Network: 3072, 53 | Addresses: 1, 54 | Cpus: 2, 55 | Templates: []string{"debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"}, 56 | }, 57 | { 58 | ID: "huge", 59 | Memory: 4096, 60 | Disk: 61440, 61 | Locations: []string{"spb0"}, 62 | Network: 4096, 63 | Addresses: 1, 64 | Cpus: 2, 65 | Templates: []string{"debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"}, 66 | }, 67 | { 68 | ID: "monster", 69 | Memory: 8192, 70 | Disk: 81920, 71 | Locations: []string{"spb0"}, 72 | Network: 5120, 73 | Addresses: 1, 74 | Cpus: 4, 75 | Templates: []string{"debian_8.1_64_001_master", "centos_7.1_64_001_master", "ubuntu_14.04_64_002_master"}, 76 | }, 77 | } 78 | 79 | if !reflect.DeepEqual(collection, expected) { 80 | t.Errorf("Configurations.Rplans returned %+v, expected: %+v", collection, expected) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /notifications.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | type NotificationsService interface { 4 | Get() (*NotificationsSettings, *Response, error) 5 | SetNotifyBalance(int) (*NotificationsSettings, *Response, error) 6 | } 7 | 8 | type NotificationsServiceOp struct { 9 | client *Client 10 | } 11 | 12 | var _ NotificationsService = &NotificationsServiceOp{} 13 | 14 | type NotificationsRequest struct { 15 | NotifyBalance int `json:"notify_balance"` 16 | } 17 | 18 | type NotificationsSettings struct { 19 | NotifyBalance int `json:"notify_balance"` 20 | Status string `json:"status,omitempty"` 21 | } 22 | 23 | func (s *NotificationsServiceOp) Get() (*NotificationsSettings, *Response, error) { 24 | path := "/v1/billing/notify" 25 | req, err := s.client.NewRequest("GET", path, nil) 26 | if err != nil { 27 | return nil, nil, err 28 | } 29 | 30 | notifSett := new(NotificationsSettings) 31 | resp, err := s.client.Do(req, notifSett) 32 | if err != nil { 33 | return nil, nil, err 34 | } 35 | return notifSett, resp, err 36 | } 37 | 38 | func (s *NotificationsServiceOp) SetNotifyBalance(num int) (*NotificationsSettings, *Response, error) { 39 | path := "/v1/billing/notify" 40 | notifyRequest := &NotificationsRequest{NotifyBalance: num} 41 | req, err := s.client.NewRequest("PUT", path, notifyRequest) 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | 46 | notifSett := new(NotificationsSettings) 47 | resp, err := s.client.Do(req, notifSett) 48 | if err != nil { 49 | return nil, nil, err 50 | } 51 | return notifSett, resp, err 52 | } 53 | -------------------------------------------------------------------------------- /notifications_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestNotificationsSettingsGet(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | mux.HandleFunc("/v1/billing/notify", func(w http.ResponseWriter, r *http.Request) { 15 | testMethod(t, r, "GET") 16 | 17 | response := `{"notify_balance": 0, "status": "ok"}` 18 | fmt.Fprint(w, response) 19 | }) 20 | 21 | notify, _, err := client.Notifications.Get() 22 | if err != nil { 23 | t.Errorf("Notifications.Get returns error: %v", err) 24 | } 25 | 26 | expected := &NotificationsSettings{ 27 | NotifyBalance: 0, 28 | Status: "ok", 29 | } 30 | if !reflect.DeepEqual(notify, expected) { 31 | t.Errorf("Notifications.Get returned %+v, expected %+v", notify, expected) 32 | } 33 | } 34 | 35 | func TestSetNotifyBalance(t *testing.T) { 36 | setup() 37 | defer teardown() 38 | 39 | mux.HandleFunc("/v1/billing/notify", func(w http.ResponseWriter, r *http.Request) { 40 | testMethod(t, r, "PUT") 41 | 42 | response := `{"notify_balance": 10000, "status": "ok"}` 43 | fmt.Fprint(w, response) 44 | }) 45 | 46 | notify, _, err := client.Notifications.SetNotifyBalance(10000) 47 | if err != nil { 48 | t.Errorf("Notifications.SetNotifyBalance returns error: %v", err) 49 | } 50 | 51 | expected := &NotificationsSettings{ 52 | NotifyBalance: 10000, 53 | Status: "ok", 54 | } 55 | if !reflect.DeepEqual(notify, expected) { 56 | t.Errorf("Notifications.SetNotifyBalance returned %+v, expected %+v", notify, expected) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scalet.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | scaletsBasePath = "/v1/scalets" 9 | tasksBasePath = "/v1/tasks" 10 | ) 11 | 12 | type ScaletService interface { 13 | List() (*[]Scalet, *Response, error) 14 | GetByID(int) (*Scalet, *Response, error) 15 | Create(*ScaletCreateRequest) (*Scalet, *Response, error) 16 | Restart(int) (*Scalet, *Response, error) 17 | Rebuild(*ScaletRebuildRequest) (*Scalet, *Response, error) 18 | Halt(int) (*Scalet, *Response, error) 19 | Start(int) (*Scalet, *Response, error) 20 | UpdatePlan(*ScaletUpdatePlanRequest) (*Scalet, *Response, error) 21 | Delete(int) (*Scalet, *Response, error) 22 | AddSSHKeyToScalet(*SSHKeyAppendRequest) (*Scalet, *Response, error) 23 | Tasks() (*[]ScaletTask, *Response, error) 24 | } 25 | 26 | type ScaletServiceOp struct { 27 | client *Client 28 | } 29 | 30 | var _ ScaletService = &ScaletServiceOp{} 31 | 32 | type Scalet struct { 33 | Name string `json:"name,omitempty"` 34 | Hostname string `json:"hostname,omitempty"` 35 | Locked bool `json:"locked,omitempty"` 36 | Location string `json:"location,omitempty"` 37 | Rplan string `json:"rplan,omitempty"` 38 | Active bool `json:"active,omitempty"` 39 | Keys []SSHKey `json:"keys,omitempty"` 40 | PublicAddress *ScaletAddress `json:"public_address,omitempty"` 41 | Status string `json:"status,omitempty"` 42 | MadeFrom string `json:"made_from,omitempty"` 43 | CTID int `json:"ctid,omitempty"` 44 | PrivateAddress *ScaletAddress `json:"private_address,omitempty"` 45 | } 46 | 47 | type ScaletAddress struct { 48 | Address string `json:"address,omitempty"` 49 | Netmask string `json:"netmask,omitempty"` 50 | Gateway string `json:"gateway,omitempty"` 51 | } 52 | 53 | type ScaletCreateRequest struct { 54 | MakeFrom string `json:"make_from"` 55 | Rplan string `json:"rplan"` 56 | DoStart bool `json:"do_start"` 57 | Name string `json:"name"` 58 | Keys []int `json:"keys"` 59 | Password string `json:"password"` 60 | Location string `json:"location"` 61 | } 62 | 63 | type ScaletRebuildRequest struct { 64 | ID int 65 | Password string `json:"password"` 66 | } 67 | 68 | type ScaletUpdatePlanRequest struct { 69 | ID int 70 | Rplan string `json:"rplan"` 71 | } 72 | 73 | type SSHKeyAppendRequest struct { 74 | CTID int 75 | Keys []int `json:"keys"` 76 | } 77 | 78 | type ScaletTask struct { 79 | ID string `json:"id,omitempty"` 80 | Location string `json:"location,omitempty"` 81 | InsertDate string `json:"d_insert,omitempty"` 82 | StartDate string `json:"d_start,omitempty"` 83 | EndDate string `json:"d_end,omitempty"` 84 | Done bool `json:"done,omitempty"` 85 | ScaletId int `json:"scalet,omitempty"` 86 | Error bool `json:"error,omitempty"` 87 | Method string `json:"method,omitempty"` 88 | } 89 | 90 | func (s Scalet) String() string { 91 | return Stringify(s) 92 | } 93 | 94 | func (s ScaletServiceOp) List() (*[]Scalet, *Response, error) { 95 | path := scaletsBasePath 96 | req, err := s.client.NewRequest("GET", path, nil) 97 | if err != nil { 98 | return nil, nil, err 99 | } 100 | 101 | scalets := &[]Scalet{} 102 | resp, err := s.client.Do(req, scalets) 103 | if err != nil { 104 | return nil, nil, err 105 | } 106 | return scalets, resp, err 107 | } 108 | 109 | func (s ScaletServiceOp) GetByID(ctid int) (*Scalet, *Response, error) { 110 | if ctid < 1 { 111 | return nil, nil, NewArgError("scaletID", "cannot be less than 1") 112 | } 113 | path := fmt.Sprintf("%s/%d", scaletsBasePath, ctid) 114 | req, err := s.client.NewRequest("GET", path, nil) 115 | if err != nil { 116 | return nil, nil, err 117 | } 118 | 119 | scalet := &Scalet{} 120 | resp, err := s.client.Do(req, scalet) 121 | if err != nil { 122 | return nil, nil, err 123 | } 124 | return scalet, resp, err 125 | } 126 | 127 | func (s ScaletServiceOp) Create(createRequest *ScaletCreateRequest) (*Scalet, *Response, error) { 128 | if createRequest == nil { 129 | return nil, nil, NewArgError("createRequest", "cannot be nil") 130 | } 131 | 132 | req, err := s.client.NewRequest("POST", scaletsBasePath, createRequest) 133 | if err != nil { 134 | return nil, nil, err 135 | } 136 | 137 | scalet := new(Scalet) 138 | resp, err := s.client.Do(req, scalet) 139 | if err != nil { 140 | return nil, nil, err 141 | } 142 | 143 | return scalet, resp, err 144 | } 145 | 146 | func (s ScaletServiceOp) Restart(ctid int) (*Scalet, *Response, error) { 147 | if ctid < 1 { 148 | return nil, nil, NewArgError("scaletID", "cannot be less than 1") 149 | } 150 | path := fmt.Sprintf("%s/%d/restart", scaletsBasePath, ctid) 151 | req, err := s.client.NewRequest("PATCH", path, nil) 152 | if err != nil { 153 | return nil, nil, err 154 | } 155 | 156 | scalet := &Scalet{} 157 | resp, err := s.client.Do(req, scalet) 158 | if err != nil { 159 | return nil, nil, err 160 | } 161 | return scalet, resp, err 162 | } 163 | 164 | func (s *ScaletServiceOp) Rebuild(rebuildRequest *ScaletRebuildRequest) (*Scalet, *Response, error) { 165 | if rebuildRequest == nil { 166 | return nil, nil, NewArgError("rebuildRequest", "cannot be nil") 167 | } 168 | 169 | path := fmt.Sprintf("%s/%d/rebuild", scaletsBasePath, rebuildRequest.ID) 170 | req, err := s.client.NewRequest("PATCH", path, rebuildRequest) 171 | if err != nil { 172 | return nil, nil, err 173 | } 174 | 175 | scalet := &Scalet{} 176 | resp, err := s.client.Do(req, scalet) 177 | if err != nil { 178 | return nil, nil, err 179 | } 180 | return scalet, resp, err 181 | } 182 | 183 | func (s *ScaletServiceOp) Halt(ctid int) (*Scalet, *Response, error) { 184 | if ctid < 1 { 185 | return nil, nil, NewArgError("scaletID", "cannot be less than 1") 186 | } 187 | path := fmt.Sprintf("%s/%d/stop", scaletsBasePath, ctid) 188 | req, err := s.client.NewRequest("PATCH", path, nil) 189 | if err != nil { 190 | return nil, nil, err 191 | } 192 | 193 | scalet := &Scalet{} 194 | resp, err := s.client.Do(req, scalet) 195 | if err != nil { 196 | return nil, nil, err 197 | } 198 | return scalet, resp, err 199 | } 200 | 201 | func (s *ScaletServiceOp) Start(ctid int) (*Scalet, *Response, error) { 202 | if ctid < 1 { 203 | return nil, nil, NewArgError("scaletID", "cannot be less than 1") 204 | } 205 | path := fmt.Sprintf("%s/%d/start", scaletsBasePath, ctid) 206 | req, err := s.client.NewRequest("PATCH", path, nil) 207 | if err != nil { 208 | return nil, nil, err 209 | } 210 | 211 | scalet := &Scalet{} 212 | resp, err := s.client.Do(req, scalet) 213 | if err != nil { 214 | return nil, nil, err 215 | } 216 | return scalet, resp, err 217 | } 218 | 219 | func (s *ScaletServiceOp) UpdatePlan(rplanUpdateRequest *ScaletUpdatePlanRequest) (*Scalet, *Response, error) { 220 | if rplanUpdateRequest == nil { 221 | return nil, nil, NewArgError("rplanUpdateRequest", "cannot be nil") 222 | } 223 | 224 | if rplanUpdateRequest.ID < 1 { 225 | return nil, nil, NewArgError("rplanUpdateRequest.ID", "cannot be less than 1") 226 | } 227 | 228 | if rplanUpdateRequest.Rplan == "" { 229 | return nil, nil, NewArgError("rplanUpdateRequest.rplan", "cannot be empty") 230 | } 231 | 232 | path := fmt.Sprintf("%s/%d/upgrade", scaletsBasePath, rplanUpdateRequest.ID) 233 | req, err := s.client.NewRequest("POST", path, rplanUpdateRequest) 234 | if err != nil { 235 | return nil, nil, err 236 | } 237 | 238 | scalet := new(Scalet) 239 | resp, err := s.client.Do(req, scalet) 240 | if err != nil { 241 | return nil, nil, err 242 | } 243 | return scalet, resp, err 244 | } 245 | 246 | func (s *ScaletServiceOp) Delete(ctid int) (*Scalet, *Response, error) { 247 | if ctid < 1 { 248 | return nil, nil, NewArgError("scaletID", "cannot be less than 1") 249 | } 250 | path := fmt.Sprintf("%s/%d", scaletsBasePath, ctid) 251 | req, err := s.client.NewRequest("DELETE", path, nil) 252 | if err != nil { 253 | return nil, nil, err 254 | } 255 | 256 | scalet := &Scalet{} 257 | resp, err := s.client.Do(req, scalet) 258 | if err != nil { 259 | return nil, nil, err 260 | } 261 | return scalet, resp, err 262 | } 263 | 264 | func (s *ScaletServiceOp) Tasks() (*[]ScaletTask, *Response, error) { 265 | req, err := s.client.NewRequest("GET", tasksBasePath, nil) 266 | if err != nil { 267 | return nil, nil, err 268 | } 269 | 270 | tasks := &[]ScaletTask{} 271 | resp, err := s.client.Do(req, tasks) 272 | if err != nil { 273 | return nil, nil, err 274 | } 275 | return tasks, resp, err 276 | } 277 | 278 | func (s *ScaletServiceOp) AddSSHKeyToScalet(appendSSHKeyRequest *SSHKeyAppendRequest) (*Scalet, *Response, error) { 279 | if appendSSHKeyRequest == nil { 280 | return nil, nil, NewArgError("appendSSHKeyRequest", "cannot be nil") 281 | } 282 | if appendSSHKeyRequest.CTID < 1 { 283 | return nil, nil, NewArgError("appendSSHKeyRequest.CTID", "cannot be less than 1") 284 | } 285 | 286 | path := fmt.Sprintf("%s/scalets/%d", sshBaseUrl, appendSSHKeyRequest.CTID) 287 | req, err := s.client.NewRequest("PATCH", path, appendSSHKeyRequest) 288 | if err != nil { 289 | return nil, nil, err 290 | } 291 | 292 | scalet := new(Scalet) 293 | resp, err := s.client.Do(req, scalet) 294 | if err != nil { 295 | return nil, nil, err 296 | } 297 | return scalet, resp, err 298 | } 299 | -------------------------------------------------------------------------------- /scalet_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestScaletList(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc("/v1/scalets", func(w http.ResponseWriter, r *http.Request) { 16 | testMethod(t, r, "GET") 17 | 18 | response := `[ 19 | { 20 | "hostname":"cs10087.vscale.ru", 21 | "locked":false, 22 | "location":"CiDK", 23 | "rplan":"monster", 24 | "name":"mytestserver", 25 | "active":true, 26 | "keys": [ 27 | { 28 | "name": "somekeyname", 29 | "id": 16 30 | } 31 | ], 32 | "public_address":{ 33 | "netmask":"255.255.255.0", 34 | "gateway":"95.213.191.1", 35 | "address":"95.213.191.70" 36 | }, 37 | "status":"started", 38 | "made_from":"ubuntu_14.04_64_002_master", 39 | "ctid":10087, 40 | "private_address":{} 41 | }, 42 | { 43 | "hostname":"cs10139.vscale.ru", 44 | "locked":false, 45 | "location":"spb0", 46 | "rplan":"medium", 47 | "name":"Maroon-Waffle", 48 | "active":true, 49 | "keys": [ 50 | { 51 | "name": "somekeyname", 52 | "id": 16 53 | } 54 | ], 55 | "public_address":{ 56 | "netmask":"255.255.255.0", 57 | "gateway":"95.213.191.1", 58 | "address":"95.213.191.121" 59 | }, 60 | "status":"started", 61 | "made_from":"ubuntu_14.04_64_002_master", 62 | "ctid":10139, 63 | "private_address":{} 64 | }, 65 | { 66 | "hostname":"cs10299.vscale.ru", 67 | "locked":false, 68 | "location":"spb0", 69 | "rplan":"large", 70 | "name":"Hollow-Star", 71 | "active":true, 72 | "keys": [ 73 | { 74 | "name": "somekeyname", 75 | "id": 16 76 | } 77 | ], 78 | "public_address":{ 79 | "netmask":"255.255.255.0", 80 | "gateway":"95.213.191.1", 81 | "address":"95.213.191.120" 82 | }, 83 | "status":"started", 84 | "made_from":"ubuntu_14.04_64_002_master", 85 | "ctid":10299, 86 | "private_address":{} 87 | }]` 88 | 89 | fmt.Fprint(w, response) 90 | }) 91 | 92 | sclt, _, err := client.Scalet.List() 93 | if err != nil { 94 | t.Errorf("Scalet.List returned error: %v", err) 95 | } 96 | 97 | expected := &[]Scalet{ 98 | Scalet{ 99 | Name: "mytestserver", 100 | Hostname: "cs10087.vscale.ru", 101 | Locked: false, 102 | Location: "CiDK", 103 | Rplan: "monster", 104 | Active: true, 105 | Keys: []SSHKey{SSHKey{ID: 16, Name: "somekeyname"}}, 106 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.70"}, 107 | Status: "started", 108 | MadeFrom: "ubuntu_14.04_64_002_master", 109 | CTID: 10087, 110 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 111 | }, 112 | Scalet{ 113 | Name: "Maroon-Waffle", 114 | Hostname: "cs10139.vscale.ru", 115 | Locked: false, 116 | Location: "spb0", 117 | Rplan: "medium", 118 | Active: true, 119 | Keys: []SSHKey{SSHKey{ID: 16, Name: "somekeyname"}}, 120 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.121"}, 121 | Status: "started", 122 | MadeFrom: "ubuntu_14.04_64_002_master", 123 | CTID: 10139, 124 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 125 | }, 126 | Scalet{ 127 | Name: "Hollow-Star", 128 | Hostname: "cs10299.vscale.ru", 129 | Locked: false, 130 | Location: "spb0", 131 | Rplan: "large", 132 | Active: true, 133 | Keys: []SSHKey{SSHKey{ID: 16, Name: "somekeyname"}}, 134 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.120"}, 135 | Status: "started", 136 | MadeFrom: "ubuntu_14.04_64_002_master", 137 | CTID: 10299, 138 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 139 | }, 140 | } 141 | 142 | if !reflect.DeepEqual(sclt, expected) { 143 | t.Errorf("Scalet.List returned %+v, expected %+v", sclt, expected) 144 | } 145 | } 146 | 147 | func TestGetScaletByID(t *testing.T) { 148 | setup() 149 | defer teardown() 150 | 151 | mux.HandleFunc("/v1/scalets/12345", func(w http.ResponseWriter, r *http.Request) { 152 | testMethod(t, r, "GET") 153 | 154 | response := ` 155 | { 156 | "private_address": {}, 157 | "active": true, 158 | "rplan": "small", 159 | "keys": [ 160 | { 161 | "name": "MacBook", 162 | "id": 1234 163 | } 164 | ], 165 | "locked": false, 166 | "status": "started", 167 | "name": "Minimum-Temple", 168 | "created": "01.12.2015 08:42:42", 169 | "ctid": 12345, 170 | "hostname": "cs12345.vscale.io", 171 | "deleted": null, 172 | "made_from": "ubuntu_14.04_64_002_master", 173 | "public_address": { 174 | "address": "95.213.199.29", 175 | "netmask": "255.255.255.0", 176 | "gateway": "95.213.199.1" 177 | }, 178 | "location": "spb0" 179 | }` 180 | 181 | fmt.Fprint(w, response) 182 | }) 183 | 184 | sclt, _, err := client.Scalet.GetByID(12345) 185 | if err != nil { 186 | t.Errorf("Scalet.GetByID returned error: %v", err) 187 | } 188 | 189 | expected := &Scalet{ 190 | Name: "Minimum-Temple", 191 | Hostname: "cs12345.vscale.io", 192 | Locked: false, 193 | Location: "spb0", 194 | Rplan: "small", 195 | Active: true, 196 | Keys: []SSHKey{SSHKey{ID: 1234, Name: "MacBook"}}, 197 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.199.1", Address: "95.213.199.29"}, 198 | Status: "started", 199 | MadeFrom: "ubuntu_14.04_64_002_master", 200 | CTID: 12345, 201 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 202 | } 203 | 204 | if !reflect.DeepEqual(sclt, expected) { 205 | t.Errorf("Scalet.GetByID returned %+v, expected %+v", sclt, expected) 206 | } 207 | } 208 | 209 | func TestScaletCreate(t *testing.T) { 210 | setup() 211 | defer teardown() 212 | 213 | createRequest := &ScaletCreateRequest{ 214 | MakeFrom: "ubuntu_14.04_64_002_master", 215 | Rplan: "medium", 216 | DoStart: true, 217 | Name: "New-Test", 218 | Keys: []int{16}, 219 | Location: "spb0", 220 | } 221 | 222 | mux.HandleFunc("/v1/scalets", func(w http.ResponseWriter, r *http.Request) { 223 | v := new(ScaletCreateRequest) 224 | err := json.NewDecoder(r.Body).Decode(v) 225 | if err != nil { 226 | t.Fatalf("decode json: %v", err) 227 | } 228 | 229 | testMethod(t, r, "POST") 230 | 231 | if !reflect.DeepEqual(v, createRequest) { 232 | t.Errorf("Request body: %+v, expected: %+v", v, createRequest) 233 | } 234 | 235 | response := ` 236 | { 237 | "status": "defined", 238 | "deleted": null, 239 | "public_address": {}, 240 | "active": false, 241 | "location": "spb0", 242 | "locked": true, 243 | "hostname": "cs11533.vscale.io", 244 | "created": "20.08.2015 14:57:04", 245 | "keys": [ 246 | { 247 | "name": "somekeyname", 248 | "id": 16 249 | } 250 | ], 251 | "private_address": {}, 252 | "made_from": "ubuntu_14.04_64_002_master", 253 | "name": "New-Test", 254 | "ctid": 11, 255 | "rplan": "medium" 256 | }` 257 | 258 | fmt.Fprint(w, response) 259 | }) 260 | 261 | scalet, _, err := client.Scalet.Create(createRequest) 262 | if err != nil { 263 | t.Errorf("Scalet.Create returned error: %v", err) 264 | } 265 | 266 | expected := &Scalet{ 267 | Name: "New-Test", 268 | Hostname: "cs11533.vscale.io", 269 | Locked: true, 270 | Location: "spb0", 271 | Rplan: "medium", 272 | Active: false, 273 | Keys: []SSHKey{ 274 | SSHKey{ 275 | Name: "somekeyname", 276 | ID: 16, 277 | }, 278 | }, 279 | PublicAddress: &ScaletAddress{}, 280 | Status: "defined", 281 | MadeFrom: "ubuntu_14.04_64_002_master", 282 | CTID: 11, 283 | PrivateAddress: &ScaletAddress{}, 284 | } 285 | 286 | if !reflect.DeepEqual(scalet, expected) { 287 | t.Errorf("Scalet.Create returned %+v, expected %+v", scalet, expected) 288 | } 289 | } 290 | 291 | func TestScaletRestart(t *testing.T) { 292 | setup() 293 | defer teardown() 294 | 295 | mux.HandleFunc("/v1/scalets/10299/restart", func(w http.ResponseWriter, r *http.Request) { 296 | testMethod(t, r, "PATCH") 297 | 298 | response := ` 299 | { 300 | "active":true, 301 | "location":"spb0", 302 | "ctid":10299, 303 | "name":"Eternal-Hungry", 304 | "created":"20.07.2015 16:54:31", 305 | "public_address":{ 306 | "gateway":"95.213.191.1", 307 | "netmask":"255.255.255.0", 308 | "address":"95.213.191.24" 309 | }, 310 | "status":"started", 311 | "private_address":{}, 312 | "rplan":"medium", 313 | "deleted":null, 314 | "hostname":"cs10299.vscale.io", 315 | "keys": [ 316 | { 317 | "name": "somekeyname", 318 | "id": 16 319 | } 320 | ], 321 | "locked":false, 322 | "made_from":"ubuntu_14.04_64_002_master" 323 | }` 324 | 325 | fmt.Fprint(w, response) 326 | }) 327 | 328 | sclt, _, err := client.Scalet.Restart(10299) 329 | if err != nil { 330 | t.Errorf("Scalet.Restart returned error: %v", err) 331 | } 332 | 333 | expected := &Scalet{ 334 | Name: "Eternal-Hungry", 335 | Hostname: "cs10299.vscale.io", 336 | Locked: false, 337 | Location: "spb0", 338 | Rplan: "medium", 339 | Active: true, 340 | Keys: []SSHKey{SSHKey{ID: 16, Name: "somekeyname"}}, 341 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.24"}, 342 | Status: "started", 343 | MadeFrom: "ubuntu_14.04_64_002_master", 344 | CTID: 10299, 345 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 346 | } 347 | 348 | if !reflect.DeepEqual(sclt, expected) { 349 | t.Errorf("Scalet.GetByID returned %+v, expected %+v", sclt, expected) 350 | } 351 | } 352 | 353 | func TestScaletRebuild(t *testing.T) { 354 | setup() 355 | defer teardown() 356 | 357 | rebuildRequest := &ScaletRebuildRequest{ 358 | ID: 15508, 359 | } 360 | 361 | mux.HandleFunc("/v1/scalets/15508/rebuild", func(w http.ResponseWriter, r *http.Request) { 362 | v := new(ScaletRebuildRequest) 363 | err := json.NewDecoder(r.Body).Decode(v) 364 | if err != nil { 365 | t.Fatalf("decode json: %v", err) 366 | } 367 | 368 | testMethod(t, r, "PATCH") 369 | 370 | response := ` 371 | { 372 | "public_address":{ 373 | "netmask":"255.255.255.0", 374 | "gateway":"95.213.194.1", 375 | "address":"95.213.194.37" 376 | }, 377 | "made_from":"ubuntu_14.04_64_002_master", 378 | "rplan":"medium", 379 | "location":"spb0", 380 | "name":"Minimum-Windshield", 381 | "created":"25.09.2015 12:19:05", 382 | "active":true, 383 | "locked":false, 384 | "status":"started", 385 | "ctid":15508, 386 | "private_address":{}, 387 | "deleted":null, 388 | "keys":[ 389 | { 390 | "id":72, 391 | "name":"key" 392 | } 393 | ], 394 | "hostname":"cs15508.vscale.io" 395 | }` 396 | 397 | fmt.Fprint(w, response) 398 | }) 399 | 400 | sclt, _, err := client.Scalet.Rebuild(rebuildRequest) 401 | if err != nil { 402 | t.Errorf("Scalet.Restart returned error: %v", err) 403 | } 404 | 405 | expected := &Scalet{ 406 | Name: "Minimum-Windshield", 407 | Hostname: "cs15508.vscale.io", 408 | Locked: false, 409 | Location: "spb0", 410 | Rplan: "medium", 411 | Active: true, 412 | Keys: []SSHKey{SSHKey{ID: 72, Name: "key"}}, 413 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.194.1", Address: "95.213.194.37"}, 414 | Status: "started", 415 | MadeFrom: "ubuntu_14.04_64_002_master", 416 | CTID: 15508, 417 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 418 | } 419 | 420 | if !reflect.DeepEqual(sclt, expected) { 421 | t.Errorf("Scalet.GetByID returned %+v, expected %+v", sclt, expected) 422 | } 423 | } 424 | 425 | func TestScaletHalt(t *testing.T) { 426 | setup() 427 | defer teardown() 428 | 429 | mux.HandleFunc("/v1/scalets/10299/stop", func(w http.ResponseWriter, r *http.Request) { 430 | testMethod(t, r, "PATCH") 431 | 432 | response := ` 433 | { 434 | "status": "started", 435 | "deleted": null, 436 | "made_from": "ubuntu_14.04_64_002_master", 437 | "created": "27.08.2015 14:22:39", 438 | "private_address": {}, 439 | "ctid": 10299, 440 | "keys": [ 441 | { 442 | "name": "key", 443 | "id": 72 444 | } 445 | ], 446 | "location": "spb0", 447 | "hostname": "cs12669.vscale.io", 448 | "locked": false, 449 | "public_address": { 450 | "netmask": "255.255.255.0", 451 | "gateway": "95.213.199.1", 452 | "address": "95.213.199.48" 453 | }, 454 | "rplan": "medium", 455 | "name": "Icy-Compass", 456 | "active": true 457 | }` 458 | 459 | fmt.Fprint(w, response) 460 | }) 461 | 462 | sclt, _, err := client.Scalet.Halt(10299) 463 | if err != nil { 464 | t.Errorf("Scalet.Halt returned error: %v", err) 465 | } 466 | 467 | expected := &Scalet{ 468 | Name: "Icy-Compass", 469 | Hostname: "cs12669.vscale.io", 470 | Locked: false, 471 | Location: "spb0", 472 | Rplan: "medium", 473 | Active: true, 474 | Keys: []SSHKey{SSHKey{ID: 72, Name: "key"}}, 475 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.199.1", Address: "95.213.199.48"}, 476 | Status: "started", 477 | MadeFrom: "ubuntu_14.04_64_002_master", 478 | CTID: 10299, 479 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 480 | } 481 | 482 | if !reflect.DeepEqual(sclt, expected) { 483 | t.Errorf("Scalet.Halt returned %+v, expected %+v", sclt, expected) 484 | } 485 | } 486 | 487 | func TestScaletStart(t *testing.T) { 488 | setup() 489 | defer teardown() 490 | 491 | mux.HandleFunc("/v1/scalets/10299/start", func(w http.ResponseWriter, r *http.Request) { 492 | testMethod(t, r, "PATCH") 493 | 494 | response := ` 495 | { 496 | "location": "spb0", 497 | "keys": [ 498 | { 499 | "name": "key", 500 | "id": 72 501 | } 502 | ], 503 | "created": "27.08.2015 14:22:39", 504 | "hostname": "cs12669.vscale.io", 505 | "ctid": 10299, 506 | "status": "stopped", 507 | "deleted": null, 508 | "rplan": "medium", 509 | "name": "Icy-Compass", 510 | "made_from": "ubuntu_14.04_64_002_master", 511 | "active": true, 512 | "locked": false, 513 | "public_address": { 514 | "gateway": "95.213.199.1", 515 | "address": "95.213.199.48", 516 | "netmask": "255.255.255.0" 517 | }, 518 | "private_address": {} 519 | }` 520 | 521 | fmt.Fprint(w, response) 522 | }) 523 | 524 | sclt, _, err := client.Scalet.Start(10299) 525 | if err != nil { 526 | t.Errorf("Scalet.Start returned error: %v", err) 527 | } 528 | 529 | expected := &Scalet{ 530 | Name: "Icy-Compass", 531 | Hostname: "cs12669.vscale.io", 532 | Locked: false, 533 | Location: "spb0", 534 | Rplan: "medium", 535 | Active: true, 536 | Keys: []SSHKey{SSHKey{ID: 72, Name: "key"}}, 537 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.199.1", Address: "95.213.199.48"}, 538 | Status: "stopped", 539 | MadeFrom: "ubuntu_14.04_64_002_master", 540 | CTID: 10299, 541 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 542 | } 543 | 544 | if !reflect.DeepEqual(sclt, expected) { 545 | t.Errorf("Scalet.Start returned %+v, expected %+v", sclt, expected) 546 | } 547 | } 548 | 549 | func TestScaletUpdatePlan(t *testing.T) { 550 | setup() 551 | defer teardown() 552 | 553 | updateRplanRequest := &ScaletUpdatePlanRequest{ 554 | ID: 10299, 555 | Rplan: "monster", 556 | } 557 | 558 | mux.HandleFunc("/v1/scalets/10299/upgrade", func(w http.ResponseWriter, r *http.Request) { 559 | v := new(ScaletUpdatePlanRequest) 560 | if err := json.NewDecoder(r.Body).Decode(v); err != nil { 561 | t.Fatalf("decode json: %v", err) 562 | } 563 | 564 | testMethod(t, r, "POST") 565 | 566 | response := ` 567 | { 568 | "hostname":"cs10299.vscale.ru", 569 | "locked":false, 570 | "location":"spb0", 571 | "rplan":"huge", 572 | "name":"MyServer", 573 | "active":true, 574 | "keys": [ 575 | { 576 | "name": "somekeyname", 577 | "id": 16 578 | } 579 | ], 580 | "public_address":{ 581 | "netmask":"255.255.255.0", 582 | "gateway":"95.213.191.1", 583 | "address":"95.213.191.120" 584 | }, 585 | "status":"started", 586 | "made_from":"ubuntu_14.04_64_002_master", 587 | "ctid":10299, 588 | "private_address":{} 589 | }` 590 | fmt.Fprint(w, response) 591 | }) 592 | 593 | sclt, _, err := client.Scalet.UpdatePlan(updateRplanRequest) 594 | if err != nil { 595 | t.Errorf("Scalet.UpdatePlan returned error: %v", err) 596 | } 597 | 598 | expected := &Scalet{ 599 | Name: "MyServer", 600 | Hostname: "cs10299.vscale.ru", 601 | Locked: false, 602 | Location: "spb0", 603 | Rplan: "huge", 604 | Active: true, 605 | Keys: []SSHKey{SSHKey{ID: 16, Name: "somekeyname"}}, 606 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.120"}, 607 | Status: "started", 608 | MadeFrom: "ubuntu_14.04_64_002_master", 609 | CTID: 10299, 610 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 611 | } 612 | if !reflect.DeepEqual(sclt, expected) { 613 | t.Errorf("Scalet.UpdatePlan returned %+v, expected %+v", sclt, expected) 614 | } 615 | } 616 | 617 | func TestScaletDelete(t *testing.T) { 618 | setup() 619 | defer teardown() 620 | 621 | mux.HandleFunc("/v1/scalets/10299", func(w http.ResponseWriter, r *http.Request) { 622 | testMethod(t, r, "DELETE") 623 | 624 | response := ` 625 | { 626 | "hostname":"cs10299.vscale.ru", 627 | "locked":false, 628 | "location":"spb0", 629 | "rplan":"huge", 630 | "name":"MyServer", 631 | "active":true, 632 | "keys": [ 633 | { 634 | "name": "somekeyname", 635 | "id": 16 636 | } 637 | ], 638 | "public_address":{ 639 | "netmask":"255.255.255.0", 640 | "gateway":"95.213.191.1", 641 | "address":"95.213.191.120" 642 | }, 643 | "status":"deleted", 644 | "made_from":"ubuntu_14.04_64_002_master", 645 | "ctid":10299, 646 | "private_address":{} 647 | }` 648 | 649 | fmt.Fprint(w, response) 650 | }) 651 | 652 | sclt, _, err := client.Scalet.Delete(10299) 653 | if err != nil { 654 | t.Errorf("Scalet.Start returned error: %v", err) 655 | } 656 | 657 | expected := &Scalet{ 658 | Name: "MyServer", 659 | Hostname: "cs10299.vscale.ru", 660 | Locked: false, 661 | Location: "spb0", 662 | Rplan: "huge", 663 | Active: true, 664 | Keys: []SSHKey{SSHKey{ID: 16, Name: "somekeyname"}}, 665 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.120"}, 666 | Status: "deleted", 667 | MadeFrom: "ubuntu_14.04_64_002_master", 668 | CTID: 10299, 669 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 670 | } 671 | 672 | if !reflect.DeepEqual(sclt, expected) { 673 | t.Errorf("Scalet.Delete returned %+v, expected %+v", sclt, expected) 674 | } 675 | } 676 | 677 | func AddSSHKeyToScalet(t *testing.T) { 678 | setup() 679 | defer teardown() 680 | 681 | appendSSHKeyToScalet := &SSHKeyAppendRequest{ 682 | CTID: 10299, 683 | Keys: []int{15, 16}, 684 | } 685 | mux.HandleFunc("/v1/sshkeys/scalets/10299", func(w http.ResponseWriter, r *http.Request) { 686 | v := new(SSHKeyAppendRequest) 687 | if err := json.NewDecoder(r.Body).Decode(v); err != nil { 688 | t.Fatalf("decode json: %v", err) 689 | } 690 | 691 | testMethod(t, r, "PATCH") 692 | 693 | response := ` 694 | { 695 | "hostname":"cs10299.vscale.ru", 696 | "locked":false, 697 | "location":"spb0", 698 | "rplan":"huge", 699 | "name":"MyServer", 700 | "active":true, 701 | "keys":[ 702 | { 703 | "id":15, 704 | "name":"publickeyname" 705 | }, 706 | { 707 | "id":16, 708 | "name":"somekeyname" 709 | } 710 | ], 711 | "public_address":{ 712 | "netmask":"255.255.255.0", 713 | "gateway":"95.213.191.1", 714 | "address":"95.213.191.120" 715 | }, 716 | "status":"started", 717 | "made_from":"ubuntu_14.04_64_002_master", 718 | "ctid":10299, 719 | "private_address":{} 720 | }` 721 | 722 | fmt.Fprint(w, response) 723 | }) 724 | 725 | sclt, _, err := client.Scalet.AddSSHKeyToScalet(appendSSHKeyToScalet) 726 | if err != nil { 727 | t.Errorf("Scalet.AddSSHKeyToScalet returned error: %v", err) 728 | } 729 | 730 | expected := &Scalet{ 731 | Name: "MyServer", 732 | Hostname: "cs10299.vscale.ru", 733 | Locked: false, 734 | Location: "spb0", 735 | Rplan: "huge", 736 | Active: true, 737 | Keys: []SSHKey{SSHKey{ID: 15, Name: "publickeyname"}, SSHKey{ID: 16, Name: "somekeyname"}}, 738 | PublicAddress: &ScaletAddress{Netmask: "255.255.255.0", Gateway: "95.213.191.1", Address: "95.213.191.120"}, 739 | Status: "started", 740 | MadeFrom: "ubuntu_14.04_64_002_master", 741 | CTID: 10299, 742 | PrivateAddress: &ScaletAddress{Netmask: "", Gateway: "", Address: ""}, 743 | } 744 | if !reflect.DeepEqual(sclt, expected) { 745 | t.Errorf("Scalet.AddSSHKeyToScalet returned %+v, expected %+v", sclt, expected) 746 | } 747 | } 748 | 749 | func TestScaletTasks(t *testing.T) { 750 | setup() 751 | defer teardown() 752 | 753 | mux.HandleFunc("/v1/tasks", func(w http.ResponseWriter, r *http.Request) { 754 | testMethod(t, r, "GET") 755 | 756 | response := ` 757 | [ 758 | { 759 | "location": "spb0", 760 | "d_insert": "2015-08-28 12:37:48", 761 | "id": "3a447f17-3577-4c16-b26c-27bd52faa7c1", 762 | "done": false, 763 | "scalet": 12835, 764 | "error": false, 765 | "d_start": "2015-08-28 09:37:48", 766 | "method": "scalet_create", 767 | "d_end": null 768 | } 769 | ]` 770 | fmt.Fprint(w, response) 771 | }) 772 | 773 | tasks, _, err := client.Scalet.Tasks() 774 | if err != nil { 775 | t.Errorf("Scalet.Tasks returned error: %v", err) 776 | } 777 | 778 | expected := &[]ScaletTask{ 779 | ScaletTask{ 780 | ID: "3a447f17-3577-4c16-b26c-27bd52faa7c1", 781 | Location: "spb0", 782 | InsertDate: "2015-08-28 12:37:48", 783 | StartDate: "2015-08-28 09:37:48", 784 | EndDate: "", 785 | Done: false, 786 | ScaletId: 12835, 787 | Error: false, 788 | Method: "scalet_create", 789 | }, 790 | } 791 | 792 | if !reflect.DeepEqual(tasks, expected) { 793 | t.Errorf("Scalet.Tasks returned %+v, expected %+v", tasks, expected) 794 | } 795 | } 796 | -------------------------------------------------------------------------------- /sshkeys.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type SSHKey struct { 8 | ID int `json:"id,omitempty"` 9 | Key string `json:"key,omitempty"` 10 | Name string `json:"name,omitempty"` 11 | } 12 | 13 | type SSHKeyCreateRequest struct { 14 | Key string `json:"key"` 15 | Name string `json:"name"` 16 | } 17 | 18 | type SSHService interface { 19 | List() (*[]SSHKey, *Response, error) 20 | Create(*SSHKeyCreateRequest) (*SSHKey, *Response, error) 21 | Delete(int) (*Response, error) 22 | } 23 | 24 | type SSHServiceOp struct { 25 | client *Client 26 | } 27 | 28 | const ( 29 | sshBaseUrl = "/v1/sshkeys" 30 | ) 31 | 32 | var _ SSHService = &SSHServiceOp{} 33 | 34 | func (s *SSHServiceOp) List() (*[]SSHKey, *Response, error) { 35 | path := sshBaseUrl 36 | req, err := s.client.NewRequest("GET", path, nil) 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | 41 | sshkeys := &[]SSHKey{} 42 | resp, err := s.client.Do(req, sshkeys) 43 | if err != nil { 44 | return nil, nil, err 45 | } 46 | return sshkeys, resp, err 47 | } 48 | 49 | func (s *SSHServiceOp) Create(createRequest *SSHKeyCreateRequest) (*SSHKey, *Response, error) { 50 | if createRequest == nil { 51 | return nil, nil, NewArgError("createRequest", "cannot be nil") 52 | } 53 | 54 | req, err := s.client.NewRequest("POST", sshBaseUrl, createRequest) 55 | if err != nil { 56 | return nil, nil, err 57 | } 58 | 59 | sshkey := new(SSHKey) 60 | resp, err := s.client.Do(req, sshkey) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | return sshkey, resp, err 66 | } 67 | 68 | func (s SSHServiceOp) Delete(sshkeyID int) (*Response, error) { 69 | if sshkeyID < 1 { 70 | return nil, NewArgError("sshkeyID", "cannot be less than 1") 71 | } 72 | 73 | path := fmt.Sprintf("%s/%d", sshBaseUrl, sshkeyID) 74 | 75 | req, err := s.client.NewRequest("DELETE", path, nil) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return s.client.Do(req, nil) 81 | } 82 | 83 | // import ( 84 | // "encoding/json" 85 | // "fmt" 86 | // ) 87 | // 88 | // type SSHKey struct { 89 | // ID int 90 | // Name, Key string 91 | // } 92 | // 93 | // func (c *Client) SSHKeys() (*[]SSHKey, error) { 94 | // ret, err := c.get("sshkeys", nil) 95 | // if err != nil { 96 | // return nil, err 97 | // } 98 | // 99 | // keys := make([]SSHKey, 0) 100 | // err = json.Unmarshal([]byte(ret), &keys) 101 | // return &keys, err 102 | // } 103 | // 104 | // func (c *Client) NewSSHKey(params map[string]interface{}) (*SSHKey, error) { 105 | // ret, err := c.post("sshkeys", params) 106 | // if err != nil { 107 | // return nil, err 108 | // } 109 | // 110 | // keys := make([]SSHKey, 0) 111 | // fmt.Println(ret) 112 | // err = json.Unmarshal([]byte(ret), &keys) 113 | // 114 | // if len(keys) > 0 { 115 | // return &keys[0], nil 116 | // } else { 117 | // return nil, err 118 | // } 119 | // } 120 | // 121 | // func (c *Client) DeleteSSHKey(id int) (string, error) { 122 | // return c.delete(fmt.Sprintf("sshkeys/%d", id), nil) 123 | // } 124 | -------------------------------------------------------------------------------- /sshkeys_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestSSHKeysList(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc("/v1/sshkeys", func(w http.ResponseWriter, r *http.Request) { 16 | testMethod(t, r, "GET") 17 | 18 | response := ` 19 | [ 20 | { 21 | "id":15, 22 | "key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFPEvMAMf4DZN34lReVkVklkAET+H57pSK1aBJ4NX2Nd+Asv5iJWBKBELYmDaRxfx6Y8nS6uYPU3EJ+qBI91NOjJSTPchjWSGGWv4SpkxXBEMjWyUob8BFn5rEjEtDMBsR8xPurcs1vkaoet6A9eXw67pVVwsdh48hKQc0DSaYVtmb08ex4uWoadzixM3GUfMnW/2AQK75dyJIhvOTHwxZEeynDFgI9fNgWLbre62uCMmlCyvMYnpG8apz9igscZycimTOWqPvhMskdHBtYBFHFAg/50NH38L52cMjP3/j1CG+crC7l6ij4e7DAVq43jyL6sWlbvpLWPPj/4MIf2W7 user@host.local", 23 | "name":"publickeyname" 24 | }, 25 | { 26 | "id":16, 27 | "key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABCCCBAQDMt+AlVdjaH1mTPbUM5bI6wYxrOriVTZh2DrA9XhSP1ndTtVsDT0QJanQSw8BvVovu5n4Ves0FqbBR+nmuHKmta+YevTlvfrbjerSQj01mTLI4VgM6RL/XAygroNIKVEmBSKJj9eitl1G1UP5f3vHiPunt2jCNlNK0rexX1klnhwTroHGB9kMjjfPJJMBiV/M0klcAQtBYaAw6VUvA5mjfKnTRJ8HtD4bEJ0O6p85h9UhxelAfi7Giu5MAvzIfwAQRrvQabdgD44OklM+oHhz/tbZo1dv2znUDgFsfKu0MXtMQPFklwlk+BZQNRsS5/1WGn5knyCqDgWUF/LD4QDIJ user@host.local", 28 | "name":"somekeyname" 29 | } 30 | ]` 31 | 32 | fmt.Fprint(w, response) 33 | }) 34 | 35 | sshkeys, _, err := client.SSHKey.List() 36 | if err != nil { 37 | t.Errorf("SSHKey.List returned error: %v", err) 38 | } 39 | 40 | expected := &[]SSHKey{ 41 | SSHKey{ 42 | ID: 15, 43 | Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFPEvMAMf4DZN34lReVkVklkAET+H57pSK1aBJ4NX2Nd+Asv5iJWBKBELYmDaRxfx6Y8nS6uYPU3EJ+qBI91NOjJSTPchjWSGGWv4SpkxXBEMjWyUob8BFn5rEjEtDMBsR8xPurcs1vkaoet6A9eXw67pVVwsdh48hKQc0DSaYVtmb08ex4uWoadzixM3GUfMnW/2AQK75dyJIhvOTHwxZEeynDFgI9fNgWLbre62uCMmlCyvMYnpG8apz9igscZycimTOWqPvhMskdHBtYBFHFAg/50NH38L52cMjP3/j1CG+crC7l6ij4e7DAVq43jyL6sWlbvpLWPPj/4MIf2W7 user@host.local", 44 | Name: "publickeyname", 45 | }, 46 | SSHKey{ 47 | ID: 16, 48 | Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABCCCBAQDMt+AlVdjaH1mTPbUM5bI6wYxrOriVTZh2DrA9XhSP1ndTtVsDT0QJanQSw8BvVovu5n4Ves0FqbBR+nmuHKmta+YevTlvfrbjerSQj01mTLI4VgM6RL/XAygroNIKVEmBSKJj9eitl1G1UP5f3vHiPunt2jCNlNK0rexX1klnhwTroHGB9kMjjfPJJMBiV/M0klcAQtBYaAw6VUvA5mjfKnTRJ8HtD4bEJ0O6p85h9UhxelAfi7Giu5MAvzIfwAQRrvQabdgD44OklM+oHhz/tbZo1dv2znUDgFsfKu0MXtMQPFklwlk+BZQNRsS5/1WGn5knyCqDgWUF/LD4QDIJ user@host.local", 49 | Name: "somekeyname", 50 | }, 51 | } 52 | 53 | if !reflect.DeepEqual(sshkeys, expected) { 54 | t.Errorf("SSHKey.List returned %+v, expected %+v", sshkeys, expected) 55 | } 56 | } 57 | 58 | func TestSSHKeysCreate(t *testing.T) { 59 | setup() 60 | defer teardown() 61 | 62 | createRequest := &SSHKeyCreateRequest{ 63 | Name: "newkey", 64 | Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClleNF5kP55FOHSdU+1bPRJ7Q4o+jOeYuM+XpasTOhNYVaZRTQYmas/7FF7YImu34kbF2jQpX2GezxafG8E+BqQyiDa0Cb18jmkHDlZNo62W16tuFMc5rsB6yRJPc9WUMC84xxgxGIVSZZAbv9wFTLyK3k6zRdnNXsfefzh6XL4jEh/I0/gnw9phs3MCSvAjHw6futhybaukEwQI5oq8WNB1JRQoNN95Dt+sAwM4Ur6CdbgLtn5jJdRyOHMM/fNfSwLxbr+Lm4xLpP+Fyuyd6gvUebR7fdCSD+2iBBpaLz5LTAX7XXOB/aizTXIIJbSbZ1PjBUmX/uS1cFLYGVfRYT user@host.local", 65 | } 66 | 67 | mux.HandleFunc("/v1/sshkeys", func(w http.ResponseWriter, r *http.Request) { 68 | v := new(SSHKeyCreateRequest) 69 | err := json.NewDecoder(r.Body).Decode(v) 70 | if err != nil { 71 | t.Fatalf("decode json: %v", err) 72 | } 73 | 74 | testMethod(t, r, "POST") 75 | 76 | if !reflect.DeepEqual(v, createRequest) { 77 | t.Errorf("Request body: %+v, expected: %+v", v, createRequest) 78 | } 79 | 80 | response := ` 81 | { 82 | "key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClleNF5kP55FOHSdU+1bPRJ7Q4o+jOeYuM+XpasTOhNYVaZRTQYmas/7FF7YImu34kbF2jQpX2GezxafG8E+BqQyiDa0Cb18jmkHDlZNo62W16tuFMc5rsB6yRJPc9WUMC84xxgxGIVSZZAbv9wFTLyK3k6zRdnNXsfefzh6XL4jEh/I0/gnw9phs3MCSvAjHw6futhybaukEwQI5oq8WNB1JRQoNN95Dt+sAwM4Ur6CdbgLtn5jJdRyOHMM/fNfSwLxbr+Lm4xLpP+Fyuyd6gvUebR7fdCSD+2iBBpaLz5LTAX7XXOB/aizTXIIJbSbZ1PjBUmX/uS1cFLYGVfRYT user@host.local", 83 | "id":16, 84 | "name":"newkey" 85 | }` 86 | 87 | fmt.Fprint(w, response) 88 | }) 89 | 90 | sshkey, _, err := client.SSHKey.Create(createRequest) 91 | if err != nil { 92 | t.Errorf("SSHKeys.Create returned error: %v", err) 93 | } 94 | 95 | expected := &SSHKey{ 96 | ID: 16, 97 | Name: "newkey", 98 | Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClleNF5kP55FOHSdU+1bPRJ7Q4o+jOeYuM+XpasTOhNYVaZRTQYmas/7FF7YImu34kbF2jQpX2GezxafG8E+BqQyiDa0Cb18jmkHDlZNo62W16tuFMc5rsB6yRJPc9WUMC84xxgxGIVSZZAbv9wFTLyK3k6zRdnNXsfefzh6XL4jEh/I0/gnw9phs3MCSvAjHw6futhybaukEwQI5oq8WNB1JRQoNN95Dt+sAwM4Ur6CdbgLtn5jJdRyOHMM/fNfSwLxbr+Lm4xLpP+Fyuyd6gvUebR7fdCSD+2iBBpaLz5LTAX7XXOB/aizTXIIJbSbZ1PjBUmX/uS1cFLYGVfRYT user@host.local", 99 | } 100 | 101 | if !reflect.DeepEqual(sshkey, expected) { 102 | t.Errorf("SSHKeys.Create returned %+v, expected %+v", sshkey, expected) 103 | } 104 | } 105 | 106 | func TestSSHKeysDelete(t *testing.T) { 107 | setup() 108 | defer teardown() 109 | 110 | mux.HandleFunc("/v1/sshkeys/1", func(w http.ResponseWriter, r *http.Request) { 111 | testMethod(t, r, "DELETE") 112 | fmt.Fprint(w, `{}`) 113 | }) 114 | 115 | _, err := client.SSHKey.Delete(1) 116 | if err != nil { 117 | t.Errorf("SSHKeys.Delete returned error: %v", err) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | var timestampType = reflect.TypeOf(Timestamp{}) 10 | 11 | // Stringify attempts to create a string representation of DigitalOcean types 12 | func Stringify(message interface{}) string { 13 | var buf bytes.Buffer 14 | v := reflect.ValueOf(message) 15 | stringifyValue(&buf, v) 16 | return buf.String() 17 | } 18 | 19 | // stringifyValue was graciously cargoculted from the goprotubuf library 20 | func stringifyValue(w *bytes.Buffer, val reflect.Value) { 21 | if val.Kind() == reflect.Ptr && val.IsNil() { 22 | _, _ = w.Write([]byte("")) 23 | return 24 | } 25 | 26 | v := reflect.Indirect(val) 27 | 28 | switch v.Kind() { 29 | case reflect.String: 30 | fmt.Fprintf(w, `"%s"`, v) 31 | case reflect.Slice: 32 | _, _ = w.Write([]byte{'['}) 33 | for i := 0; i < v.Len(); i++ { 34 | if i > 0 { 35 | _, _ = w.Write([]byte{' '}) 36 | } 37 | 38 | stringifyValue(w, v.Index(i)) 39 | } 40 | 41 | _, _ = w.Write([]byte{']'}) 42 | return 43 | case reflect.Struct: 44 | if v.Type().Name() != "" { 45 | _, _ = w.Write([]byte(v.Type().String())) 46 | } 47 | 48 | // special handling of Timestamp values 49 | if v.Type() == timestampType { 50 | fmt.Fprintf(w, "{%s}", v.Interface()) 51 | return 52 | } 53 | 54 | _, _ = w.Write([]byte{'{'}) 55 | 56 | var sep bool 57 | for i := 0; i < v.NumField(); i++ { 58 | fv := v.Field(i) 59 | if fv.Kind() == reflect.Ptr && fv.IsNil() { 60 | continue 61 | } 62 | if fv.Kind() == reflect.Slice && fv.IsNil() { 63 | continue 64 | } 65 | 66 | if sep { 67 | _, _ = w.Write([]byte(", ")) 68 | } else { 69 | sep = true 70 | } 71 | 72 | _, _ = w.Write([]byte(v.Type().Field(i).Name)) 73 | _, _ = w.Write([]byte{':'}) 74 | stringifyValue(w, fv) 75 | } 76 | 77 | _, _ = w.Write([]byte{'}'}) 78 | default: 79 | if v.CanInterface() { 80 | fmt.Fprint(w, v.Interface()) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /timestamp.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | // Timestamp represents a time that can be unmarshalled from a JSON string 9 | // formatted as either an RFC3339 or Unix timestamp. All 10 | // exported methods of time.Time can be called on Timestamp. 11 | type Timestamp struct { 12 | time.Time 13 | } 14 | 15 | func (t Timestamp) String() string { 16 | return t.Time.String() 17 | } 18 | 19 | // UnmarshalJSON implements the json.Unmarshaler interface. 20 | // Time is expected in RFC3339 or Unix format. 21 | func (t *Timestamp) UnmarshalJSON(data []byte) error { 22 | str := string(data) 23 | i, err := strconv.ParseInt(str, 10, 64) 24 | if err == nil { 25 | t.Time = time.Unix(i, 0) 26 | } else { 27 | t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str) 28 | } 29 | return err 30 | } 31 | 32 | // Equal reports whether t and u are equal based on time.Equal 33 | func (t Timestamp) Equal(u Timestamp) bool { 34 | return t.Time.Equal(u.Time) 35 | } 36 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * API 2 | ** DONE Account 3 | CLOSED: [2015-12-01 Tue 17:30] 4 | *** DONE Полная информация о текущем аккаунте 5 | CLOSED: [2015-12-01 Tue 17:30] 6 | ** DONE Servers 7 | CLOSED: [2015-12-04 Fri 11:48] 8 | *** DONE Получение списка серверов 9 | CLOSED: [2015-12-01 Tue 17:38] 10 | *** DONE Создание сервера 11 | CLOSED: [2015-12-01 Tue 17:38] 12 | *** DONE Просмотр информации о сервере 13 | CLOSED: [2015-12-01 Tue 17:38] 14 | *** DONE Перезагрузка сервера 15 | CLOSED: [2015-12-01 Tue 17:38] 16 | *** DONE Переустановка операционной системы на сервере 17 | CLOSED: [2015-12-01 Tue 17:38] 18 | *** DONE Выключение сервера 19 | CLOSED: [2015-12-01 Tue 17:59] 20 | *** DONE Включение сервера 21 | CLOSED: [2015-12-01 Tue 18:24] 22 | *** DONE Апгрейд конфигурации 23 | CLOSED: [2015-12-02 Wed 11:28] 24 | *** DONE Удаление сервера 25 | CLOSED: [2015-12-04 Fri 09:43] 26 | *** DONE Просмотр информации о статусе текущих операций 27 | CLOSED: [2015-12-04 Fri 11:48] 28 | *** DONE Добавление SSH-ключа на сервер 29 | CLOSED: [2015-12-04 Fri 10:32] 30 | ** DONE Background 31 | CLOSED: [2015-12-09 Wed 10:43] 32 | *** DONE Получение списка дата-центров 33 | CLOSED: [2015-12-09 Wed 10:30] 34 | *** DONE Получение списка доступных образов 35 | CLOSED: [2015-12-09 Wed 10:43] 36 | ** TODO Configurations 37 | *** DONE Просмотр информации о доступных конфигурациях 38 | CLOSED: [2015-12-10 Thu 01:11] 39 | *** TODO Просмотр информации о стоимости конфигураций и трафика 40 | ** DONE SSH keys 41 | CLOSED: [2015-12-01 Tue 17:37] 42 | *** DONE Получение списка доступных ключей 43 | CLOSED: [2015-12-01 Tue 17:37] 44 | *** DONE Добавление нового ключа 45 | CLOSED: [2015-12-01 Tue 17:37] 46 | *** DONE Удаление ключа из клиентской панели 47 | CLOSED: [2015-12-01 Tue 17:37] 48 | ** DONE Notifications 49 | CLOSED: [2015-12-11 Fri 09:57] 50 | *** DONE Просмотр настроек уведомлений об исчерпании баланса 51 | CLOSED: [2015-12-11 Fri 09:57] 52 | *** DONE Изменение настроек уведомлений об исчерпании баланса 53 | CLOSED: [2015-12-11 Fri 09:57] 54 | ** TODO Billing 55 | *** TODO Просмотр информации о текущем состоянии баланса 56 | *** TODO Просмотр информации о пополнении счёта 57 | *** TODO Просмотр информации о списаниях 58 | ** TODO Tickets 59 | * CLI 60 | *** TODO Получение списка тикетов 61 | *** TODO Создание нового тикета 62 | *** TODO Просмотр комментариев к тикету 63 | *** TODO Добавление нового комментария 64 | *** TODO Закрытие тикета 65 | -------------------------------------------------------------------------------- /vscale.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "reflect" 12 | 13 | "github.com/google/go-querystring/query" 14 | ) 15 | 16 | const ( 17 | defaultApiEndpoint = "https://api.vscale.io" 18 | mediaType = "application/json" 19 | ) 20 | 21 | type Client struct { 22 | client *http.Client 23 | BaseURL *url.URL 24 | token string 25 | 26 | // Vscale services 27 | Account AccountService 28 | Background BackgroundService 29 | Configurations ConfigurationsService 30 | Notifications NotificationsService 31 | Scalet ScaletService 32 | SSHKey SSHService 33 | 34 | // Optional 35 | onRequestCompleted RequestCompletionCallback 36 | } 37 | 38 | // RequestCompletionCallback defines the type of the request callback function 39 | type RequestCompletionCallback func(*http.Request, *http.Response) 40 | 41 | type Response struct { 42 | *http.Response 43 | } 44 | 45 | type ErrorResponse struct { 46 | Response *http.Response 47 | Message string 48 | } 49 | 50 | type ArgError struct { 51 | arg, reason string 52 | } 53 | 54 | var _ error = &ArgError{} 55 | 56 | func NewArgError(arg, reason string) *ArgError { 57 | return &ArgError{ 58 | arg: arg, 59 | reason: reason, 60 | } 61 | } 62 | 63 | func (e *ArgError) Error() string { 64 | return fmt.Sprintf("%s is invalid because %s", e.arg, e.reason) 65 | } 66 | 67 | func New(token string) *Client { 68 | return NewClient(http.DefaultClient, token) 69 | } 70 | 71 | func NewClient(httpClient *http.Client, token string) *Client { 72 | if httpClient == nil { 73 | httpClient = http.DefaultClient 74 | } 75 | 76 | baseUrl, _ := url.Parse(defaultApiEndpoint) 77 | 78 | c := &Client{client: httpClient, BaseURL: baseUrl, token: token} 79 | 80 | c.Account = &AccountServiceOp{client: c} 81 | c.Background = &BackgroundServiceOp{client: c} 82 | c.Configurations = &ConfigurationsServiceOp{client: c} 83 | c.Notifications = &NotificationsServiceOp{client: c} 84 | c.Scalet = &ScaletServiceOp{client: c} 85 | c.SSHKey = &SSHServiceOp{client: c} 86 | 87 | return c 88 | } 89 | 90 | func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { 91 | rel, err := url.Parse(urlStr) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | u := c.BaseURL.ResolveReference(rel) 97 | buf := new(bytes.Buffer) 98 | if body != nil { 99 | if err := json.NewEncoder(buf).Encode(body); err != nil { 100 | return nil, err 101 | } 102 | } 103 | 104 | req, err := http.NewRequest(method, u.String(), buf) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | req.Header.Add("Content-Type", mediaType) 110 | req.Header.Add("Accept", mediaType) 111 | req.Header.Add("X-Token", c.token) 112 | 113 | return req, nil 114 | } 115 | 116 | func newResponse(r *http.Response) *Response { 117 | response := Response{Response: r} 118 | return &response 119 | } 120 | 121 | func (c *Client) OnRequestCompleted(rc RequestCompletionCallback) { 122 | c.onRequestCompleted = rc 123 | } 124 | 125 | func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { 126 | resp, err := c.client.Do(req) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | if c.onRequestCompleted != nil { 132 | c.onRequestCompleted(req, resp) 133 | } 134 | 135 | defer func() { 136 | if rerr := resp.Body.Close(); err != nil { 137 | err = rerr 138 | } 139 | }() 140 | 141 | response := newResponse(resp) 142 | 143 | if err = CheckResponse(resp); err != nil { 144 | return response, err 145 | } 146 | 147 | if v != nil { 148 | if w, ok := v.(io.Writer); ok { 149 | if _, err := io.Copy(w, resp.Body); err != nil { 150 | return nil, err 151 | } 152 | } else { 153 | if err := json.NewDecoder(resp.Body).Decode(v); err != nil { 154 | return nil, err 155 | } 156 | } 157 | } 158 | 159 | return response, err 160 | } 161 | 162 | func (r *ErrorResponse) Error() string { 163 | return fmt.Sprintf("%v %v: %d %v", 164 | r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message) 165 | } 166 | 167 | func CheckResponse(r *http.Response) error { 168 | if c := r.StatusCode; c >= 200 && c <= 299 { 169 | return nil 170 | } 171 | 172 | errorResponse := &ErrorResponse{Response: r} 173 | data, err := ioutil.ReadAll(r.Body) 174 | if err == nil && len(data) > 0 { 175 | err := json.Unmarshal(data, errorResponse) 176 | if err != nil { 177 | return err 178 | } 179 | } 180 | 181 | return errorResponse 182 | } 183 | 184 | func addOptions(s string, opt interface{}) (string, error) { 185 | v := reflect.ValueOf(opt) 186 | 187 | if v.Kind() == reflect.Ptr && v.IsNil() { 188 | return s, nil 189 | } 190 | 191 | origURL, err := url.Parse(s) 192 | if err != nil { 193 | return s, err 194 | } 195 | 196 | origValues := origURL.Query() 197 | newValues, err := query.Values(opt) 198 | if err != nil { 199 | return s, err 200 | } 201 | 202 | for k, v := range newValues { 203 | origValues[k] = v 204 | } 205 | origURL.RawQuery = origValues.Encode() 206 | return origURL.String(), nil 207 | } 208 | -------------------------------------------------------------------------------- /vscale_test.go: -------------------------------------------------------------------------------- 1 | package vscale 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "net/url" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | mux *http.ServeMux 12 | client *Client 13 | server *httptest.Server 14 | ) 15 | 16 | const ( 17 | testToken = "testtoken" 18 | ) 19 | 20 | func setup() { 21 | mux = http.NewServeMux() 22 | server = httptest.NewServer(mux) 23 | 24 | client = NewClient(nil, "testtoken") 25 | url, _ := url.Parse(server.URL) 26 | client.BaseURL = url 27 | } 28 | 29 | func teardown() { 30 | server.Close() 31 | } 32 | 33 | func testMethod(t *testing.T, r *http.Request, expected string) { 34 | if expected != r.Method { 35 | t.Errorf("Request method = %v, expected %v", r.Method, expected) 36 | } 37 | 38 | if token := r.Header.Get("X-Token"); token != testToken { 39 | t.Errorf("Request token %v should equal test token %v", token, testToken) 40 | } 41 | } 42 | --------------------------------------------------------------------------------