Compare commits

...

34 Commits

Author SHA1 Message Date
b666789c7d v1.6.6 2023-10-23 12:40:54 +03:00
b069c31745 v1.6.5 2023-10-11 12:18:27 +03:00
2953ef0a85 v1.6.4 2023-10-03 16:44:32 +03:00
50a4d5ade2 v1.6.3 2023-09-29 12:28:16 +03:00
1575b75fa6 v1.6.2 2023-09-28 19:34:23 +03:00
35fa4af0d6 v1.6.0 2023-09-28 15:37:28 +03:00
d3e6309ef8 v1.6.0-teta 2023-09-27 15:06:18 +03:00
78a4152471 v1.6.0-zeta 2023-09-25 19:11:33 +03:00
3e55195831 1.6.0-epsilon 2023-09-24 14:41:21 +03:00
9f5e76aee4 1.6.0-delta 2023-09-24 12:11:31 +03:00
df8b045e77 v1.5.8 2023-09-18 14:13:55 +03:00
4d9b8fc9d8 v1.5.7 2023-09-04 18:48:22 +03:00
e8270453cc 1.5.6 2023-08-30 13:54:30 +03:00
7de095302b v1.5.5 2023-08-25 13:53:43 +03:00
a3711057ba v1.5.4 2023-08-22 15:28:39 +03:00
1c59ca338a v1.5.3 2023-08-15 11:42:30 +03:00
f1529c9aac v1.5.2 2023-08-09 19:33:50 +03:00
040235f92f v1.5.1 2023-07-24 18:13:52 +03:00
4a152cb44c v1.5.0 2023-07-24 16:43:10 +03:00
c78b1348e3 v1.5.0 2023-07-24 15:13:04 +03:00
8f152a2f63 v1.5.0 2023-07-21 15:14:10 +03:00
0be4d8fb0c v1.5.0-epsilon 2023-07-13 18:32:21 +03:00
Никита Сорокин
5025a17ea4 v1.5.0-delta 2023-07-13 15:28:07 +03:00
7c787f6fce v1.5.0-gamma2 2023-07-07 12:40:03 +03:00
20fd7ab50c v1.5.0-gamma-qf 2023-06-30 11:24:26 +03:00
f50f71ab0d v1.5.0-gamma 2023-06-30 11:21:47 +03:00
29c7f143fe v1.4.7 2023-06-29 15:52:49 +03:00
264538f492 v1.4.6 2023-06-23 15:13:22 +03:00
c06a3198f6 v1.4.5 2023-06-19 15:06:31 +03:00
c9e4ae6afe v1.4.4 2023-06-01 16:50:10 +03:00
2a1593f45f v1.5.0-beta 2023-05-18 13:55:28 +03:00
190f24dac1 v1.4.3 2023-05-18 13:37:48 +03:00
256dba5134 v1.5.0-alfa 2023-05-15 19:27:02 +03:00
b7137683ab v1.4.2 2023-05-15 10:55:36 +03:00
438 changed files with 14809 additions and 4943 deletions

5
.gitignore vendored
View File

@@ -1,2 +1,5 @@
cmd/
.idea/
.idea/
.vscode/
.fleet/
.DS_Store

View File

@@ -1,6 +1,10 @@
## Version 1.4.1
## Version 1.6.6
### Bug Fixes
## Bugfix
- Fixed model RecordExtNet in cloudbroker/extnet/models for correct work of get request
- Fixed json tags in ItemResourceConsumption model and delete extra model Consumed in cloudbroker/account/models
- Fixed statelessSepId field type from uint64 to int64 in cloudbroker/compute/models for correct work of list request
- Fixed cloudapi/k8s/workerAdd returning value type
- Fixed cloudapi/cloudbroker/account/create/update SendAccessEmails field tags
## Feature
- Added GetRaw and ListRaw methods that give response as an array of bytes for cloudAPI groups: account, compute, k8s, disks, rg, bservice, disks,extnet, flipgroup, image, k8ci, lb, locations(list), sizes(list), stack, tasks, vins
- Added GetRaw and ListRaw methods that give response as an array of bytes for cloudbroker groups: account, apiaccess, compute, disks, extnet, flipgroup, grid, group, image, k8ci, k8s, lb, rg, sep, stack, tasks, user, vgpu, vins

124
README.md
View File

@@ -9,6 +9,8 @@ Decort SDK - это библиотека, написанная на языке G
- Версия 1.2.x Decort-SDK соответствует 3.8.5 версии платформы
- Версия 1.3.x Decort-SDK соответствует 3.8.5 версии платформы
- Версия 1.4.x Decort-SDK соответствует 3.8.6 версии платформы
- Версия 1.5.x Decort-SDK соответствует 3.8.7 версии платформы
- Версия 1.6.x Decort-SDK соответствует 3.8.8 версии платформы
## Оглавление
@@ -61,12 +63,11 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
- `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов;
- `BService` - управление группами виртуальных машин (computes);
- `Compute` - управление виртуальными машинами (индивидуально);
- `ComputeCI` - управление конвейром для создания виртуальных машин;
- `Disks` - управление виртуальными дисками;
- `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ;
- `FLIPgroup` - управление группами "плавающими" ip - адресами;
- `Image` - управление образами операционных систем;
- `K8CI` - управление конвейром для создания кластера;
- `K8CI` - получение информации о конвейере для создания кластера;
- `K8S` - управление кластерами kubernetes;
- `KVMPPC` - создание виртуальной машины Power PC (IBM);
- `KVMx86` - создание виртуальной машины x86;
@@ -74,6 +75,7 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
- `Locations` - получение информации о grid площадки;
- `RG` - управление ресурсными группами аккаунта;
- `Sizes` - получение информации о потребляемых ресурсах виртуальными машинами и дисками;
- `Stack` - получение информации о вычислительных узлах;
- `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера);
- `VINS` - управление виртуальными изолированными сетями.
@@ -83,30 +85,39 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
Данная группа ручек позволяет выполнять следующие операции в платформе:
- `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов;
- `APIAccess` - управление доступом к API и его объектам;
- `Backup` - управление резервным копированием;
- `Compute` - управление виртуальными машинами (индивидуально);
- `Disks` - управление виртуальными дисками;
- `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ;
- `FLIPGroup` - управление группами с «плавающими» ip адресами;
- `Grid` - управление площадками;
- `Group` - управление группами пользователей;
- `Image` - управление образами операционных систем;
- `K8CI` - управление конвейром для создания кластера;
- `K8S` - управление кластерами kubernetes;
- `KVMPPC` - создание виртуальной машины Power PC (IBM);
- `KVMx86` - создание виртуальной машины x86;
- `LB` - управление балансировщиками нагрузки;
- `PCIDevice` - управление устройствами;
- `RG` - управление ресурсными группами аккаунта;
- `SEP` - управление storage endpoint (sep);
- `Stack` - получение информации о вычислительных узлах;
- `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера);
- `User` - управление пользователями (индивидуально);
- `VGPU` - управление виртуальными графическими процессорами;
- `VINS` - управление виртуальными изолированными сетями.
## Работа с библиотекой
Алгоритм работы с библиотекой выглядит следующим образом:
1. Настройка конфигурации клиента.
2. Парсинг конфигурации из файла.
3. Создание клиента.
4. Создание структуры запроса.
5. Выполнение запроса.
1. Выполнение одного из действий:
- настройка конфигурации клиента;
- парсинг конфигурации из файла.
2. Создание клиента.
3. Создание структуры запроса.
4. Выполнение запроса.
### Настройка конфигурации клиента
@@ -118,7 +129,8 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
| SSOURL | string | Да | URL адрес сервиса аутентификации и авторизации |
| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие |
| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата, по умолчанию - true |
| Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата |
| Token | string | Нет | JWT токен |
#### Пример конфигурации клиента
@@ -127,6 +139,7 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
)
func main(){
// Настройка конфигурации
cfg := config.Config{
@@ -136,6 +149,8 @@ func main(){
DecortURL: "https://mr4.digitalenergy.online",
Retries: 5,
}
cfg.SetTimeout(5 * time.Minute)
}
```
@@ -165,6 +180,7 @@ func main() {
"ssoUrl": "https://sso.digitalenergy.online",
"decortUrl": "https://mr4.digitalenergy.online",
"retries": 5,
"timeout": "5m",
"sslSkipVerify": false
}
```
@@ -177,6 +193,7 @@ appSecret: <APP_SECRET>
ssoUrl: https://sso.digitalenergy.online
decortUrl: https://mr4.digitalenergy.online
retries: 5
timeout: 5m
sslSkipVerify: false
```
@@ -204,6 +221,8 @@ func main() {
Retries: 5,
}
cfg.SetTimeout(5 * time.Minute)
// Создание клиента
client := decort.New(cfg)
}
@@ -222,7 +241,6 @@ func main() {
- `pkg/cloudapi/account` - для `Account`
- `pkg/cloudapi/bservice` - для `Basic Service`
- `pkg/cloudapi/compute` - для `Compute`
- `pkg/cloudapi/computeci` - для `ComputeCI`
- `pkg/cloudapi/disks` - для `Disks`
- `pkg/cloudapi/extnet` - для `ExtNet`
- `pkg/cloudapi/flipgroup` - для `FLIPGroup`
@@ -234,24 +252,33 @@ func main() {
- `pkg/cloudapi/lb` - для `LB`
- `pkg/cloudapi/locations` - для `Locations`
- `pkg/cloudapi/rg` - для `RG`
- `pkg/cloudapi/sizes` - для `RG`
- `pkg/cloudapi/sizes` - для `Sizes`
- `pkg/cloudapi/stack` - для `Stack`
- `pkg/cloudapi/tasks` - для `Tasks`
- `pkg/cloudapi/vins` - для `VINS`
- **cloudbroker**:
- `pkg/cloudbroker/account` - для `Account`
- `pkg/cloudbroker/apiaccess` - для `APIAccess`
- `pkg/cloudbroker/backup` - для `Backup`
- `pkg/cloudbroker/compute` - для `Compute`
- `pkg/cloudbroker/disks` - для `Disks`
- `pkg/cloudbroker/extnet` - для `ExtNet`
- `pkg/cloudbroker/flipgroup` - для `FLIPGroup`
- `pkg/cloudbroker/grid` - для `Grid`
- `pkg/cloudbroker/group` - для `Group`
- `pkg/cloudbroker/image` - для `Image`
- `pkg/cloudbroker/k8ci` - для `K8CI`
- `pkg/cloudbroker/k8s` - для `K8S`
- `pkg/cloudbroker/kvmppc` - для `KVMPPC`
- `pkg/cloudbroker/kvmx86` - для `KVMX86`
- `pkg/cloudbroker/lb` - для `LB`
- `pkg/cloudbroker/pcidevice` - для `PCIDevice`
- `pkg/cloudbroker/rg` - для `RG`
- `pkg/cloudbroker/sep` - для `SEP`
- `pkg/cloudbroker/stack` - для `Stack`
- `pkg/cloudbroker/tasks` - для `Tasks`
- `pkg/cloudbroker/user` - для `User`
- `pkg/cloudbroker/vgpu` - для `VGPU`
- `pkg/cloudbroker/vins` - для `VINS`
Все поля структуры имеют описание, в которых содержится:
@@ -388,7 +415,7 @@ func main() {
Чтобы выполнить запрос, необходимо:
1. Вызвать у клиента метод, отвечаеющий за определение группы API для взаимодействия, это может быть `.CloudAPI()`, либо `.CloudBroker()`. Данные методы возвращаеют соответствующие структуры, с помощью которых можно соверать запросы.
1. Вызвать у клиента метод, отвечаеющий за определение группы API для взаимодействия, это может быть `.CloudAPI()`, либо `.CloudBroker()`. Данные методы возвращаеют соответствующие структуры, с помощью которых можно совершать запросы.
2. Вызвать у возвращенной структуры метод, определяющий группу ручек для взаимодействия.
Доступные методы для `.CloudAPI()`:
@@ -396,7 +423,6 @@ func main() {
- `.Account()` - для работы с `Account`
- `.BService()` - для работы с `BService`
- `.Compute()` - для работы с `Compute`
- `.ComputeCI()` - для работы с `ComputeCI`
- `.Disks()` - для работы с `Disks`
- `.ExtNet()` - для работы с `ExtNet`
- `.FLIPgroup()` - для работы с `FLIPGroup`
@@ -409,37 +435,46 @@ func main() {
- `.Locations()` - для работы с `Locations`
- `.RG()` - для работы с `RG`
- `.Sizes()` - для работы с `Sizes`
- `.Stack()` - для работы с `Stack`
- `.Tasks()` - для работы с `Tasks`
- `.VINS()` - для работы с `VINS`
Доступные методы для `.CloudBroker()`:
- `.Account()` - для работы с `Account`
- `.APIAccess()` - для работы с `APIAccess`
- `.Backup()` - для работы с `Backup`
- `.Compute()` - для работы с `Compute`
- `.Disks()` - для работы с `Disks`
- `.ExtNet()` - для работы с `ExtNet`
- `.FLIPGroup()` - для работы с `FLIPGroup`
- `.Grid()` - для работы с `Grid`
- `.Group()` - для работы с `Group`
- `.Image()` - для работы с `Image`
- `.K8CI()` - для работы с `K8CI`
- `.K8S()` - для работы с `K8S`
- `.KVMPPC()` - для работы с `KVMPPC`
- `.KVMx86()` - для работы с `KVMX86`
- `.LB()` - для работы с `LB`
- `.PCIDevice()` - для работы с `PCIDevice`
- `.RG()` - для работы с `RG`
- `.SEP()` - для работы с `SEP`
- `.Stack()` - для работы с `Stack`
- `.Tasks()` - для работы с `Tasks`
- `.User()` - для работы с `User`
- `.VGPU()` - для работы с `VGPU`
- `.VINS()` - для работы с `VINS`
3. Вызвать метод, отвечающий за выполнение запроса и передать в него:
- контекст;
- структуру запроса.
У кождой группы ручек API имеются свои доступные методы, которые определяются платформой.
У каждой группы ручек API имеются свои доступные методы, которые определяются платформой.
4. Обработать результат и ошибки.
Т.к. все вызовы методов идут последовательно, можно их объеденить в конвейер:
Общий вид вонвейра будет выглядеть так:
Общий вид конвейера будет выглядеть так:
```go
client.<API>.<группа>.<метод>
@@ -492,6 +527,56 @@ func main() {
}
```
Для запросов Get и List реализованы запросы GetRaw и ListRaw, которые возвращают ответ не в виде соответствующей структуры, а в виде массива байт (JSON).
Выполнение таких запросов происходит аналогично.
#### Пример выполнения GetRaw и ListRaw запросов
```go
package main
import (
"log"
"fmt"
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/account"
)
func main() {
// Настройка конфигурации
cfg := config.Config{
AppID: "<APPID>",
AppSecret: "<APPSECRET>",
SSOURL: "https://sso.digitalenergy.online",
DecortURL: "https://mr4.digitalenergy.online",
Retries: 5,
}
// Создание клиента
client := decort.New(cfg)
// 1. Создание структуры запроса GetRequest на создание аккаунта и выполнение GetRaw запроса с помощью конвейера
req1 := account.GetRequest{
AccountID: 123,
}
res1, err := client.CloudAPI().Account().GetRaw(context.Background(), req1)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(res1))
// 2. Создание структуры запроса ListRequest на получение аккаунтов и выполнение ListRaw запроса с помощью конвейера
req2 := account.ListRequest{}
res2, err := client.CloudAPI().Account().ListRaw(context.Background(), req2)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(res2))
}
```
### Фильтрация
Для каждого `ListRequest` в SDK есть группа функций для фильтрации ответа платформы. Для того чтобы произвести фильтрацию по заданным полям, достаточно описать анонимную функцию (предикат) в `.FilterFunc()`, например:
@@ -647,6 +732,8 @@ func main() {
DecortURL: "<DECORT_URL>",
Retries: 5,
}
cfg.SetTimeout(5 * time.Minute)
// Создание клиента
client := decort.New(cfg)
@@ -693,7 +780,8 @@ func main() {
| Password | string | Да | пароль legacy пользователя |
| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие |
| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата, по умолчанию - true |
| Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата |
| Token | string | Нет | JWT токен |
#### Пример конфигурации legacy клиента
@@ -711,6 +799,8 @@ func main(){
DecortURL: "https://mr4.digitalenergy.online",
Retries: 5,
}
legacyCfg.SetTimeout(5 * time.Minute)
}
```
@@ -739,6 +829,7 @@ func main() {
"password": "<PASSWORD>",
"decortUrl": "https://mr4.digitalenergy.online",
"retries": 5,
"timeout": "5m",
"sslSkipVerify": true
}
```
@@ -749,6 +840,7 @@ username: <USERNAME>
password: <PASSWORD>
decortUrl: https://mr4.digitalenergy.online
retries: 5
timeout: 5m
sslSkipVerify: true
```
### Создание legacy клиента
@@ -774,6 +866,8 @@ func main() {
Retries: 5,
}
legacyCfg.SetTimeout(5 * time.Minute)
// Создание клиента
legacyClient := decort.NewLegacy(cfg)
}

407
client.go
View File

