Compare commits

...

31 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
432 changed files with 14347 additions and 4969 deletions

3
.gitignore vendored
View File

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

View File

@@ -1,4 +1,10 @@
## Version 1.4.3 ## Version 1.6.6
### Bug Fixes ## Bugfix
- Fixed possible nil-pointer reference to validator instance (in concurrent conditions). - 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
## 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.2.x Decort-SDK соответствует 3.8.5 версии платформы
- Версия 1.3.x Decort-SDK соответствует 3.8.5 версии платформы - Версия 1.3.x Decort-SDK соответствует 3.8.5 версии платформы
- Версия 1.4.x Decort-SDK соответствует 3.8.6 версии платформы - Версия 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` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов; - `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов;
- `BService` - управление группами виртуальных машин (computes); - `BService` - управление группами виртуальных машин (computes);
- `Compute` - управление виртуальными машинами (индивидуально); - `Compute` - управление виртуальными машинами (индивидуально);
- `ComputeCI` - управление конвейром для создания виртуальных машин;
- `Disks` - управление виртуальными дисками; - `Disks` - управление виртуальными дисками;
- `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ; - `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ;
- `FLIPgroup` - управление группами "плавающими" ip - адресами; - `FLIPgroup` - управление группами "плавающими" ip - адресами;
- `Image` - управление образами операционных систем; - `Image` - управление образами операционных систем;
- `K8CI` - управление конвейром для создания кластера; - `K8CI` - получение информации о конвейере для создания кластера;
- `K8S` - управление кластерами kubernetes; - `K8S` - управление кластерами kubernetes;
- `KVMPPC` - создание виртуальной машины Power PC (IBM); - `KVMPPC` - создание виртуальной машины Power PC (IBM);
- `KVMx86` - создание виртуальной машины x86; - `KVMx86` - создание виртуальной машины x86;
@@ -74,6 +75,7 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
- `Locations` - получение информации о grid площадки; - `Locations` - получение информации о grid площадки;
- `RG` - управление ресурсными группами аккаунта; - `RG` - управление ресурсными группами аккаунта;
- `Sizes` - получение информации о потребляемых ресурсах виртуальными машинами и дисками; - `Sizes` - получение информации о потребляемых ресурсах виртуальными машинами и дисками;
- `Stack` - получение информации о вычислительных узлах;
- `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера); - `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера);
- `VINS` - управление виртуальными изолированными сетями. - `VINS` - управление виртуальными изолированными сетями.
@@ -83,30 +85,39 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
Данная группа ручек позволяет выполнять следующие операции в платформе: Данная группа ручек позволяет выполнять следующие операции в платформе:
- `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов; - `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов;
- `APIAccess` - управление доступом к API и его объектам;
- `Backup` - управление резервным копированием;
- `Compute` - управление виртуальными машинами (индивидуально); - `Compute` - управление виртуальными машинами (индивидуально);
- `Disks` - управление виртуальными дисками; - `Disks` - управление виртуальными дисками;
- `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ; - `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ;
- `FLIPGroup` - управление группами с «плавающими» ip адресами;
- `Grid` - управление площадками; - `Grid` - управление площадками;
- `Group` - управление группами пользователей;
- `Image` - управление образами операционных систем; - `Image` - управление образами операционных систем;
- `K8CI` - управление конвейром для создания кластера; - `K8CI` - управление конвейром для создания кластера;
- `K8S` - управление кластерами kubernetes; - `K8S` - управление кластерами kubernetes;
- `KVMPPC` - создание виртуальной машины Power PC (IBM); - `KVMPPC` - создание виртуальной машины Power PC (IBM);
- `KVMx86` - создание виртуальной машины x86; - `KVMx86` - создание виртуальной машины x86;
- `LB` - управление балансировщиками нагрузки; - `LB` - управление балансировщиками нагрузки;
- `PCIDevice` - управление устройствами;
- `RG` - управление ресурсными группами аккаунта; - `RG` - управление ресурсными группами аккаунта;
- `SEP` - управление storage endpoint (sep); - `SEP` - управление storage endpoint (sep);
- `Stack` - получение информации о вычислительных узлах;
- `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера); - `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера);
- `User` - управление пользователями (индивидуально);
- `VGPU` - управление виртуальными графическими процессорами;
- `VINS` - управление виртуальными изолированными сетями. - `VINS` - управление виртуальными изолированными сетями.
## Работа с библиотекой ## Работа с библиотекой
Алгоритм работы с библиотекой выглядит следующим образом: Алгоритм работы с библиотекой выглядит следующим образом:
1. Настройка конфигурации клиента. 1. Выполнение одного из действий:
2. Парсинг конфигурации из файла. - настройка конфигурации клиента;
3. Создание клиента. - парсинг конфигурации из файла.
4. Создание структуры запроса. 2. Создание клиента.
5. Выполнение запроса. 3. Создание структуры запроса.
4. Выполнение запроса.
### Настройка конфигурации клиента ### Настройка конфигурации клиента
@@ -118,7 +129,8 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
| SSOURL | string | Да | URL адрес сервиса аутентификации и авторизации | | SSOURL | string | Да | URL адрес сервиса аутентификации и авторизации |
| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | | DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие |
| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | | Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата, по умолчанию - true | | Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата |
| Token | string | Нет | JWT токен | | Token | string | Нет | JWT токен |
#### Пример конфигурации клиента #### Пример конфигурации клиента
@@ -127,6 +139,7 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
import ( import (
"repository.basistech.ru/BASIS/decort-golang-sdk/config" "repository.basistech.ru/BASIS/decort-golang-sdk/config"
) )
func main(){ func main(){
// Настройка конфигурации // Настройка конфигурации
cfg := config.Config{ cfg := config.Config{
@@ -136,6 +149,8 @@ func main(){
DecortURL: "https://mr4.digitalenergy.online", DecortURL: "https://mr4.digitalenergy.online",
Retries: 5, Retries: 5,
} }
cfg.SetTimeout(5 * time.Minute)
} }
``` ```
@@ -165,6 +180,7 @@ func main() {
"ssoUrl": "https://sso.digitalenergy.online", "ssoUrl": "https://sso.digitalenergy.online",
"decortUrl": "https://mr4.digitalenergy.online", "decortUrl": "https://mr4.digitalenergy.online",
"retries": 5, "retries": 5,
"timeout": "5m",
"sslSkipVerify": false "sslSkipVerify": false
} }
``` ```
@@ -177,6 +193,7 @@ appSecret: <APP_SECRET>
ssoUrl: https://sso.digitalenergy.online ssoUrl: https://sso.digitalenergy.online
decortUrl: https://mr4.digitalenergy.online decortUrl: https://mr4.digitalenergy.online
retries: 5 retries: 5
timeout: 5m
sslSkipVerify: false sslSkipVerify: false
``` ```
@@ -204,6 +221,8 @@ func main() {
Retries: 5, Retries: 5,
} }
cfg.SetTimeout(5 * time.Minute)
// Создание клиента // Создание клиента
client := decort.New(cfg) client := decort.New(cfg)
} }
@@ -222,7 +241,6 @@ func main() {
- `pkg/cloudapi/account` - для `Account` - `pkg/cloudapi/account` - для `Account`
- `pkg/cloudapi/bservice` - для `Basic Service` - `pkg/cloudapi/bservice` - для `Basic Service`
- `pkg/cloudapi/compute` - для `Compute` - `pkg/cloudapi/compute` - для `Compute`
- `pkg/cloudapi/computeci` - для `ComputeCI`
- `pkg/cloudapi/disks` - для `Disks` - `pkg/cloudapi/disks` - для `Disks`
- `pkg/cloudapi/extnet` - для `ExtNet` - `pkg/cloudapi/extnet` - для `ExtNet`
- `pkg/cloudapi/flipgroup` - для `FLIPGroup` - `pkg/cloudapi/flipgroup` - для `FLIPGroup`
@@ -234,24 +252,33 @@ func main() {
- `pkg/cloudapi/lb` - для `LB` - `pkg/cloudapi/lb` - для `LB`
- `pkg/cloudapi/locations` - для `Locations` - `pkg/cloudapi/locations` - для `Locations`
- `pkg/cloudapi/rg` - для `RG` - `pkg/cloudapi/rg` - для `RG`
- `pkg/cloudapi/sizes` - для `RG` - `pkg/cloudapi/sizes` - для `Sizes`
- `pkg/cloudapi/stack` - для `Stack`
- `pkg/cloudapi/tasks` - для `Tasks` - `pkg/cloudapi/tasks` - для `Tasks`
- `pkg/cloudapi/vins` - для `VINS` - `pkg/cloudapi/vins` - для `VINS`
- **cloudbroker**: - **cloudbroker**:
- `pkg/cloudbroker/account` - для `Account` - `pkg/cloudbroker/account` - для `Account`
- `pkg/cloudbroker/apiaccess` - для `APIAccess`
- `pkg/cloudbroker/backup` - для `Backup`
- `pkg/cloudbroker/compute` - для `Compute` - `pkg/cloudbroker/compute` - для `Compute`
- `pkg/cloudbroker/disks` - для `Disks` - `pkg/cloudbroker/disks` - для `Disks`
- `pkg/cloudbroker/extnet` - для `ExtNet` - `pkg/cloudbroker/extnet` - для `ExtNet`
- `pkg/cloudbroker/flipgroup` - для `FLIPGroup`
- `pkg/cloudbroker/grid` - для `Grid` - `pkg/cloudbroker/grid` - для `Grid`
- `pkg/cloudbroker/group` - для `Group`
- `pkg/cloudbroker/image` - для `Image` - `pkg/cloudbroker/image` - для `Image`
- `pkg/cloudbroker/k8ci` - для `K8CI` - `pkg/cloudbroker/k8ci` - для `K8CI`
- `pkg/cloudbroker/k8s` - для `K8S` - `pkg/cloudbroker/k8s` - для `K8S`
- `pkg/cloudbroker/kvmppc` - для `KVMPPC` - `pkg/cloudbroker/kvmppc` - для `KVMPPC`
- `pkg/cloudbroker/kvmx86` - для `KVMX86` - `pkg/cloudbroker/kvmx86` - для `KVMX86`
- `pkg/cloudbroker/lb` - для `LB` - `pkg/cloudbroker/lb` - для `LB`
- `pkg/cloudbroker/pcidevice` - для `PCIDevice`
- `pkg/cloudbroker/rg` - для `RG` - `pkg/cloudbroker/rg` - для `RG`
- `pkg/cloudbroker/sep` - для `SEP` - `pkg/cloudbroker/sep` - для `SEP`
- `pkg/cloudbroker/stack` - для `Stack`
- `pkg/cloudbroker/tasks` - для `Tasks` - `pkg/cloudbroker/tasks` - для `Tasks`
- `pkg/cloudbroker/user` - для `User`
- `pkg/cloudbroker/vgpu` - для `VGPU`
- `pkg/cloudbroker/vins` - для `VINS` - `pkg/cloudbroker/vins` - для `VINS`
Все поля структуры имеют описание, в которых содержится: Все поля структуры имеют описание, в которых содержится:
@@ -388,7 +415,7 @@ func main() {
Чтобы выполнить запрос, необходимо: Чтобы выполнить запрос, необходимо:
1. Вызвать у клиента метод, отвечаеющий за определение группы API для взаимодействия, это может быть `.CloudAPI()`, либо `.CloudBroker()`. Данные методы возвращаеют соответствующие структуры, с помощью которых можно соверать запросы. 1. Вызвать у клиента метод, отвечаеющий за определение группы API для взаимодействия, это может быть `.CloudAPI()`, либо `.CloudBroker()`. Данные методы возвращаеют соответствующие структуры, с помощью которых можно совершать запросы.
2. Вызвать у возвращенной структуры метод, определяющий группу ручек для взаимодействия. 2. Вызвать у возвращенной структуры метод, определяющий группу ручек для взаимодействия.
Доступные методы для `.CloudAPI()`: Доступные методы для `.CloudAPI()`:
@@ -396,7 +423,6 @@ func main() {
- `.Account()` - для работы с `Account` - `.Account()` - для работы с `Account`
- `.BService()` - для работы с `BService` - `.BService()` - для работы с `BService`
- `.Compute()` - для работы с `Compute` - `.Compute()` - для работы с `Compute`
- `.ComputeCI()` - для работы с `ComputeCI`
- `.Disks()` - для работы с `Disks` - `.Disks()` - для работы с `Disks`
- `.ExtNet()` - для работы с `ExtNet` - `.ExtNet()` - для работы с `ExtNet`
- `.FLIPgroup()` - для работы с `FLIPGroup` - `.FLIPgroup()` - для работы с `FLIPGroup`
@@ -409,37 +435,46 @@ func main() {
- `.Locations()` - для работы с `Locations` - `.Locations()` - для работы с `Locations`
- `.RG()` - для работы с `RG` - `.RG()` - для работы с `RG`
- `.Sizes()` - для работы с `Sizes` - `.Sizes()` - для работы с `Sizes`
- `.Stack()` - для работы с `Stack`
- `.Tasks()` - для работы с `Tasks` - `.Tasks()` - для работы с `Tasks`
- `.VINS()` - для работы с `VINS` - `.VINS()` - для работы с `VINS`
Доступные методы для `.CloudBroker()`: Доступные методы для `.CloudBroker()`:
- `.Account()` - для работы с `Account` - `.Account()` - для работы с `Account`
- `.APIAccess()` - для работы с `APIAccess`
- `.Backup()` - для работы с `Backup`
- `.Compute()` - для работы с `Compute` - `.Compute()` - для работы с `Compute`
- `.Disks()` - для работы с `Disks` - `.Disks()` - для работы с `Disks`
- `.ExtNet()` - для работы с `ExtNet` - `.ExtNet()` - для работы с `ExtNet`
- `.FLIPGroup()` - для работы с `FLIPGroup`
- `.Grid()` - для работы с `Grid` - `.Grid()` - для работы с `Grid`
- `.Group()` - для работы с `Group`
- `.Image()` - для работы с `Image` - `.Image()` - для работы с `Image`
- `.K8CI()` - для работы с `K8CI` - `.K8CI()` - для работы с `K8CI`
- `.K8S()` - для работы с `K8S` - `.K8S()` - для работы с `K8S`
- `.KVMPPC()` - для работы с `KVMPPC` - `.KVMPPC()` - для работы с `KVMPPC`
- `.KVMx86()` - для работы с `KVMX86` - `.KVMx86()` - для работы с `KVMX86`
- `.LB()` - для работы с `LB` - `.LB()` - для работы с `LB`
- `.PCIDevice()` - для работы с `PCIDevice`
- `.RG()` - для работы с `RG` - `.RG()` - для работы с `RG`
- `.SEP()` - для работы с `SEP` - `.SEP()` - для работы с `SEP`
- `.Stack()` - для работы с `Stack`
- `.Tasks()` - для работы с `Tasks` - `.Tasks()` - для работы с `Tasks`
- `.User()` - для работы с `User`
- `.VGPU()` - для работы с `VGPU`
- `.VINS()` - для работы с `VINS` - `.VINS()` - для работы с `VINS`
3. Вызвать метод, отвечающий за выполнение запроса и передать в него: 3. Вызвать метод, отвечающий за выполнение запроса и передать в него:
- контекст; - контекст;
- структуру запроса. - структуру запроса.
У кождой группы ручек API имеются свои доступные методы, которые определяются платформой. У каждой группы ручек API имеются свои доступные методы, которые определяются платформой.
4. Обработать результат и ошибки. 4. Обработать результат и ошибки.
Т.к. все вызовы методов идут последовательно, можно их объеденить в конвейер: Т.к. все вызовы методов идут последовательно, можно их объеденить в конвейер:
Общий вид вонвейра будет выглядеть так: Общий вид конвейера будет выглядеть так:
```go ```go
client.<API>.<группа>.<метод> 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()`, например: Для каждого `ListRequest` в SDK есть группа функций для фильтрации ответа платформы. Для того чтобы произвести фильтрацию по заданным полям, достаточно описать анонимную функцию (предикат) в `.FilterFunc()`, например:
@@ -648,6 +733,8 @@ func main() {
Retries: 5, Retries: 5,
} }
cfg.SetTimeout(5 * time.Minute)
// Создание клиента // Создание клиента
client := decort.New(cfg) client := decort.New(cfg)
@@ -693,7 +780,8 @@ func main() {
| Password | string | Да | пароль legacy пользователя | | Password | string | Да | пароль legacy пользователя |
| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | | DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие |
| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | | Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата, по умолчанию - true | | Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений |
| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата |
| Token | string | Нет | JWT токен | | Token | string | Нет | JWT токен |
#### Пример конфигурации legacy клиента #### Пример конфигурации legacy клиента
@@ -711,6 +799,8 @@ func main(){
DecortURL: "https://mr4.digitalenergy.online", DecortURL: "https://mr4.digitalenergy.online",
Retries: 5, Retries: 5,
} }
legacyCfg.SetTimeout(5 * time.Minute)
} }
``` ```
@@ -739,6 +829,7 @@ func main() {
"password": "<PASSWORD>", "password": "<PASSWORD>",
"decortUrl": "https://mr4.digitalenergy.online", "decortUrl": "https://mr4.digitalenergy.online",
"retries": 5, "retries": 5,
"timeout": "5m",
"sslSkipVerify": true "sslSkipVerify": true
} }
``` ```
@@ -749,6 +840,7 @@ username: <USERNAME>
password: <PASSWORD> password: <PASSWORD>
decortUrl: https://mr4.digitalenergy.online decortUrl: https://mr4.digitalenergy.online
retries: 5 retries: 5
timeout: 5m
sslSkipVerify: true sslSkipVerify: true
``` ```
### Создание legacy клиента ### Создание legacy клиента
@@ -774,6 +866,8 @@ func main() {
Retries: 5, Retries: 5,
} }
legacyCfg.SetTimeout(5 * time.Minute)
// Создание клиента // Создание клиента
legacyClient := decort.NewLegacy(cfg) legacyClient := decort.NewLegacy(cfg)
} }

403
client.go
View File

@@ -1,24 +1,34 @@
package decortsdk package decortsdk
import ( import (
"bytes"
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt"
"io" "io"
"mime/multipart"
"net/http" "net/http"
"strconv"
"strings" "strings"
"sync"
"time"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" "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" "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" "github.com/google/go-querystring/query"
"repository.basistech.ru/BASIS/decort-golang-sdk/config" "repository.basistech.ru/BASIS/decort-golang-sdk/config"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/client"
) )
// HTTP-client for platform // HTTP-client for platform
type DecortClient struct { type DecortClient struct {
decortURL string decortURL string
client *http.Client client *http.Client
cfg config.Config
expiryTime time.Time
mutex *sync.Mutex
} }
// Сlient builder // Сlient builder
@@ -27,9 +37,25 @@ func New(cfg config.Config) *DecortClient {
cfg.Retries = 5 cfg.Retries = 5
} }
var expiryTime time.Time
if cfg.Token != "" {
expiryTime = time.Now().AddDate(0, 0, 1)
}
return &DecortClient{ return &DecortClient{
decortURL: cfg.DecortURL, 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 // DecortApiCall method for sending requests to the platform
func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { 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) values, err := query.Values(params)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -56,7 +359,11 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@@ -73,3 +380,91 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p
return respBytes, nil 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 ( import (
"encoding/json" "encoding/json"
"os" "os"
"time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
@@ -41,9 +42,18 @@ type Config struct {
// Required: false // Required: false
Retries uint64 `json:"retries" yaml:"retries"` Retries uint64 `json:"retries" yaml:"retries"`
// Skip verify, true by default // Skip verify
// Required: false // Required: false
SSLSkipVerify bool `json:"sslSkipVerify" yaml:"sslSkipVerify"` 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. // ParseConfigJSON parses Config from specified JSON-formatted file.

View File

@@ -3,6 +3,7 @@ package config
import ( import (
"encoding/json" "encoding/json"
"os" "os"
"time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
@@ -35,9 +36,18 @@ type LegacyConfig struct {
// Required: false // Required: false
Retries uint64 `json:"retries" yaml:"retries"` Retries uint64 `json:"retries" yaml:"retries"`
// Skip verify, true by default // Skip verify
// Required: false // Required: false
SSLSkipVerify bool `json:"sslSkipVerify" yaml:"sslSkipVerify"` 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. // 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 package validators
import ( import (
"github.com/go-playground/validator/v10"
"regexp" "regexp"
"strings" "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. // protoValidator is used to validate Proto fields.
func protoValidator(fe validator.FieldLevel) bool { func protoValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String() fieldValue := fe.Field().String()
@@ -256,3 +264,11 @@ func strictLooseValidator(fe validator.FieldLevel) bool {
return StringInSlice(fieldValue, strictLooseValues) 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(), fe.Field(),
joinValues(computeDataDisksValues)) joinValues(computeDataDisksValues))
case "computeDriver":
return fmt.Sprintf("%s %s must be one of the following: %s",
prefix,
fe.Field(),
joinValues(computeDriverValues))
// Disk Validators // Disk Validators
case "diskType": case "diskType":
return fmt.Sprintf("%s %s must be one of the following: %s", return fmt.Sprintf("%s %s must be one of the following: %s",
@@ -121,6 +127,12 @@ func errorMessage(fe validator.FieldError) string {
fe.Field(), fe.Field(),
joinValues(flipgroupClientTypeValues)) joinValues(flipgroupClientTypeValues))
// k8s Validators
case "workerGroupName":
return fmt.Sprintf("%s %s must be more 3 symbol",
prefix,
fe.Field())
// KVM_X86/KVM_PPC Validators // KVM_X86/KVM_PPC Validators
case "kvmNetType": case "kvmNetType":
return fmt.Sprintf("%s %s must be one of the following: %s", return fmt.Sprintf("%s %s must be one of the following: %s",

View File

@@ -30,6 +30,11 @@ func registerAllValidators(validate *validator.Validate) error {
return err return err
} }
err = validate.RegisterValidation("computeDriver", computeDriverValidator)
if err != nil {
return err
}
err = validate.RegisterValidation("accessType", accessTypeValidator) err = validate.RegisterValidation("accessType", accessTypeValidator)
if err != nil { if err != nil {
return err return err
@@ -175,5 +180,10 @@ func registerAllValidators(validate *validator.Validate) error {
return err return err
} }
err = validate.RegisterValidation("workerGroupName", workerGroupNameValidator)
if err != nil {
return err
}
return nil return nil
} }

View File

@@ -6,7 +6,7 @@ var (
resTypesValues = []string{"compute", "vins", "k8s", "openshift", "lb", "flipgroup"} resTypesValues = []string{"compute", "vins", "k8s", "openshift", "lb", "flipgroup"}
protoValues = []string{"tcp", "udp"} 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"} bserviceModeValues = []string{"ABSOLUTE", "RELATIVE"}
@@ -17,6 +17,7 @@ var (
computeNetTypeValues = []string{"EXTNET", "VINS"} computeNetTypeValues = []string{"EXTNET", "VINS"}
computeOrderValues = []string{"cdrom", "network", "hd"} computeOrderValues = []string{"cdrom", "network", "hd"}
computeDataDisksValues = []string{"KEEP", "DETACH", "DESTROY"} computeDataDisksValues = []string{"KEEP", "DETACH", "DESTROY"}
computeDriverValues = []string{"KVM_X86", "SVA_KVM_X86"}
diskTypeValues = []string{"B", "T", "D"} diskTypeValues = []string{"B", "T", "D"}

View File

@@ -1,16 +1,24 @@
package decortsdk package decortsdk
import ( import (
"bytes"
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt"
"io" "io"
"mime/multipart"
"net/http" "net/http"
"net/url"
"strconv"
"strings" "strings"
"sync"
"time"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
"repository.basistech.ru/BASIS/decort-golang-sdk/config" "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"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
) )
@@ -18,6 +26,9 @@ import (
type LegacyDecortClient struct { type LegacyDecortClient struct {
decortURL string decortURL string
client *http.Client client *http.Client
cfg config.LegacyConfig
expiryTime time.Time
mutex *sync.Mutex
} }
// Legacy client builder // Legacy client builder
@@ -26,9 +37,25 @@ func NewLegacy(cfg config.LegacyConfig) *LegacyDecortClient {
cfg.Retries = 5 cfg.Retries = 5
} }
var expiryTime time.Time
if cfg.Token != "" {
expiryTime = time.Now().AddDate(0, 0, 1)
}
return &LegacyDecortClient{ return &LegacyDecortClient{
decortURL: cfg.DecortURL, 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 // DecortApiCall method for sending requests to the platform
func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { 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) values, err := query.Values(params)
if err != nil { if err != nil {
return nil, err 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) req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+"/restmachine"+url, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp, err := ldc.client.Do(req) resp, err := ldc.do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -72,3 +246,88 @@ func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url st
return respBytes, nil 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 { func (la ListAccounts) FilterFunc(predicate func(ItemAccount) bool) ListAccounts {
var result ListAccounts var result ListAccounts
for _, acc := range la { for _, acc := range la.Data {
if predicate(acc) { if predicate(acc) {
result = append(result, acc) result.Data = append(result.Data, acc)
} }
} }
result.EntryCount = uint64(len(result.Data))
return result return result
} }
// FindOne returns first found ItemAccount. // FindOne returns first found ItemAccount.
// If none was found, returns an empty struct. // If none was found, returns an empty struct.
func (la ListAccounts) FindOne() ItemAccount { func (la ListAccounts) FindOne() ItemAccount {
if len(la) == 0 { if len(la.Data) == 0 {
return ItemAccount{} return ItemAccount{}
} }
return la[0] return la.Data[0]
} }

View File

@@ -5,7 +5,8 @@ import (
) )
var accounts = ListAccounts{ var accounts = ListAccounts{
ItemAccount{ Data: []ItemAccount{
{
ACL: []RecordACL{ ACL: []RecordACL{
{ {
IsExplicit: true, IsExplicit: true,
@@ -23,7 +24,7 @@ var accounts = ListAccounts{
Status: "CONFIRMED", Status: "CONFIRMED",
UpdatedTime: 1676645275, UpdatedTime: 1676645275,
}, },
ItemAccount{ {
ACL: []RecordACL{ ACL: []RecordACL{
{ {
IsExplicit: true, IsExplicit: true,
@@ -41,7 +42,7 @@ var accounts = ListAccounts{
Status: "CONFIRMED", Status: "CONFIRMED",
UpdatedTime: 1676645275, UpdatedTime: 1676645275,
}, },
ItemAccount{ {
ACL: []RecordACL{ ACL: []RecordACL{
{ {
IsExplicit: true, IsExplicit: true,
@@ -67,6 +68,8 @@ var accounts = ListAccounts{
Status: "DELETED", Status: "DELETED",
UpdatedTime: 1676878820, UpdatedTime: 1676878820,
}, },
},
EntryCount: 3,
} }
func TestFilterByID(t *testing.T) { func TestFilterByID(t *testing.T) {
@@ -100,11 +103,11 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) { func TestFilterByStatus(t *testing.T) {
actual := accounts.FilterByStatus("CONFIRMED") actual := accounts.FilterByStatus("CONFIRMED")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("Expected 2 elements in slice, found: ", len(actual)) t.Fatal("Expected 2 elements in slice, found: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Status != "CONFIRMED" { if item.Status != "CONFIRMED" {
t.Fatal("expected CONFIRMED, found: ", item.Status) t.Fatal("expected CONFIRMED, found: ", item.Status)
} }
@@ -116,7 +119,7 @@ func TestFilterFunc(t *testing.T) {
return ia.DeletedTime == 0 return ia.DeletedTime == 0
}) })
for _, item := range actual { for _, item := range actual.Data {
if item.DeletedTime != 0 { if item.DeletedTime != 0 {
t.Fatal("Expected DeletedTime = 0, found: ", item.DeletedTime) t.Fatal("Expected DeletedTime = 0, found: ", item.DeletedTime)
} }
@@ -126,21 +129,21 @@ func TestFilterFunc(t *testing.T) {
func TestSortingByCreatedTime(t *testing.T) { func TestSortingByCreatedTime(t *testing.T) {
actual := accounts.SortByCreatedTime(false) actual := accounts.SortByCreatedTime(false)
if actual[0].Name != "std" { if actual.Data[0].Name != "std" {
t.Fatal("Expected account std as earliest, found: ", actual[0].Name) t.Fatal("Expected account std as earliest, found: ", actual.Data[0].Name)
} }
actual = accounts.SortByCreatedTime(true) actual = accounts.SortByCreatedTime(true)
if actual[0].Name != "std_broker" { if actual.Data[0].Name != "std_broker" {
t.Fatal("Expected account std_broker as latest, found: ", actual[0].Name) t.Fatal("Expected account std_broker as latest, found: ", actual.Data[0].Name)
} }
} }
func TestFilterEmpty(t *testing.T) { func TestFilterEmpty(t *testing.T) {
actual := accounts.FilterByID(0) actual := accounts.FilterByID(0)
if len(actual) != 0 { if len(actual.Data) != 0 {
t.Fatal("Expected 0 found, actual: ", len(actual)) 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" "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 { type GetRequest struct {
// ID an account // ID an account
// Required: true // 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) { func (a Account) Get(ctx context.Context, req GetRequest) (*RecordAccount, error) {
err := validators.ValidateRequest(req) res, err := a.GetRaw(ctx, 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -41,3 +32,18 @@ func (a Account) Get(ctx context.Context, req GetRequest) (*RecordAccount, error
return &info, nil 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_M: consumed memory in MB
// - CU_C: number of cpu cores // - CU_C: number of cpu cores
// - CU_D: consumed vdisk storage in GB // - CU_D: consumed vdisk storage in GB
// - CU_DM: consumed max vdisk storage in GB
// - CU_I: number of public IPs // - CU_I: number of public IPs
func (a Account) GetConsumedAccountUnits(ctx context.Context, req GetConsumedAccountUnitsRequest) (*ResourceLimits, error) { func (a Account) GetConsumedAccountUnits(ctx context.Context, req GetConsumedAccountUnitsRequest) (*ResourceLimits, error) {
err := validators.ValidateRequest(req) err := validators.ValidateRequest(req)

View File

@@ -26,6 +26,7 @@ type GetConsumedCloudUnitsByTypeRequest struct {
// - CU_M: returns consumed memory in MB // - CU_M: returns consumed memory in MB
// - CU_C: returns number of virtual cpu cores // - CU_C: returns number of virtual cpu cores
// - CU_D: returns consumed virtual disk storage in GB // - 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_S: returns consumed primary storage (NAS) in TB
// - CU_A: returns consumed secondary storage (Archive) in TB // - CU_A: returns consumed secondary storage (Archive) in TB
// - CU_NO: returns sent/received network transfer in operator in GB // - 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_M: reserved memory in MB
// - CU_C: number of cpu cores // - CU_C: number of cpu cores
// - CU_D: reserved vdisk storage in GB // - CU_D: reserved vdisk storage in GB
// - CU_DM: reserved max vdisk storage in GB
// - CU_I: number of public IPs // - CU_I: number of public IPs
func (a Account) GetReservedAccountUnits(ctx context.Context, req GetReservedAccountUnitsRequest) (*ResourceLimits, error) { func (a Account) GetReservedAccountUnits(ctx context.Context, req GetReservedAccountUnitsRequest) (*ResourceLimits, error) {
err := validators.ValidateRequest(req) 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" "net/http"
) )
// Request struct for get list of accounts // ListRequest struct to get list of accounts
type ListRequest struct { 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 // Page number
// Required: false // Required: false
Page uint64 `url:"page" json:"page"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size // Page size
// Required: false // 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 // 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) { func (a Account) List(ctx context.Context, req ListRequest) (*ListAccounts, error) {
url := "/cloudapi/account/list" res, err := a.ListRaw(ctx, req)
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -33,5 +47,13 @@ func (a Account) List(ctx context.Context, req ListRequest) (ListAccounts, error
return nil, err 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 // ID an account
// Required: true // Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +78,5 @@ func (a Account) ListComputes(ctx context.Context, req ListComputesRequest) (Lis
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -10,15 +10,27 @@ import (
type ListDeletedRequest struct { type ListDeletedRequest struct {
// Page number // Page number
// Required: false // Required: false
Page uint64 `url:"page" json:"page"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size // Page size
// Required: false // 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 // 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" url := "/cloudapi/account/listDeleted"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) 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 nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -13,10 +13,34 @@ type ListDisksRequest struct {
// ID an account // ID an account
// Required: true // Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +62,5 @@ func (a Account) ListDisks(ctx context.Context, req ListDisksRequest) (ListDisks
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -10,13 +10,45 @@ import (
// Request struct for get list FLIPGroups // Request struct for get list FLIPGroups
type ListFLIPGroupsRequest struct { type ListFLIPGroupsRequest struct {
// ID an account // ID of the account
// Required: true // Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +70,5 @@ func (a Account) ListFLIPGroups(ctx context.Context, req ListFLIPGroupsRequest)
return nil, err 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 // ID an account
// Required: true // Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { 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 nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -16,11 +16,31 @@ type ListTemplatesRequest struct {
// Include deleted images // Include deleted images
// Required: false // 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -42,5 +62,5 @@ func (a Account) ListTemplates(ctx context.Context, req ListTemplatesRequest) (L
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -13,10 +13,34 @@ type ListVINSRequest struct {
// ID an account // ID an account
// Required: true // Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { 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 nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -32,6 +32,9 @@ type ResourceLimits struct {
// Disk size, GB // Disk size, GB
CUD float64 `json:"CU_D"` CUD float64 `json:"CU_D"`
// Max disk size, GB
CUDM float64 `json:"CU_DM"`
// Number of public IP addresses // Number of public IP addresses
CUI float64 `json:"CU_I"` CUI float64 `json:"CU_I"`
@@ -70,7 +73,11 @@ type ItemAccount struct {
} }
// List of accounts // List of accounts
type ListAccounts []ItemAccount type ListAccounts struct {
Data []ItemAccount `json:"data"`
EntryCount uint64 `json:"entryCount"`
}
// Resources used // Resources used
type Resource struct { type Resource struct {
@@ -81,7 +88,7 @@ type Resource struct {
DiskSize float64 `json:"disksize"` DiskSize float64 `json:"disksize"`
// Max disk size // Max disk size
DiskSizeMax uint64 `json:"disksizemax"` DiskSizeMax float64 `json:"disksizemax"`
// Number of External IPs // Number of External IPs
ExtIPs int64 `json:"extips"` ExtIPs int64 `json:"extips"`
@@ -105,16 +112,33 @@ type DiskUsage struct {
DiskSize float64 `json:"disksize"` DiskSize float64 `json:"disksize"`
// Disk size max // 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 // Information about resources
type Resources struct { type ItemResourceConsumption struct {
// Current information about resources // Current information about resources
Current Resource `json:"Current"` Consumed Resource `json:"consumed"`
// Reserved information about resources // 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 // Information about computes
@@ -140,9 +164,6 @@ type RecordAccount struct {
// DCLocation // DCLocation
DCLocation string `json:"DCLocation"` DCLocation string `json:"DCLocation"`
// Resources
Resources Resources `json:"Resources"`
// CKey // CKey
CKey string `json:"_ckey"` CKey string `json:"_ckey"`
@@ -286,7 +307,13 @@ type ItemCompute struct {
} }
// List of computes // List of computes
type ListComputes []ItemCompute type ListComputes struct {
// Data
Data []ItemCompute `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about disk // Main information about disk
type ItemDisk struct { type ItemDisk struct {
@@ -313,7 +340,13 @@ type ItemDisk struct {
} }
// List of disks // List of disks
type ListDisks []ItemDisk type ListDisks struct {
// Data
Data []ItemDisk `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about VINS // Main information about VINS
type ItemVINS struct { type ItemVINS struct {
@@ -370,7 +403,13 @@ type ItemVINS struct {
} }
// List of VINS // List of VINS
type ListVINS []ItemVINS type ListVINS struct {
// Data
Data []ItemVINS `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main info about audit // Main info about audit
type ItemAudit struct { type ItemAudit struct {
@@ -484,7 +523,13 @@ type ItemRG struct {
} }
// List of Resource groups // 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 // Main information about template
type ItemTemplate struct { type ItemTemplate struct {
@@ -520,7 +565,13 @@ type ItemTemplate struct {
} }
// List of templates // List of templates
type ListTemplates []ItemTemplate type ListTemplates struct {
// Data
Data []ItemTemplate `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// Main information about FLIPGroup // Main information about FLIPGroup
type ItemFLIPGroup struct { type ItemFLIPGroup struct {
@@ -589,4 +640,10 @@ type ItemFLIPGroup struct {
} }
// List of FLIPGroups // 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 // - First argument -> prefix
// - Second argument -> indent // - Second argument -> indent
func (la ListAccounts) Serialize(params ...string) (serialization.Serialized, error) { func (la ListAccounts) Serialize(params ...string) (serialization.Serialized, error) {
if len(la) == 0 { if len(la.Data) == 0 {
return []byte{}, nil return []byte{}, nil
} }

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ package bservice
import "testing" import "testing"
var bservices = ListBasicServices{ var bservices = ListBasicServices{
Data: []ItemBasicService{
{ {
AccountID: 1, AccountID: 1,
AccountName: "std_1", AccountName: "std_1",
@@ -75,6 +76,8 @@ var bservices = ListBasicServices{
UpdatedTime: 0, UpdatedTime: 0,
UserManaged: true, UserManaged: true,
}, },
},
EntryCount: 3,
} }
func TestFilterByID(t *testing.T) { func TestFilterByID(t *testing.T) {
@@ -104,11 +107,11 @@ func TestFilterByRGID(t *testing.T) {
func TestFilterByStatus(t *testing.T) { func TestFilterByStatus(t *testing.T) {
actual := bservices.FilterByStatus("CREATED") actual := bservices.FilterByStatus("CREATED")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual)) t.Fatal("expected 2 found, actual: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Status != "CREATED" { if item.Status != "CREATED" {
t.Fatal("expected Status 'CREATED', found: ", item.Status) t.Fatal("expected Status 'CREATED', found: ", item.Status)
} }
@@ -118,11 +121,11 @@ func TestFilterByStatus(t *testing.T) {
func TestFilterByTechStatus(t *testing.T) { func TestFilterByTechStatus(t *testing.T) {
actual := bservices.FilterByTechStatus("STOPPED") actual := bservices.FilterByTechStatus("STOPPED")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual)) t.Fatal("expected 2 found, actual: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.TechStatus != "STOPPED" { if item.TechStatus != "STOPPED" {
t.Fatal("expected TechStatus 'STOPPED', found: ", item.TechStatus) t.Fatal("expected TechStatus 'STOPPED', found: ", item.TechStatus)
} }
@@ -134,8 +137,8 @@ func TestFilterFunc(t *testing.T) {
return ibs.CreatedBy == "sample_user_2@decs3o" return ibs.CreatedBy == "sample_user_2@decs3o"
}) })
if len(actual) > 1 { if len(actual.Data) > 1 {
t.Fatal("expected 1 found, actual: ", len(actual)) t.Fatal("expected 1 found, actual: ", len(actual.Data))
} }
if actual.FindOne().CreatedBy != "sample_user_2@decs3o" { if actual.FindOne().CreatedBy != "sample_user_2@decs3o" {
@@ -146,7 +149,7 @@ func TestFilterFunc(t *testing.T) {
func TestSortByCreatedTime(t *testing.T) { func TestSortByCreatedTime(t *testing.T) {
actual := bservices.SortByCreatedTime(true) 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") t.Fatal("expected descending order, found ascending")
} }
} }

View File

@@ -8,15 +8,32 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" "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 { type GetRequest struct {
// ID of the service to query information // ID of the service to query information
// Required: true // Required: true
ServiceID uint64 `url:"serviceId" json:"serviceId" validate:"required"` 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) { 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -26,17 +43,6 @@ func (b BService) Get(ctx context.Context, req GetRequest) (*RecordBasicService,
url := "/cloudapi/bservice/get" url := "/cloudapi/bservice/get"
bsRaw, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req) res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { return res, err
return nil, err
}
info := RecordBasicService{}
err = json.Unmarshal(bsRaw, &info)
if err != nil {
return nil, err
}
return &info, nil
} }

View File

@@ -68,6 +68,10 @@ type GroupAddRequest struct {
// Time of Compute Group readiness // Time of Compute Group readiness
// Required: false // Required: false
TimeoutStart uint64 `url:"timeoutStart,omitempty" json:"timeoutStart,omitempty"` 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. // GroupAdd creates new Compute Group within BasicService.

View File

@@ -6,16 +6,40 @@ import (
"net/http" "net/http"
) )
// Request struct for get list/deleted list BasicService instances // ListRequest struct to get list of BasicService instances
type ListRequest struct { 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 // ID of the account to query for BasicService instances
// Required: false // Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"` 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 // ID of the resource group to query for BasicService instances
// Required: false // Required: false
RGID uint64 `url:"rgId,omitempty" json:"rgId,omitempty"` 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 // Page number
// Required: false // Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -25,40 +49,27 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"` Size uint64 `url:"size,omitempty" json:"size,omitempty"`
} }
// List gets list BasicService instances associated with the specified Resource Group // 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) { 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" url := "/cloudapi/bservice/list"
res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req) res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { return res, err
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
} }

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 // 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 // - First argument -> prefix
// - Second argument -> indent // - Second argument -> indent
func (lbs ListBasicServices) Serialize(params ...string) (serialization.Serialized, error) { func (lbs ListBasicServices) Serialize(params ...string) (serialization.Serialized, error) {
if len(lbs) == 0 { if lbs.EntryCount == 0 {
return []byte{}, nil return []byte{}, nil
} }

View File

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

View File

@@ -18,12 +18,12 @@ type CreateTemplateRequest struct {
// Name to assign to the template being created // Name to assign to the template being created
// Required: true // Required: true
Name string `url:"name" json:"name" validate:"required"` Name string `url:"name" json:"name" validate:"required"`
}
// Async API call type wrapperCreateTemplateRequest struct {
// For async call use CreateTemplateAsync CreateTemplateRequest
// For sync call use CreateTemplate
// Required: true Async bool `url:"async"`
async bool `url:"async"`
} }
// CreateTemplate create template from compute instance // 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" 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 { if err != nil {
return 0, err 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" 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 { if err != nil {
return "", err 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 // Required: true
Size uint64 `url:"size" json:"size" validate:"required"` 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 // Type of the disk
// Should be one of: // Should be one of:
// - D // - D
@@ -34,6 +29,11 @@ type DiskAddRequest struct {
// Required: false // Required: false
DiskType string `url:"diskType,omitempty" json:"diskType,omitempty" validate:"omitempty,computeDiskType"` 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 // Pool name
// By default will be chosen automatically // By default will be chosen automatically
// Required: false // Required: false

View File

@@ -20,7 +20,7 @@ type DiskDelRequest struct {
// False if disk is to be deleted to recycle bin // False if disk is to be deleted to recycle bin
// Required: true // Required: true
Permanently bool `url:"permanently" json:"permanently" validate:"required"` Permanently bool `url:"permanently" json:"permanently"`
} }
// DiskDel delete disk and detach from compute // 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. // 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) caller := k8s.New(decortClient)
req := k8s.GetRequest{ req := k8s.GetRequest{
@@ -89,7 +89,9 @@ func (lc ListComputes) FilterByK8SID(ctx context.Context, k8sID uint64, decortCl
return false 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. // 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. // 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) caller := lb.New(decortClient)
req := lb.GetRequest{ 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 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. // FilterFunc allows filtering ListComputes based on a user-specified predicate.
func (lc ListComputes) FilterFunc(predicate func(ItemCompute) bool) ListComputes { func (lc ListComputes) FilterFunc(predicate func(ItemCompute) bool) ListComputes {
var result ListComputes var result ListComputes
for _, item := range lc { for _, item := range lc.Data {
if predicate(item) { if predicate(item) {
result = append(result, item) result.Data = append(result.Data, item)
} }
} }
result.EntryCount = uint64(len(result.Data))
return result return result
} }
// FindOne returns first found ItemCompute // FindOne returns first found ItemCompute
// If none was found, returns an empty struct. // If none was found, returns an empty struct.
func (lc ListComputes) FindOne() ItemCompute { func (lc ListComputes) FindOne() ItemCompute {
if len(lc) == 0 { if len(lc.Data) == 0 {
return ItemCompute{} return ItemCompute{}
} }
return lc[0] return lc.Data[0]
} }

View File

@@ -3,7 +3,8 @@ package compute
import "testing" import "testing"
var computes = ListComputes{ var computes = ListComputes{
ItemCompute{ Data: []ItemCompute{
{
ACL: ListACL{}, ACL: ListACL{},
AccountID: 132847, AccountID: 132847,
AccountName: "std_2", AccountName: "std_2",
@@ -84,7 +85,7 @@ var computes = ListComputes{
VINSConnected: 0, VINSConnected: 0,
VirtualImageID: 0, VirtualImageID: 0,
}, },
ItemCompute{ {
ACL: ListACL{}, ACL: ListACL{},
AccountID: 132848, AccountID: 132848,
AccountName: "std_broker", AccountName: "std_broker",
@@ -147,6 +148,8 @@ var computes = ListComputes{
VINSConnected: 0, VINSConnected: 0,
VirtualImageID: 0, VirtualImageID: 0,
}, },
},
EntryCount: 2,
} }
func TestFilterByID(t *testing.T) { func TestFilterByID(t *testing.T) {
@@ -158,8 +161,8 @@ func TestFilterByID(t *testing.T) {
actualEmpty := computes.FilterByID(0) actualEmpty := computes.FilterByID(0)
if len(actualEmpty) != 0 { if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty)) t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
} }
} }
@@ -174,7 +177,7 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) { func TestFilterByStatus(t *testing.T) {
actual := computes.FilterByStatus("ENABLED") actual := computes.FilterByStatus("ENABLED")
for _, item := range actual { for _, item := range actual.Data {
if item.Status != "ENABLED" { if item.Status != "ENABLED" {
t.Fatal("expected ENABLED status, found: ", item.Status) t.Fatal("expected ENABLED status, found: ", item.Status)
} }
@@ -202,11 +205,11 @@ func TestFilterFunc(t *testing.T) {
return ic.Registered == true return ic.Registered == true
}) })
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 elements found, actual: ", len(actual)) t.Fatal("expected 2 elements found, actual: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Registered != true { if item.Registered != true {
t.Fatal("expected Registered to be true, actual: ", item.Registered) t.Fatal("expected Registered to be true, actual: ", item.Registered)
} }
@@ -216,26 +219,26 @@ func TestFilterFunc(t *testing.T) {
func TestSortingByCreatedTime(t *testing.T) { func TestSortingByCreatedTime(t *testing.T) {
actual := computes.SortByCreatedTime(false) actual := computes.SortByCreatedTime(false)
if actual[0].Name != "test" { if actual.Data[0].Name != "test" {
t.Fatal("expected 'test', found: ", actual[0].Name) t.Fatal("expected 'test', found: ", actual.Data[0].Name)
} }
actual = computes.SortByCreatedTime(true) actual = computes.SortByCreatedTime(true)
if actual[0].Name != "compute_2" { if actual.Data[0].Name != "compute_2" {
t.Fatal("expected 'compute_2', found: ", actual[0].Name) t.Fatal("expected 'compute_2', found: ", actual.Data[0].Name)
} }
} }
func TestSortingByCPU(t *testing.T) { func TestSortingByCPU(t *testing.T) {
actual := computes.SortByCPU(false) actual := computes.SortByCPU(false)
if actual[0].CPU != 4 { if actual.Data[0].CPU != 4 {
t.Fatal("expected 4 CPU cores, found: ", actual[0].CPU) t.Fatal("expected 4 CPU cores, found: ", actual.Data[0].CPU)
} }
actual = computes.SortByCPU(true) actual = computes.SortByCPU(true)
if actual[0].CPU != 6 { if actual.Data[0].CPU != 6 {
t.Fatal("expected 6 CPU cores, found: ", actual[0].CPU) 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" "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 { type GetRequest struct {
// ID of compute instance // ID of compute instance
// Required: true // Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"` 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) { func (c Compute) Get(ctx context.Context, req GetRequest) (*RecordCompute, error) {
err := validators.ValidateRequest(req) res, err := c.GetRaw(ctx, 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -40,3 +31,18 @@ func (c Compute) Get(ctx context.Context, req GetRequest) (*RecordCompute, error
return &info, nil 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" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
) )
// Request struct for get list GPU for compute // Request struct for getting Compute's customFields
type ListGPURequest struct { type GetCustomFieldsRequest struct {
// ID of compute instance // Compute ID
// Required: true // Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"` 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 // GetCustomFields gets Compute's customFields
func (c Compute) ListGPU(ctx context.Context, req ListGPURequest) ([]interface{}, error) { func (c Compute) GetCustomFields(ctx context.Context, req GetCustomFieldsRequest) (interface{}, error) {
err := validators.ValidateRequest(req) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { 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) res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := make([]interface{}, 0) var info interface{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &info)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return list, nil return &info, nil
} }

View File

@@ -6,8 +6,48 @@ import (
"net/http" "net/http"
) )
// Request struct for get list available computes // ListRequest struct to get list of available computes
type ListRequest struct { 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 // Include deleted computes
// Required: false // Required: false
IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"` IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"`
@@ -23,10 +63,8 @@ type ListRequest struct {
// List gets list of the available computes. // List gets list of the available computes.
// Filtering based on status is possible // Filtering based on status is possible
func (c Compute) List(ctx context.Context, req ListRequest) (ListComputes, error) { func (c Compute) List(ctx context.Context, req ListRequest) (*ListComputes, error) {
url := "/cloudapi/compute/list" res, err := c.ListRaw(ctx, req)
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -38,5 +76,13 @@ func (c Compute) List(ctx context.Context, req ListRequest) (ListComputes, error
return nil, err 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 // Request struct for get deleted computes list
type ListDeletedRequest struct { 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 // Page number
// Required: false // Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -18,7 +54,7 @@ type ListDeletedRequest struct {
} }
// ListDeleted gets list all deleted computes // 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" url := "/cloudapi/compute/listDeleted"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req) 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 nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -13,10 +13,34 @@ type ListPCIDeviceRequest struct {
// Identifier compute // Identifier compute
// Required: true // Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -31,12 +55,12 @@ func (c Compute) ListPCIDevice(ctx context.Context, req ListPCIDeviceRequest) ([
return nil, err return nil, err
} }
list := []interface{}{} list := ListPCIDevices{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &list)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -13,10 +13,34 @@ type ListVGPURequest struct {
// Identifier compute // Identifier compute
// Required: true // Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { 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) res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := []interface{}{} list := ListVGPUs{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &list)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

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

@@ -16,7 +16,7 @@ type PFWListRequest struct {
} }
// PFWList gets compute port forwards list // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { 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 nil, err
} }
return list, nil return &list, nil
} }

View File

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

View File

@@ -12,7 +12,7 @@ import (
// - First argument -> prefix // - First argument -> prefix
// - Second argument -> indent // - Second argument -> indent
func (lc ListComputes) Serialize(params ...string) (serialization.Serialized, error) { func (lc ListComputes) Serialize(params ...string) (serialization.Serialized, error) {
if len(lc) == 0 { if lc.EntryCount == 0 {
return []byte{}, nil 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +38,5 @@ func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (Lis
return nil, err 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. // If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByCPU(inverse bool) ListComputes { func (lc ListComputes) SortByCPU(inverse bool) ListComputes {
if len(lc) < 2 { if len(lc.Data) < 2 {
return lc return lc
} }
sort.Slice(lc, func(i, j int) bool { sort.Slice(lc.Data, func(i, j int) bool {
if inverse { 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 return lc
@@ -25,16 +25,16 @@ func (lc ListComputes) SortByCPU(inverse bool) ListComputes {
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByRAM(inverse bool) ListComputes { func (lc ListComputes) SortByRAM(inverse bool) ListComputes {
if len(lc) < 2 { if len(lc.Data) < 2 {
return lc return lc
} }
sort.Slice(lc, func(i, j int) bool { sort.Slice(lc.Data, func(i, j int) bool {
if inverse { 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 return lc
@@ -44,16 +44,16 @@ func (lc ListComputes) SortByRAM(inverse bool) ListComputes {
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByCreatedTime(inverse bool) ListComputes { func (lc ListComputes) SortByCreatedTime(inverse bool) ListComputes {
if len(lc) < 2 { if len(lc.Data) < 2 {
return lc return lc
} }
sort.Slice(lc, func(i, j int) bool { sort.Slice(lc.Data, func(i, j int) bool {
if inverse { 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 return lc
@@ -63,16 +63,16 @@ func (lc ListComputes) SortByCreatedTime(inverse bool) ListComputes {
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByUpdatedTime(inverse bool) ListComputes { func (lc ListComputes) SortByUpdatedTime(inverse bool) ListComputes {
if len(lc) < 2 { if len(lc.Data) < 2 {
return lc return lc
} }
sort.Slice(lc, func(i, j int) bool { sort.Slice(lc.Data, func(i, j int) bool {
if inverse { 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 return lc
@@ -82,16 +82,16 @@ func (lc ListComputes) SortByUpdatedTime(inverse bool) ListComputes {
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lc ListComputes) SortByDeletedTime(inverse bool) ListComputes { func (lc ListComputes) SortByDeletedTime(inverse bool) ListComputes {
if len(lc) < 2 { if len(lc.Data) < 2 {
return lc return lc
} }
sort.Slice(lc, func(i, j int) bool { sort.Slice(lc.Data, func(i, j int) bool {
if inverse { 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 return lc

View File

@@ -16,7 +16,7 @@ type UserListRequest struct {
} }
// UserList gets users list for compute // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -31,7 +31,7 @@ func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL,
return nil, err return nil, err
} }
list := RecordACL{} list := ListUsers{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &list)
if err != nil { 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. // 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) caller := k8s.New(decortClient)
req := k8s.GetRequest{ req := k8s.GetRequest{
@@ -76,20 +76,22 @@ func (ld ListDisks) FilterByK8SID(ctx context.Context, k8sID uint64, decortClien
var result ListDisks var result ListDisks
for _, masterCompute := range cluster.K8SGroups.Masters.DetailedInfo { 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 _, workerGroup := range cluster.K8SGroups.Workers {
for _, workerCompute := range workerGroup.DetailedInfo { 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. // 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) caller := lb.New(decortClient)
req := lb.GetRequest{ req := lb.GetRequest{
@@ -102,15 +104,141 @@ func (ld ListDisks) FilterByLBID(ctx context.Context, lbID uint64, decortClient
} }
var result ListDisks 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.PrimaryNode.ComputeID)...)
result = append(result, ld.FilterByComputeID(foundLB.SecondaryNode.ComputeID)...) result = append(result, ld.FilterByComputeID(foundLB.SecondaryNode.ComputeID)...)
return result, nil return result, nil
} }
// FilterFunc allows filtering ListDisks based on a user-specified predicate. // FilterFunc allows filtering ListSearchDisks based on a user-specified predicate.
func (ld ListDisks) FilterFunc(predicate func(ItemDisk) bool) ListDisks { func (ld ListSearchDisks) FilterFunc(predicate func(ItemDisk) bool) ListSearchDisks {
var result ListDisks var result ListSearchDisks
for _, item := range ld { for _, item := range ld {
if predicate(item) { if predicate(item) {
@@ -123,7 +251,7 @@ func (ld ListDisks) FilterFunc(predicate func(ItemDisk) bool) ListDisks {
// FindOne returns first found ItemDisk // FindOne returns first found ItemDisk
// If none was found, returns an empty struct. // If none was found, returns an empty struct.
func (ld ListDisks) FindOne() ItemDisk { func (ld ListSearchDisks) FindOne() ItemDisk {
if len(ld) == 0 { if len(ld) == 0 {
return ItemDisk{} return ItemDisk{}
} }
@@ -171,21 +299,23 @@ func (lu ListDisksUnattached) FilterByTechStatus(techStatus string) ListDisksUna
func (lu ListDisksUnattached) FilterFunc(predicate func(ItemDiskUnattached) bool) ListDisksUnattached { func (lu ListDisksUnattached) FilterFunc(predicate func(ItemDiskUnattached) bool) ListDisksUnattached {
var result ListDisksUnattached var result ListDisksUnattached
for _, item := range lu { for _, item := range lu.Data {
if predicate(item) { if predicate(item) {
result = append(result, item) result.Data = append(result.Data, item)
} }
} }
result.EntryCount = uint64(len(result.Data))
return result return result
} }
// FindOne returns first found ItemDiskUnattached // FindOne returns first found ItemDiskUnattached
// If none was found, returns an empty struct. // If none was found, returns an empty struct.
func (lu ListDisksUnattached) FindOne() ItemDiskUnattached { func (lu ListDisksUnattached) FindOne() ItemDiskUnattached {
if len(lu) == 0 { if len(lu.Data) == 0 {
return ItemDiskUnattached{} return ItemDiskUnattached{}
} }
return lu[0] return lu.Data[0]
} }

View File

@@ -4,7 +4,186 @@ import (
"testing" "testing"
) )
var techStatusAllocated = "ALLOCATED"
var disks = ListDisks{ 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{ ItemDisk{
MachineID: 0, MachineID: 0,
MachineName: "", MachineName: "",
@@ -44,7 +223,7 @@ var disks = ListDisks{
SizeUsed: 2, SizeUsed: 2,
Snapshots: []ItemSnapshot{}, Snapshots: []ItemSnapshot{},
Status: "ASSIGNED", Status: "ASSIGNED",
TechStatus: "ALLOCATED", TechStatus: techStatusAllocated,
Type: "B", Type: "B",
VMID: 48500, VMID: 48500,
}, },
@@ -88,14 +267,14 @@ var disks = ListDisks{
SizeUsed: 4, SizeUsed: 4,
Snapshots: []ItemSnapshot{}, Snapshots: []ItemSnapshot{},
Status: "ASSIGNED", Status: "ASSIGNED",
TechStatus: "ALLOCATED", TechStatus: techStatusAllocated,
Type: "B", Type: "B",
VMID: 48502, VMID: 48502,
}, },
} }
func TestFilterByID(t *testing.T) { func TestListSearchDisks_FilterByID(t *testing.T) {
actual := disks.FilterByID(65193) actual := searchDisks.FilterByID(65193)
if len(actual) == 0 { if len(actual) == 0 {
t.Fatal("No elements were found") t.Fatal("No elements were found")
@@ -108,8 +287,8 @@ func TestFilterByID(t *testing.T) {
} }
} }
func TestFilterByName(t *testing.T) { func TestListSearchDisks_FilterByName(t *testing.T) {
actual := disks.FilterByName("bootdisk") actual := searchDisks.FilterByName("bootdisk")
if len(actual) != 2 { if len(actual) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual)) t.Fatal("expected 2 elements, found: ", len(actual))
@@ -122,8 +301,8 @@ func TestFilterByName(t *testing.T) {
} }
} }
func TestFilterByStatus(t *testing.T) { func TestListSearchDisks_FilterByStatus(t *testing.T) {
actual := disks.FilterByStatus("ASSIGNED") actual := searchDisks.FilterByStatus("ASSIGNED")
if len(actual) == 0 { if len(actual) == 0 {
t.Fatal("No elements were found") t.Fatal("No elements were found")
@@ -136,22 +315,22 @@ func TestFilterByStatus(t *testing.T) {
} }
} }
func TestFilterByTechStatus(t *testing.T) { func TestListSearchDisks_FilterByTechStatus(t *testing.T) {
actual := disks.FilterByTechStatus("ALLOCATED") actual := searchDisks.FilterByTechStatus(techStatusAllocated)
if len(actual) == 0 { if len(actual) == 0 {
t.Fatal("No elements were found") t.Fatal("No elements were found")
} }
for _, item := range actual { for _, item := range actual {
if item.TechStatus != "ALLOCATED" { if item.TechStatus != techStatusAllocated {
t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus) t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus)
} }
} }
} }
func TestFilterFunc(t *testing.T) { func TestListSearchDisks_FilterFunc(t *testing.T) {
actual := disks.FilterFunc(func(id ItemDisk) bool { actual := searchDisks.FilterFunc(func(id ItemDisk) bool {
return len(id.PresentTo) == 2 return len(id.PresentTo) == 2
}) })
@@ -164,14 +343,14 @@ func TestFilterFunc(t *testing.T) {
} }
} }
func TestSortByCreatedTime(t *testing.T) { func TestListSearchDisks_SortByCreatedTime(t *testing.T) {
actual := disks.SortByCreatedTime(false) actual := searchDisks.SortByCreatedTime(false)
if actual[0].ID != 65191 { if actual[0].ID != 65191 {
t.Fatal("expected ID 65191, found: ", actual[0].ID) t.Fatal("expected ID 65191, found: ", actual[0].ID)
} }
actual = disks.SortByCreatedTime(true) actual = searchDisks.SortByCreatedTime(true)
if actual[0].ID != 65193 { if actual[0].ID != 65193 {
t.Fatal("expected ID 65193, found: ", actual[0].ID) t.Fatal("expected ID 65193, found: ", actual[0].ID)
@@ -179,6 +358,7 @@ func TestSortByCreatedTime(t *testing.T) {
} }
var unattachedDisks = ListDisksUnattached{ var unattachedDisks = ListDisksUnattached{
Data: []ItemDiskUnattached{
{ {
CKey: "", CKey: "",
Meta: []interface{}{ Meta: []interface{}{
@@ -227,7 +407,7 @@ var unattachedDisks = ListDisksUnattached{
SizeUsed: 0, SizeUsed: 0,
Snapshots: nil, Snapshots: nil,
Status: "CREATED", Status: "CREATED",
TechStatus: "ALLOCATED", TechStatus: techStatusAllocated,
Type: "D", Type: "D",
VMID: 0, VMID: 0,
}, },
@@ -282,16 +462,18 @@ var unattachedDisks = ListDisksUnattached{
SizeUsed: 0, SizeUsed: 0,
Snapshots: nil, Snapshots: nil,
Status: "CREATED", Status: "CREATED",
TechStatus: "ALLOCATED", TechStatus: techStatusAllocated,
Type: "B", Type: "B",
VMID: 0, VMID: 0,
}, },
},
EntryCount: 2,
} }
func TestListDisksUnattached_FilterByID(t *testing.T) { func TestListDisksUnattached_FilterByID(t *testing.T) {
actual := unattachedDisks.FilterByID(22636) actual := unattachedDisks.FilterByID(22636)
if len(actual) == 0 { if len(actual.Data) == 0 {
t.Fatal("No elements were found") t.Fatal("No elements were found")
} }
@@ -305,11 +487,11 @@ func TestListDisksUnattached_FilterByID(t *testing.T) {
func TestListDisksUnattached_FilterByName(t *testing.T) { func TestListDisksUnattached_FilterByName(t *testing.T) {
actual := unattachedDisks.FilterByName("test_disk") actual := unattachedDisks.FilterByName("test_disk")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual)) t.Fatal("expected 2 elements, found: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Name != "test_disk" { if item.Name != "test_disk" {
t.Fatal("expected 'test_disk' name, found: ", item.Name) 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) { func TestListDisksUnattached_FilterByStatus(t *testing.T) {
actual := unattachedDisks.FilterByStatus("CREATED") actual := unattachedDisks.FilterByStatus("CREATED")
if len(actual) == 0 { if len(actual.Data) == 0 {
t.Fatal("No elements were found") t.Fatal("No elements were found")
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Status != "CREATED" { if item.Status != "CREATED" {
t.Fatal("expected 'CREATED' status, found: ", item.Status) t.Fatal("expected 'CREATED' status, found: ", item.Status)
} }
@@ -331,14 +513,14 @@ func TestListDisksUnattached_FilterByStatus(t *testing.T) {
} }
func TestListDisksUnattached_FilterByTechStatus(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") t.Fatal("No elements were found")
} }
for _, item := range actual { for _, item := range actual.Data {
if item.TechStatus != "ALLOCATED" { if item.TechStatus != techStatusAllocated {
t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus) t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus)
} }
} }
@@ -349,26 +531,26 @@ func TestListDisksUnattached_FilterFunc(t *testing.T) {
return len(id.PresentTo) == 2 return len(id.PresentTo) == 2
}) })
if len(actual) == 0 { if len(actual.Data) == 0 {
t.Fatal("No elements were found") t.Fatal("No elements were found")
} }
if len(actual[0].PresentTo) != 2 { if len(actual.Data[0].PresentTo) != 2 {
t.Fatal("expected 2 elements in PresentTo, found: ", len(actual[0].PresentTo)) t.Fatal("expected 2 elements in PresentTo, found: ", len(actual.Data[0].PresentTo))
} }
} }
func TestListDisksUnattached_SortByCreatedTime(t *testing.T) { func TestListDisksUnattached_SortByCreatedTime(t *testing.T) {
actual := unattachedDisks.SortByCreatedTime(false) actual := unattachedDisks.SortByCreatedTime(false)
if actual[0].ID != 22636 { if actual.Data[0].ID != 22636 {
t.Fatal("expected ID 22636, found: ", actual[0].ID) t.Fatal("expected ID 22636, found: ", actual.Data[0].ID)
} }
actual = unattachedDisks.SortByCreatedTime(true) actual = unattachedDisks.SortByCreatedTime(true)
if actual[0].ID != 22637 { if actual.Data[0].ID != 22637 {
t.Fatal("expected ID 22637, found: ", actual[0].ID) 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" "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 { type GetRequest struct {
// ID of the disk // ID of the disk
// Required: true // Required: true
DiskID uint64 `url:"diskId" json:"diskId" validate:"required"` 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 // 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) { func (d Disks) Get(ctx context.Context, req GetRequest) (*RecordDisk, error) {
err := validators.ValidateRequest(req) res, err := d.GetRaw(ctx, 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -41,3 +32,19 @@ func (d Disks) Get(ctx context.Context, req GetRequest) (*RecordDisk, error) {
return &info, nil 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" "net/http"
) )
// Request struct for get list/list_deleted of disks // ListRequest struct to get list of disks
type ListRequest struct { 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 // ID of the account the disks belong to
// Required: false // Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"` AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"`
@@ -16,6 +40,14 @@ type ListRequest struct {
// Required: false // Required: false
Type string `url:"type,omitempty" json:"type,omitempty"` 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 // Page number
// Required: false // Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -25,40 +57,27 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"` Size uint64 `url:"size,omitempty" json:"size,omitempty"`
} }
// List gets list the created disks belonging to an account // 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) { 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" url := "/cloudapi/disks/list"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req) res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { return res, err
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
} }

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 // Show detailed disk types by seps
// Required: true // Required: true
Detailed bool `url:"detailed" json:"detailed" validate:"required"` 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 // 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" url := "/cloudapi/disks/listTypes"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req) 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 return nil, err
} }
list := make([]interface{}, 0) list := ListTypes{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &list)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -8,13 +8,49 @@ import (
// Request struct for get list unattached disk // Request struct for get list unattached disk
type ListUnattachedRequest struct { 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 // ID of the account
// Required: false // Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"` 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 // 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" url := "/cloudapi/disks/listUnattached"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req) 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 nil, err
} }
return list, nil return &list, nil
} }

View File

@@ -248,11 +248,24 @@ type ItemDiskUnattached struct {
VMID uint64 `json:"vmid"` VMID uint64 `json:"vmid"`
} }
// List of disks searched
type ListSearchDisks []ItemDisk
// List of disks // List of disks
type ListDisks []ItemDisk type ListDisks struct {
// Data
Data []ItemDisk `json:"data"`
// Entry count
EntryCount uint64 `json:"entryCount"`
}
// List of unattached disks // List of unattached disks
type ListDisksUnattached []ItemDiskUnattached type ListDisksUnattached struct {
Data []ItemDiskUnattached `json:"data"`
EntryCount uint64 `json:"entryCount"`
}
// Main information about snapshot // Main information about snapshot
type ItemSnapshot struct { type ItemSnapshot struct {
@@ -262,6 +275,8 @@ type ItemSnapshot struct {
// Label // Label
Label string `json:"label"` Label string `json:"label"`
ReferenceID string `json:"referenceId"`
// Resource ID // Resource ID
ResID string `json:"resId"` ResID string `json:"resId"`
@@ -427,3 +442,11 @@ type RecordDisk struct {
// Virtual machine ID // Virtual machine ID
VMID uint64 `json:"vmid"` 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 // 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" url := "/cloudapi/disks/search"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req) 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 return nil, err
} }
list := ListDisks{} list := ListSearchDisks{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &list)
if err != nil { if err != nil {

View File

@@ -12,6 +12,26 @@ import (
// - First argument -> prefix // - First argument -> prefix
// - Second argument -> indent // - Second argument -> indent
func (ld ListDisks) Serialize(params ...string) (serialization.Serialized, error) { 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 { if len(ld) == 0 {
return []byte{}, nil return []byte{}, nil
} }
@@ -48,7 +68,7 @@ func (idisk ItemDisk) Serialize(params ...string) (serialization.Serialized, err
// - First argument -> prefix // - First argument -> prefix
// - Second argument -> indent // - Second argument -> indent
func (lu ListDisksUnattached) Serialize(params ...string) (serialization.Serialized, error) { func (lu ListDisksUnattached) Serialize(params ...string) (serialization.Serialized, error) {
if len(lu) == 0 { if lu.EntryCount == 0 {
return []byte{}, nil return []byte{}, nil
} }

View File

@@ -6,6 +6,63 @@ import "sort"
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (ld ListDisks) SortByCreatedTime(inverse bool) ListDisks { 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 { if len(ld) < 2 {
return ld return ld
} }
@@ -21,10 +78,10 @@ func (ld ListDisks) SortByCreatedTime(inverse bool) ListDisks {
return ld 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. // 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 { if len(ld) < 2 {
return ld return ld
} }
@@ -40,10 +97,10 @@ func (ld ListDisks) SortByDestructionTime(inverse bool) ListDisks {
return ld 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. // 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 { if len(ld) < 2 {
return ld return ld
} }
@@ -63,16 +120,16 @@ func (ld ListDisks) SortByDeletedTime(inverse bool) ListDisks {
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByCreatedTime(inverse bool) ListDisksUnattached { func (lu ListDisksUnattached) SortByCreatedTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 { if len(lu.Data) < 2 {
return lu return lu
} }
sort.Slice(lu, func(i, j int) bool { sort.Slice(lu.Data, func(i, j int) bool {
if inverse { 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 return lu
@@ -82,16 +139,16 @@ func (lu ListDisksUnattached) SortByCreatedTime(inverse bool) ListDisksUnattache
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByDestructionTime(inverse bool) ListDisksUnattached { func (lu ListDisksUnattached) SortByDestructionTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 { if len(lu.Data) < 2 {
return lu return lu
} }
sort.Slice(lu, func(i, j int) bool { sort.Slice(lu.Data, func(i, j int) bool {
if inverse { 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 return lu
@@ -101,16 +158,16 @@ func (lu ListDisksUnattached) SortByDestructionTime(inverse bool) ListDisksUnatt
// //
// If inverse param is set to true, the order is reversed. // If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByDeletedTime(inverse bool) ListDisksUnattached { func (lu ListDisksUnattached) SortByDeletedTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 { if len(lu.Data) < 2 {
return lu return lu
} }
sort.Slice(lu, func(i, j int) bool { sort.Slice(lu.Data, func(i, j int) bool {
if inverse { 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 return lu

View File

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

View File

@@ -3,24 +3,26 @@ package extnet
import "testing" import "testing"
var extnets = ListExtNets{ var extnets = ListExtNets{
ItemExtNet{ Data: []ItemExtNet{
{
ID: 3, ID: 3,
IPCIDR: "176.118.164.0/24", IPCIDR: "176.118.164.0/24",
Name: "176.118.164.0/24", Name: "176.118.164.0/24",
Status: "ENABLED", Status: "ENABLED",
}, },
ItemExtNet{ {
ID: 10, ID: 10,
IPCIDR: "45.134.255.0/24", IPCIDR: "45.134.255.0/24",
Name: "45.134.255.0/24", Name: "45.134.255.0/24",
Status: "ENABLED", Status: "ENABLED",
}, },
ItemExtNet{ {
ID: 13, ID: 13,
IPCIDR: "88.218.249.0/24", IPCIDR: "88.218.249.0/24",
Name: "88.218.249.0/24", Name: "88.218.249.0/24",
Status: "DISABLED", Status: "DISABLED",
}, },
},
} }
func TestFilterByID(t *testing.T) { func TestFilterByID(t *testing.T) {
@@ -43,11 +45,11 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) { func TestFilterByStatus(t *testing.T) {
actual := extnets.FilterByStatus("ENABLED") actual := extnets.FilterByStatus("ENABLED")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual)) t.Fatal("expected 2 found, actual: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Status != "ENABLED" { if item.Status != "ENABLED" {
t.Fatal("expected Status 'ENABLED', found: ", item.Status) t.Fatal("expected Status 'ENABLED', found: ", item.Status)
} }
@@ -59,7 +61,7 @@ func TestFilterFunc(t *testing.T) {
return ien.IPCIDR == ien.Name return ien.IPCIDR == ien.Name
}) })
if len(actual) != 3 { if len(actual.Data) != 3 {
t.Fatal("expected 3 elements, found: ", len(actual)) 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" "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 { type GetRequest struct {
// ID of external network // ID of external network
// Required: true // Required: true
NetID uint64 `url:"net_id" json:"net_id" validate:"required"` 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) { func (e ExtNet) Get(ctx context.Context, req GetRequest) (*RecordExtNet, error) {
err := validators.ValidateRequest(req) res, err := e.GetRaw(ctx, 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -40,3 +31,18 @@ func (e ExtNet) Get(ctx context.Context, req GetRequest) (*RecordExtNet, error)
return &info, nil 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" "net/http"
) )
// Request struct for get list external network // ListRequest struct to get list of external network
type ListRequest struct { type ListRequest struct {
// Filter by account ID // Find by account ID
// Required: false // Required: false
AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"` 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 // Page number
// Required: false // Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -21,11 +45,9 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"` Size uint64 `url:"size,omitempty" json:"size,omitempty"`
} }
// List gets list all available external networks // List gets list of all available external networks as a ListExtNets struct
func (e ExtNet) List(ctx context.Context, req ListRequest) (ListExtNets, error) { func (e ExtNet) List(ctx context.Context, req ListRequest) (*ListExtNets, error) {
url := "/cloudapi/extnet/list" res, err := e.ListRaw(ctx, req)
res, err := e.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -37,5 +59,13 @@ func (e ExtNet) List(ctx context.Context, req ListRequest) (ListExtNets, error)
return nil, err 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 // Filter by account ID
// Required: true // Required: true
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` 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 // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -38,5 +54,5 @@ func (e ExtNet) ListComputes(ctx context.Context, req ListComputesRequest) (List
return nil, err return nil, err
} }
return list, nil return &list, nil
} }

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ type CreateRequest struct {
} }
// Create method will create a new FLIPGorup in the specified Account // 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
@@ -60,7 +60,7 @@ func (f FLIPGroup) Create(ctx context.Context, req CreateRequest) (*RecordFLIPGr
return nil, err return nil, err
} }
info := RecordFLIPGroup{} info := RecordFLIPGroupCreated{}
err = json.Unmarshal(res, &info) err = json.Unmarshal(res, &info)
if err != nil { 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" "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 { type GetRequest struct {
// FLIPGroup ID // FLIPGroup ID
// Required: true // Required: true
FLIPGroupID uint64 `url:"flipgroupId" json:"flipgroupId" validate:"required"` FLIPGroupID uint64 `url:"flipgroupId" json:"flipgroupId" validate:"required"`
} }
// Get gets details of the specified Floating IP group // Get gets details of the specified Floating IP group as a RecordFLIPGroup struct
func (f FLIPGroup) Get(ctx context.Context, req GetRequest) (*ItemFLIPGroup, error) { 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) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { 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" url := "/cloudapi/flipgroup/get"
res, err := f.client.DecortApiCall(ctx, http.MethodPost, url, req) res, err := f.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { return res, err
return nil, err
}
info := ItemFLIPGroup{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
} }

View File

@@ -6,8 +6,36 @@ import (
"net/http" "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 { 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 // Page number
// Required: false // Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"` Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -17,11 +45,9 @@ type ListRequest struct {
Size uint64 `url:"size,omitempty" json:"size,omitempty"` Size uint64 `url:"size,omitempty" json:"size,omitempty"`
} }
// List gets list FLIPGroup managed cluster instances available to the current user // 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) { func (f FLIPGroup) List(ctx context.Context, req ListRequest) (*ListFLIPGroups, error) {
url := "/cloudapi/flipgroup/list" res, err := f.ListRaw(ctx, req)
res, err := f.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -33,5 +59,13 @@ func (f FLIPGroup) List(ctx context.Context, req ListRequest) (ListFLIPGroups, e
return nil, err 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 package flipgroup
// Main information about FLIPGroup // Main information about FLIPGroup
type RecordFLIPGroup struct { type RecordFLIPGroupCreated struct {
// Default GW // Default GW
DefaultGW string `json:"defaultGW"` DefaultGW string `json:"defaultGW"`
@@ -18,8 +18,7 @@ type RecordFLIPGroup struct {
NetMask uint64 `json:"netmask"` NetMask uint64 `json:"netmask"`
} }
// Detailed information about FLIPGroup type RecordFLIPGroup struct {
type ItemFLIPGroup struct {
// Account ID // Account ID
AccountID uint64 `json:"accountId"` AccountID uint64 `json:"accountId"`
@@ -29,7 +28,7 @@ type ItemFLIPGroup struct {
// List of client IDs // List of client IDs
ClientIDs []uint64 `json:"clientIds"` ClientIDs []uint64 `json:"clientIds"`
// Client names list // Client names
ClientNames []string `json:"clientNames"` ClientNames []string `json:"clientNames"`
// Client type // Client type
@@ -102,5 +101,69 @@ type ItemFLIPGroup struct {
UpdatedTime uint64 `json:"updatedTime"` 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 // 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 // - First argument -> prefix
// - Second argument -> indent // - Second argument -> indent
func (lfg ListFLIPGroups) Serialize(params ...string) (serialization.Serialized, error) { func (lfg ListFLIPGroups) Serialize(params ...string) (serialization.Serialized, error) {
if len(lfg) == 0 { if len(lfg.Data) == 0 {
return []byte{}, nil 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 { func (li ListImages) FilterFunc(predicate func(ItemImage) bool) ListImages {
var result ListImages var result ListImages
for _, item := range li { for _, item := range li.Data {
if predicate(item) { if predicate(item) {
result = append(result, item) result.Data = append(result.Data, item)
} }
} }
result.EntryCount = uint64(len(result.Data))
return result return result
} }
// FindOne returns first found ItemImage // FindOne returns first found ItemImage
// If none was found, returns an empty struct. // If none was found, returns an empty struct.
func (li ListImages) FindOne() ItemImage { func (li ListImages) FindOne() ItemImage {
if len(li) == 0 { if len(li.Data) == 0 {
return ItemImage{} return ItemImage{}
} }
return li[0] return li.Data[0]
} }

View File

@@ -3,7 +3,8 @@ package image
import "testing" import "testing"
var images = ListImages{ var images = ListImages{
ItemImage{ Data: []ItemImage{
{
AccountID: 0, AccountID: 0,
Architecture: "X86_64", Architecture: "X86_64",
BootType: "bios", BootType: "bios",
@@ -23,7 +24,7 @@ var images = ListImages{
Username: "", Username: "",
Virtual: false, Virtual: false,
}, },
ItemImage{ {
AccountID: 0, AccountID: 0,
Architecture: "X86_64", Architecture: "X86_64",
BootType: "bois", BootType: "bois",
@@ -43,7 +44,7 @@ var images = ListImages{
Username: "", Username: "",
Virtual: true, Virtual: true,
}, },
ItemImage{ {
AccountID: 1, AccountID: 1,
Architecture: "X86_64", Architecture: "X86_64",
BootType: "bios", BootType: "bios",
@@ -63,6 +64,8 @@ var images = ListImages{
Username: "", Username: "",
Virtual: false, Virtual: false,
}, },
},
EntryCount: 3,
} }
func TestFilterByID(t *testing.T) { func TestFilterByID(t *testing.T) {
@@ -84,11 +87,11 @@ func TestFilterByName(t *testing.T) {
func TestFilterByStatus(t *testing.T) { func TestFilterByStatus(t *testing.T) {
actual := images.FilterByStatus("CREATED") actual := images.FilterByStatus("CREATED")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual)) t.Fatal("expected 2 found, actual: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.Status != "CREATED" { if item.Status != "CREATED" {
t.Fatal("expected Status 'CREATED', found: ", item.Status) t.Fatal("expected Status 'CREATED', found: ", item.Status)
} }
@@ -98,11 +101,11 @@ func TestFilterByStatus(t *testing.T) {
func TestFilterByBootType(t *testing.T) { func TestFilterByBootType(t *testing.T) {
actual := images.FilterByBootType("bios") actual := images.FilterByBootType("bios")
if len(actual) != 2 { if len(actual.Data) != 2 {
t.Fatal("expected 2 found, actual: ", len(actual)) t.Fatal("expected 2 found, actual: ", len(actual.Data))
} }
for _, item := range actual { for _, item := range actual.Data {
if item.BootType != "bios" { if item.BootType != "bios" {
t.Fatal("expected BootType 'bios', found: ", item.BootType) t.Fatal("expected BootType 'bios', found: ", item.BootType)
} }
@@ -114,11 +117,11 @@ func TestFilterFunc(t *testing.T) {
return ii.Virtual == true return ii.Virtual == true
}) })
if len(actual) != 1 { if len(actual.Data) != 1 {
t.Fatal("expected 1 found, actual: ", len(actual)) 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") t.Fatal("expected Virtual true, found false")
} }
} }

View File

@@ -8,7 +8,7 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
) )
// Request struct for get detailed information about image // GetRequest struct to get detailed information about image
type GetRequest struct { type GetRequest struct {
// ID of image to get // ID of image to get
// Required: true // Required: true
@@ -20,18 +20,9 @@ type GetRequest struct {
} }
// Get gets image by ID. // Get gets image by ID.
// Returns image if user has rights on it // Returns image as a RecordImage struct if user has rights on it
func (i Image) Get(ctx context.Context, req GetRequest) (*RecordImage, error) { func (i Image) Get(ctx context.Context, req GetRequest) (*RecordImage, error) {
err := validators.ValidateRequest(req) res, err := i.GetRaw(ctx, req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return nil, validators.ValidationError(validationError)
}
}
url := "/cloudapi/image/get"
res, err := i.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -45,3 +36,19 @@ func (i Image) Get(ctx context.Context, req GetRequest) (*RecordImage, error) {
return &info, nil return &info, nil
} }
// GetRaw gets image by ID.
// Returns image as an array of bytes if user has rights on it
func (i Image) 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/image/get"
res, err := i.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

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