@@ -1,24 +1,34 @@
package decortsdk
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"strconv"
"strings"
"sync"
"time"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi"
k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
k8s_cb"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s"
"github.com/google/go-querystring/query"
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/client"
)
// HTTP-client for platform
type DecortClient struct {
decortURL string
client *http.Client
decortURL string
client *http.Client
cfg config.Config
expiryTime time.Time
mutex *sync.Mutex
}
// Сlient builder
@@ -27,9 +37,25 @@ func New(cfg config.Config) *DecortClient {
cfg.Retries = 5
}
var expiryTime time.Time
if cfg.Token != "" {
expiryTime = time.Now().AddDate(0, 0, 1)
}
return &DecortClient{
decortURL: cfg.DecortURL,
client: client.NewHttpClient(cfg),
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
//nolint:gosec
InsecureSkipVerify: cfg.SSLSkipVerify,
},
},
},
cfg: cfg,
expiryTime: expiryTime,
mutex: &sync.Mutex{},
}
}
@@ -45,6 +71,283 @@ func (dc *DecortClient) CloudBroker() *cloudbroker.CloudBroker {
// DecortApiCall method for sending requests to the platform
func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
k8sCaCreateReq, okCa := params.(k8s_ca.CreateRequest)
k8sCbCreateReq, okCb := params.(k8s_cb.CreateRequest)
if okCa {
reqBody := &bytes.Buffer{}
writer := multipart.NewWriter(reqBody)
if k8sCaCreateReq.OidcCertificate != "" {
part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt")
_, _ = io.Copy(part, strings.NewReader(k8sCaCreateReq.OidcCertificate))
}
_ = writer.WriteField("name", k8sCaCreateReq.Name)
_ = writer.WriteField("rgId", strconv.FormatUint(k8sCaCreateReq.RGID, 10))
_ = writer.WriteField("k8ciId", strconv.FormatUint(k8sCaCreateReq.K8SCIID, 10))
_ = writer.WriteField("workerGroupName", k8sCaCreateReq.WorkerGroupName)
_ = writer.WriteField("networkPlugin", k8sCaCreateReq.NetworkPlugin)
if k8sCaCreateReq.MasterSEPID != 0 {
_ = writer.WriteField("masterSepId", strconv.FormatUint(k8sCaCreateReq.MasterSEPID, 10))
}
if k8sCaCreateReq.MasterSEPPool != "" {
_ = writer.WriteField("masterSepPool", k8sCaCreateReq.MasterSEPPool)
}
if k8sCaCreateReq.WorkerSEPID != 0 {
_ = writer.WriteField("workerSepId", strconv.FormatUint(k8sCaCreateReq.WorkerSEPID, 10))
}
if k8sCaCreateReq.WorkerSEPPool != "" {
_ = writer.WriteField("workerSepPool", k8sCaCreateReq.WorkerSEPPool)
}
if k8sCaCreateReq.Labels != nil {
for _, v := range k8sCaCreateReq.Labels {
_ = writer.WriteField("labels", v)
}
}
if k8sCaCreateReq.Taints != nil {
for _, v := range k8sCaCreateReq.Taints {
_ = writer.WriteField("taints", v)
}
}
if k8sCaCreateReq.Annotations != nil {
for _, v := range k8sCaCreateReq.Annotations {
_ = writer.WriteField("annotations", v)
}
}
if k8sCaCreateReq.MasterCPU != 0 {
_ = writer.WriteField("masterCpu", strconv.FormatUint(uint64(k8sCaCreateReq.MasterCPU), 10))
}
if k8sCaCreateReq.MasterNum != 0 {
_ = writer.WriteField("masterNum", strconv.FormatUint(uint64(k8sCaCreateReq.MasterNum), 10))
}
if k8sCaCreateReq.MasterRAM != 0 {
_ = writer.WriteField("masterRam", strconv.FormatUint(uint64(k8sCaCreateReq.MasterRAM), 10))
}
if k8sCaCreateReq.MasterDisk != 0 {
_ = writer.WriteField("masterDisk", strconv.FormatUint(uint64(k8sCaCreateReq.MasterDisk), 10))
}
if k8sCaCreateReq.WorkerCPU != 0 {
_ = writer.WriteField("workerCpu", strconv.FormatUint(uint64(k8sCaCreateReq.WorkerCPU), 10))
}
if k8sCaCreateReq.WorkerNum != 0 {
_ = writer.WriteField("workerNum", strconv.FormatUint(uint64(k8sCaCreateReq.WorkerNum), 10))
}
if k8sCaCreateReq.WorkerRAM != 0 {
_ = writer.WriteField("workerRam", strconv.FormatUint(uint64(k8sCaCreateReq.WorkerRAM), 10))
}
if k8sCaCreateReq.WorkerDisk != 0 {
_ = writer.WriteField("workerDisk", strconv.FormatUint(uint64(k8sCaCreateReq.WorkerDisk), 10))
}
if k8sCaCreateReq.ExtNetID != 0 {
_ = writer.WriteField("extnetId", strconv.FormatUint(k8sCaCreateReq.ExtNetID, 10))
}
if k8sCaCreateReq.VinsId != 0 {
_ = writer.WriteField("vinsId", strconv.FormatUint(k8sCaCreateReq.VinsId, 10))
}
if !k8sCaCreateReq.WithLB {
_ = writer.WriteField("withLB", strconv.FormatBool(k8sCaCreateReq.WithLB))
}
_ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(k8sCaCreateReq.HighlyAvailable))
if k8sCaCreateReq.AdditionalSANs != nil {
for _, v := range k8sCaCreateReq.AdditionalSANs {
_ = writer.WriteField("additionalSANs", v)
}
}
if k8sCaCreateReq.InitConfiguration != "" {
_ = writer.WriteField("initConfiguration", k8sCaCreateReq.InitConfiguration)
}
if k8sCaCreateReq.ClusterConfiguration != "" {
_ = writer.WriteField("clusterConfiguration", k8sCaCreateReq.ClusterConfiguration)
}
if k8sCaCreateReq.KubeletConfiguration != "" {
_ = writer.WriteField("kubeletConfiguration", k8sCaCreateReq.KubeletConfiguration)
}
if k8sCaCreateReq.KubeProxyConfiguration != "" {
_ = writer.WriteField("kubeProxyConfiguration", k8sCaCreateReq.KubeProxyConfiguration)
}
if k8sCaCreateReq.JoinConfiguration != "" {
_ = writer.WriteField("joinConfiguration", k8sCaCreateReq.JoinConfiguration)
}
if k8sCaCreateReq.Description != "" {
_ = writer.WriteField("desc", k8sCaCreateReq.Description)
}
if k8sCaCreateReq.UserData != "" {
_ = writer.WriteField("userData", k8sCaCreateReq.UserData)
}
_ = writer.WriteField("extnetOnly", strconv.FormatBool(k8sCaCreateReq.ExtNetOnly))
_ = writer.FormDataContentType()
ct := writer.FormDataContentType()
writer.Close()
req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+"/restmachine"+url, reqBody)
if err != nil {
return nil, err
}
if err = dc.getToken(ctx); err != nil {
return nil, err
}
resp, err := dc.domp(req, ct)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New(string(respBytes))
}
return respBytes, nil
} else if okCb {
reqBody := &bytes.Buffer{}
writer := multipart.NewWriter(reqBody)
if k8sCbCreateReq.OidcCertificate != "" {
part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt")
_, _ = io.Copy(part, strings.NewReader(k8sCbCreateReq.OidcCertificate))
}
_ = writer.WriteField("name", k8sCbCreateReq.Name)
_ = writer.WriteField("rgId", strconv.FormatUint(k8sCbCreateReq.RGID, 10))
_ = writer.WriteField("k8ciId", strconv.FormatUint(k8sCbCreateReq.K8CIID, 10))
_ = writer.WriteField("workerGroupName", k8sCbCreateReq.WorkerGroupName)
_ = writer.WriteField("networkPlugin", k8sCbCreateReq.NetworkPlugin)
if k8sCbCreateReq.MasterSEPID != 0 {
_ = writer.WriteField("masterSepId", strconv.FormatUint(k8sCbCreateReq.MasterSEPID, 10))
}
if k8sCbCreateReq.MasterSEPPool != "" {
_ = writer.WriteField("masterSepPool", k8sCbCreateReq.MasterSEPPool)
}
if k8sCbCreateReq.WorkerSEPID != 0 {
_ = writer.WriteField("workerSepId", strconv.FormatUint(k8sCbCreateReq.WorkerSEPID, 10))
}
if k8sCbCreateReq.WorkerSEPPool != "" {
_ = writer.WriteField("workerSepPool", k8sCbCreateReq.WorkerSEPPool)
}
if k8sCbCreateReq.Labels != nil {
for _, v := range k8sCbCreateReq.Labels {
_ = writer.WriteField("labels", v)
}
}
if k8sCbCreateReq.Taints != nil {
for _, v := range k8sCbCreateReq.Taints {
_ = writer.WriteField("taints", v)
}
}
if k8sCbCreateReq.Annotations != nil {
for _, v := range k8sCbCreateReq.Annotations {
_ = writer.WriteField("annotations", v)
}
}
if k8sCbCreateReq.MasterCPU != 0 {
_ = writer.WriteField("masterCpu", strconv.FormatUint(k8sCbCreateReq.MasterCPU, 10))
}
if k8sCbCreateReq.MasterNum != 0 {
_ = writer.WriteField("masterNum", strconv.FormatUint(k8sCbCreateReq.MasterNum, 10))
}
if k8sCbCreateReq.MasterRAM != 0 {
_ = writer.WriteField("masterRam", strconv.FormatUint(k8sCbCreateReq.MasterRAM, 10))
}
if k8sCbCreateReq.MasterDisk != 0 {
_ = writer.WriteField("masterDisk", strconv.FormatUint(k8sCbCreateReq.MasterDisk, 10))
}
if k8sCbCreateReq.WorkerCPU != 0 {
_ = writer.WriteField("workerCpu", strconv.FormatUint(k8sCbCreateReq.WorkerCPU, 10))
}
if k8sCbCreateReq.WorkerNum != 0 {
_ = writer.WriteField("workerNum", strconv.FormatUint(k8sCbCreateReq.WorkerNum, 10))
}
if k8sCbCreateReq.WorkerRAM != 0 {
_ = writer.WriteField("workerRam", strconv.FormatUint(k8sCbCreateReq.WorkerRAM, 10))
}
if k8sCbCreateReq.WorkerDisk != 0 {
_ = writer.WriteField("workerDisk", strconv.FormatUint(k8sCbCreateReq.WorkerDisk, 10))
}
if k8sCbCreateReq.ExtNetID != 0 {
_ = writer.WriteField("extnetId", strconv.FormatUint(k8sCbCreateReq.ExtNetID, 10))
}
if k8sCbCreateReq.VinsId != 0 {
_ = writer.WriteField("vinsId", strconv.FormatUint(k8sCbCreateReq.VinsId, 10))
}
if !k8sCbCreateReq.WithLB {
_ = writer.WriteField("withLB", strconv.FormatBool(k8sCbCreateReq.WithLB))
}
_ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(k8sCbCreateReq.HighlyAvailable))
if k8sCbCreateReq.AdditionalSANs != nil {
for _, v := range k8sCbCreateReq.AdditionalSANs {
_ = writer.WriteField("additionalSANs", v)
}
}
if k8sCbCreateReq.InitConfiguration != "" {
_ = writer.WriteField("initConfiguration", k8sCbCreateReq.InitConfiguration)
}
if k8sCbCreateReq.ClusterConfiguration != "" {
_ = writer.WriteField("clusterConfiguration", k8sCbCreateReq.ClusterConfiguration)
}
if k8sCbCreateReq.KubeletConfiguration != "" {
_ = writer.WriteField("kubeletConfiguration", k8sCbCreateReq.KubeletConfiguration)
}
if k8sCbCreateReq.KubeProxyConfiguration != "" {
_ = writer.WriteField("kubeProxyConfiguration", k8sCbCreateReq.KubeProxyConfiguration)
}
if k8sCbCreateReq.JoinConfiguration != "" {
_ = writer.WriteField("joinConfiguration", k8sCbCreateReq.JoinConfiguration)
}
if k8sCbCreateReq.Description != "" {
_ = writer.WriteField("desc", k8sCbCreateReq.Description)
}
if k8sCbCreateReq.UserData != "" {
_ = writer.WriteField("userData", k8sCbCreateReq.UserData)
}
_ = writer.WriteField("extnetOnly", strconv.FormatBool(k8sCbCreateReq.ExtNetOnly))
_ = writer.FormDataContentType()
ct := writer.FormDataContentType()
writer.Close()
req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+"/restmachine"+url, reqBody)
if err != nil {
return nil, err
}
if err = dc.getToken(ctx); err != nil {
return nil, err
}
resp, err := dc.domp(req, ct)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New(string(respBytes))
}
return respBytes, nil
}
values, err := query.Values(params)
if err != nil {
return nil, err
@@ -56,7 +359,11 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p
return nil, err
}
resp, err := dc.client.Do(req)
if err = dc.getToken(ctx); err != nil {
return nil, err
}
resp, err := dc.do(req)
if err != nil {
return nil, err
}
@@ -73,3 +380,91 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p
return respBytes, nil
}
func (dc *DecortClient) getToken(ctx context.Context) error {
dc.mutex.Lock()
defer dc.mutex.Unlock()
if dc.cfg.Token == "" || time.Now().After(dc.expiryTime) {
body := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&response_type=id_token", dc.cfg.AppID, dc.cfg.AppSecret)
bodyReader := strings.NewReader(body)
dc.cfg.SSOURL = strings.TrimSuffix(dc.cfg.SSOURL, "/")
req, _ := http.NewRequestWithContext(ctx, "POST", dc.cfg.SSOURL+"/v1/oauth/access_token", bodyReader)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := dc.client.Do(req)
if err != nil {
return fmt.Errorf("cannot get token: %w", err)
}
tokenBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("cannot get token: %s", tokenBytes)
}
token := string(tokenBytes)
dc.cfg.Token = token
dc.expiryTime = time.Now().AddDate(0, 0, 1)
}
return nil
}
func (dc *DecortClient) do(req *http.Request) (*http.Response, error) {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "bearer "+dc.cfg.Token)
req.Header.Set("Accept", "application/json")
// var resp *http.Response
// var err error
buf, _ := io.ReadAll(req.Body)
// req = req.Clone(req.Context())
// for i := uint64(0); i < dc.cfg.Retries; i++ {
req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := dc.client.Do(req)
// if err == nil {
if resp.StatusCode == 200 {
return resp, err
}
respBytes, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("%s", respBytes)
resp.Body.Close()
// }
// }
return nil, fmt.Errorf("could not execute request: %w", err)
}
func (dc *DecortClient) domp(req *http.Request, ctype string) (*http.Response, error) {
req.Header.Add("Content-Type", ctype)
req.Header.Add("Authorization", "bearer "+dc.cfg.Token)
req.Header.Set("Accept", "application/json")
// var resp *http.Response
// var err error
buf, _ := io.ReadAll(req.Body)
// req = req.Clone(req.Context())
// for i := uint64(0); i < dc.cfg.Retries; i++ {
req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := dc.client.Do(req)
// if err == nil {
if resp.StatusCode == 200 {
return resp, err
}
respBytes, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("%s", respBytes)
resp.Body.Close()
// }
// }
return nil, fmt.Errorf("could not execute request: %w", err)
}

View File

@@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"os"
"time"
"gopkg.in/yaml.v3"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
@@ -41,9 +42,18 @@ type Config struct {
// Required: false
Retries uint64 `json:"retries" yaml:"retries"`
// Skip verify, true by default
// Skip verify
// Required: false
SSLSkipVerify bool `json:"sslSkipVerify" yaml:"sslSkipVerify"`
// HTTP client timeout, unlimited if left empty
// Required: false
Timeout Duration `json:"timeout" yaml:"timeout"`
}
// SetTimeout is used to set HTTP client timeout.
func (c *Config) SetTimeout(dur time.Duration) {
c.Timeout = Duration(dur)
}
// ParseConfigJSON parses Config from specified JSON-formatted file.

View File

@@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"os"
"time"
"gopkg.in/yaml.v3"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
@@ -35,9 +36,18 @@ type LegacyConfig struct {
// Required: false
Retries uint64 `json:"retries" yaml:"retries"`
// Skip verify, true by default
// Skip verify
// Required: false
SSLSkipVerify bool `json:"sslSkipVerify" yaml:"sslSkipVerify"`
// HTTP client timeout, unlimited if left empty
// Required: false
Timeout Duration `json:"timeout" yaml:"timeout"`
}
// SetTimeout is used to set HTTP client timeout.
func (c *LegacyConfig) SetTimeout(dur time.Duration) {
c.Timeout = Duration(dur)
}
// ParseLegacyConfigJSON parses LegacyConfig from specified JSON-formatted file.

50
config/timeouts.go Normal file
View File

@@ -0,0 +1,50 @@
package config
import (
"encoding/json"
"fmt"
"time"
)
// Duration is a wrapper around time.Duration (used for better user experience)
type Duration time.Duration
func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
var v interface{}
if err := unmarshal(&v); err != nil {
return err
}
switch value := v.(type) {
case string:
tmp, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(tmp)
return nil
default:
return fmt.Errorf("invalid duration %v", value)
}
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case string:
tmp, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(tmp)
return nil
default:
return fmt.Errorf("invalid duration %v", value)
}
}
func (d *Duration) Get() time.Duration {
return time.Duration(*d)
}

View File

@@ -1,40 +0,0 @@
package client
import (
"crypto/tls"
"net/http"
"time"
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
)
func NewHttpClient(cfg config.Config) *http.Client {
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{
//nolint:gosec
InsecureSkipVerify: cfg.SSLSkipVerify,
},
}
var expiredTime time.Time
if cfg.Token != "" {
expiredTime = time.Now().AddDate(0, 0, 1)
}
return &http.Client{
Transport: &transport{
base: transCfg,
retries: cfg.Retries,
clientID: cfg.AppID,
clientSecret: cfg.AppSecret,
ssoURL: cfg.SSOURL,
token: cfg.Token,
expiryTime: expiredTime,
//TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Timeout: 5 * time.Minute,
}
}

View File

@@ -1,33 +0,0 @@
package client
import (
"crypto/tls"
"net/http"
"net/url"
"time"
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
)
// NewLegacyHttpClient creates legacy HTTP Client
func NewLegacyHttpClient(cfg config.LegacyConfig) *http.Client {
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{
//nolint:gosec
InsecureSkipVerify: cfg.SSLSkipVerify,
},
}
return &http.Client{
Transport: &transportLegacy{
base: transCfg,
username: url.QueryEscape(cfg.Username),
password: url.QueryEscape(cfg.Password),
retries: cfg.Retries,
token: cfg.Token,
decortURL: cfg.DecortURL,
},
Timeout: 5 * time.Minute,
}
}

View File

@@ -1,70 +0,0 @@
package client
import (
"fmt"
"io"
"net/http"
"strings"
"time"
)
type transportLegacy struct {
base http.RoundTripper
username string
password string
retries uint64
token string
decortURL string
}
func (t *transportLegacy) RoundTrip(request *http.Request) (*http.Response, error) {
if t.token == "" {
body := fmt.Sprintf("username=%s&password=%s", t.username, t.password)
bodyReader := strings.NewReader(body)
req, _ := http.NewRequestWithContext(request.Context(), "POST", t.decortURL+"/restmachine/cloudapi/user/authenticate", bodyReader)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := t.base.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("unable to get token: %w", err)
}
tokenBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unable to get token: %s", tokenBytes)
}
token := string(tokenBytes)
t.token = token
}
tokenValue := fmt.Sprintf("&authkey=%s", t.token)
tokenReader := strings.NewReader(tokenValue)
newBody := io.MultiReader(request.Body, tokenReader)
req, _ := http.NewRequestWithContext(request.Context(), request.Method, request.URL.String(), newBody)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
var resp *http.Response
var err error
for i := uint64(0); i < t.retries; i++ {
resp, err = t.base.RoundTrip(req)
if err == nil {
if resp.StatusCode == 200 {
return resp, nil
}
respBytes, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("%s", respBytes)
resp.Body.Close()
}
time.Sleep(time.Second * 5)
}
return nil, fmt.Errorf("could not execute request: %w", err)
}

View File

@@ -1,69 +0,0 @@
package client
import (
"fmt"
"io"
"net/http"
"strings"
"time"
)
type transport struct {
base http.RoundTripper
retries uint64
clientID string
clientSecret string
token string
ssoURL string
expiryTime time.Time
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.token == "" || time.Now().After(t.expiryTime) {
body := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&response_type=id_token", t.clientID, t.clientSecret)
bodyReader := strings.NewReader(body)
t.ssoURL = strings.TrimSuffix(t.ssoURL, "/")
req, _ := http.NewRequestWithContext(req.Context(), "POST", t.ssoURL+"/v1/oauth/access_token", bodyReader)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := t.base.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("cannot get token: %w", err)
}
tokenBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("cannot get token: %s", tokenBytes)
}
token := string(tokenBytes)
t.token = token
t.expiryTime = time.Now().AddDate(0, 0, 1)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "bearer "+t.token)
req.Header.Set("Accept", "application/json")
var resp *http.Response
var err error
for i := uint64(0); i < t.retries; i++ {
resp, err = t.base.RoundTrip(req)
if err == nil {
if resp.StatusCode == 200 {
return resp, nil
}
respBytes, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("%s", respBytes)
resp.Body.Close()
}
//logrus.Errorf("Could not execute request: %v. Retrying %d/%d", err, i+1, t.retries)
time.Sleep(time.Second * 5)
}
return nil, fmt.Errorf("could not execute request: %w", err)
}

View File

@@ -1,11 +1,19 @@
package validators
import (
"github.com/go-playground/validator/v10"
"regexp"
"strings"
"github.com/go-playground/validator/v10"
)
// computeDriverValidator is used to validate Driver field in kvmx86/kvmppc create.
func computeDriverValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
return StringInSlice(fieldValue, computeDriverValues)
}
// protoValidator is used to validate Proto fields.
func protoValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
@@ -256,3 +264,11 @@ func strictLooseValidator(fe validator.FieldLevel) bool {
return StringInSlice(fieldValue, strictLooseValues)
}
// name workerGroup must be more 3 symbol
func workerGroupNameValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
fieldValue = strings.Trim(fieldValue, " ")
return len(fieldValue) >= 3
}

View File

@@ -107,6 +107,12 @@ func errorMessage(fe validator.FieldError) string {
fe.Field(),
joinValues(computeDataDisksValues))
case "computeDriver":
return fmt.Sprintf("%s %s must be one of the following: %s",
prefix,
fe.Field(),
joinValues(computeDriverValues))
// Disk Validators
case "diskType":
return fmt.Sprintf("%s %s must be one of the following: %s",
@@ -121,6 +127,12 @@ func errorMessage(fe validator.FieldError) string {
fe.Field(),
joinValues(flipgroupClientTypeValues))
// k8s Validators
case "workerGroupName":
return fmt.Sprintf("%s %s must be more 3 symbol",
prefix,
fe.Field())
// KVM_X86/KVM_PPC Validators
case "kvmNetType":
return fmt.Sprintf("%s %s must be one of the following: %s",

View File

@@ -7,29 +7,20 @@ import (
)
var (
once sync.Once
instance *DecortValidator
once sync.Once
decortValidator = validator.New()
)
type DecortValidator struct {
decortValidator *validator.Validate
}
// getDecortValidator returns singleton instance of DecortValidator.
func getDecortValidator() *validator.Validate {
if instance == nil {
once.Do(func() {
instance = new(DecortValidator)
instance.decortValidator = validator.New()
once.Do(func() {
err := registerAllValidators(decortValidator)
if err != nil {
panic(err)
}
})
err := registerAllValidators(instance.decortValidator)
if err != nil {
panic(err)
}
})
}
return instance.decortValidator
return decortValidator
}
// registerAllValidators registers all custom validators in DecortValidator.
@@ -39,6 +30,11 @@ func registerAllValidators(validate *validator.Validate) error {
return err
}
err = validate.RegisterValidation("computeDriver", computeDriverValidator)
if err != nil {
return err
}
err = validate.RegisterValidation("accessType", accessTypeValidator)
if err != nil {
return err
@@ -184,5 +180,10 @@ func registerAllValidators(validate *validator.Validate) error {
return err
}
err = validate.RegisterValidation("workerGroupName", workerGroupNameValidator)
if err != nil {
return err
}
return nil
}

View File

@@ -6,7 +6,7 @@ var (
resTypesValues = []string{"compute", "vins", "k8s", "openshift", "lb", "flipgroup"}
protoValues = []string{"tcp", "udp"}
accountCUTypeValues = []string{"CU_M", "CU_C", "CU_D", "CU_S", "CU_A", "CU_NO", "CU_I", "CU_NP"}
accountCUTypeValues = []string{"CU_M", "CU_C", "CU_D", "CU_DM", "CU_S", "CU_A", "CU_NO", "CU_I", "CU_NP"}
bserviceModeValues = []string{"ABSOLUTE", "RELATIVE"}
@@ -17,6 +17,7 @@ var (
computeNetTypeValues = []string{"EXTNET", "VINS"}
computeOrderValues = []string{"cdrom", "network", "hd"}
computeDataDisksValues = []string{"KEEP", "DETACH", "DESTROY"}
computeDriverValues = []string{"KVM_X86", "SVA_KVM_X86"}
diskTypeValues = []string{"B", "T", "D"}

View File

@@ -1,23 +1,34 @@
package decortsdk
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/google/go-querystring/query"
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/client"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
)
// Legacy HTTP-client for platform
type LegacyDecortClient struct {
decortURL string
client *http.Client
decortURL string
client *http.Client
cfg config.LegacyConfig
expiryTime time.Time
mutex *sync.Mutex
}
// Legacy client builder
@@ -26,9 +37,25 @@ func NewLegacy(cfg config.LegacyConfig) *LegacyDecortClient {
cfg.Retries = 5
}
var expiryTime time.Time
if cfg.Token != "" {
expiryTime = time.Now().AddDate(0, 0, 1)
}
return &LegacyDecortClient{
decortURL: cfg.DecortURL,
client: client.NewLegacyHttpClient(cfg),
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
//nolint:gosec
InsecureSkipVerify: cfg.SSLSkipVerify,
},
},
},
cfg: cfg,
expiryTime: expiryTime,
mutex: &sync.Mutex{},
}
}
@@ -44,18 +71,165 @@ func (ldc *LegacyDecortClient) CloudBroker() *cloudbroker.CloudBroker {
// DecortApiCall method for sending requests to the platform
func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
if k8sCreateReq, ok := params.(k8s.CreateRequest); ok {
reqBody := &bytes.Buffer{}
writer := multipart.NewWriter(reqBody)
if k8sCreateReq.OidcCertificate != "" {
part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt")
_, _ = io.Copy(part, strings.NewReader(k8sCreateReq.OidcCertificate))
}
_ = writer.WriteField("name", k8sCreateReq.Name)
_ = writer.WriteField("rgId", strconv.FormatUint(k8sCreateReq.RGID, 10))
_ = writer.WriteField("k8ciId", strconv.FormatUint(k8sCreateReq.K8SCIID, 10))
_ = writer.WriteField("workerGroupName", k8sCreateReq.WorkerGroupName)
_ = writer.WriteField("networkPlugin", k8sCreateReq.NetworkPlugin)
if k8sCreateReq.MasterSEPID != 0 {
_ = writer.WriteField("masterSepId", strconv.FormatUint(k8sCreateReq.MasterSEPID, 10))
}
if k8sCreateReq.MasterSEPPool != "" {
_ = writer.WriteField("masterSepPool", k8sCreateReq.MasterSEPPool)
}
if k8sCreateReq.WorkerSEPID != 0 {
_ = writer.WriteField("workerSepId", strconv.FormatUint(k8sCreateReq.WorkerSEPID, 10))
}
if k8sCreateReq.WorkerSEPPool != "" {
_ = writer.WriteField("workerSepPool", k8sCreateReq.WorkerSEPPool)
}
if k8sCreateReq.Labels != nil {
for _, v := range k8sCreateReq.Labels {
_ = writer.WriteField("labels", v)
}
}
if k8sCreateReq.Taints != nil {
for _, v := range k8sCreateReq.Taints {
_ = writer.WriteField("taints", v)
}
}
if k8sCreateReq.Annotations != nil {
for _, v := range k8sCreateReq.Annotations {
_ = writer.WriteField("annotations", v)
}
}
if k8sCreateReq.MasterCPU != 0 {
_ = writer.WriteField("masterCpu", strconv.FormatUint(uint64(k8sCreateReq.MasterCPU), 10))
}
if k8sCreateReq.MasterNum != 0 {
_ = writer.WriteField("masterNum", strconv.FormatUint(uint64(k8sCreateReq.MasterNum), 10))
}
if k8sCreateReq.MasterRAM != 0 {
_ = writer.WriteField("masterRam", strconv.FormatUint(uint64(k8sCreateReq.MasterRAM), 10))
}
if k8sCreateReq.MasterDisk != 0 {
_ = writer.WriteField("masterDisk", strconv.FormatUint(uint64(k8sCreateReq.MasterDisk), 10))
}
if k8sCreateReq.WorkerCPU != 0 {
_ = writer.WriteField("workerCpu", strconv.FormatUint(uint64(k8sCreateReq.WorkerCPU), 10))
}
if k8sCreateReq.WorkerNum != 0 {
_ = writer.WriteField("workerNum", strconv.FormatUint(uint64(k8sCreateReq.WorkerNum), 10))
}
if k8sCreateReq.WorkerRAM != 0 {
_ = writer.WriteField("workerRam", strconv.FormatUint(uint64(k8sCreateReq.WorkerRAM), 10))
}
if k8sCreateReq.WorkerDisk != 0 {
_ = writer.WriteField("workerDisk", strconv.FormatUint(uint64(k8sCreateReq.WorkerDisk), 10))
}
if k8sCreateReq.ExtNetID != 0 {
_ = writer.WriteField("extnetId", strconv.FormatUint(k8sCreateReq.ExtNetID, 10))
}
if k8sCreateReq.VinsId != 0 {
_ = writer.WriteField("vinsId", strconv.FormatUint(k8sCreateReq.VinsId, 10))
}
if !k8sCreateReq.WithLB {
_ = writer.WriteField("withLB", strconv.FormatBool(k8sCreateReq.WithLB))
}
_ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(k8sCreateReq.HighlyAvailable))
if k8sCreateReq.AdditionalSANs != nil {
for _, v := range k8sCreateReq.AdditionalSANs {
_ = writer.WriteField("additionalSANs", v)
}
}
if k8sCreateReq.InitConfiguration != "" {
_ = writer.WriteField("initConfiguration", k8sCreateReq.InitConfiguration)
}
if k8sCreateReq.ClusterConfiguration != "" {
_ = writer.WriteField("clusterConfiguration", k8sCreateReq.ClusterConfiguration)
}
if k8sCreateReq.KubeletConfiguration != "" {
_ = writer.WriteField("kubeletConfiguration", k8sCreateReq.KubeletConfiguration)
}
if k8sCreateReq.KubeProxyConfiguration != "" {
_ = writer.WriteField("kubeProxyConfiguration", k8sCreateReq.KubeProxyConfiguration)
}
if k8sCreateReq.JoinConfiguration != "" {
_ = writer.WriteField("joinConfiguration", k8sCreateReq.JoinConfiguration)
}
if k8sCreateReq.Description != "" {
_ = writer.WriteField("desc", k8sCreateReq.Description)
}
if k8sCreateReq.UserData != "" {
_ = writer.WriteField("userData", k8sCreateReq.UserData)
}
_ = writer.WriteField("extnetOnly", strconv.FormatBool(k8sCreateReq.ExtNetOnly))
_ = writer.FormDataContentType()
ct := writer.FormDataContentType()
if err := ldc.getToken(ctx); err != nil {
return nil, err
}
_ = writer.WriteField("authkey", ldc.cfg.Token)
writer.Close()
req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+"/restmachine"+url, reqBody)
if err != nil {
return nil, err
}
resp, err := ldc.domp(req, ct)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New(string(respBytes))
}
return respBytes, nil
}
values, err := query.Values(params)
if err != nil {
return nil, err
}
body := strings.NewReader(values.Encode())
if err = ldc.getToken(ctx); err != nil {
return nil, err
}
body := strings.NewReader(values.Encode() + fmt.Sprintf("&authkey=%s", ldc.cfg.Token))
req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+"/restmachine"+url, body)
if err != nil {
return nil, err
}
resp, err := ldc.client.Do(req)
resp, err := ldc.do(req)
if err != nil {
return nil, err
}
@@ -72,3 +246,88 @@ func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url st
return respBytes, nil
}
func (ldc *LegacyDecortClient) getToken(ctx context.Context) error {
ldc.mutex.Lock()
defer ldc.mutex.Unlock()
if ldc.cfg.Token == "" || time.Now().After(ldc.expiryTime) {
body := fmt.Sprintf("username=%s&password=%s", url.QueryEscape(ldc.cfg.Username), url.QueryEscape(ldc.cfg.Password))
bodyReader := strings.NewReader(body)
req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := ldc.client.Do(req)
if err != nil {
return fmt.Errorf("unable to get token: %w", err)
}
tokenBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("unable to get token: %s", tokenBytes)
}
token := string(tokenBytes)
ldc.cfg.Token = token
ldc.expiryTime = time.Now().AddDate(0, 0, 1)
}
return nil
}
func (ldc *LegacyDecortClient) do(req *http.Request) (*http.Response, error) {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
// var resp *http.Response
// var err error
buf, _ := io.ReadAll(req.Body)
// req = req.Clone(req.Context())
// for i := uint64(0); i < ldc.cfg.Retries; i++ {
req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := ldc.client.Do(req)
// if err == nil {
if resp.StatusCode == 200 {
return resp, err
}
respBytes, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("%s", respBytes)
resp.Body.Close()
// }
// }
return nil, fmt.Errorf("could not execute request: %w", err)
}
func (ldc *LegacyDecortClient) domp(req *http.Request, ctype string) (*http.Response, error) {
req.Header.Add("Content-Type", ctype)
req.Header.Add("Authorization", "bearer "+ldc.cfg.Token)
req.Header.Set("Accept", "application/json")
// var resp *http.Response
// var err error
buf, _ := io.ReadAll(req.Body)
// req = req.Clone(req.Context())
// for i := uint64(0); i < ldc.cfg.Retries; i++ {
req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := ldc.client.Do(req)
// if err == nil {
if resp.StatusCode == 200 {
return resp, err
}
respBytes, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("%s", respBytes)
resp.Body.Close()
// }
// }
return nil, fmt.Errorf("could not execute request: %w", err)
}

View File

@@ -48,21 +48,23 @@ func (la ListAccounts) FilterByUserGroupID(userGroupID string) ListAccounts {
func (la ListAccounts) FilterFunc(predicate func(ItemAccount) bool) ListAccounts {
var result ListAccounts
for _, acc := range la {
for _, acc := range la.Data {
if predicate(acc) {
result = append(result, acc)
result.Data = append(result.Data, acc)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemAccount.
// If none was found, returns an empty struct.
func (la ListAccounts) FindOne() ItemAccount {
if len(la) == 0 {
if len(la.Data) == 0 {
return ItemAccount{}
}
return la[0]
return la.Data[0]
}

View File

@@ -5,68 +5,71 @@ import (
)
var accounts = ListAccounts{
ItemAccount{
ACL: []RecordACL{
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "timofey_tkachev_1@decs3o",
Data: []ItemAccount{
{
ACL: []RecordACL{
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "timofey_tkachev_1@decs3o",
},
},
CreatedTime: 1676645275,
DeletedTime: 0,
ID: 132846,
Name: "std",
Status: "CONFIRMED",
UpdatedTime: 1676645275,
},
CreatedTime: 1676645275,
DeletedTime: 0,
ID: 132846,
Name: "std",
Status: "CONFIRMED",
UpdatedTime: 1676645275,
},
ItemAccount{
ACL: []RecordACL{
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "not_really_timofey_tkachev_1@decs3o",
{
ACL: []RecordACL{
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "not_really_timofey_tkachev_1@decs3o",
},
},
CreatedTime: 1676878820,
DeletedTime: 0,
ID: 132847,
Name: "std_2",
Status: "CONFIRMED",
UpdatedTime: 1676645275,
},
CreatedTime: 1676878820,
DeletedTime: 0,
ID: 132847,
Name: "std_2",
Status: "CONFIRMED",
UpdatedTime: 1676645275,
},
ItemAccount{
ACL: []RecordACL{
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "timofey_tkachev_1@decs3o",
},
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "second_account@decs3o",
{
ACL: []RecordACL{
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "timofey_tkachev_1@decs3o",
},
{
IsExplicit: true,
GUID: "",
Rights: "CXDRAU",
Status: "CONFIRMED",
Type: "U",
UgroupID: "second_account@decs3o",
},
},
CreatedTime: 1676883850,
DeletedTime: 1676883899,
ID: 132848,
Name: "std_broker",
Status: "DELETED",
UpdatedTime: 1676878820,
},
CreatedTime: 1676883850,
DeletedTime: 1676883899,
ID: 132848,
Name: "std_broker",
Status: "DELETED",
UpdatedTime: 1676878820,
},
EntryCount: 3,
}
func TestFilterByID(t *testing.T) {
@@ -100,11 +103,11 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) {
actual := accounts.FilterByStatus("CONFIRMED")
if len(actual) != 2 {
t.Fatal("Expected 2 elements in slice, found: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("Expected 2 elements in slice, found: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Status != "CONFIRMED" {
t.Fatal("expected CONFIRMED, found: ", item.Status)
}
@@ -116,7 +119,7 @@ func TestFilterFunc(t *testing.T) {
return ia.DeletedTime == 0
})
for _, item := range actual {
for _, item := range actual.Data {
if item.DeletedTime != 0 {
t.Fatal("Expected DeletedTime = 0, found: ", item.DeletedTime)
}
@@ -126,21 +129,21 @@ func TestFilterFunc(t *testing.T) {
func TestSortingByCreatedTime(t *testing.T) {
actual := accounts.SortByCreatedTime(false)
if actual[0].Name != "std" {
t.Fatal("Expected account std as earliest, found: ", actual[0].Name)
if actual.Data[0].Name != "std" {
t.Fatal("Expected account std as earliest, found: ", actual.Data[0].Name)
}
actual = accounts.SortByCreatedTime(true)
if actual[0].Name != "std_broker" {
t.Fatal("Expected account std_broker as latest, found: ", actual[0].Name)
if actual.Data[0].Name != "std_broker" {
t.Fatal("Expected account std_broker as latest, found: ", actual.Data[0].Name)
}
}
func TestFilterEmpty(t *testing.T) {
actual := accounts.FilterByID(0)
if len(actual) != 0 {
t.Fatal("Expected 0 found, actual: ", len(actual))
if len(actual.Data) != 0 {
t.Fatal("Expected 0 found, actual: ", len(actual.Data))
}
}

View File

@@ -8,25 +8,16 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for get information about account
// GetRequest struct to get information about account
type GetRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
}
// Get gets account details
// Get gets account details as a RecordAccount struct
func (a Account) Get(ctx context.Context, req GetRequest) (*RecordAccount, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/account/get"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := a.GetRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -41,3 +32,18 @@ func (a Account) Get(ctx context.Context, req GetRequest) (*RecordAccount, error
return &info, nil
}
// GetRaw gets account details as an array of bytes
func (a Account) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/account/get"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -20,6 +20,7 @@ type GetConsumedAccountUnitsRequest struct {
// - CU_M: consumed memory in MB
// - CU_C: number of cpu cores
// - CU_D: consumed vdisk storage in GB
// - CU_DM: consumed max vdisk storage in GB
// - CU_I: number of public IPs
func (a Account) GetConsumedAccountUnits(ctx context.Context, req GetConsumedAccountUnitsRequest) (*ResourceLimits, error) {
err := validators.ValidateRequest(req)

View File

@@ -26,6 +26,7 @@ type GetConsumedCloudUnitsByTypeRequest struct {
// - CU_M: returns consumed memory in MB
// - CU_C: returns number of virtual cpu cores
// - CU_D: returns consumed virtual disk storage in GB
// - CU_DM: returns consumed max virtual disk storage in GB
// - CU_S: returns consumed primary storage (NAS) in TB
// - CU_A: returns consumed secondary storage (Archive) in TB
// - CU_NO: returns sent/received network transfer in operator in GB

View File

@@ -21,6 +21,7 @@ type GetReservedAccountUnitsRequest struct {
// - CU_M: reserved memory in MB
// - CU_C: number of cpu cores
// - CU_D: reserved vdisk storage in GB
// - CU_DM: reserved max vdisk storage in GB
// - CU_I: number of public IPs
func (a Account) GetReservedAccountUnits(ctx context.Context, req GetReservedAccountUnitsRequest) (*ResourceLimits, error) {
err := validators.ValidateRequest(req)

View File

@@ -0,0 +1,42 @@
package account
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for getting resource consumption
type GetResourceConsumptionRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
}
// GetResourceConsumption show amount of consumed and reserved resources (cpu, ram, disk) by specific account
func (a Account) GetResourceConsumption(ctx context.Context, req GetResourceConsumptionRequest) (*RecordResourceConsumption, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/account/getResourceConsumption"
info := RecordResourceConsumption{}
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@@ -6,22 +6,36 @@ import (
"net/http"
)
// Request struct for get list of accounts
// ListRequest struct to get list of accounts
type ListRequest struct {
// Find by ID
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by access control list
// Required: false
ACL string `url:"acl,omitempty" json:"acl,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page" json:"page"`
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size" json:"size"`
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list all accounts the user has access to
func (a Account) List(ctx context.Context, req ListRequest) (ListAccounts, error) {
url := "/cloudapi/account/list"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
// List gets a list of all accounts the user has access to a ListAccounts struct
func (a Account) List(ctx context.Context, req ListRequest) (*ListAccounts, error) {
res, err := a.ListRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -33,5 +47,13 @@ func (a Account) List(ctx context.Context, req ListRequest) (ListAccounts, error
return nil, err
}
return list, nil
return &list, nil
}
// ListRaw gets a list of all accounts the user has access to as an array of bytes
func (a Account) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
url := "/cloudapi/account/list"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -13,10 +13,50 @@ type ListComputesRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Find by compute id
// Required: false
ComputeID uint64 `url:"computeId,omitempty" json:"computeId,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by resource group name
// Required: false
RGName string `url:"rgName,omitempty" json:"rgName,omitempty"`
// Find by resource group id
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by tech status
// Required: false
TechStatus string `url:"techStatus,omitempty" json:"techStatus,omitempty"`
// Find by ip address
// Required: false
IPAddress string `url:"ipAddress,omitempty" json:"ipAddress,omitempty"`
// Find by external network name
// Required: false
ExtNetName string `url:"extNetName,omitempty" json:"extNetName,omitempty"`
// Find by external network id
// Required: false
ExtNetID uint64 `url:"extNetId,omitempty" json:"extNetId,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListComputes gets list all compute instances under specified account, accessible by the user
func (a Account) ListComputes(ctx context.Context, req ListComputesRequest) (ListComputes, error) {
func (a Account) ListComputes(ctx context.Context, req ListComputesRequest) (*ListComputes, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +78,5 @@ func (a Account) ListComputes(ctx context.Context, req ListComputesRequest) (Lis
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -10,15 +10,27 @@ import (
type ListDeletedRequest struct {
// Page number
// Required: false
Page uint64 `url:"page" json:"page"`
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size" json:"size"`
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Find by ID
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by access control list
// Required: false
ACL string `url:"acl,omitempty" json:"acl,omitempty"`
}
// ListDeleted gets list all deleted accounts the user has access to
func (a Account) ListDeleted(ctx context.Context, req ListDeletedRequest) (ListAccounts, error) {
func (a Account) ListDeleted(ctx context.Context, req ListDeletedRequest) (*ListAccounts, error) {
url := "/cloudapi/account/listDeleted"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
@@ -33,5 +45,5 @@ func (a Account) ListDeleted(ctx context.Context, req ListDeletedRequest) (ListA
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -13,10 +13,34 @@ type ListDisksRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Find by disk id
// Required: false
DiskID uint64 `url:"diskId,omitempty" json:"diskId,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by max size disk
// Required: false
DiskMaxSize uint64 `url:"diskMaxSize,omitempty" json:"diskMaxSize,omitempty"`
// Type of the disks
// Required: false
Type string `url:"type,omitempty" json:"type,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListDisks gets list all currently unattached disks under specified account
func (a Account) ListDisks(ctx context.Context, req ListDisksRequest) (ListDisks, error) {
func (a Account) ListDisks(ctx context.Context, req ListDisksRequest) (*ListDisks, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +62,5 @@ func (a Account) ListDisks(ctx context.Context, req ListDisksRequest) (ListDisks
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -10,13 +10,45 @@ import (
// Request struct for get list FLIPGroups
type ListFLIPGroupsRequest struct {
// ID an account
// ID of the account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by vinsId
// Required: false
VINSID uint64 `url:"vinsId,omitempty" json:"vinsId,omitempty"`
// Find by VINS name
// Required: false
VINSName string `url:"vinsName,omitempty" json:"vinsName,omitempty"`
// Find by external network id
// Required: false
ExtNetID uint64 `url:"extnetId,omitempty" json:"extnetId,omitempty"`
// Find by IP
// Required: false
ByIP string `url:"byIp,omitempty" json:"byIp,omitempty"`
// Find by flipGroup Id
// Required: false
FLIPGroupID uint64 `url:"flipGroupId,omitempty" json:"flipGroupId,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListFLIPGroups gets list all FLIPGroups under specified account, accessible by the user
func (a Account) ListFLIPGroups(ctx context.Context, req ListFLIPGroupsRequest) (ListFLIPGroups, error) {
func (a Account) ListFLIPGroups(ctx context.Context, req ListFLIPGroupsRequest) (*ListFLIPGroups, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +70,5 @@ func (a Account) ListFLIPGroups(ctx context.Context, req ListFLIPGroupsRequest)
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -0,0 +1,26 @@
package account
import (
"context"
"encoding/json"
"net/http"
)
// ListResourceConsumption show data list amount of consumed and reserved resources (cpu, ram, disk) by specific accounts
func (a Account) ListResourceConsumption(ctx context.Context) (*ListResourceConsumption, error) {
url := "/cloudapi/account/listResourceConsumption"
info := ListResourceConsumption{}
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@@ -13,10 +13,38 @@ type ListRGRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Find by resource group id
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by vinsId
// Required: false
VINSID uint64 `url:"vinsId,omitempty" json:"vinsId,omitempty"`
// Find by VM ID
// Required: false
VMID uint64 `url:"vmId,omitempty" json:"vmId,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
}
// ListRG gets list all resource groups under specified account, accessible by the user
func (a Account) ListRG(ctx context.Context, req ListRGRequest) (ListRG, error) {
func (a Account) ListRG(ctx context.Context, req ListRGRequest) (*ListRG, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +66,5 @@ func (a Account) ListRG(ctx context.Context, req ListRGRequest) (ListRG, error)
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -12,15 +12,35 @@ import (
type ListTemplatesRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Include deleted images
// Required: false
IncludeDeleted bool `url:"includedeleted" json:"includedeleted"`
IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"`
// Find by image id
// Required: false
ImageID uint64 `url:"imageId,omitempty" json:"imageId,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by type
// Required: false
Type string `url:"type,omitempty" json:"type,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListTemplates gets list templates which can be managed by this account
func (a Account) ListTemplates(ctx context.Context, req ListTemplatesRequest) (ListTemplates, error) {
func (a Account) ListTemplates(ctx context.Context, req ListTemplatesRequest) (*ListTemplates, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -42,5 +62,5 @@ func (a Account) ListTemplates(ctx context.Context, req ListTemplatesRequest) (L
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -13,10 +13,34 @@ type ListVINSRequest struct {
// ID an account
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Find by VINS ID
// Required: false
VINSID uint64 `url:"vins,omitempty" json:"vinsId,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by resource group id
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by external network ip
// Required: false
ExtIP string `url:"extIp,omitempty" json:"extIp,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListVINS gets list all ViNSes under specified account, accessible by the user
func (a Account) ListVINS(ctx context.Context, req ListVINSRequest) (ListVINS, error) {
func (a Account) ListVINS(ctx context.Context, req ListVINSRequest) (*ListVINS, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +62,5 @@ func (a Account) ListVINS(ctx context.Context, req ListVINSRequest) (ListVINS, e
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -32,6 +32,9 @@ type ResourceLimits struct {
// Disk size, GB
CUD float64 `json:"CU_D"`
// Max disk size, GB
CUDM float64 `json:"CU_DM"`
// Number of public IP addresses
CUI float64 `json:"CU_I"`
@@ -70,7 +73,11 @@ type ItemAccount struct {
}
// List of accounts
type ListAccounts []ItemAccount
type ListAccounts struct {
Data []ItemAccount `json:"data"`
EntryCount uint64 `json:"entryCount"`
}
// Resources used
type Resource struct {
@@ -81,7 +88,7 @@ type Resource struct {
DiskSize float64 `json:"disksize"`
// Max disk size
DiskSizeMax uint64 `json:"disksizemax"`
DiskSizeMax float64 `json:"disksizemax"`
// Number of External IPs
ExtIPs int64 `json:"extips"`
@@ -105,16 +112,33 @@ type DiskUsage struct {
DiskSize float64 `json:"disksize"`
// Disk size max
DiskSizeMax uint64 `json:"disksizemax"`
DiskSizeMax float64 `json:"disksizemax"`
}
// Information about resource consumption
type RecordResourceConsumption struct {
ItemResourceConsumption
// Resource limits
ResourceLimits ResourceLimits `json:"resourceLimits"`
}
// Information about resources
type Resources struct {
type ItemResourceConsumption struct {
// Current information about resources
Current Resource `json:"Current"`
Consumed Resource `json:"consumed"`
// Reserved information about resources
Reserved Resource `json:"Reserved"`
Reserved Resource `json:"reserved"`
// Account ID
AccountID uint64 `json:"id"`
}
type ListResourceConsumption struct {
Data []ItemResourceConsumption `json:"data"`
EntryCount uint64 `json:"entryCount"`
}
// Information about computes
@@ -140,9 +164,6 @@ type RecordAccount struct {
// DCLocation
DCLocation string `json:"DCLocation"`
// Resources
Resources Resources `json:"Resources"`
// CKey
CKey string `json:"_ckey"`
@@ -286,7 +307,13 @@ type ItemCompute struct {
}
// List of computes
type ListComputes []ItemCompute
type ListComputes struct {
// Data
Data []ItemCompute `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about disk
type ItemDisk struct {
@@ -313,7 +340,13 @@ type ItemDisk struct {
}
// List of disks
type ListDisks []ItemDisk
type ListDisks struct {
// Data
Data []ItemDisk `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about VINS
type ItemVINS struct {
@@ -370,7 +403,13 @@ type ItemVINS struct {
}
// List of VINS
type ListVINS []ItemVINS
type ListVINS struct {
// Data
Data []ItemVINS `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main info about audit
type ItemAudit struct {
@@ -484,7 +523,13 @@ type ItemRG struct {
}
// List of Resource groups
type ListRG []ItemRG
type ListRG struct {
// Data
Data []ItemRG `json:"data"`
// Enrtry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about template
type ItemTemplate struct {
@@ -520,7 +565,13 @@ type ItemTemplate struct {
}
// List of templates
type ListTemplates []ItemTemplate
type ListTemplates struct {
// Data
Data []ItemTemplate `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about FLIPGroup
type ItemFLIPGroup struct {
@@ -589,4 +640,10 @@ type ItemFLIPGroup struct {
}
// List of FLIPGroups
type ListFLIPGroups []ItemFLIPGroup
type ListFLIPGroups struct {
// Data
Data []ItemFLIPGroup `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}

View File

@@ -12,7 +12,7 @@ import (
// - First argument -> prefix
// - Second argument -> indent
func (la ListAccounts) Serialize(params ...string) (serialization.Serialized, error) {
if len(la) == 0 {
if len(la.Data) == 0 {
return []byte{}, nil
}

View File

@@ -6,16 +6,16 @@ import "sort"
//
// If inverse param is set to true, the order is reversed.
func (la ListAccounts) SortByCreatedTime(inverse bool) ListAccounts {
if len(la) < 2 {
if len(la.Data) < 2 {
return la
}
sort.Slice(la, func(i, j int) bool {
sort.Slice(la.Data, func(i, j int) bool {
if inverse {
return la[i].CreatedTime > la[j].CreatedTime
return la.Data[i].CreatedTime > la.Data[j].CreatedTime
}
return la[i].CreatedTime < la[j].CreatedTime
return la.Data[i].CreatedTime < la.Data[j].CreatedTime
})
return la
@@ -25,16 +25,16 @@ func (la ListAccounts) SortByCreatedTime(inverse bool) ListAccounts {
//
// If inverse param is set to true, the order is reversed.
func (la ListAccounts) SortByUpdatedTime(inverse bool) ListAccounts {
if len(la) < 2 {
if len(la.Data) < 2 {
return la
}
sort.Slice(la, func(i, j int) bool {
sort.Slice(la.Data, func(i, j int) bool {
if inverse {
return la[i].UpdatedTime > la[j].UpdatedTime
return la.Data[i].UpdatedTime > la.Data[j].UpdatedTime
}
return la[i].UpdatedTime < la[j].UpdatedTime
return la.Data[i].UpdatedTime < la.Data[j].UpdatedTime
})
return la
@@ -44,16 +44,16 @@ func (la ListAccounts) SortByUpdatedTime(inverse bool) ListAccounts {
//
// If inverse param is set to true, the order is reversed.
func (la ListAccounts) SortByDeletedTime(inverse bool) ListAccounts {
if len(la) < 2 {
if len(la.Data) < 2 {
return la
}
sort.Slice(la, func(i, j int) bool {
sort.Slice(la.Data, func(i, j int) bool {
if inverse {
return la[i].DeletedTime > la[j].DeletedTime
return la.Data[i].DeletedTime > la.Data[j].DeletedTime
}
return la[i].DeletedTime < la[j].DeletedTime
return la.Data[i].DeletedTime < la.Data[j].DeletedTime
})
return la

View File

@@ -49,21 +49,23 @@ func (lbs ListBasicServices) FilterByTechStatus(techStatus string) ListBasicServ
func (lbs ListBasicServices) FilterFunc(predicate func(ItemBasicService) bool) ListBasicServices {
var result ListBasicServices
for _, item := range lbs {
for _, item := range lbs.Data {
if predicate(item) {
result = append(result, item)
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(lbs.Data))
return result
}
// FindOne returns first found ItemBasicService
// If none was found, returns an empty struct.
func (lbs ListBasicServices) FindOne() ItemBasicService {
if len(lbs) == 0 {
if lbs.EntryCount == 0 {
return ItemBasicService{}
}
return lbs[0]
return lbs.Data[0]
}

View File

@@ -3,78 +3,81 @@ package bservice
import "testing"
var bservices = ListBasicServices{
{
AccountID: 1,
AccountName: "std_1",
BaseDomain: "",
CreatedBy: "sample_user_1@decs3o",
CreatedTime: 1677743675,
DeletedBy: "",
DeletedTime: 0,
GID: 212,
Groups: []uint64{},
GUID: 1,
ID: 1,
Name: "bservice_1",
ParentSrvID: 0,
ParentSrvType: "",
RGID: 7971,
RGName: "rg_1",
SSHUser: "",
Status: "CREATED",
TechStatus: "STOPPED",
UpdatedBy: "",
UpdatedTime: 0,
UserManaged: true,
},
{
AccountID: 2,
AccountName: "std_2",
BaseDomain: "",
CreatedBy: "sample_user_1@decs3o",
CreatedTime: 1677743736,
DeletedBy: "",
DeletedTime: 0,
GID: 212,
Groups: []uint64{},
GUID: 2,
ID: 2,
Name: "bservice_2",
ParentSrvID: 0,
ParentSrvType: "",
RGID: 7972,
RGName: "rg_2",
SSHUser: "",
Status: "CREATED",
TechStatus: "STOPPED",
UpdatedBy: "",
UpdatedTime: 0,
UserManaged: true,
},
{
AccountID: 3,
AccountName: "std_3",
BaseDomain: "",
CreatedBy: "sample_user_2@decs3o",
CreatedTime: 1677743830,
DeletedBy: "",
DeletedTime: 0,
GID: 212,
Groups: []uint64{},
GUID: 3,
ID: 3,
Name: "bservice_3",
ParentSrvID: 0,
ParentSrvType: "",
RGID: 7973,
RGName: "rg_3",
SSHUser: "",
Status: "ENABLED",
TechStatus: "STARTED",
UpdatedBy: "",
UpdatedTime: 0,
UserManaged: true,
Data: []ItemBasicService{
{
AccountID: 1,
AccountName: "std_1",
BaseDomain: "",
CreatedBy: "sample_user_1@decs3o",
CreatedTime: 1677743675,
DeletedBy: "",
DeletedTime: 0,
GID: 212,
Groups: []uint64{},
GUID: 1,
ID: 1,
Name: "bservice_1",
ParentSrvID: 0,
ParentSrvType: "",
RGID: 7971,
RGName: "rg_1",
SSHUser: "",
Status: "CREATED",
TechStatus: "STOPPED",
UpdatedBy: "",
UpdatedTime: 0,
UserManaged: true,
},
{
AccountID: 2,
AccountName: "std_2",
BaseDomain: "",
CreatedBy: "sample_user_1@decs3o",
CreatedTime: 1677743736,
DeletedBy: "",
DeletedTime: 0,
GID: 212,
Groups: []uint64{},
GUID: 2,
ID: 2,
Name: "bservice_2",
ParentSrvID: 0,
ParentSrvType: "",
RGID: 7972,
RGName: "rg_2",
SSHUser: "",
Status: "CREATED",
TechStatus: "STOPPED",
UpdatedBy: "",
UpdatedTime: 0,
UserManaged: true,
},
{
AccountID: 3,
AccountName: "std_3",
BaseDomain: "",
CreatedBy: "sample_user_2@decs3o",
CreatedTime: 1677743830,
DeletedBy: "",
DeletedTime: 0,
GID: 212,
Groups: []uint64{},
GUID: 3,
ID: 3,
Name: "bservice_3",
ParentSrvID: 0,
ParentSrvType: "",
RGID: 7973,
RGName: "rg_3",
SSHUser: "",
Status: "ENABLED",
TechStatus: "STARTED",
UpdatedBy: "",
UpdatedTime: 0,
UserManaged: true,
},
},
EntryCount: 3,
}
func TestFilterByID(t *testing.T) {
@@ -104,11 +107,11 @@ func TestFilterByRGID(t *testing.T) {
func TestFilterByStatus(t *testing.T) {
actual := bservices.FilterByStatus("CREATED")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Status != "CREATED" {
t.Fatal("expected Status 'CREATED', found: ", item.Status)
}
@@ -118,11 +121,11 @@ func TestFilterByStatus(t *testing.T) {
func TestFilterByTechStatus(t *testing.T) {
actual := bservices.FilterByTechStatus("STOPPED")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.TechStatus != "STOPPED" {
t.Fatal("expected TechStatus 'STOPPED', found: ", item.TechStatus)
}
@@ -134,8 +137,8 @@ func TestFilterFunc(t *testing.T) {
return ibs.CreatedBy == "sample_user_2@decs3o"
})
if len(actual) > 1 {
t.Fatal("expected 1 found, actual: ", len(actual))
if len(actual.Data) > 1 {
t.Fatal("expected 1 found, actual: ", len(actual.Data))
}
if actual.FindOne().CreatedBy != "sample_user_2@decs3o" {
@@ -146,7 +149,7 @@ func TestFilterFunc(t *testing.T) {
func TestSortByCreatedTime(t *testing.T) {
actual := bservices.SortByCreatedTime(true)
if actual[0].CreatedTime != 1677743830 || actual[2].CreatedTime != 1677743675 {
if actual.Data[0].CreatedTime != 1677743830 || actual.Data[2].CreatedTime != 1677743675 {
t.Fatal("expected descending order, found ascending")
}
}

View File

@@ -8,15 +8,32 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for get detailed information about service
// GetRequest struct to get detailed information about service
type GetRequest struct {
// ID of the service to query information
// Required: true
ServiceID uint64 `url:"serviceId" json:"serviceId" validate:"required"`
}
// Get gets detailed specifications for the BasicService.
// Get gets detailed specifications for the BasicService as a RecordBasicService struct
func (b BService) Get(ctx context.Context, req GetRequest) (*RecordBasicService, error) {
res, err := b.GetRaw(ctx, req)
if err != nil {
return nil, err
}
info := RecordBasicService{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}
// GetRaw gets detailed specifications for the BasicService as an array of bytes
func (b BService) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -26,17 +43,6 @@ func (b BService) Get(ctx context.Context, req GetRequest) (*RecordBasicService,
url := "/cloudapi/bservice/get"
bsRaw, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
info := RecordBasicService{}
err = json.Unmarshal(bsRaw, &info)
if err != nil {
return nil, err
}
return &info, nil
res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -68,6 +68,10 @@ type GroupAddRequest struct {
// Time of Compute Group readiness
// Required: false
TimeoutStart uint64 `url:"timeoutStart,omitempty" json:"timeoutStart,omitempty"`
// Meta data for working group computes, format YAML "user_data": 1111
// Required: false
UserData string `url:"userData,omitempty" json:"userData,omitempty"`
}
// GroupAdd creates new Compute Group within BasicService.

View File

@@ -6,16 +6,40 @@ import (
"net/http"
)
// Request struct for get list/deleted list BasicService instances
// ListRequest struct to get list of BasicService instances
type ListRequest struct {
// Find by ID
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// ID of the account to query for BasicService instances
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// Find by resource group name
// Required: false
RGName string `url:"rgName,omitempty" json:"rgName,omitempty"`
// ID of the resource group to query for BasicService instances
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by tech status
// Required: false
TechStatus string `url:"techStatus,omitempty" json:"techStatus,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Find by account name
// Required: false
AccountName string `url:"accountName,omitempty" json:"accountName,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -25,40 +49,27 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list BasicService instances associated with the specified Resource Group
func (b BService) List(ctx context.Context, req ListRequest) (ListBasicServices, error) {
// List gets list of BasicService instances associated with the specified Resource Group as a ListBasicServices struct
func (b BService) List(ctx context.Context, req ListRequest) (*ListBasicServices, error) {
res, err := b.ListRaw(ctx, req)
if err != nil {
return nil, err
}
list := ListBasicServices{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return &list, nil
}
// ListRaw gets list of BasicService instances associated with the specified Resource Group as an array of bytes
func (b BService) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
url := "/cloudapi/bservice/list"
res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListBasicServices{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
}
// ListDeleted gets list deleted BasicService instances associated with the specified Resource Group
func (b BService) ListDeleted(ctx context.Context, req ListRequest) (ListBasicServices, error) {
url := "/cloudapi/bservice/listDeleted"
res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListBasicServices{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
return res, err
}

View File

@@ -0,0 +1,45 @@
package bservice
import (
"context"
"encoding/json"
"net/http"
)
// Request struct for get list of deleted BasicService instances
type ListDeletedRequest struct {
// ID of the account to query for BasicService instances
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// ID of the resource group to query for BasicService instances
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListDeleted gets list deleted BasicService instances associated with the specified Resource Group
func (b BService) ListDeleted(ctx context.Context, req ListDeletedRequest) (*ListBasicServices, error) {
url := "/cloudapi/bservice/listDeleted"
res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListBasicServices{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return &list, nil
}

View File

@@ -373,4 +373,8 @@ type ItemBasicService struct {
}
// List of BasicServices
type ListBasicServices []ItemBasicService
type ListBasicServices struct {
Data []ItemBasicService `json:"data"`
EntryCount uint64 `json:"entryCount"`
}

View File

@@ -12,7 +12,7 @@ import (
// - First argument -> prefix
// - Second argument -> indent
func (lbs ListBasicServices) Serialize(params ...string) (serialization.Serialized, error) {
if len(lbs) == 0 {
if lbs.EntryCount == 0 {
return []byte{}, nil
}

View File

@@ -6,16 +6,16 @@ import "sort"
//
// If inverse param is set to true, the order is reversed.
func (lbs ListBasicServices) SortByCreatedTime(inverse bool) ListBasicServices {
if len(lbs) < 2 {
if lbs.EntryCount < 2 {
return lbs
}
sort.Slice(lbs, func(i, j int) bool {
sort.Slice(lbs.Data, func(i, j int) bool {
if inverse {
return lbs[i].CreatedTime > lbs[j].CreatedTime
return lbs.Data[i].CreatedTime > lbs.Data[j].CreatedTime
}
return lbs[i].CreatedTime < lbs[j].CreatedTime
return lbs.Data[i].CreatedTime < lbs.Data[j].CreatedTime
})
return lbs
@@ -25,16 +25,16 @@ func (lbs ListBasicServices) SortByCreatedTime(inverse bool) ListBasicServices {
//
// If inverse param is set to true, the order is reversed.
func (lbs ListBasicServices) SortByUpdatedTime(inverse bool) ListBasicServices {
if len(lbs) < 2 {
if lbs.EntryCount < 2 {
return lbs
}
sort.Slice(lbs, func(i, j int) bool {
sort.Slice(lbs.Data, func(i, j int) bool {
if inverse {
return lbs[i].UpdatedTime > lbs[j].UpdatedTime
return lbs.Data[i].UpdatedTime > lbs.Data[j].UpdatedTime
}
return lbs[i].UpdatedTime < lbs[j].UpdatedTime
return lbs.Data[i].UpdatedTime < lbs.Data[j].UpdatedTime
})
return lbs
@@ -44,16 +44,16 @@ func (lbs ListBasicServices) SortByUpdatedTime(inverse bool) ListBasicServices {
//
// If inverse param is set to true, the order is reversed.
func (lbs ListBasicServices) SortByDeletedTime(inverse bool) ListBasicServices {
if len(lbs) < 2 {
if lbs.EntryCount < 2 {
return lbs
}
sort.Slice(lbs, func(i, j int) bool {
sort.Slice(lbs.Data, func(i, j int) bool {
if inverse {
return lbs[i].DeletedTime > lbs[j].DeletedTime
return lbs.Data[i].DeletedTime > lbs.Data[j].DeletedTime
}
return lbs[i].DeletedTime < lbs[j].DeletedTime
return lbs.Data[i].DeletedTime < lbs.Data[j].DeletedTime
})
return lbs

View File

@@ -18,12 +18,12 @@ type CreateTemplateRequest struct {
// Name to assign to the template being created
// Required: true
Name string `url:"name" json:"name" validate:"required"`
}
// Async API call
// For async call use CreateTemplateAsync
// For sync call use CreateTemplate
// Required: true
async bool `url:"async"`
type wrapperCreateTemplateRequest struct {
CreateTemplateRequest
Async bool `url:"async"`
}
// CreateTemplate create template from compute instance
@@ -35,11 +35,14 @@ func (c Compute) CreateTemplate(ctx context.Context, req CreateTemplateRequest)
}
}
req.async = false
reqWrapped := wrapperCreateTemplateRequest{
CreateTemplateRequest: req,
Async: false,
}
url := "/cloudapi/compute/createTemplate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped)
if err != nil {
return 0, err
}
@@ -61,11 +64,14 @@ func (c Compute) CreateTemplateAsync(ctx context.Context, req CreateTemplateRequ
}
}
req.async = true
reqWrapped := wrapperCreateTemplateRequest{
CreateTemplateRequest: req,
Async: true,
}
url := "/cloudapi/compute/createTemplate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped)
if err != nil {
return "", err
}

View File

@@ -0,0 +1,40 @@
package compute
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for deleting compute's custome fields
type DeleteCustomFieldsRequest struct {
// ID of the compute
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
}
// DeleteCustomFields deletes computes custom fields
func (c Compute) DeleteCustomFields(ctx context.Context, req DeleteCustomFieldsRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudapi/compute/deleteCustomFields"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -22,11 +22,6 @@ type DiskAddRequest struct {
// Required: true
Size uint64 `url:"size" json:"size" validate:"required"`
// Storage endpoint provider ID
// By default the same with boot disk
// Required: false
SepID uint64 `url:"sepId,omitempty" json:"sepId,omitempty"`
// Type of the disk
// Should be one of:
// - D
@@ -34,6 +29,11 @@ type DiskAddRequest struct {
// Required: false
DiskType string `url:"diskType,omitempty" json:"diskType,omitempty" validate:"omitempty,computeDiskType"`
// Storage endpoint provider ID
// By default the same with boot disk
// Required: false
SepID uint64 `url:"sepId,omitempty" json:"sepId,omitempty"`
// Pool name
// By default will be chosen automatically
// Required: false

View File

@@ -20,7 +20,7 @@ type DiskDelRequest struct {
// False if disk is to be deleted to recycle bin
// Required: true
Permanently bool `url:"permanently" json:"permanently" validate:"required"`
Permanently bool `url:"permanently" json:"permanently"`
}
// DiskDel delete disk and detach from compute

View File

@@ -59,7 +59,7 @@ func (lc ListComputes) FilterByDiskID(diskID uint64) ListComputes {
}
// FilterByK8SID returns master and worker nodes (ListComputes) inside specified K8S cluster.
func (lc ListComputes) FilterByK8SID(ctx context.Context, k8sID uint64, decortClient interfaces.Caller) (ListComputes, error) {
func (lc ListComputes) FilterByK8SID(ctx context.Context, k8sID uint64, decortClient interfaces.Caller) (*ListComputes, error) {
caller := k8s.New(decortClient)
req := k8s.GetRequest{
@@ -89,7 +89,9 @@ func (lc ListComputes) FilterByK8SID(ctx context.Context, k8sID uint64, decortCl
return false
}
return lc.FilterFunc(predicate), nil
res := lc.FilterFunc(predicate)
return &res, nil
}
// K8SMasters is used to filter master nodes. Best used after FilterByK8SID function.
@@ -121,7 +123,7 @@ func (lc ListComputes) FilterByK8SWorkers() ListComputes {
}
// FilterByLBID returns ListComputes used by specified Load Balancer.
func (lc ListComputes) FilterByLBID(ctx context.Context, lbID uint64, decortClient interfaces.Caller) (ListComputes, error) {
func (lc ListComputes) FilterByLBID(ctx context.Context, lbID uint64, decortClient interfaces.Caller) (*ListComputes, error) {
caller := lb.New(decortClient)
req := lb.GetRequest{
@@ -137,28 +139,32 @@ func (lc ListComputes) FilterByLBID(ctx context.Context, lbID uint64, decortClie
return ic.ID == foundLB.PrimaryNode.ComputeID || ic.ID == foundLB.SecondaryNode.ComputeID
}
return lc.FilterFunc(predicate), nil
res := lc.FilterFunc(predicate)
return &res, nil
}
// FilterFunc allows filtering ListComputes based on a user-specified predicate.
func (lc ListComputes) FilterFunc(predicate func(ItemCompute) bool) ListComputes {
var result ListComputes
for _, item := range lc {
for _, item := range lc.Data {
if predicate(item) {
result = append(result, item)
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemCompute
// If none was found, returns an empty struct.
func (lc ListComputes) FindOne() ItemCompute {
if len(lc) == 0 {
if len(lc.Data) == 0 {
return ItemCompute{}
}
return lc[0]
return lc.Data[0]
}

View File

@@ -3,150 +3,153 @@ package compute
import "testing"
var computes = ListComputes{
ItemCompute{
ACL: ListACL{},
AccountID: 132847,
AccountName: "std_2",
AffinityLabel: "",
AffinityRules: []ItemRule{
{
GUID: "",
Key: "aff_key",
Mode: "ANY",
Policy: "RECOMMENDED",
Topology: "compute",
Value: "aff_val",
Data: []ItemCompute{
{
ACL: ListACL{},
AccountID: 132847,
AccountName: "std_2",
AffinityLabel: "",
AffinityRules: []ItemRule{
{
GUID: "",
Key: "aff_key",
Mode: "ANY",
Policy: "RECOMMENDED",
Topology: "compute",
Value: "aff_val",
},
},
},
AffinityWeight: 0,
AntiAffinityRules: []ItemRule{
{
GUID: "",
Key: "antiaff_key",
Mode: "ANY",
Policy: "RECOMMENDED",
Topology: "compute",
Value: "antiaff_val",
AffinityWeight: 0,
AntiAffinityRules: []ItemRule{
{
GUID: "",
Key: "antiaff_key",
Mode: "ANY",
Policy: "RECOMMENDED",
Topology: "compute",
Value: "antiaff_val",
},
},
},
Architecture: "X86_64",
BootOrder: []string{
"hd", "cdrom",
},
BootDiskSize: 0,
CloneReference: 0,
Clones: []uint64{},
ComputeCIID: 0,
CPU: 4,
CreatedBy: "timofey_tkachev_1@decs3o",
CreatedTime: 1676975175,
CustomFields: map[string]interface{}{},
DeletedBy: "",
DeletedTime: 0,
Description: "",
Devices: nil,
Disks: []InfoDisk{
{
ID: 65191,
PCISlot: 6,
Architecture: "X86_64",
BootOrder: []string{
"hd", "cdrom",
},
BootDiskSize: 0,
CloneReference: 0,
Clones: []uint64{},
ComputeCIID: 0,
CPU: 4,
CreatedBy: "timofey_tkachev_1@decs3o",
CreatedTime: 1676975175,
CustomFields: map[string]interface{}{},
DeletedBy: "",
DeletedTime: 0,
Description: "",
Devices: nil,
Disks: []InfoDisk{
{
ID: 65191,
PCISlot: 6,
},
},
Driver: "KVM_X86",
GID: 212,
GUID: 48500,
ID: 48500,
ImageID: 9884,
Interfaces: []ItemVNFInterface{},
LockStatus: "UNLOCKED",
ManagerID: 0,
ManagerType: "",
MigrationJob: 0,
Milestones: 363500,
Name: "test",
Pinned: false,
RAM: 4096,
ReferenceID: "c7cb19ac-af4a-4067-852f-c5572949207e",
Registered: true,
ResName: "compute-48500",
RGID: 79724,
RGName: "std_broker2",
SnapSets: []ItemSnapSet{},
StatelessSepID: 0,
StatelessSepType: "",
Status: "ENABLED",
Tags: map[string]string{},
TechStatus: "STOPPED",
TotalDiskSize: 2,
UpdatedBy: "",
UpdatedTime: 1677058904,
UserManaged: true,
VGPUs: []uint64{},
VINSConnected: 0,
VirtualImageID: 0,
},
{
ACL: ListACL{},
AccountID: 132848,
AccountName: "std_broker",
AffinityLabel: "",
AffinityRules: []ItemRule{},
AffinityWeight: 0,
AntiAffinityRules: []ItemRule{},
Architecture: "X86_64",
BootOrder: []string{
"hd", "cdrom",
},
BootDiskSize: 0,
CloneReference: 0,
Clones: []uint64{},
ComputeCIID: 0,
CPU: 6,
CreatedBy: "timofey_tkachev_1@decs3o",
CreatedTime: 1677579436,
CustomFields: map[string]interface{}{},
DeletedBy: "",
DeletedTime: 0,
Description: "",
Devices: nil,
Disks: []InfoDisk{
{
ID: 65248,
PCISlot: 6,
},
},
Driver: "KVM_X86",
GID: 212,
GUID: 48556,
ID: 48556,
ImageID: 9884,
Interfaces: []ItemVNFInterface{},
LockStatus: "UNLOCKED",
ManagerID: 0,
ManagerType: "",
MigrationJob: 0,
Milestones: 363853,
Name: "compute_2",
Pinned: false,
RAM: 4096,
ReferenceID: "a542c449-5b1c-4f90-88c5-7bb5f8ae68ff",
Registered: true,
ResName: "compute-48556",
RGID: 79727,
RGName: "sdk_negative_fields_test",
SnapSets: []ItemSnapSet{},
StatelessSepID: 0,
StatelessSepType: "",
Status: "ENABLED",
Tags: map[string]string{},
TechStatus: "STARTED",
TotalDiskSize: 1,
UpdatedBy: "",
UpdatedTime: 1677579436,
UserManaged: true,
VGPUs: []uint64{},
VINSConnected: 0,
VirtualImageID: 0,
},
Driver: "KVM_X86",
GID: 212,
GUID: 48500,
ID: 48500,
ImageID: 9884,
Interfaces: []ItemVNFInterface{},
LockStatus: "UNLOCKED",
ManagerID: 0,
ManagerType: "",
MigrationJob: 0,
Milestones: 363500,
Name: "test",
Pinned: false,
RAM: 4096,
ReferenceID: "c7cb19ac-af4a-4067-852f-c5572949207e",
Registered: true,
ResName: "compute-48500",
RGID: 79724,
RGName: "std_broker2",
SnapSets: []ItemSnapSet{},
StatelessSepID: 0,
StatelessSepType: "",
Status: "ENABLED",
Tags: map[string]string{},
TechStatus: "STOPPED",
TotalDiskSize: 2,
UpdatedBy: "",
UpdatedTime: 1677058904,
UserManaged: true,
VGPUs: []uint64{},
VINSConnected: 0,
VirtualImageID: 0,
},
ItemCompute{
ACL: ListACL{},
AccountID: 132848,
AccountName: "std_broker",
AffinityLabel: "",
AffinityRules: []ItemRule{},
AffinityWeight: 0,
AntiAffinityRules: []ItemRule{},
Architecture: "X86_64",
BootOrder: []string{
"hd", "cdrom",
},
BootDiskSize: 0,
CloneReference: 0,
Clones: []uint64{},
ComputeCIID: 0,
CPU: 6,
CreatedBy: "timofey_tkachev_1@decs3o",
CreatedTime: 1677579436,
CustomFields: map[string]interface{}{},
DeletedBy: "",
DeletedTime: 0,
Description: "",
Devices: nil,
Disks: []InfoDisk{
{
ID: 65248,
PCISlot: 6,
},
},
Driver: "KVM_X86",
GID: 212,
GUID: 48556,
ID: 48556,
ImageID: 9884,
Interfaces: []ItemVNFInterface{},
LockStatus: "UNLOCKED",
ManagerID: 0,
ManagerType: "",
MigrationJob: 0,
Milestones: 363853,
Name: "compute_2",
Pinned: false,
RAM: 4096,
ReferenceID: "a542c449-5b1c-4f90-88c5-7bb5f8ae68ff",
Registered: true,
ResName: "compute-48556",
RGID: 79727,
RGName: "sdk_negative_fields_test",
SnapSets: []ItemSnapSet{},
StatelessSepID: 0,
StatelessSepType: "",
Status: "ENABLED",
Tags: map[string]string{},
TechStatus: "STARTED",
TotalDiskSize: 1,
UpdatedBy: "",
UpdatedTime: 1677579436,
UserManaged: true,
VGPUs: []uint64{},
VINSConnected: 0,
VirtualImageID: 0,
},
EntryCount: 2,
}
func TestFilterByID(t *testing.T) {
@@ -158,8 +161,8 @@ func TestFilterByID(t *testing.T) {
actualEmpty := computes.FilterByID(0)
if len(actualEmpty) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty))
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
@@ -174,7 +177,7 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) {
actual := computes.FilterByStatus("ENABLED")
for _, item := range actual {
for _, item := range actual.Data {
if item.Status != "ENABLED" {
t.Fatal("expected ENABLED status, found: ", item.Status)
}
@@ -202,11 +205,11 @@ func TestFilterFunc(t *testing.T) {
return ic.Registered == true
})
if len(actual) != 2 {
t.Fatal("expected 2 elements found, actual: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 elements found, actual: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Registered != true {
t.Fatal("expected Registered to be true, actual: ", item.Registered)
}
@@ -216,26 +219,26 @@ func TestFilterFunc(t *testing.T) {
func TestSortingByCreatedTime(t *testing.T) {
actual := computes.SortByCreatedTime(false)
if actual[0].Name != "test" {
t.Fatal("expected 'test', found: ", actual[0].Name)
if actual.Data[0].Name != "test" {
t.Fatal("expected 'test', found: ", actual.Data[0].Name)
}
actual = computes.SortByCreatedTime(true)
if actual[0].Name != "compute_2" {
t.Fatal("expected 'compute_2', found: ", actual[0].Name)
if actual.Data[0].Name != "compute_2" {
t.Fatal("expected 'compute_2', found: ", actual.Data[0].Name)
}
}
func TestSortingByCPU(t *testing.T) {
actual := computes.SortByCPU(false)
if actual[0].CPU != 4 {
t.Fatal("expected 4 CPU cores, found: ", actual[0].CPU)
if actual.Data[0].CPU != 4 {
t.Fatal("expected 4 CPU cores, found: ", actual.Data[0].CPU)
}
actual = computes.SortByCPU(true)
if actual[0].CPU != 6 {
t.Fatal("expected 6 CPU cores, found: ", actual[0].CPU)
if actual.Data[0].CPU != 6 {
t.Fatal("expected 6 CPU cores, found: ", actual.Data[0].CPU)
}
}

View File

@@ -8,25 +8,16 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request for get information about compute
// GetRequest struct to get information about compute
type GetRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
}
// Get Gets information about compute
// Get gets information about compute as a RecordCompute struct
func (c Compute) Get(ctx context.Context, req GetRequest) (*RecordCompute, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/compute/get"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := c.GetRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -40,3 +31,18 @@ func (c Compute) Get(ctx context.Context, req GetRequest) (*RecordCompute, error
return &info, nil
}
// GetRaw gets information about compute as an array of bytes
func (c Compute) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/compute/get"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -8,19 +8,15 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for get list GPU for compute
type ListGPURequest struct {
// ID of compute instance
// Request struct for getting Compute's customFields
type GetCustomFieldsRequest struct {
// Compute ID
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
// Also list destroyed
// Required: false
ListDestroyed bool `url:"list_destroyed,omitempty" json:"list_destroyed,omitempty"`
}
// ListVGPU gets list GPU for compute
func (c Compute) ListGPU(ctx context.Context, req ListGPURequest) ([]interface{}, error) {
// GetCustomFields gets Compute's customFields
func (c Compute) GetCustomFields(ctx context.Context, req GetCustomFieldsRequest) (interface{}, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -28,19 +24,19 @@ func (c Compute) ListGPU(ctx context.Context, req ListGPURequest) ([]interface{}
}
}
url := "/cloudbroker/compute/listGpu"
url := "/cloudapi/compute/getCustomFields"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := make([]interface{}, 0)
var info interface{}
err = json.Unmarshal(res, &list)
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return list, nil
return &info, nil
}

View File

@@ -6,8 +6,48 @@ import (
"net/http"
)
// Request struct for get list available computes
// ListRequest struct to get list of available computes
type ListRequest struct {
// Find by ID
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by account ID
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// Find by resource group name
// Required: false
RGName string `url:"rgName,omitempty" json:"rgName,omitempty"`
// Find by resource group ID
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by tech status
// Required: false
TechStatus string `url:"techStatus,omitempty" json:"techStatus,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Find by IP address
// Required: false
IPAddress string `url:"ipAddress,omitempty" json:"ipAddress,omitempty"`
// Find by external network name
// Required: false
ExtNetName string `url:"extNetName,omitempty" json:"extNetName,omitempty"`
// Find by external network ID
// Required: false
ExtNetID uint64 `url:"extNetId,omitempty" json:"extNetId,omitempty"`
// Include deleted computes
// Required: false
IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"`
@@ -23,10 +63,8 @@ type ListRequest struct {
// List gets list of the available computes.
// Filtering based on status is possible
func (c Compute) List(ctx context.Context, req ListRequest) (ListComputes, error) {
url := "/cloudapi/compute/list"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
func (c Compute) List(ctx context.Context, req ListRequest) (*ListComputes, error) {
res, err := c.ListRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -38,5 +76,13 @@ func (c Compute) List(ctx context.Context, req ListRequest) (ListComputes, error
return nil, err
}
return list, nil
return &list, nil
}
// ListRaw gets list of the available computes.
func (c Compute) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
url := "/cloudapi/compute/list"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -8,6 +8,42 @@ import (
// Request struct for get deleted computes list
type ListDeletedRequest struct {
// Find by ID
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by account ID
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// Find by resource group name
// Required: false
RGName string `url:"rgName,omitempty" json:"rgName,omitempty"`
// Find by resource group ID
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by tech status
// Required: false
TechStatus string `url:"techStatus,omitempty" json:"techStatus,omitempty"`
// Find by IP address
// Required: false
IPAddress string `url:"ipAddress,omitempty" json:"ipAddress,omitempty"`
// Find by external network name
// Required: false
ExtNetName string `url:"extNetName,omitempty" json:"extNetName,omitempty"`
// Find by external network ID
// Required: false
ExtNetID uint64 `url:"extNetId,omitempty" json:"extNetId,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -18,7 +54,7 @@ type ListDeletedRequest struct {
}
// ListDeleted gets list all deleted computes
func (c Compute) ListDeleted(ctx context.Context, req ListDeletedRequest) (ListComputes, error) {
func (c Compute) ListDeleted(ctx context.Context, req ListDeletedRequest) (*ListComputes, error) {
url := "/cloudapi/compute/listDeleted"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
@@ -33,5 +69,5 @@ func (c Compute) ListDeleted(ctx context.Context, req ListDeletedRequest) (ListC
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -13,10 +13,34 @@ type ListPCIDeviceRequest struct {
// Identifier compute
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
// Find by resource group ID
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by device id
// Required: false
DevID uint64 `url:"devId,omitempty" json:"devId,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListPCIDevice gets list PCI device
func (c Compute) ListPCIDevice(ctx context.Context, req ListPCIDeviceRequest) ([]interface{}, error) {
func (c Compute) ListPCIDevice(ctx context.Context, req ListPCIDeviceRequest) (*ListPCIDevices, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -31,12 +55,12 @@ func (c Compute) ListPCIDevice(ctx context.Context, req ListPCIDeviceRequest) ([
return nil, err
}
list := []interface{}{}
list := ListPCIDevices{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -13,10 +13,34 @@ type ListVGPURequest struct {
// Identifier compute
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
// Find by GPU id
// Required: false
GPUID uint64 `url:"gpuId,omitempty" json:"gpuId,omitempty"`
// Find by type
// Required: false
Type string `url:"type,omitempty" json:"type,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Include deleted computes. If using field 'status', then includedeleted will be ignored
// Required: false
IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"`
}
// ListVGPU gets list vGPU
func (c Compute) ListVGPU(ctx context.Context, req ListVGPURequest) ([]interface{}, error) {
func (c Compute) ListVGPU(ctx context.Context, req ListVGPURequest) (*ListVGPUs, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -24,19 +48,19 @@ func (c Compute) ListVGPU(ctx context.Context, req ListVGPURequest) ([]interface
}
}
url := "/cloudapi/compute/listVgpu"
url := "/cloudapi/compute/listVGpu"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := []interface{}{}
list := ListVGPUs{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -14,6 +14,14 @@ type RecordACL struct {
RGACL ListACL `json:"rgAcl"`
}
type ListUsers struct {
// Data
Data RecordACL `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
type Explicit bool
func (e *Explicit) UnmarshalJSON(b []byte) error {
@@ -89,7 +97,13 @@ type ItemSnapshot struct {
}
// List of snapshots
type ListSnapShots []ItemSnapshot
type ListSnapShots struct {
// Data
Data []ItemSnapshot `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about port forward
type ItemPFW struct {
@@ -116,7 +130,13 @@ type ItemPFW struct {
}
// List port forwards
type ListPFWs []ItemPFW
type ListPFWs struct {
// Data
Data []ItemPFW `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about affinity relations
type RecordAffinityRelations struct {
@@ -403,7 +423,7 @@ type RecordCompute struct {
SnapSets ListSnapSets `json:"snapSets"`
// Stateless SepID
StatelessSepID uint64 `json:"statelessSepId"`
StatelessSepID int64 `json:"statelessSepId"`
// Stateless SepType
StatelessSepType string `json:"statelessSepType"`
@@ -673,6 +693,9 @@ type SnapshotExtend struct {
// Label
Label string `json:"label"`
// Reference ID
ReferenceID string `json:"referenceId"`
// Resource ID
ResID string `json:"resId"`
@@ -859,7 +882,7 @@ type ItemCompute struct {
SnapSets ListSnapSets `json:"snapSets"`
// Stateless SepID
StatelessSepID uint64 `json:"statelessSepId"`
StatelessSepID int64 `json:"statelessSepId"`
// Stateless SepType
StatelessSepType string `json:"statelessSepType"`
@@ -905,4 +928,28 @@ type InfoDisk struct {
}
// List information about computes
type ListComputes []ItemCompute
type ListComputes struct {
// Data
Data []ItemCompute `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// List VGPUs
type ListVGPUs struct {
// Data
Data []interface{} `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// List PCI devices
type ListPCIDevices struct {
// Data
Data []interface{} `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}

View File

@@ -19,8 +19,9 @@ type PFWAddRequest struct {
PublicPortStart uint64 `url:"publicPortStart" json:"publicPortStart" validate:"required"`
// End port number (inclusive) for the ranged rule
// Default value: -1
// Required: false
PublicPortEnd uint64 `url:"publicPortEnd,omitempty" json:"publicPortEnd,omitempty"`
PublicPortEnd int64 `url:"publicPortEnd,omitempty" json:"publicPortEnd,omitempty"`
// Internal base port number
// Required: true

View File

@@ -16,7 +16,7 @@ type PFWListRequest struct {
}
// PFWList gets compute port forwards list
func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (ListPFWs, error) {
func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (*ListPFWs, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +38,5 @@ func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (ListPFWs, err
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -17,16 +17,16 @@ type ResizeRequest struct {
// New CPU count.
// Pass 0 if no change to CPU count is required
// Required: false
Force bool `url:"force,omitempty" json:"force,omitempty"`
CPU uint64 `url:"cpu,omitempty" json:"cpu,omitempty"`
// New RAM volume in MB.
// Pass 0 if no change to RAM volume is required
// Required: false
CPU uint64 `url:"cpu,omitempty" json:"cpu,omitempty"`
RAM uint64 `url:"ram,omitempty" json:"ram,omitempty"`
// Force compute resize
// Required: false
RAM uint64 `url:"ram,omitempty" json:"ram,omitempty"`
Force bool `url:"force,omitempty" json:"force,omitempty"`
}
// Resize resize compute instance

View File

@@ -12,7 +12,7 @@ import (
// - First argument -> prefix
// - Second argument -> indent
func (lc ListComputes) Serialize(params ...string) (serialization.Serialized, error) {
if len(lc) == 0 {
if lc.EntryCount == 0 {
return []byte{}, nil
}

View File

@@ -0,0 +1,43 @@
package compute
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for setting customFields values for the Compute
type SetCustomFieldsRequest struct {
// ID of the compute
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
// Custom fields for Compute. Must be dict.
// Required: true
CustomFields string `url:"customFields" json:"customFields" validate:"required"`
}
// SetCustomFields sets customFields values for the Compute
func (c Compute) SetCustomFields(ctx context.Context, req SetCustomFieldsRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudapi/compute/setCustomFields"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -16,7 +16,7 @@ type SnapshotListRequest struct {
}
// SnapshotList gets list compute snapshots
func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (ListSnapShots, error) {
func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (*ListSnapShots, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +38,5 @@ func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (Lis
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -6,16 +6,16 @@ import "sort"
//
// If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByCPU(inverse bool) ListComputes {
if len(lc) < 2 {
if len(lc.Data) < 2 {
return lc
}
sort.Slice(lc, func(i, j int) bool {
sort.Slice(lc.Data, func(i, j int) bool {
if inverse {
return lc[i].CPU > lc[j].CPU
return lc.Data[i].CPU > lc.Data[j].CPU
}
return lc[i].CPU < lc[j].CPU
return lc.Data[i].CPU < lc.Data[j].CPU
})
return lc
@@ -25,16 +25,16 @@ func (lc ListComputes) SortByCPU(inverse bool) ListComputes {
//
// If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByRAM(inverse bool) ListComputes {
if len(lc) < 2 {
if len(lc.Data) < 2 {
return lc
}
sort.Slice(lc, func(i, j int) bool {
sort.Slice(lc.Data, func(i, j int) bool {
if inverse {
return lc[i].RAM > lc[j].RAM
return lc.Data[i].RAM > lc.Data[j].RAM
}
return lc[i].RAM < lc[j].RAM
return lc.Data[i].RAM < lc.Data[j].RAM
})
return lc
@@ -44,16 +44,16 @@ func (lc ListComputes) SortByRAM(inverse bool) ListComputes {
//
// If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByCreatedTime(inverse bool) ListComputes {
if len(lc) < 2 {
if len(lc.Data) < 2 {
return lc
}
sort.Slice(lc, func(i, j int) bool {
sort.Slice(lc.Data, func(i, j int) bool {
if inverse {
return lc[i].CreatedTime > lc[j].CreatedTime
return lc.Data[i].CreatedTime > lc.Data[j].CreatedTime
}
return lc[i].CreatedTime < lc[j].CreatedTime
return lc.Data[i].CreatedTime < lc.Data[j].CreatedTime
})
return lc
@@ -63,16 +63,16 @@ func (lc ListComputes) SortByCreatedTime(inverse bool) ListComputes {
//
// If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByUpdatedTime(inverse bool) ListComputes {
if len(lc) < 2 {
if len(lc.Data) < 2 {
return lc
}
sort.Slice(lc, func(i, j int) bool {
sort.Slice(lc.Data, func(i, j int) bool {
if inverse {
return lc[i].UpdatedTime > lc[j].UpdatedTime
return lc.Data[i].UpdatedTime > lc.Data[j].UpdatedTime
}
return lc[i].UpdatedTime < lc[j].UpdatedTime
return lc.Data[i].UpdatedTime < lc.Data[j].UpdatedTime
})
return lc
@@ -82,16 +82,16 @@ func (lc ListComputes) SortByUpdatedTime(inverse bool) ListComputes {
//
// If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByDeletedTime(inverse bool) ListComputes {
if len(lc) < 2 {
if len(lc.Data) < 2 {
return lc
}
sort.Slice(lc, func(i, j int) bool {
sort.Slice(lc.Data, func(i, j int) bool {
if inverse {
return lc[i].DeletedTime > lc[j].DeletedTime
return lc.Data[i].DeletedTime > lc.Data[j].DeletedTime
}
return lc[i].DeletedTime < lc[j].DeletedTime
return lc.Data[i].DeletedTime < lc.Data[j].DeletedTime
})
return lc

View File

@@ -16,7 +16,7 @@ type UserListRequest struct {
}
// UserList gets users list for compute
func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL, error) {
func (c Compute) UserList(ctx context.Context, req UserListRequest) (*ListUsers, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -31,7 +31,7 @@ func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL,
return nil, err
}
list := RecordACL{}
list := ListUsers{}
err = json.Unmarshal(res, &list)
if err != nil {

View File

@@ -1,10 +0,0 @@
package cloudapi
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/computeci"
)
// Accessing the ComputeCI method group
func (ca *CloudAPI) ComputeCI() *computeci.ComputeCI {
return computeci.New(ca.client)
}

View File

@@ -1,18 +0,0 @@
// API Actor for managing ComputeCI. This actor is a final API for admin to manage ComputeCI
package computeci
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
)
// Structure for creating request to computeci
type ComputeCI struct {
client interfaces.Caller
}
// Builder for computeci endpoints
func New(client interfaces.Caller) *ComputeCI {
return &ComputeCI{
client,
}
}

View File

@@ -1,51 +0,0 @@
package computeci
// FilterByID returns ListComputeCI with specified ID.
func (lci ListComputeCI) FilterByID(id uint64) ListComputeCI {
predicate := func(ic ItemComputeCI) bool {
return ic.ID == id
}
return lci.FilterFunc(predicate)
}
// FilterByName returns ListComputeCI with specified Name.
func (lci ListComputeCI) FilterByName(name string) ListComputeCI {
predicate := func(ic ItemComputeCI) bool {
return ic.Name == name
}
return lci.FilterFunc(predicate)
}
// FilterByStatus returns ListComputeCI with specified Status.
func (lci ListComputeCI) FilterByStatus(status string) ListComputeCI {
predicate := func(ic ItemComputeCI) bool {
return ic.Status == status
}
return lci.FilterFunc(predicate)
}
// FilterFunc allows filtering ListComputeCI based on a user-specified predicate.
func (lci ListComputeCI) FilterFunc(predicate func(ItemComputeCI) bool) ListComputeCI {
var result ListComputeCI
for _, item := range lci {
if predicate(item) {
result = append(result, item)
}
}
return result
}
// FindOne returns first found ItemComputeCI
// If none was found, returns an empty struct.
func (lci ListComputeCI) FindOne() ItemComputeCI {
if len(lci) == 0 {
return ItemComputeCI{}
}
return lci[0]
}

View File

@@ -1,95 +0,0 @@
package computeci
import "testing"
var computeciItems = ListComputeCI{
{
CustomFields: map[string]interface{}{},
Description: "",
Drivers: []string{
"KVM_X86",
},
GUID: 1,
ID: 1,
Name: "computeci_1",
Status: "ENABLED",
Template: "",
},
{
CustomFields: map[string]interface{}{},
Description: "",
Drivers: []string{
"KVM_X86",
},
GUID: 2,
ID: 2,
Name: "computeci_2",
Status: "ENABLED",
Template: "",
},
{
CustomFields: map[string]interface{}{},
Description: "",
Drivers: []string{
"SVA_KVM_X86",
},
GUID: 3,
ID: 3,
Name: "computeci_3",
Status: "DISABLED",
Template: "",
},
}
func TestFilterByID(t *testing.T) {
actual := computeciItems.FilterByID(2).FindOne()
if actual.ID != 2 {
t.Fatal("expected ID 2, found: ", actual.ID)
}
}
func TestFilterByName(t *testing.T) {
actual := computeciItems.FilterByName("computeci_3").FindOne()
if actual.Name != "computeci_3" {
t.Fatal("expected Name 'computeci_2', found: ", actual.Name)
}
}
func TestFilterByStatus(t *testing.T) {
actual := computeciItems.FilterByStatus("ENABLED")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
}
for _, item := range actual {
if item.Status != "ENABLED" {
t.Fatal("expected Status 'ENABLED', found: ", item.Status)
}
}
}
func TestFilterFunc(t *testing.T) {
actual := computeciItems.FilterFunc(func(icc ItemComputeCI) bool {
for _, item := range icc.Drivers {
if item == "KVM_X86" {
return true
}
}
return false
})
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
}
for _, item := range actual {
for _, driver := range item.Drivers {
if driver != "KVM_X86" {
t.Fatal("expected 'KVM_X86' Driver, found: ", driver)
}
}
}
}

View File

@@ -1,42 +0,0 @@
package computeci
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for information about computeci
type GetRequest struct {
// ID of the Compute CI
// Required: true
ComputeCIID uint64 `url:"computeciId" json:"computeciId" validate:"required"`
}
// Get gets information about computeci by ID
func (c ComputeCI) Get(ctx context.Context, req GetRequest) (*ItemComputeCI, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validatonError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validatonError)
}
}
url := "/cloudapi/computeci/get"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
info := ItemComputeCI{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@@ -1,41 +0,0 @@
package computeci
import (
"context"
"encoding/json"
"net/http"
)
// Request struct for get list of computeci
type ListRequest struct {
// If true list deleted instances as well
// Required: false
IncludeDeleted bool `url:"includeDeleted,omitempty" json:"includeDeleted,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list of computeci instances
func (c ComputeCI) List(ctx context.Context, req ListRequest) (ListComputeCI, error) {
url := "/cloudapi/computeci/list"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListComputeCI{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
}

View File

@@ -1,31 +0,0 @@
package computeci
// Main information about computeci
type ItemComputeCI struct {
// Custom fields
CustomFields map[string]interface{} `json:"customFields"`
// Description
Description string `json:"desc"`
// List drivers
Drivers []string `json:"drivers"`
// GUID
GUID uint64 `json:"guid"`
// ID
ID uint64 `json:"id"`
// Name
Name string `json:"name"`
// Status
Status string `json:"status"`
// Template
Template string `json:"template"`
}
// List of computeci instances
type ListComputeCI []ItemComputeCI

View File

@@ -61,7 +61,7 @@ func (ld ListDisks) FilterByComputeID(computeID uint64) ListDisks {
}
// FilterByK8SID is used to filter ListDisks by specified K8S cluster.
func (ld ListDisks) FilterByK8SID(ctx context.Context, k8sID uint64, decortClient interfaces.Caller) (ListDisks, error) {
func (ld ListDisks) FilterByK8SID(ctx context.Context, k8sID uint64, decortClient interfaces.Caller) (*ListDisks, error) {
caller := k8s.New(decortClient)
req := k8s.GetRequest{
@@ -76,20 +76,22 @@ func (ld ListDisks) FilterByK8SID(ctx context.Context, k8sID uint64, decortClien
var result ListDisks
for _, masterCompute := range cluster.K8SGroups.Masters.DetailedInfo {
result = append(result, ld.FilterByComputeID(masterCompute.ID)...)
result.Data = append(result.Data, ld.FilterByComputeID(masterCompute.ID).Data...)
}
for _, workerGroup := range cluster.K8SGroups.Workers {
for _, workerCompute := range workerGroup.DetailedInfo {
result = append(result, ld.FilterByComputeID(workerCompute.ID)...)
result.Data = append(result.Data, ld.FilterByComputeID(workerCompute.ID).Data...)
}
}
return result, nil
result.EntryCount = uint64(len(result.Data))
return &result, nil
}
// FilterByLBID is used to filter ListDisks used by computes inside specified Load Balancer.
func (ld ListDisks) FilterByLBID(ctx context.Context, lbID uint64, decortClient interfaces.Caller) (ListDisks, error) {
func (ld ListDisks) FilterByLBID(ctx context.Context, lbID uint64, decortClient interfaces.Caller) (*ListDisks, error) {
caller := lb.New(decortClient)
req := lb.GetRequest{
@@ -102,15 +104,141 @@ func (ld ListDisks) FilterByLBID(ctx context.Context, lbID uint64, decortClient
}
var result ListDisks
result.Data = append(result.Data, ld.FilterByComputeID(foundLB.PrimaryNode.ComputeID).Data...)
result.Data = append(result.Data, ld.FilterByComputeID(foundLB.SecondaryNode.ComputeID).Data...)
result.EntryCount = uint64(len(result.Data))
return &result, nil
}
// FilterFunc allows filtering ListDisks based on a user-specified predicate.
func (ld ListDisks) FilterFunc(predicate func(ItemDisk) bool) ListDisks {
var result ListDisks
for _, item := range ld.Data {
if predicate(item) {
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemDisk
// If none was found, returns an empty struct.
func (ld ListDisks) FindOne() ItemDisk {
if len(ld.Data) == 0 {
return ItemDisk{}
}
return ld.Data[0]
}
// FilterByID returns ListSearchDisks with specified ID.
func (ld ListSearchDisks) FilterByID(id uint64) ListSearchDisks {
predicate := func(idisk ItemDisk) bool {
return idisk.ID == id
}
return ld.FilterFunc(predicate)
}
// FilterByName returns ListSearchDisks with specified Name.
func (ld ListSearchDisks) FilterByName(name string) ListSearchDisks {
predicate := func(idisk ItemDisk) bool {
return idisk.Name == name
}
return ld.FilterFunc(predicate)
}
// FilterByStatus returns ListSearchDisks with specified Status.
func (ld ListSearchDisks) FilterByStatus(status string) ListSearchDisks {
predicate := func(idisk ItemDisk) bool {
return idisk.Status == status
}
return ld.FilterFunc(predicate)
}
// FilterByTechStatus returns ListSearchDisks with specified TechStatus.
func (ld ListSearchDisks) FilterByTechStatus(techStatus string) ListSearchDisks {
predicate := func(idisk ItemDisk) bool {
return idisk.TechStatus == techStatus
}
return ld.FilterFunc(predicate)
}
// FilterByComputeID is used to filter ListSearchDisks attached to specified compute.
func (ld ListSearchDisks) FilterByComputeID(computeID uint64) ListSearchDisks {
predicate := func(idisk ItemDisk) bool {
for k := range idisk.Computes {
if k == strconv.FormatUint(computeID, 10) {
return true
}
}
return false
}
return ld.FilterFunc(predicate)
}
// FilterByK8SID is used to filter ListSearchDisks by specified K8S cluster.
func (ld ListSearchDisks) FilterByK8SID(ctx context.Context, k8sID uint64, decortClient interfaces.Caller) (ListSearchDisks, error) {
caller := k8s.New(decortClient)
req := k8s.GetRequest{
K8SID: k8sID,
}
cluster, err := caller.Get(ctx, req)
if err != nil {
return nil, err
}
var result ListSearchDisks
for _, masterCompute := range cluster.K8SGroups.Masters.DetailedInfo {
result = append(result, ld.FilterByComputeID(masterCompute.ID)...)
}
for _, workerGroup := range cluster.K8SGroups.Workers {
for _, workerCompute := range workerGroup.DetailedInfo {
result = append(result, ld.FilterByComputeID(workerCompute.ID)...)
}
}
return result, nil
}
// FilterByLBID is used to filter ListSearchDisks used by computes inside specified Load Balancer.
func (ld ListSearchDisks) FilterByLBID(ctx context.Context, lbID uint64, decortClient interfaces.Caller) (ListSearchDisks, error) {
caller := lb.New(decortClient)
req := lb.GetRequest{
LBID: lbID,
}
foundLB, err := caller.Get(ctx, req)
if err != nil {
return nil, err
}
var result ListSearchDisks
result = append(result, ld.FilterByComputeID(foundLB.PrimaryNode.ComputeID)...)
result = append(result, ld.FilterByComputeID(foundLB.SecondaryNode.ComputeID)...)
return result, nil
}
// FilterFunc allows filtering ListDisks based on a user-specified predicate.
func (ld ListDisks) FilterFunc(predicate func(ItemDisk) bool) ListDisks {
var result ListDisks
// FilterFunc allows filtering ListSearchDisks based on a user-specified predicate.
func (ld ListSearchDisks) FilterFunc(predicate func(ItemDisk) bool) ListSearchDisks {
var result ListSearchDisks
for _, item := range ld {
if predicate(item) {
@@ -123,7 +251,7 @@ func (ld ListDisks) FilterFunc(predicate func(ItemDisk) bool) ListDisks {
// FindOne returns first found ItemDisk
// If none was found, returns an empty struct.
func (ld ListDisks) FindOne() ItemDisk {
func (ld ListSearchDisks) FindOne() ItemDisk {
if len(ld) == 0 {
return ItemDisk{}
}
@@ -171,21 +299,23 @@ func (lu ListDisksUnattached) FilterByTechStatus(techStatus string) ListDisksUna
func (lu ListDisksUnattached) FilterFunc(predicate func(ItemDiskUnattached) bool) ListDisksUnattached {
var result ListDisksUnattached
for _, item := range lu {
for _, item := range lu.Data {
if predicate(item) {
result = append(result, item)
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemDiskUnattached
// If none was found, returns an empty struct.
func (lu ListDisksUnattached) FindOne() ItemDiskUnattached {
if len(lu) == 0 {
if len(lu.Data) == 0 {
return ItemDiskUnattached{}
}
return lu[0]
return lu.Data[0]
}

View File

@@ -4,7 +4,186 @@ import (
"testing"
)
var techStatusAllocated = "ALLOCATED"
var disks = ListDisks{
Data: []ItemDisk{
{
MachineID: 0,
MachineName: "",
DeviceName: "vda",
AccountID: 132847,
AccountName: "std_2",
ACL: map[string]interface{}{},
Computes: map[string]string{
"48500": "test",
},
CreatedTime: 1676975177,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
GID: 212,
ID: 65191,
ImageID: 9884,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
Name: "bootdisk",
Order: 0,
Params: "",
ParentID: 0,
PCISlot: 6,
Pool: "vmstor",
PresentTo: []uint64{
27,
},
PurgeTime: 0,
ResID: "sample",
ResName: "sample",
Role: "",
Shareable: false,
SizeMax: 2,
SizeUsed: 2,
Snapshots: []ItemSnapshot{},
Status: "ASSIGNED",
TechStatus: techStatusAllocated,
Type: "B",
VMID: 48500,
},
{
MachineID: 0,
MachineName: "",
DeviceName: "vda",
AccountID: 132852,
AccountName: "std",
ACL: map[string]interface{}{},
Computes: map[string]string{
"48502": "stdvm2",
},
CreatedTime: 1676982606,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
GID: 212,
ID: 65193,
ImageID: 9885,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
Name: "bootdisk",
Order: 0,
Params: "",
ParentID: 0,
PCISlot: 6,
Pool: "vmstor",
PresentTo: []uint64{
27,
27,
},
PurgeTime: 0,
ResID: "sample",
ResName: "sample",
Role: "",
Shareable: false,
SizeMax: 4,
SizeUsed: 4,
Snapshots: []ItemSnapshot{},
Status: "ASSIGNED",
TechStatus: techStatusAllocated,
Type: "B",
VMID: 48502,
},
},
EntryCount: 2,
}
func TestListDisks_FilterByID(t *testing.T) {
actual := disks.FilterByID(65193)
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
actualItem := actual.FindOne()
if actualItem.ID != 65193 {
t.Fatal("expected ID 65193, found: ", actualItem.ID)
}
}
func TestListDisks_FilterByName(t *testing.T) {
actual := disks.FilterByName("bootdisk")
if len(actual.Data) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual.Data))
}
for _, item := range actual.Data {
if item.Name != "bootdisk" {
t.Fatal("expected 'bootdisk' name, found: ", item.Name)
}
}
}
func TestListDisks_FilterByStatus(t *testing.T) {
actual := disks.FilterByStatus("ASSIGNED")
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual.Data {
if item.Status != "ASSIGNED" {
t.Fatal("expected 'ASSIGNED' status, found: ", item.Status)
}
}
}
func TestListDisks_FilterByTechStatus(t *testing.T) {
actual := disks.FilterByTechStatus(techStatusAllocated)
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual.Data {
if item.TechStatus != techStatusAllocated {
t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus)
}
}
}
func TestListDisks_FilterFunc(t *testing.T) {
actual := disks.FilterFunc(func(id ItemDisk) bool {
return len(id.PresentTo) == 2
})
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
if len(actual.Data[0].PresentTo) != 2 {
t.Fatal("expected 2 elements in PresentTo, found: ", len(actual.Data[0].PresentTo))
}
}
func TestListDisks_SortByCreatedTime(t *testing.T) {
actual := disks.SortByCreatedTime(false)
if actual.Data[0].ID != 65191 {
t.Fatal("expected ID 65191, found: ", actual.Data[0].ID)
}
actual = disks.SortByCreatedTime(true)
if actual.Data[0].ID != 65193 {
t.Fatal("expected ID 65193, found: ", actual.Data[0].ID)
}
}
var searchDisks = ListSearchDisks{
ItemDisk{
MachineID: 0,
MachineName: "",
@@ -44,7 +223,7 @@ var disks = ListDisks{
SizeUsed: 2,
Snapshots: []ItemSnapshot{},
Status: "ASSIGNED",
TechStatus: "ALLOCATED",
TechStatus: techStatusAllocated,
Type: "B",
VMID: 48500,
},
@@ -88,14 +267,14 @@ var disks = ListDisks{
SizeUsed: 4,
Snapshots: []ItemSnapshot{},
Status: "ASSIGNED",
TechStatus: "ALLOCATED",
TechStatus: techStatusAllocated,
Type: "B",
VMID: 48502,
},
}
func TestFilterByID(t *testing.T) {
actual := disks.FilterByID(65193)
func TestListSearchDisks_FilterByID(t *testing.T) {
actual := searchDisks.FilterByID(65193)
if len(actual) == 0 {
t.Fatal("No elements were found")
@@ -108,8 +287,8 @@ func TestFilterByID(t *testing.T) {
}
}
func TestFilterByName(t *testing.T) {
actual := disks.FilterByName("bootdisk")
func TestListSearchDisks_FilterByName(t *testing.T) {
actual := searchDisks.FilterByName("bootdisk")
if len(actual) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual))
@@ -122,8 +301,8 @@ func TestFilterByName(t *testing.T) {
}
}
func TestFilterByStatus(t *testing.T) {
actual := disks.FilterByStatus("ASSIGNED")
func TestListSearchDisks_FilterByStatus(t *testing.T) {
actual := searchDisks.FilterByStatus("ASSIGNED")
if len(actual) == 0 {
t.Fatal("No elements were found")
@@ -136,22 +315,22 @@ func TestFilterByStatus(t *testing.T) {
}
}
func TestFilterByTechStatus(t *testing.T) {
actual := disks.FilterByTechStatus("ALLOCATED")
func TestListSearchDisks_FilterByTechStatus(t *testing.T) {
actual := searchDisks.FilterByTechStatus(techStatusAllocated)
if len(actual) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual {
if item.TechStatus != "ALLOCATED" {
if item.TechStatus != techStatusAllocated {
t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus)
}
}
}
func TestFilterFunc(t *testing.T) {
actual := disks.FilterFunc(func(id ItemDisk) bool {
func TestListSearchDisks_FilterFunc(t *testing.T) {
actual := searchDisks.FilterFunc(func(id ItemDisk) bool {
return len(id.PresentTo) == 2
})
@@ -164,14 +343,14 @@ func TestFilterFunc(t *testing.T) {
}
}
func TestSortByCreatedTime(t *testing.T) {
actual := disks.SortByCreatedTime(false)
func TestListSearchDisks_SortByCreatedTime(t *testing.T) {
actual := searchDisks.SortByCreatedTime(false)
if actual[0].ID != 65191 {
t.Fatal("expected ID 65191, found: ", actual[0].ID)
}
actual = disks.SortByCreatedTime(true)
actual = searchDisks.SortByCreatedTime(true)
if actual[0].ID != 65193 {
t.Fatal("expected ID 65193, found: ", actual[0].ID)
@@ -179,119 +358,122 @@ func TestSortByCreatedTime(t *testing.T) {
}
var unattachedDisks = ListDisksUnattached{
{
CKey: "",
Meta: []interface{}{
"cloudbroker",
"disk",
1,
Data: []ItemDiskUnattached{
{
CKey: "",
Meta: []interface{}{
"cloudbroker",
"disk",
1,
},
AccountID: 149,
AccountName: "test_account1",
ACL: map[string]interface{}{},
BootPartition: 0,
CreatedTime: 1681477547,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
DiskPath: "",
GID: 2002,
GUID: 22636,
ID: 22636,
ImageID: 0,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
IQN: "",
Login: "",
Milestones: 43834,
Name: "test_disk",
Order: 0,
Params: "",
ParentID: 0,
Password: "",
PCISlot: -1,
Pool: "data05",
PresentTo: []uint64{},
PurgeAttempts: 0,
PurgeTime: 0,
RealityDeviceNumber: 0,
ReferenceID: "",
ResID: "79bd3bd8-3424-48d3-963f-1870d506f169",
ResName: "volumes/volume_22636",
Role: "",
SEPID: 1,
Shareable: false,
SizeMax: 0,
SizeUsed: 0,
Snapshots: nil,
Status: "CREATED",
TechStatus: techStatusAllocated,
Type: "D",
VMID: 0,
},
AccountID: 149,
AccountName: "test_account1",
ACL: map[string]interface{}{},
BootPartition: 0,
CreatedTime: 1681477547,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
DiskPath: "",
GID: 2002,
GUID: 22636,
ID: 22636,
ImageID: 0,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
{
CKey: "",
Meta: []interface{}{
"cloudbroker",
"disk",
1,
},
AccountID: 150,
AccountName: "test_account",
ACL: map[string]interface{}{},
BootPartition: 0,
CreatedTime: 1681477558,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
DiskPath: "",
GID: 2002,
GUID: 22637,
ID: 22637,
ImageID: 0,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
IQN: "",
Login: "",
Milestones: 43834,
Name: "test_disk",
Order: 0,
Params: "",
ParentID: 0,
Password: "",
PCISlot: -1,
Pool: "data05",
PresentTo: []uint64{
27,
27,
},
PurgeAttempts: 0,
PurgeTime: 0,
RealityDeviceNumber: 0,
ReferenceID: "",
ResID: "79bd3bd8-3424-48d3-963f-1870d506f169",
ResName: "volumes/volume_22637",
Role: "",
SEPID: 1,
Shareable: false,
SizeMax: 0,
SizeUsed: 0,
Snapshots: nil,
Status: "CREATED",
TechStatus: techStatusAllocated,
Type: "B",
VMID: 0,
},
IQN: "",
Login: "",
Milestones: 43834,
Name: "test_disk",
Order: 0,
Params: "",
ParentID: 0,
Password: "",
PCISlot: -1,
Pool: "data05",
PresentTo: []uint64{},
PurgeAttempts: 0,
PurgeTime: 0,
RealityDeviceNumber: 0,
ReferenceID: "",
ResID: "79bd3bd8-3424-48d3-963f-1870d506f169",
ResName: "volumes/volume_22636",
Role: "",
SEPID: 1,
Shareable: false,
SizeMax: 0,
SizeUsed: 0,
Snapshots: nil,
Status: "CREATED",
TechStatus: "ALLOCATED",
Type: "D",
VMID: 0,
},
{
CKey: "",
Meta: []interface{}{
"cloudbroker",
"disk",
1,
},
AccountID: 150,
AccountName: "test_account",
ACL: map[string]interface{}{},
BootPartition: 0,
CreatedTime: 1681477558,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
DiskPath: "",
GID: 2002,
GUID: 22637,
ID: 22637,
ImageID: 0,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
IQN: "",
Login: "",
Milestones: 43834,
Name: "test_disk",
Order: 0,
Params: "",
ParentID: 0,
Password: "",
PCISlot: -1,
Pool: "data05",
PresentTo: []uint64{
27,
27,
},
PurgeAttempts: 0,
PurgeTime: 0,
RealityDeviceNumber: 0,
ReferenceID: "",
ResID: "79bd3bd8-3424-48d3-963f-1870d506f169",
ResName: "volumes/volume_22637",
Role: "",
SEPID: 1,
Shareable: false,
SizeMax: 0,
SizeUsed: 0,
Snapshots: nil,
Status: "CREATED",
TechStatus: "ALLOCATED",
Type: "B",
VMID: 0,
},
EntryCount: 2,
}
func TestListDisksUnattached_FilterByID(t *testing.T) {
actual := unattachedDisks.FilterByID(22636)
if len(actual) == 0 {
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
@@ -305,11 +487,11 @@ func TestListDisksUnattached_FilterByID(t *testing.T) {
func TestListDisksUnattached_FilterByName(t *testing.T) {
actual := unattachedDisks.FilterByName("test_disk")
if len(actual) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Name != "test_disk" {
t.Fatal("expected 'test_disk' name, found: ", item.Name)
}
@@ -319,11 +501,11 @@ func TestListDisksUnattached_FilterByName(t *testing.T) {
func TestListDisksUnattached_FilterByStatus(t *testing.T) {
actual := unattachedDisks.FilterByStatus("CREATED")
if len(actual) == 0 {
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Status != "CREATED" {
t.Fatal("expected 'CREATED' status, found: ", item.Status)
}
@@ -331,14 +513,14 @@ func TestListDisksUnattached_FilterByStatus(t *testing.T) {
}
func TestListDisksUnattached_FilterByTechStatus(t *testing.T) {
actual := unattachedDisks.FilterByTechStatus("ALLOCATED")
actual := unattachedDisks.FilterByTechStatus(techStatusAllocated)
if len(actual) == 0 {
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual {
if item.TechStatus != "ALLOCATED" {
for _, item := range actual.Data {
if item.TechStatus != techStatusAllocated {
t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus)
}
}
@@ -349,26 +531,26 @@ func TestListDisksUnattached_FilterFunc(t *testing.T) {
return len(id.PresentTo) == 2
})
if len(actual) == 0 {
if len(actual.Data) == 0 {
t.Fatal("No elements were found")
}
if len(actual[0].PresentTo) != 2 {
t.Fatal("expected 2 elements in PresentTo, found: ", len(actual[0].PresentTo))
if len(actual.Data[0].PresentTo) != 2 {
t.Fatal("expected 2 elements in PresentTo, found: ", len(actual.Data[0].PresentTo))
}
}
func TestListDisksUnattached_SortByCreatedTime(t *testing.T) {
actual := unattachedDisks.SortByCreatedTime(false)
if actual[0].ID != 22636 {
t.Fatal("expected ID 22636, found: ", actual[0].ID)
if actual.Data[0].ID != 22636 {
t.Fatal("expected ID 22636, found: ", actual.Data[0].ID)
}
actual = unattachedDisks.SortByCreatedTime(true)
if actual[0].ID != 22637 {
t.Fatal("expected ID 22637, found: ", actual[0].ID)
if actual.Data[0].ID != 22637 {
t.Fatal("expected ID 22637, found: ", actual.Data[0].ID)
}
}

View File

@@ -8,26 +8,17 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for get information about disk
// GetRequest struct to get information about disk
type GetRequest struct {
// ID of the disk
// Required: true
DiskID uint64 `url:"diskId" json:"diskId" validate:"required"`
}
// Get gets disk details
// Get gets disk details as a RecordDisk struct
// Notice: the devicename field is the name as it is passed to the kernel (kname in linux) for unattached disks this field has no relevant value
func (d Disks) Get(ctx context.Context, req GetRequest) (*RecordDisk, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/disks/get"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := d.GetRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -41,3 +32,19 @@ func (d Disks) Get(ctx context.Context, req GetRequest) (*RecordDisk, error) {
return &info, nil
}
// GetRaw gets disk details as an array of bytes
// Notice: the devicename field is the name as it is passed to the kernel (kname in linux) for unattached disks this field has no relevant value
func (d Disks) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/disks/get"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -6,8 +6,32 @@ import (
"net/http"
)
// Request struct for get list/list_deleted of disks
// ListRequest struct to get list of disks
type ListRequest struct {
// Find by id
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by account name
// Required: false
AccountName string `url:"accountName,omitempty" json:"accountName,omitempty"`
// Find by max size disk
// Required: false
DiskMaxSize int64 `url:"diskMaxSize,omitempty" json:"diskMaxSize,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Find by shared, true or false
// Required: false
Shared bool `url:"shared,omitempty" json:"shared,omitempty"`
// ID of the account the disks belong to
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
@@ -16,6 +40,14 @@ type ListRequest struct {
// Required: false
Type string `url:"type,omitempty" json:"type,omitempty"`
// Find by sep ID
// Required: false
SEPID uint64 `url:"sepId,omitempty" json:"sepId,omitempty"`
// Find by pool name
// Required: false
Pool string `url:"pool,omitempty" json:"pool,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -25,40 +57,27 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list the created disks belonging to an account
func (d Disks) List(ctx context.Context, req ListRequest) (ListDisks, error) {
// List gets list of the created disks belonging to an account as a ListDisks struct
func (d Disks) List(ctx context.Context, req ListRequest) (*ListDisks, error) {
res, err := d.ListRaw(ctx, req)
if err != nil {
return nil, err
}
list := ListDisks{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return &list, nil
}
// ListRaw gets list of the created disks belonging to an account as an array of bytes
func (d Disks) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
url := "/cloudapi/disks/list"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListDisks{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
}
// ListDeleted gets list the deleted disks belonging to an account
func (d Disks) ListDeleted(ctx context.Context, req ListRequest) (ListDisks, error) {
url := "/cloudapi/disks/listDeleted"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListDisks{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
return res, err
}

View File

@@ -0,0 +1,65 @@
package disks
import (
"context"
"encoding/json"
"net/http"
)
// Request struct for get list of deleted disks
type ListDeletedRequest struct {
// Find by id
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by account name
// Required: false
AccountName string `url:"accountName,omitempty" json:"accountName,omitempty"`
// Find by max size disk
// Required: false
DiskMaxSize int64 `url:"diskMaxSize,omitempty" json:"diskMaxSize,omitempty"`
// Find by shared, true or false
// Required: false
Shared bool `url:"shared,omitempty" json:"shared,omitempty"`
// ID of the account the disks belong to
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// Type of the disks
// Required: false
Type string `url:"type,omitempty" json:"type,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListDeleted gets list the deleted disks belonging to an account
func (d Disks) ListDeleted(ctx context.Context, req ListDeletedRequest) (*ListDisks, error) {
url := "/cloudapi/disks/listDeleted"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListDisks{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return &list, nil
}

View File

@@ -11,10 +11,18 @@ type ListTypesRequest struct {
// Show detailed disk types by seps
// Required: true
Detailed bool `url:"detailed" json:"detailed" validate:"required"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListTypes gets list defined disk types
func (d Disks) ListTypes(ctx context.Context, req ListTypesRequest) ([]interface{}, error) {
func (d Disks) ListTypes(ctx context.Context, req ListTypesRequest) (*ListTypes, error) {
url := "/cloudapi/disks/listTypes"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
@@ -22,12 +30,12 @@ func (d Disks) ListTypes(ctx context.Context, req ListTypesRequest) ([]interface
return nil, err
}
list := make([]interface{}, 0)
list := ListTypes{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -8,13 +8,49 @@ import (
// Request struct for get list unattached disk
type ListUnattachedRequest struct {
// Find by id
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by account name
// Required: false
AccountName string `url:"accountName,omitempty" json:"accountName,omitempty"`
// Find by max size disk
// Required: false
DiskMaxSize int64 `url:"diskMaxSize,omitempty" json:"diskMaxSize,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Type of the disks
// Required: false
Type string `url:"type,omitempty" json:"type,omitempty"`
// ID of the account
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// Find by sep ID
// Required: false
SEPID uint64 `url:"sepId,omitempty" json:"sepId,omitempty"`
// Find by pool name
// Required: false
Pool string `url:"pool,omitempty" json:"pool,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListUnattached gets list of unattached disks
func (d Disks) ListUnattached(ctx context.Context, req ListUnattachedRequest) (ListDisksUnattached, error) {
func (d Disks) ListUnattached(ctx context.Context, req ListUnattachedRequest) (*ListDisksUnattached, error) {
url := "/cloudapi/disks/listUnattached"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
@@ -29,5 +65,5 @@ func (d Disks) ListUnattached(ctx context.Context, req ListUnattachedRequest) (L
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -248,11 +248,24 @@ type ItemDiskUnattached struct {
VMID uint64 `json:"vmid"`
}
// List of disks searched
type ListSearchDisks []ItemDisk
// List of disks
type ListDisks []ItemDisk
type ListDisks struct {
// Data
Data []ItemDisk `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// List of unattached disks
type ListDisksUnattached []ItemDiskUnattached
type ListDisksUnattached struct {
Data []ItemDiskUnattached `json:"data"`
EntryCount uint64 `json:"entryCount"`
}
// Main information about snapshot
type ItemSnapshot struct {
@@ -262,6 +275,8 @@ type ItemSnapshot struct {
// Label
Label string `json:"label"`
ReferenceID string `json:"referenceId"`
// Resource ID
ResID string `json:"resId"`
@@ -427,3 +442,11 @@ type RecordDisk struct {
// Virtual machine ID
VMID uint64 `json:"vmid"`
}
type ListTypes struct {
// Data
Data []interface{} `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}

View File

@@ -21,7 +21,7 @@ type SearchRequest struct {
}
// Search search disks
func (d Disks) Search(ctx context.Context, req SearchRequest) (ListDisks, error) {
func (d Disks) Search(ctx context.Context, req SearchRequest) (ListSearchDisks, error) {
url := "/cloudapi/disks/search"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
@@ -29,7 +29,7 @@ func (d Disks) Search(ctx context.Context, req SearchRequest) (ListDisks, error)
return nil, err
}
list := ListDisks{}
list := ListSearchDisks{}
err = json.Unmarshal(res, &list)
if err != nil {

View File

@@ -12,6 +12,26 @@ import (
// - First argument -> prefix
// - Second argument -> indent
func (ld ListDisks) Serialize(params ...string) (serialization.Serialized, error) {
if ld.EntryCount == 0 {
return []byte{}, nil
}
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(ld, prefix, indent)
}
return json.Marshal(ld)
}
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (ld ListSearchDisks) Serialize(params ...string) (serialization.Serialized, error) {
if len(ld) == 0 {
return []byte{}, nil
}
@@ -48,7 +68,7 @@ func (idisk ItemDisk) Serialize(params ...string) (serialization.Serialized, err
// - First argument -> prefix
// - Second argument -> indent
func (lu ListDisksUnattached) Serialize(params ...string) (serialization.Serialized, error) {
if len(lu) == 0 {
if lu.EntryCount == 0 {
return []byte{}, nil
}

View File

@@ -6,6 +6,63 @@ import "sort"
//
// If inverse param is set to true, the order is reversed.
func (ld ListDisks) SortByCreatedTime(inverse bool) ListDisks {
if len(ld.Data) < 2 {
return ld
}
sort.Slice(ld.Data, func(i, j int) bool {
if inverse {
return ld.Data[i].CreatedTime > ld.Data[j].CreatedTime
}
return ld.Data[i].CreatedTime < ld.Data[j].CreatedTime
})
return ld
}
// SortByDestructionTime sorts ListDisks by the DestructionTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (ld ListDisks) SortByDestructionTime(inverse bool) ListDisks {
if len(ld.Data) < 2 {
return ld
}
sort.Slice(ld.Data, func(i, j int) bool {
if inverse {
return ld.Data[i].DestructionTime > ld.Data[j].DestructionTime
}
return ld.Data[i].DestructionTime < ld.Data[j].DestructionTime
})
return ld
}
// SortByDeletedTime sorts ListDisks by the DeletedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (ld ListDisks) SortByDeletedTime(inverse bool) ListDisks {
if len(ld.Data) < 2 {
return ld
}
sort.Slice(ld.Data, func(i, j int) bool {
if inverse {
return ld.Data[i].DeletedTime > ld.Data[j].DeletedTime
}
return ld.Data[i].DeletedTime < ld.Data[j].DeletedTime
})
return ld
}
// SortByCreatedTime sorts ListSearchDisks by the CreatedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (ld ListSearchDisks) SortByCreatedTime(inverse bool) ListSearchDisks {
if len(ld) < 2 {
return ld
}
@@ -21,10 +78,10 @@ func (ld ListDisks) SortByCreatedTime(inverse bool) ListDisks {
return ld
}
// SortByDestructionTime sorts ListDisks by the DestructionTime field in ascending order.
// SortByDestructionTime sorts ListSearchDisks by the DestructionTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (ld ListDisks) SortByDestructionTime(inverse bool) ListDisks {
func (ld ListSearchDisks) SortByDestructionTime(inverse bool) ListSearchDisks {
if len(ld) < 2 {
return ld
}
@@ -40,10 +97,10 @@ func (ld ListDisks) SortByDestructionTime(inverse bool) ListDisks {
return ld
}
// SortByDeletedTime sorts ListDisks by the DeletedTime field in ascending order.
// SortByDeletedTime sorts ListSearchDisks by the DeletedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (ld ListDisks) SortByDeletedTime(inverse bool) ListDisks {
func (ld ListSearchDisks) SortByDeletedTime(inverse bool) ListSearchDisks {
if len(ld) < 2 {
return ld
}
@@ -63,16 +120,16 @@ func (ld ListDisks) SortByDeletedTime(inverse bool) ListDisks {
//
// If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByCreatedTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 {
if len(lu.Data) < 2 {
return lu
}
sort.Slice(lu, func(i, j int) bool {
sort.Slice(lu.Data, func(i, j int) bool {
if inverse {
return lu[i].CreatedTime > lu[j].CreatedTime
return lu.Data[i].CreatedTime > lu.Data[j].CreatedTime
}
return lu[i].CreatedTime < lu[j].CreatedTime
return lu.Data[i].CreatedTime < lu.Data[j].CreatedTime
})
return lu
@@ -82,16 +139,16 @@ func (lu ListDisksUnattached) SortByCreatedTime(inverse bool) ListDisksUnattache
//
// If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByDestructionTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 {
if len(lu.Data) < 2 {
return lu
}
sort.Slice(lu, func(i, j int) bool {
sort.Slice(lu.Data, func(i, j int) bool {
if inverse {
return lu[i].DestructionTime > lu[j].DestructionTime
return lu.Data[i].DestructionTime > lu.Data[j].DestructionTime
}
return lu[i].DestructionTime < lu[j].DestructionTime
return lu.Data[i].DestructionTime < lu.Data[j].DestructionTime
})
return lu
@@ -101,16 +158,16 @@ func (lu ListDisksUnattached) SortByDestructionTime(inverse bool) ListDisksUnatt
//
// If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByDeletedTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 {
if len(lu.Data) < 2 {
return lu
}
sort.Slice(lu, func(i, j int) bool {
sort.Slice(lu.Data, func(i, j int) bool {
if inverse {
return lu[i].DeletedTime > lu[j].DeletedTime
return lu.Data[i].DeletedTime > lu.Data[j].DeletedTime
}
return lu[i].DeletedTime < lu[j].DeletedTime
return lu.Data[i].DeletedTime < lu.Data[j].DeletedTime
})
return lu

View File

@@ -31,21 +31,23 @@ func (lenet ListExtNets) FilterByStatus(status string) ListExtNets {
func (lenet ListExtNets) FilterFunc(predicate func(ItemExtNet) bool) ListExtNets {
var result ListExtNets
for _, item := range lenet {
for _, item := range lenet.Data {
if predicate(item) {
result = append(result, item)
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemExtNet
// If none was found, returns an empty struct.
func (lenet ListExtNets) FindOne() ItemExtNet {
if len(lenet) == 0 {
if lenet.EntryCount == 0 {
return ItemExtNet{}
}
return lenet[0]
return lenet.Data[0]
}

View File

@@ -3,23 +3,25 @@ package extnet
import "testing"
var extnets = ListExtNets{
ItemExtNet{
ID: 3,
IPCIDR: "176.118.164.0/24",
Name: "176.118.164.0/24",
Status: "ENABLED",
},
ItemExtNet{
ID: 10,
IPCIDR: "45.134.255.0/24",
Name: "45.134.255.0/24",
Status: "ENABLED",
},
ItemExtNet{
ID: 13,
IPCIDR: "88.218.249.0/24",
Name: "88.218.249.0/24",
Status: "DISABLED",
Data: []ItemExtNet{
{
ID: 3,
IPCIDR: "176.118.164.0/24",
Name: "176.118.164.0/24",
Status: "ENABLED",
},
{
ID: 10,
IPCIDR: "45.134.255.0/24",
Name: "45.134.255.0/24",
Status: "ENABLED",
},
{
ID: 13,
IPCIDR: "88.218.249.0/24",
Name: "88.218.249.0/24",
Status: "DISABLED",
},
},
}
@@ -43,11 +45,11 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) {
actual := extnets.FilterByStatus("ENABLED")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Status != "ENABLED" {
t.Fatal("expected Status 'ENABLED', found: ", item.Status)
}
@@ -59,7 +61,7 @@ func TestFilterFunc(t *testing.T) {
return ien.IPCIDR == ien.Name
})
if len(actual) != 3 {
t.Fatal("expected 3 elements, found: ", len(actual))
if len(actual.Data) != 3 {
t.Fatal("expected 3 elements, found: ", len(actual.Data))
}
}

View File

@@ -8,25 +8,16 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for get detailed information about external network
// GetRequest struct to get detailed information about external network
type GetRequest struct {
// ID of external network
// Required: true
NetID uint64 `url:"net_id" json:"net_id" validate:"required"`
}
// Get gets detailed information about external network
// Get gets detailed information about external network as a RecordExtNet struct
func (e ExtNet) Get(ctx context.Context, req GetRequest) (*RecordExtNet, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/extnet/get"
res, err := e.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := e.GetRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -40,3 +31,18 @@ func (e ExtNet) Get(ctx context.Context, req GetRequest) (*RecordExtNet, error)
return &info, nil
}
// GetRaw gets detailed information about external network as an array of bytes
func (e ExtNet) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/extnet/get"
res, err := e.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -6,12 +6,36 @@ import (
"net/http"
)
// Request struct for get list external network
// ListRequest struct to get list of external network
type ListRequest struct {
// Filter by account ID
// Find by account ID
// Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
// Find by ID
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by network ip address
// Required: false
Network string `url:"network,omitempty" json:"network,omitempty"`
// Find by vlan ID
// Required: false
VLANID uint64 `url:"vlanId,omitempty" json:"vlanId,omitempty"`
// Find by vnfDevices ID
// Required: false
VNFDevID uint64 `url:"vnfDevId,omitempty" json:"vnfDevId,omitempty"`
// Find by status
// Required: false
Status string `url:"status,omitempty" json:"status,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -21,11 +45,9 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list all available external networks
func (e ExtNet) List(ctx context.Context, req ListRequest) (ListExtNets, error) {
url := "/cloudapi/extnet/list"
res, err := e.client.DecortApiCall(ctx, http.MethodPost, url, req)
// List gets list of all available external networks as a ListExtNets struct
func (e ExtNet) List(ctx context.Context, req ListRequest) (*ListExtNets, error) {
res, err := e.ListRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -37,5 +59,13 @@ func (e ExtNet) List(ctx context.Context, req ListRequest) (ListExtNets, error)
return nil, err
}
return list, nil
return &list, nil
}
// ListRaw gets list of all available external networks as an array of bytes
func (e ExtNet) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
url := "/cloudapi/extnet/list"
res, err := e.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -13,10 +13,26 @@ type ListComputesRequest struct {
// Filter by account ID
// Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Find by rg ID
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by compute ID
// Required: false
ComputeID uint64 `url:"computeId,omitempty" json:"computeId,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// ListComputes gets computes from account with extnets
func (e ExtNet) ListComputes(ctx context.Context, req ListComputesRequest) (ListExtNetComputes, error) {
func (e ExtNet) ListComputes(ctx context.Context, req ListComputesRequest) (*ListExtNetComputes, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +54,5 @@ func (e ExtNet) ListComputes(ctx context.Context, req ListComputesRequest) (List
return nil, err
}
return list, nil
return &list, nil
}

View File

@@ -25,7 +25,11 @@ type ItemExtNetExtend struct {
}
// List of information about external network
type ListExtNets []ItemExtNet
type ListExtNets struct {
Data []ItemExtNet `json:"data"`
EntryCount uint64 `json:"entryCount"`
}
// List of extend information about external network
type ListExtNetExtends []ItemExtNetExtend
@@ -55,7 +59,13 @@ type ItemExtNetCompute struct {
}
// List of information about computes with external network
type ListExtNetComputes []ItemExtNetCompute
type ListExtNetComputes struct {
// Data
Data []ItemExtNetCompute `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// QOS
type QOS struct {
@@ -114,6 +124,12 @@ type Excluded struct {
// ClientType
ClientType string `json:"clientType"`
// Domain name
DomainName string `json:"domainname"`
// Host name
HostName string `json:"hostname"`
// IP
IP string `json:"ip"`
@@ -135,11 +151,8 @@ type RecordExtNet struct {
// Meta
Meta []interface{} `json:"_meta"`
// CheckIPs
CheckIPs []string `json:"checkIPs"`
// CheckIps
CheckIps []string `json:"checkIps"`
CheckIPs []string `json:"checkIps"`
// Default
Default bool `json:"default"`

View File

@@ -12,7 +12,7 @@ import (
// - First argument -> prefix
// - Second argument -> indent
func (lenet ListExtNets) Serialize(params ...string) (serialization.Serialized, error) {
if len(lenet) == 0 {
if lenet.EntryCount == 0 {
return []byte{}, nil
}

View File

@@ -45,7 +45,7 @@ type CreateRequest struct {
}
// Create method will create a new FLIPGorup in the specified Account
func (f FLIPGroup) Create(ctx context.Context, req CreateRequest) (*RecordFLIPGroup, error) {
func (f FLIPGroup) Create(ctx context.Context, req CreateRequest) (*RecordFLIPGroupCreated, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -60,7 +60,7 @@ func (f FLIPGroup) Create(ctx context.Context, req CreateRequest) (*RecordFLIPGr
return nil, err
}
info := RecordFLIPGroup{}
info := RecordFLIPGroupCreated{}
err = json.Unmarshal(res, &info)
if err != nil {

View File

@@ -1,96 +0,0 @@
package flipgroup
// FilterByID returns ListFLIPGroups with specified ID.
func (lfg ListFLIPGroups) FilterByID(id uint64) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.ID == id
}
return lfg.FilterFunc(predicate)
}
// FilterByAccountID returns ListFLIPGroups with specified AccountID.
func (lfg ListFLIPGroups) FilterByAccountID(accountID uint64) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.AccountID == accountID
}
return lfg.FilterFunc(predicate)
}
// FilterByRGID returns ListFLIPGroups with specified RGID.
func (lfg ListFLIPGroups) FilterByRGID(rgID uint64) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.RGID == rgID
}
return lfg.FilterFunc(predicate)
}
// FilterByName returns ListFLIPGroups with specified Name.
func (lfg ListFLIPGroups) FilterByName(name string) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.Name == name
}
return lfg.FilterFunc(predicate)
}
// FilterByStatus returns ListFLIPGroups with specified Status.
func (lfg ListFLIPGroups) FilterByStatus(status string) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.Status == status
}
return lfg.FilterFunc(predicate)
}
// FilterByCreatedBy returns ListFLIPGroups created by specified user.
func (lfg ListFLIPGroups) FilterByCreatedBy(createdBy string) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.CreatedBy == createdBy
}
return lfg.FilterFunc(predicate)
}
// FilterByUpdatedBy returns ListFLIPGroups updated by specified user.
func (lfg ListFLIPGroups) FilterByUpdatedBy(updatedBy string) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.UpdatedBy == updatedBy
}
return lfg.FilterFunc(predicate)
}
// FilterByDeletedBy returns ListFLIPGroups deleted by specified user.
func (lfg ListFLIPGroups) FilterByDeletedBy(deletedBy string) ListFLIPGroups {
predicate := func(ifg ItemFLIPGroup) bool {
return ifg.DeletedBy == deletedBy
}
return lfg.FilterFunc(predicate)
}
// FilterFunc allows filtering ListFLIPGroups based on a user-specified predicate.
func (lfg ListFLIPGroups) FilterFunc(predicate func(ItemFLIPGroup) bool) ListFLIPGroups {
var result ListFLIPGroups
for _, item := range lfg {
if predicate(item) {
result = append(result, item)
}
}
return result
}
// FindOne returns first found ItemFLIPGroup
// If none was found, returns an empty struct.
func (lfg ListFLIPGroups) FindOne() ItemFLIPGroup {
if len(lfg) == 0 {
return ItemFLIPGroup{}
}
return lfg[0]
}

View File

@@ -1,189 +0,0 @@
package flipgroup
import "testing"
var flipgroups = ListFLIPGroups{
{
AccountID: 1,
AccountName: "std_1",
ClientIDs: []uint64{
1,
},
ClientNames: []string{
"compute_1",
},
ClientType: "compute",
ConnID: 1,
ConnType: "",
CreatedBy: "sample_user_1@decs3o",
CreatedTime: 1234456789,
DefaultGW: "",
DeletedBy: "",
DeletedTime: 0,
Description: "",
GID: 212,
GUID: 1,
ID: 1,
IP: "",
Milestones: 999001,
Name: "flipgroup_1",
NetID: 111,
NetType: "EXTNET",
Network: "",
RGID: 7971,
RGName: "rg_1",
Status: "CREATED",
UpdatedBy: "",
UpdatedTime: 0,
},
{
AccountID: 2,
AccountName: "std_2",
ClientIDs: []uint64{
2,
},
ClientNames: []string{
"compute_2",
},
ClientType: "compute",
ConnID: 2,
ConnType: "",
CreatedBy: "sample_user_1@decs3o",
CreatedTime: 1234456830,
DefaultGW: "",
DeletedBy: "sample_user_1@decs3o",
DeletedTime: 1234456879,
Description: "",
GID: 212,
GUID: 2,
ID: 2,
IP: "",
Milestones: 999002,
Name: "flipgroup_2",
NetID: 222,
NetType: "EXTNET",
Network: "",
RGID: 7972,
RGName: "rg_2",
Status: "DESTROYED",
UpdatedBy: "",
UpdatedTime: 0,
},
{
AccountID: 3,
AccountName: "std_3",
ClientIDs: []uint64{
3,
},
ClientNames: []string{
"compute_3",
},
ClientType: "compute",
ConnID: 3,
ConnType: "",
CreatedBy: "sample_user_2@decs3o",
CreatedTime: 1234456866,
DefaultGW: "",
DeletedBy: "",
DeletedTime: 0,
Description: "",
GID: 212,
GUID: 3,
ID: 3,
IP: "",
Milestones: 999003,
Name: "flipgroup_3",
NetID: 223,
NetType: "EXTNET",
Network: "",
RGID: 7973,
RGName: "rg_3",
Status: "CREATED",
UpdatedBy: "",
UpdatedTime: 0,
},
}
func TestFilterByID(t *testing.T) {
actual := flipgroups.FilterByID(3).FindOne()
if actual.ID != 3 {
t.Fatal("expected ID 3, found: ", actual.ID)
}
}
func TestFilterByAccountID(t *testing.T) {
actual := flipgroups.FilterByAccountID(1).FindOne()
if actual.AccountID != 1 {
t.Fatal("expected AccountID 1, found: ", actual.AccountID)
}
}
func TestFilterByRGID(t *testing.T) {
actual := flipgroups.FilterByRGID(7972).FindOne()
if actual.RGID != 7972 {
t.Fatal("expected RGID 7972, found: ", actual.RGID)
}
}
func TestFilterByName(t *testing.T) {
actual := flipgroups.FilterByName("flipgroup_1").FindOne()
if actual.Name != "flipgroup_1" {
t.Fatal("expected Name 'flipgroup_1', found: ", actual.Name)
}
}
func TestFilterByStatus(t *testing.T) {
actual := flipgroups.FilterByStatus("CREATED")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
}
for _, item := range actual {
if item.Status != "CREATED" {
t.Fatal("expected Status 'CREATED', found: ", item.Status)
}
}
}
func TestFilterByCreatedBy(t *testing.T) {
actual := flipgroups.FilterByCreatedBy("sample_user_1@decs3o")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
}
for _, item := range actual {
if item.CreatedBy != "sample_user_1@decs3o" {
t.Fatal("expected CreatedBy 'sample_user_1@decs3o', found: ", item.CreatedBy)
}
}
}
func TestFilterFunc(t *testing.T) {
actual := flipgroups.FilterFunc(func(ifg ItemFLIPGroup) bool {
return ifg.NetType == "EXTNET"
})
if len(actual) != 3 {
t.Fatal("expected 3 found, actual: ", len(actual))
}
for _, item := range actual {
if item.NetType != "EXTNET" {
t.Fatal("expected NetType 'EXTNET', found: ", item.NetType)
}
}
}
func TestSortByCreatedTime(t *testing.T) {
actual := flipgroups.SortByCreatedTime(false)
if actual[0].CreatedTime != 1234456789 || actual[2].CreatedTime != 1234456866 {
t.Fatal("expected ascending order, found descending")
}
}

View File

@@ -8,15 +8,32 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Request struct for get information about FLIPGroup
// GetRequest struct to get information about FLIPGroup
type GetRequest struct {
// FLIPGroup ID
// Required: true
FLIPGroupID uint64 `url:"flipgroupId" json:"flipgroupId" validate:"required"`
}
// Get gets details of the specified Floating IP group
func (f FLIPGroup) Get(ctx context.Context, req GetRequest) (*ItemFLIPGroup, error) {
// Get gets details of the specified Floating IP group as a RecordFLIPGroup struct
func (f FLIPGroup) Get(ctx context.Context, req GetRequest) (*RecordFLIPGroup, error) {
res, err := f.GetRaw(ctx, req)
if err != nil {
return nil, err
}
info := RecordFLIPGroup{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}
// GetRaw gets details of the specified Floating IP group as an array of bytes
func (f FLIPGroup) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
@@ -27,16 +44,5 @@ func (f FLIPGroup) Get(ctx context.Context, req GetRequest) (*ItemFLIPGroup, err
url := "/cloudapi/flipgroup/get"
res, err := f.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
info := ItemFLIPGroup{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
return res, err
}

View File

@@ -6,8 +6,36 @@ import (
"net/http"
)
// Request struct for get list FLIPGroup available to the current user
// ListRequest struct to get list of FLIPGroup available to the current user
type ListRequest struct {
// Find by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Find by vinsId
// Required: false
VINSID uint64 `url:"vinsId,omitempty" json:"vinsId,omitempty"`
// Find by VINS name
// Required: false
VINSName string `url:"vinsName,omitempty" json:"vinsName,omitempty"`
// Find by extnetId
// Required: false
ExtNetID uint64 `url:"extnetId,omitempty" json:"extnetId,omitempty"`
// Find by IP
// Required: false
ByIP string `url:"byIp,omitempty" json:"byIp,omitempty"`
// Find by resource group ID
// Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"`
// Find by id
// Required: false
ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -17,11 +45,9 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list FLIPGroup managed cluster instances available to the current user
func (f FLIPGroup) List(ctx context.Context, req ListRequest) (ListFLIPGroups, error) {
url := "/cloudapi/flipgroup/list"
res, err := f.client.DecortApiCall(ctx, http.MethodPost, url, req)
// List gets list of FLIPGroup managed cluster instances available to the current user as a ListFLIPGroups struct
func (f FLIPGroup) List(ctx context.Context, req ListRequest) (*ListFLIPGroups, error) {
res, err := f.ListRaw(ctx, req)
if err != nil {
return nil, err
}
@@ -33,5 +59,13 @@ func (f FLIPGroup) List(ctx context.Context, req ListRequest) (ListFLIPGroups, e
return nil, err
}
return list, nil
return &list, nil
}
// ListRaw gets list of FLIPGroup managed cluster instances available to the current user as an array of bytes
func (f FLIPGroup) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
url := "/cloudapi/flipgroup/list"
res, err := f.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -1,7 +1,7 @@
package flipgroup
// Main information about FLIPGroup
type RecordFLIPGroup struct {
type RecordFLIPGroupCreated struct {
// Default GW
DefaultGW string `json:"defaultGW"`
@@ -18,8 +18,7 @@ type RecordFLIPGroup struct {
NetMask uint64 `json:"netmask"`
}
// Detailed information about FLIPGroup
type ItemFLIPGroup struct {
type RecordFLIPGroup struct {
// Account ID
AccountID uint64 `json:"accountId"`
@@ -29,7 +28,7 @@ type ItemFLIPGroup struct {
// List of client IDs
ClientIDs []uint64 `json:"clientIds"`
// Client names list
// Client names
ClientNames []string `json:"clientNames"`
// Client type
@@ -102,5 +101,69 @@ type ItemFLIPGroup struct {
UpdatedTime uint64 `json:"updatedTime"`
}
// Detailed information about FLIPGroup
type ItemFLIPGroup struct {
// CKey
CKey string `json:"_ckey"`
// Meta
Meta []interface{} `json:"_meta"`
// Account ID
AccountID uint64 `json:"accountId"`
// List of client IDs
ClientIDs []uint64 `json:"clientIds"`
// Client type
ClientType string `json:"clientType"`
// Connection ID
ConnID uint64 `json:"connId"`
// Connection type
ConnType string `json:"connType"`
// Default GW
DefaultGW string `json:"defaultGW"`
// Description
Description string `json:"desc"`
// Grid ID
GID uint64 `json:"gid"`
// GUID
GUID uint64 `json:"guid"`
// ID
ID uint64 `json:"id"`
// IP
IP string `json:"ip"`
// Milestones
Milestones uint64 `json:"milestones"`
// Name
Name string `json:"name"`
// Network ID
NetID uint64 `json:"netId"`
// Network type
NetType string `json:"netType"`
// NetMask
NetMask uint64 `json:"netmask"`
// Status
Status string `json:"status"`
}
// List of FLIPGroup
type ListFLIPGroups []ItemFLIPGroup
type ListFLIPGroups struct {
Data []ItemFLIPGroup `json:"data"`
EntryCount uint64 `json:"entryCount"`
}

View File

@@ -12,7 +12,7 @@ import (
// - First argument -> prefix
// - Second argument -> indent
func (lfg ListFLIPGroups) Serialize(params ...string) (serialization.Serialized, error) {
if len(lfg) == 0 {
if len(lfg.Data) == 0 {
return []byte{}, nil
}

View File

@@ -1,60 +0,0 @@
package flipgroup
import "sort"
// SortByCreatedTime sorts ListFLIPGroups by the CreatedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (lfg ListFLIPGroups) SortByCreatedTime(inverse bool) ListFLIPGroups {
if len(lfg) < 2 {
return lfg
}
sort.Slice(lfg, func(i, j int) bool {
if inverse {
return lfg[i].CreatedTime > lfg[j].CreatedTime
}
return lfg[i].CreatedTime < lfg[j].CreatedTime
})
return lfg
}
// SortByUpdatedTime sorts ListFLIPGroups by the UpdatedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (lfg ListFLIPGroups) SortByUpdatedTime(inverse bool) ListFLIPGroups {
if len(lfg) < 2 {
return lfg
}
sort.Slice(lfg, func(i, j int) bool {
if inverse {
return lfg[i].UpdatedTime > lfg[j].UpdatedTime
}
return lfg[i].UpdatedTime < lfg[j].UpdatedTime
})
return lfg
}
// SortByDeletedTime sorts ListFLIPGroups by the DeletedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (lfg ListFLIPGroups) SortByDeletedTime(inverse bool) ListFLIPGroups {
if len(lfg) < 2 {
return lfg
}
sort.Slice(lfg, func(i, j int) bool {
if inverse {
return lfg[i].DeletedTime > lfg[j].DeletedTime
}
return lfg[i].DeletedTime < lfg[j].DeletedTime
})
return lfg
}

View File

@@ -40,21 +40,23 @@ func (li ListImages) FilterByBootType(bootType string) ListImages {
func (li ListImages) FilterFunc(predicate func(ItemImage) bool) ListImages {
var result ListImages
for _, item := range li {
for _, item := range li.Data {
if predicate(item) {
result = append(result, item)
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemImage
// If none was found, returns an empty struct.
func (li ListImages) FindOne() ItemImage {
if len(li) == 0 {
if len(li.Data) == 0 {
return ItemImage{}
}
return li[0]
return li.Data[0]
}

View File

@@ -3,66 +3,69 @@ package image
import "testing"
var images = ListImages{
ItemImage{
AccountID: 0,
Architecture: "X86_64",
BootType: "bios",
Bootable: true,
Description: "",
Drivers: []string{
"KVM_X86",
Data: []ItemImage{
{
AccountID: 0,
Architecture: "X86_64",
BootType: "bios",
Bootable: true,
Description: "",
Drivers: []string{
"KVM_X86",
},
HotResize: true,
ID: 9882,
LinkTo: 0,
Name: "u16",
Pool: "vmstor",
Size: 5,
Status: "CREATED",
Type: "linux",
Username: "",
Virtual: false,
},
HotResize: true,
ID: 9882,
LinkTo: 0,
Name: "u16",
Pool: "vmstor",
Size: 5,
Status: "CREATED",
Type: "linux",
Username: "",
Virtual: false,
},
ItemImage{
AccountID: 0,
Architecture: "X86_64",
BootType: "bois",
Bootable: true,
Description: "",
Drivers: []string{
"KVM_X86",
{
AccountID: 0,
Architecture: "X86_64",
BootType: "bois",
Bootable: true,
Description: "",
Drivers: []string{
"KVM_X86",
},
HotResize: false,
ID: 9884,
LinkTo: 0,
Name: "alpine-virt-3.17",
Pool: "vmstor",
Size: 1,
Status: "CREATED",
Type: "linux",
Username: "",
Virtual: true,
},
HotResize: false,
ID: 9884,
LinkTo: 0,
Name: "alpine-virt-3.17",
Pool: "vmstor",
Size: 1,
Status: "CREATED",
Type: "linux",
Username: "",
Virtual: true,
},
ItemImage{
AccountID: 1,
Architecture: "X86_64",
BootType: "bios",
Bootable: true,
Description: "",
Drivers: []string{
"KVM_X86",
{
AccountID: 1,
Architecture: "X86_64",
BootType: "bios",
Bootable: true,
Description: "",
Drivers: []string{
"KVM_X86",
},
HotResize: true,
ID: 9885,
LinkTo: 0,
Name: "test",
Pool: "vmstor",
Size: 4,
Status: "DESTROYED",
Type: "linux",
Username: "",
Virtual: false,
},
HotResize: true,
ID: 9885,
LinkTo: 0,
Name: "test",
Pool: "vmstor",
Size: 4,
Status: "DESTROYED",
Type: "linux",
Username: "",
Virtual: false,
},
EntryCount: 3,
}
func TestFilterByID(t *testing.T) {
@@ -84,11 +87,11 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) {
actual := images.FilterByStatus("CREATED")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.Status != "CREATED" {
t.Fatal("expected Status 'CREATED', found: ", item.Status)
}
@@ -98,11 +101,11 @@ func TestFilterByStatus(t *testing.T) {
func TestFilterByBootType(t *testing.T) {
actual := images.FilterByBootType("bios")
if len(actual) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual))
if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual.Data))
}
for _, item := range actual {
for _, item := range actual.Data {
if item.BootType != "bios" {
t.Fatal("expected BootType 'bios', found: ", item.BootType)
}
@@ -110,15 +113,15 @@ func TestFilterByBootType(t *testing.T) {
}
func TestFilterFunc(t *testing.T) {
actual := images.FilterFunc(func(ii ItemImage) bool {
return ii.Virtual == true
})
actual := images.FilterFunc(func(ii ItemImage) bool {
return ii.Virtual == true
})
if len(actual) != 1 {
t.Fatal("expected 1 found, actual: ", len(actual))
if len(actual.Data) != 1 {
t.Fatal("expected 1 found, actual: ", len(actual.Data))
}
if actual[0].Virtual != true {
if actual.Data[0].Virtual != true {
t.Fatal("expected Virtual true, found false")
}
}

Some files were not shown because too many files have changed in this diff Show More