From d137c7507ab1c4006b62422e4b61d78749dca3eb Mon Sep 17 00:00:00 2001 From: nlloskutova Date: Thu, 14 Mar 2024 14:52:56 +0300 Subject: [PATCH] v1.7.6 --- CHANGELOG.md | 8 +- README.md | 555 ++++++++++++++---- client.go | 453 +++++++------- client_bvs.go | 380 ++++++++++++ config/config_bvs.go | 216 +++++++ config/timeouts.go | 4 + go.mod | 7 +- go.sum | 15 +- interfaces/caller.go | 3 + internal/constants/constants.go | 7 + legacy-client.go | 393 ++++--------- pkg/cloudapi/bservice/disable.go | 2 +- pkg/cloudapi/bservice/models.go | 8 +- pkg/cloudapi/compute/models.go | 6 + pkg/cloudapi/image/get.go | 2 +- pkg/cloudapi/k8ci/list.go | 2 +- pkg/cloudapi/k8s/create.go | 2 +- pkg/cloudapi/k8s/delete.go | 2 +- pkg/cloudapi/lb/list.go | 2 +- pkg/cloudapi/lb/restart.go | 5 + pkg/cloudapi/locations/models.go | 3 + pkg/cloudapi/rg/ids.go | 11 + pkg/cloudapi/rg/models.go | 18 +- pkg/cloudapi/vins/create_in_rg.go | 3 +- pkg/cloudbroker/audit.go | 10 + pkg/cloudbroker/audit/audit.go | 15 + pkg/cloudbroker/audit/get.go | 46 ++ pkg/cloudbroker/audit/linked_jobs.go | 46 ++ pkg/cloudbroker/audit/list.go | 64 ++ pkg/cloudbroker/audit/models.go | 102 ++++ .../compute/delete_custom_fields.go | 38 ++ pkg/cloudbroker/compute/ids.go | 4 +- pkg/cloudbroker/compute/models.go | 33 +- pkg/cloudbroker/compute/pfw_list.go | 4 +- pkg/cloudbroker/compute/snapshot_list.go | 6 +- pkg/cloudbroker/compute/user_list.go | 4 +- pkg/cloudbroker/grid/get_diagnosis.go | 3 +- pkg/cloudbroker/grid/models.go | 6 + pkg/cloudbroker/k8ci/access_add.go | 36 ++ pkg/cloudbroker/k8ci/access_remove.go | 36 ++ pkg/cloudbroker/k8s/create.go | 4 +- pkg/cloudbroker/lb/make_highly_available.go | 6 +- pkg/cloudbroker/rg/affinity_groups_list.go | 8 + pkg/cloudbroker/rg/ids.go | 9 + pkg/cloudbroker/rg/list.go | 4 + pkg/cloudbroker/rg/models.go | 19 +- pkg/cloudbroker/stack/stack.go | 1 - pkg/cloudbroker/vins/create_in_rg.go | 3 +- samples/config/bvs-config.json | 21 + samples/config/bvs-config.yml | 18 + 50 files changed, 1949 insertions(+), 704 deletions(-) create mode 100644 client_bvs.go create mode 100644 config/config_bvs.go create mode 100644 internal/constants/constants.go create mode 100644 pkg/cloudbroker/audit.go create mode 100644 pkg/cloudbroker/audit/audit.go create mode 100644 pkg/cloudbroker/audit/get.go create mode 100644 pkg/cloudbroker/audit/linked_jobs.go create mode 100644 pkg/cloudbroker/audit/list.go create mode 100644 pkg/cloudbroker/audit/models.go create mode 100644 pkg/cloudbroker/compute/delete_custom_fields.go create mode 100644 pkg/cloudbroker/k8ci/access_add.go create mode 100644 pkg/cloudbroker/k8ci/access_remove.go create mode 100644 samples/config/bvs-config.json create mode 100644 samples/config/bvs-config.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index adecfc9..908ea69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -## Version 1.6.13 +## Version 1.7.6 ### Bugfix -- Fix url for Disable method in cloudapi/bservice \ No newline at end of file + +- Fix allowed network plugin value from "weawenet" to "weavenet" in validators for cloudapi/k8s, cloudbroker/k8s and cloudbroker/k8ci +- Delete omitempty from json, url tags in field Permanently in model DeleteRequest in cloudbroker/k8ci +- Delete ItemAffinityGroup and ListAffinityGroup models and change Data field type in ListAffinityGroups model in order to fix panic for AffinityGroupsList method in cloudapi/rg +- Fix ListAffinityGroups.IDs method accordingly \ No newline at end of file diff --git a/README.md b/README.md index 9ce9989..bbba2f2 100644 --- a/README.md +++ b/README.md @@ -11,34 +11,50 @@ Decort SDK - это библиотека, написанная на языке G - Версия 1.4.x Decort-SDK соответствует 3.8.6 версии платформы - Версия 1.5.x Decort-SDK соответствует 3.8.7 версии платформы - Версия 1.6.x Decort-SDK соответствует 3.8.8 версии платформы + - Версия 1.7.х Decort-SDK соответствует 3.8.9 версии платформы ## Оглавление - [Установка](#установка) - [Список API](#список-api) - - [Cloudapi](#cloudapi) - - [Cloudbroker](#cloudbroker) + - [Cloudapi](#cloudapi) + - [Cloudbroker](#cloudbroker) - [Работа с библиотекой](#работа-с-библиотекой) - - [Настройка конфигурации клиента](#настройка-конфигурации-клиента) - - [Пример конфигурации клиента](#пример-конфигурации-клиента) - - [Парсинг конфигурации из файла](#парсинг-конфигурации-из-файла) - - [Пример JSON конфигурации](#пример-json-конфигурации) - - [Пример YAML конфигурации](#пример-yaml-конфигурации) - - [Создание клиента](#создание-клиента) - - [Создание структуры запроса](#cоздание-структуры-запроса) - - [Выполнение запроса](#выполнение-запроса) - - [Фильтрация](#фильтрация) - - [Сортировка](#сортировка) - - [Сериализация](#сериализация) + - [Настройка конфигурации клиента](#настройка-конфигурации-клиента) + - [Пример конфигурации клиента](#пример-конфигурации-клиента) + - [Парсинг конфигурации из файла](#парсинг-конфигурации-из-файла) + - [Пример JSON конфигурации](#пример-json-конфигурации) + - [Пример YAML конфигурации](#пример-yaml-конфигурации) + - [Создание клиента](#создание-клиента) + - [Создание структуры запроса](#cоздание-структуры-запроса) + - [Выполнение запроса](#выполнение-запроса) + - [Фильтрация](#фильтрация) + - [Сортировка](#сортировка) + - [Сериализация](#сериализация) + - [Получение списка уникальных идентификаторов (ID) объекта](#получение-списка-уникальных-идентификаторов-id-объекта) - [Работа с legacy клиентом](#работа-с-legacy-клиентом) - - [Настройка конфигурации legacy клиента](#настройка-конфигурации-legacy-клиента) - - [Пример конфигурации legacy клиента](#пример-конфигурации-legacy-клиента) - - [Парсинг legacy конфигурации из файла](#парсинг-legacy-конфигурации-из-файла) - - [Пример legacy JSON конфигурации](#пример-legacy-json-конфигурации) - - [Пример legacy YAML конфигурации](#пример-legacy-yaml-конфигурации) - - [Создание legacy клиента](#создание-legacy-клиента) - - [Создание структуры запроса](#cоздание-структуры-запроса) - - [Выполнение запроса](#выполнение-запроса) + - [Настройка конфигурации legacy клиента](#настройка-конфигурации-legacy-клиента) + - [Пример конфигурации legacy клиента](#пример-конфигурации-legacy-клиента) + - [Парсинг legacy конфигурации из файла](#парсинг-legacy-конфигурации-из-файла) + - [Пример legacy JSON конфигурации](#пример-legacy-json-конфигурации) + - [Пример legacy YAML конфигурации](#пример-legacy-yaml-конфигурации) + - [Создание legacy клиента](#создание-legacy-клиента) + - [Создание структуры запроса](#cоздание-структуры-запроса) + - [Выполнение запроса](#выполнение-запроса) +- [Работа с BVS клиентом](#работа-с-bvs-клиентом) + - [Настройка параметров BVS в кабинете администратора](#настройка-параметров-bvs-в-кабинете-администратора) + - [Настройка конфигурации BVS клиента](#настройка-конфигурации-bvs-клиента) + - [Описание структуры token](#описание-структуры-token) + - [Пример конфигурации BVS клиента](#пример-конфигурации-bvs-клиента) + - [Парсинг BVS конфигурации из файла](#парсинг-bvs-конфигурации-из-файла) + - [Парсинг BVS токена из файла](#парсинг-bvs-токена-из-файла) + - [Пример BVS JSON конфигурации](#пример-bvs-json-конфигурации) + - [Пример BVS YAML конфигурации](#пример-bvs-yaml-конфигурации) + - [Создание BVS клиента](#создание-bvs-клиента) + - [Пример создания BVS клиента](#пример-создания-bvs-клиента) + - [Пример получения BVS токена](#пример-получения-bvs-токена) + - [Пример обновления BVS токена](#пример-обновления-bvs-токена) + - [Пример выполнения запроса](#пример-выполнения-запроса) ## Установка @@ -113,8 +129,8 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk Алгоритм работы с библиотекой выглядит следующим образом: 1. Выполнение одного из действий: - - настройка конфигурации клиента; - - парсинг конфигурации из файла. +- настройка конфигурации клиента; +- парсинг конфигурации из файла. 2. Создание клиента. 3. Создание структуры запроса. 4. Выполнение запроса. @@ -156,7 +172,7 @@ func main(){ #### Парсинг конфигурации из файла -Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigJSON` (или `ParseConfigYAML`) из пакета config. +Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigJSON` (или `ParseConfigYAML`) из пакета config.
*См. пример файлов конфигурации ниже и в директории `samples/`.* @@ -167,7 +183,7 @@ import ( func main() { // Парсинг конфигурации из JSON-файла - cfg := config.ParseConfigJSON("") + cfg, _ := config.ParseConfigJSON("") } ``` @@ -238,48 +254,48 @@ func main() { В каждом пакете находятся пакеты групп API: - **cloudapi**: - - `pkg/cloudapi/account` - для `Account` - - `pkg/cloudapi/bservice` - для `Basic Service` - - `pkg/cloudapi/compute` - для `Compute` - - `pkg/cloudapi/disks` - для `Disks` - - `pkg/cloudapi/extnet` - для `ExtNet` - - `pkg/cloudapi/flipgroup` - для `FLIPGroup` - - `pkg/cloudapi/image` - для `Image` - - `pkg/cloudapi/k8ci` - для `K8CI` - - `pkg/cloudapi/k8s` - для `K8S` - - `pkg/cloudapi/kvmppc` - для `KVMPPC` - - `pkg/cloudapi/kvmx86` - для `KVMX86` - - `pkg/cloudapi/lb` - для `LB` - - `pkg/cloudapi/locations` - для `Locations` - - `pkg/cloudapi/rg` - для `RG` - - `pkg/cloudapi/sizes` - для `Sizes` - - `pkg/cloudapi/stack` - для `Stack` - - `pkg/cloudapi/tasks` - для `Tasks` - - `pkg/cloudapi/vins` - для `VINS` + - `pkg/cloudapi/account` - для `Account` + - `pkg/cloudapi/bservice` - для `Basic Service` + - `pkg/cloudapi/compute` - для `Compute` + - `pkg/cloudapi/disks` - для `Disks` + - `pkg/cloudapi/extnet` - для `ExtNet` + - `pkg/cloudapi/flipgroup` - для `FLIPGroup` + - `pkg/cloudapi/image` - для `Image` + - `pkg/cloudapi/k8ci` - для `K8CI` + - `pkg/cloudapi/k8s` - для `K8S` + - `pkg/cloudapi/kvmppc` - для `KVMPPC` + - `pkg/cloudapi/kvmx86` - для `KVMX86` + - `pkg/cloudapi/lb` - для `LB` + - `pkg/cloudapi/locations` - для `Locations` + - `pkg/cloudapi/rg` - для `RG` + - `pkg/cloudapi/sizes` - для `Sizes` + - `pkg/cloudapi/stack` - для `Stack` + - `pkg/cloudapi/tasks` - для `Tasks` + - `pkg/cloudapi/vins` - для `VINS` - **cloudbroker**: - - `pkg/cloudbroker/account` - для `Account` - - `pkg/cloudbroker/apiaccess` - для `APIAccess` - - `pkg/cloudbroker/backup` - для `Backup` - - `pkg/cloudbroker/compute` - для `Compute` - - `pkg/cloudbroker/disks` - для `Disks` - - `pkg/cloudbroker/extnet` - для `ExtNet` - - `pkg/cloudbroker/flipgroup` - для `FLIPGroup` - - `pkg/cloudbroker/grid` - для `Grid` - - `pkg/cloudbroker/group` - для `Group` - - `pkg/cloudbroker/image` - для `Image` - - `pkg/cloudbroker/k8ci` - для `K8CI` - - `pkg/cloudbroker/k8s` - для `K8S` - - `pkg/cloudbroker/kvmppc` - для `KVMPPC` - - `pkg/cloudbroker/kvmx86` - для `KVMX86` - - `pkg/cloudbroker/lb` - для `LB` - - `pkg/cloudbroker/pcidevice` - для `PCIDevice` - - `pkg/cloudbroker/rg` - для `RG` - - `pkg/cloudbroker/sep` - для `SEP` - - `pkg/cloudbroker/stack` - для `Stack` - - `pkg/cloudbroker/tasks` - для `Tasks` - - `pkg/cloudbroker/user` - для `User` - - `pkg/cloudbroker/vgpu` - для `VGPU` - - `pkg/cloudbroker/vins` - для `VINS` + - `pkg/cloudbroker/account` - для `Account` + - `pkg/cloudbroker/apiaccess` - для `APIAccess` + - `pkg/cloudbroker/backup` - для `Backup` + - `pkg/cloudbroker/compute` - для `Compute` + - `pkg/cloudbroker/disks` - для `Disks` + - `pkg/cloudbroker/extnet` - для `ExtNet` + - `pkg/cloudbroker/flipgroup` - для `FLIPGroup` + - `pkg/cloudbroker/grid` - для `Grid` + - `pkg/cloudbroker/group` - для `Group` + - `pkg/cloudbroker/image` - для `Image` + - `pkg/cloudbroker/k8ci` - для `K8CI` + - `pkg/cloudbroker/k8s` - для `K8S` + - `pkg/cloudbroker/kvmppc` - для `KVMPPC` + - `pkg/cloudbroker/kvmx86` - для `KVMX86` + - `pkg/cloudbroker/lb` - для `LB` + - `pkg/cloudbroker/pcidevice` - для `PCIDevice` + - `pkg/cloudbroker/rg` - для `RG` + - `pkg/cloudbroker/sep` - для `SEP` + - `pkg/cloudbroker/stack` - для `Stack` + - `pkg/cloudbroker/tasks` - для `Tasks` + - `pkg/cloudbroker/user` - для `User` + - `pkg/cloudbroker/vgpu` - для `VGPU` + - `pkg/cloudbroker/vins` - для `VINS` Все поля структуры имеют описание, в которых содержится: @@ -420,50 +436,50 @@ func main() { Доступные методы для `.CloudAPI()`: - - `.Account()` - для работы с `Account` - - `.BService()` - для работы с `BService` - - `.Compute()` - для работы с `Compute` - - `.Disks()` - для работы с `Disks` - - `.ExtNet()` - для работы с `ExtNet` - - `.FLIPgroup()` - для работы с `FLIPGroup` - - `.Image()` - для работы с `Image` - - `.K8CI()` - для работы с `K8CI` - - `.K8S()` - для работы с `K8S` - - `.KVMPPC()` - для работы с `KVMPPC` - - `.KVMx86()` - для работы с `KVMX86` - - `.LB()` - для работы с `LB` - - `.Locations()` - для работы с `Locations` - - `.RG()` - для работы с `RG` - - `.Sizes()` - для работы с `Sizes` - - `.Stack()` - для работы с `Stack` - - `.Tasks()` - для работы с `Tasks` - - `.VINS()` - для работы с `VINS` + - `.Account()` - для работы с `Account` + - `.BService()` - для работы с `BService` + - `.Compute()` - для работы с `Compute` + - `.Disks()` - для работы с `Disks` + - `.ExtNet()` - для работы с `ExtNet` + - `.FLIPgroup()` - для работы с `FLIPGroup` + - `.Image()` - для работы с `Image` + - `.K8CI()` - для работы с `K8CI` + - `.K8S()` - для работы с `K8S` + - `.KVMPPC()` - для работы с `KVMPPC` + - `.KVMx86()` - для работы с `KVMX86` + - `.LB()` - для работы с `LB` + - `.Locations()` - для работы с `Locations` + - `.RG()` - для работы с `RG` + - `.Sizes()` - для работы с `Sizes` + - `.Stack()` - для работы с `Stack` + - `.Tasks()` - для работы с `Tasks` + - `.VINS()` - для работы с `VINS` Доступные методы для `.CloudBroker()`: - - `.Account()` - для работы с `Account` - - `.APIAccess()` - для работы с `APIAccess` - - `.Backup()` - для работы с `Backup` - - `.Compute()` - для работы с `Compute` - - `.Disks()` - для работы с `Disks` - - `.ExtNet()` - для работы с `ExtNet` - - `.FLIPGroup()` - для работы с `FLIPGroup` - - `.Grid()` - для работы с `Grid` - - `.Group()` - для работы с `Group` - - `.Image()` - для работы с `Image` - - `.K8CI()` - для работы с `K8CI` - - `.K8S()` - для работы с `K8S` - - `.KVMPPC()` - для работы с `KVMPPC` - - `.KVMx86()` - для работы с `KVMX86` - - `.LB()` - для работы с `LB` - - `.PCIDevice()` - для работы с `PCIDevice` - - `.RG()` - для работы с `RG` - - `.SEP()` - для работы с `SEP` - - `.Stack()` - для работы с `Stack` - - `.Tasks()` - для работы с `Tasks` - - `.User()` - для работы с `User` - - `.VGPU()` - для работы с `VGPU` - - `.VINS()` - для работы с `VINS` + - `.Account()` - для работы с `Account` + - `.APIAccess()` - для работы с `APIAccess` + - `.Backup()` - для работы с `Backup` + - `.Compute()` - для работы с `Compute` + - `.Disks()` - для работы с `Disks` + - `.ExtNet()` - для работы с `ExtNet` + - `.FLIPGroup()` - для работы с `FLIPGroup` + - `.Grid()` - для работы с `Grid` + - `.Group()` - для работы с `Group` + - `.Image()` - для работы с `Image` + - `.K8CI()` - для работы с `K8CI` + - `.K8S()` - для работы с `K8S` + - `.KVMPPC()` - для работы с `KVMPPC` + - `.KVMx86()` - для работы с `KVMX86` + - `.LB()` - для работы с `LB` + - `.PCIDevice()` - для работы с `PCIDevice` + - `.RG()` - для работы с `RG` + - `.SEP()` - для работы с `SEP` + - `.Stack()` - для работы с `Stack` + - `.Tasks()` - для работы с `Tasks` + - `.User()` - для работы с `User` + - `.VGPU()` - для работы с `VGPU` + - `.VINS()` - для работы с `VINS` 3. Вызвать метод, отвечающий за выполнение запроса и передать в него: @@ -634,7 +650,7 @@ filtered := resp. ```go func main() { // Чтение конфигурации из файла - cfg := config.ParseConfigJSON("") + cfg, _ := config.ParseConfigJSON("") // Создание клиента client := decort.New(cfg) @@ -766,7 +782,7 @@ func main() { ``` -### Получение списка уникальных идентификаторов (ID) объекта +### Получение списка уникальных идентификаторов ID объекта Для всех структур, имеющих поля со списками объектов с уникальными числовыми идентификаторами (ID), добавлены методы IDs(), возвращающие массивы уникальных идентификаторов объектов в этих списках. @@ -863,7 +879,7 @@ import ( func main() { // Парсинг конфигурации из YAML-файла - legacyCfg := config.ParseLegacyConfigYAML("") + legacyCfg, _ := config.ParseLegacyConfigYAML("") } ``` @@ -915,7 +931,7 @@ func main() { legacyCfg.SetTimeout(5 * time.Minute) // Создание клиента - legacyClient := decort.NewLegacy(cfg) + legacyClient := decort.NewLegacy(legacyCfg) } ``` @@ -936,12 +952,13 @@ func main() { legacyCfg := config.LegacyConfig{ Username: "", Password: "", + Domain: "dynamix", DecortURL: "https://mr4.digitalenergy.online", Retries: 5, } // Создание клиента - legacyClient := decort.NewLegacy(cfg) + legacyClient := decort.NewLegacy(legacyCfg) // Создание структуры запроса // CreateRequest - реквест на создание виртуальной машины @@ -954,10 +971,322 @@ func main() { } // Выполнение запроса - res, err := client.CloudAPI().KVMX86().Create(context.Background(), req) + res, err := legacyClient.CloudAPI().KVMX86().Create(context.Background(), req) + if err != nil { + log.Fatal(err) + } + + fmt.Println(res) +} +``` + +## Работа с BVS клиентом + +Работа с BVS клиентом применяется для пользователей, которые используют для авторизации BVS. + +### Настройка параметров BVS в кабинете администратора + +Для корректной работы функции обновления токена необходимо соблюдать следующие условия: + +на странице администратора по следующему пути: домены-<имя вашего домена>-(символ i)-токены +- параметр "Время жизни токена доступа" - устанавливается для всего домена. Не применяется есть по следующему пути: безопасность-клиентские_системы-(символ i)-токены-"Время жизни токена доступа" не выставлено иное время, которое имеет больший приоритет для конкретной клиентской системы +- параметр "Время простоя сессии" - время жизни токена обновления. В случае указания количества минут меньше, чем время жизни токена, то обновление токена будет работать некорректно. Редомендуется указывать время или равное или больше времени жизни токена +- параметр "Максимальное время жизни сессии" - время в течение которого возможно производить обновление токена. Если данный параметр будет равен времени жизни токена, то обновление токена будет работать некорректно. Редомендуется указывать время больше времени жизни токена + +### Настройка конфигурации BVS клиента + +Сначала, необходимо создать переменную конфигурации клиента. Конфигурация состоит как из обязательных, так и необязательных полей. + +| Поле | Тип | Обязательный | Описание | +| ------------- | --------------------------------------------- | ------------ | ------------------------------------------------------------------ | +| Username | string | Да | username пользователя | +| Password | string | Да | пароль пользователя | +| AppID | string | Да | app_id ключа для выполнения запросов | +| AppSecret | string | Да | app_secret ключ для выполнения запроса | +| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | +| SSOURL | string | Да | URL адрес сервиса аутентификации и авторизации | +| Domain | string | Да | Имя домена | +| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | +| Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений | +| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата | +| Token | struct{} [см. ниже](#описание-структуры-token)| Нет | JWT токен | +| PathCfg | string | Нет | Путь записи конфигурации в файл | +| PathToken | string | Нет | Путь записи токена в файл | +| TimeToRefresh | uint | Нет | Количество минут, за сколько до истечения срока действия токена выполнится его обновление, по умолчанию - 1 минута | + +### Описание структуры token +| Параметр | Тип | Описание | +| ------------ | ------ | ------------------------------- | +| AccessToken | string | Токен | +| TokenType | string | Тип токена | +| RefreshToken | string | Токен для запроса на обновление | +| Expiry | time | Время жизни токена | + +#### Пример конфигурации BVS клиента + +```go +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/config" +) + +func main(){ + // Настройка конфигурации + BvsCfg := config.BVSConfig{ + AppID: "", + AppSecret: "", + Username: "", + Password: "", + SSOURL: "https://bvs-delta.qa.loc:8443", + DecortURL: "https://delta.qa.loc", + Domain: "dynamix", + Retries: 5, + } + + BvsCfg.SetTimeout(5 * time.Minute) +} +``` + +#### Парсинг BVS конфигурации из файла + +Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigBVSJSON` (или `ParseConfigBVSYAML`) из пакета config. +
+*См. пример файлов конфигурации ниже и в директории `samples/`.* + +```go +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/config" +) + +func main() { + // Парсинг конфигурации из YAML-файла + BVSCfg, _ := config.ParseConfigBVSYAML("") +} +``` + +#### Парсинг BVS токена из файла + +Также возможно создать переменную токена из JSON или YAML файла, используя функцию `ParseTokenBVSJSON` (или `ParseTokenBVSYAML`) из пакета config. +
+*См. пример файлов конфигурации ниже и в директории `samples/`.* + +```go +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/config" +) + +func main() { + // Парсинг токена из json-файла + BVSToken, _ := config.ParseTokenBVSJSON("") +} +``` + +#### Пример BVS JSON конфигурации + +```json +{ + "username": "", + "password": "", + "appId": "", + "appSecret": "", + "ssoUrl": "https://bvs-delta.qa.loc:8443", + "decortUrl": "https://delta.qa.loc", + "domain": "dynamix", + "token": { + "access_token": "string_token", + "token_type": "bearer", + "refresh_token": "string_refresh_token", + "expiry": "2023-11-24T12:40:27.954150524+03:00" + }, + "retries": 5, + "sslSkipVerify": true, + "timeout": "5m", + "path_cfg": "config", + "path_token": "token", + "timeToRefresh": 5 +} +``` + +#### Пример BVS YAML конфигурации +```yaml + username: + password: + appId: + appSecret: + ssoUrl: https://bvs-delta.qa.loc:8443 + decortUrl: https://delta.qa.loc + domain: dynamix + token": + access_token: string_token + token_type: bearer + refresh_token: string_refresh_token + expiry: 2023-11-24T12:40:27.954150524+03:00 + retries: 5 + sslSkipVerify: true + timeout: 5m + path_cfg: config + path_token: token + timeToRefresh: 5 +``` +### Создание BVS клиента + +Создание клиента происходит с помощью функции-строителя `NewBVS` из основного пакета `decort-sdk`, для избежания проблем с именами, пакету можно присвоить алиас `decort`. Функция принимает конфигурацию, возвращает структуру `DecortClient`, с помощью которой можно взаимодействовать с платформой. + +#### Пример создания BVS клиента + +```go +package main + +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + decort "repository.basistech.ru/BASIS/decort-golang-sdk" +) + +func main() { + // Настройка конфигурации + BVSCfg := config.BVSConfig{ + Username: "", + Password: "", + AppID: "", + AppSecret: "", + SSOURL: "https://bvs-delta.qa.loc:8443", + DecortURL: "https://mr4.digitalenergy.online", + Domain: "dynamix", + Retries: 5, + } + + BVSCfg.SetTimeout(5 * time.Minute) + + // Создание клиента + BVSClient := decort.NewBVS(BVSCfg) +} +``` + +#### Пример получения BVS токена + +В случае указания значения в переменной конфигурации `PathCfg` токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` + +```go +package main + +import ( + "fmt" + + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + decort "repository.basistech.ru/BASIS/decort-golang-sdk" +) + +func main() { + // Настройка конфигурации + BVSCfg := config.BVSConfig{ + Username: "", + Password: "", + AppID: "", + AppSecret: "", + SSOURL: "https://bvs-delta.qa.loc:8443", + DecortURL: "https://mr4.digitalenergy.online", + Domain: "dynamix", + PathCfg: "config", + Retries: 5, + } + + // Создание клиента + BVSClient := decort.NewBVS(BVSCfg) + + // Выполнение запроса на получение токена + token, err := BVSClient.GetToken(context.Background()) + if err != nil { + log.Fatal(err) + } + + fmt.Println(token) +} +``` + +#### Пример обновления BVS токена + +В случае указания значения в переменной конфигурации `PathCfg` обновленный токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` + +```go +package main + +import ( + "fmt" + + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + decort "repository.basistech.ru/BASIS/decort-golang-sdk" +) + +func main() { + // Настройка конфигурации + BVSCfg := config.BVSConfig{ + Username: "", + Password: "", + AppID: "", + AppSecret: "", + SSOURL: "https://bvs-delta.qa.loc:8443", + DecortURL: "https://mr4.digitalenergy.online", + Domain: "dynamix", + PathToken: "token", + Retries: 5, + } + + // Создание клиента + BVSClient := decort.NewBVS(BVSCfg) + + // Выполнение запроса на обновление токена + token, err := BVSClient.RefreshToken(context.Background()) + if err != nil { + log.Fatal(err) + } + + fmt.Println(token) +} +``` + +#### Пример выполнения запроса + +```go +package main + +import ( + "fmt" + + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + decort "repository.basistech.ru/BASIS/decort-golang-sdk" +) + +func main() { + // Настройка конфигурации + BVSCfg := config.BVSConfig{ + Username: "", + Password: "", + AppID: "", + AppSecret: "", + SSOURL: "https://bvs-delta.qa.loc:8443", + DecortURL: "https://mr4.digitalenergy.online", + Domain: "dynamix", + Retries: 5, + } + + // Создание клиента + BVSClient := decort.NewBVS(BVSCfg) + + // Создание структуры запроса + // CreateRequest - реквест на создание виртуальной машины + req := kvmx86.CreateRequest{ + RGID: 123, + Name: "compute", + CPU: 4, + RAM: 4096, + ImageID: 321, + } + + // Выполнение запроса + res, err := BVSClient.CloudAPI().KVMX86().Create(context.Background(), req) if err != nil { log.Fatal(err) } fmt.Println(res) } +``` diff --git a/client.go b/client.go index 5e4a66a..730d923 100644 --- a/client.go +++ b/client.go @@ -8,6 +8,7 @@ import ( "io" "mime/multipart" "net/http" + "reflect" "strconv" "strings" "sync" @@ -15,13 +16,12 @@ import ( "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" - k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" - k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s" ) -// HTTP-client for platform +// DecortClient is HTTP-client for platform type DecortClient struct { decortURL string client *http.Client @@ -70,35 +70,54 @@ func (dc *DecortClient) CloudBroker() *cloudbroker.CloudBroker { // DecortApiCall method for sending requests to the platform func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { - k8sCaCreateReq, okCa := params.(k8s_ca.CreateRequest) - k8sCbCreateReq, okCb := params.(k8s_cb.CreateRequest) - var body *bytes.Buffer - var ctype string - - if okCa { - body, ctype = createK8sCloudApi(k8sCaCreateReq) - } else if okCb { - body, ctype = createK8sCloudBroker(k8sCbCreateReq) - } else { - values, err := query.Values(params) - if err != nil { - return nil, err - } - body = bytes.NewBufferString(values.Encode()) + values, err := query.Values(params) + if err != nil { + return nil, err } - req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+"/restmachine"+url, body) + body := bytes.NewBufferString(values.Encode()) + + req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } + // get token if err = dc.getToken(ctx); err != nil { return nil, err } // perform request - var respBytes []byte - respBytes, err = dc.do(req, ctype) + respBytes, err := dc.do(req, "") + if err != nil { + return nil, err + } + + return respBytes, err +} + +// DecortApiCallMP method for sending requests to the platform +func (dc *DecortClient) DecortApiCallMP(ctx context.Context, method, url string, params interface{}) ([]byte, error) { + body, ctype, err := multiPartReq(params) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+constants.Restmachine+url, body) + if err != nil { + return nil, err + } + + // get token + if err = dc.getToken(ctx); err != nil { + return nil, err + } + + // perform request + respBytes, err := dc.do(req, ctype) + if err != nil { + return nil, err + } return respBytes, err } @@ -107,41 +126,53 @@ 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, "/") + // new token is not needed + if dc.cfg.Token != "" && !time.Now().After(dc.expiryTime) { + return nil + } - req, _ := http.NewRequestWithContext(ctx, "POST", dc.cfg.SSOURL+"/v1/oauth/access_token", bodyReader) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + // set up request headers and body + 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) - resp, err := dc.client.Do(req) - if err != nil { - return fmt.Errorf("cannot get token: %w", err) - } + dc.cfg.SSOURL = strings.TrimSuffix(dc.cfg.SSOURL, "/") - tokenBytes, _ := io.ReadAll(resp.Body) - resp.Body.Close() + req, _ := http.NewRequestWithContext(ctx, "POST", dc.cfg.SSOURL+"/v1/oauth/access_token", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - if resp.StatusCode != 200 { - return fmt.Errorf("cannot get token: %s", tokenBytes) - } + // request token + resp, err := dc.client.Do(req) + if err != nil || resp == nil { + return fmt.Errorf("cannot get token: %w", err) + } + defer resp.Body.Close() - token := string(tokenBytes) + var tokenBytes []byte + tokenBytes, err = io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("cannot get token: %w", err) + } - dc.cfg.Token = token - dc.expiryTime = time.Now().AddDate(0, 0, 1) + if resp.StatusCode != 200 { + return fmt.Errorf("cannot get token: %s", tokenBytes) } + // save token in config + token := string(tokenBytes) + dc.cfg.Token = token + dc.expiryTime = time.Now().AddDate(0, 0, 1) + return nil } +// do method performs request and returns response as an array of bytes and nil error in case of response status code 200. +// In any other cases do returns nil response and error. +// Retries are implemented in case of connection reset errors. func (dc *DecortClient) do(req *http.Request, ctype string) ([]byte, error) { + // set up request headers and body + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if ctype != "" { - req.Header.Add("Content-Type", ctype) - } else { - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Content-Type", ctype) } req.Header.Add("Authorization", "bearer "+dc.cfg.Token) @@ -152,13 +183,51 @@ func (dc *DecortClient) do(req *http.Request, ctype string) ([]byte, error) { return nil, err } + req.Body.Close() req.Body = io.NopCloser(bytes.NewBuffer(buf)) resp, err := dc.client.Do(req) - if err != nil || resp == nil { + if resp != nil { + defer resp.Body.Close() + } + + // retries logic GOES HERE + // get http response + //var resp *http.Response + //for i := uint64(0); i < dc.cfg.Retries; i++ { + // req := req.Clone(req.Context()) + // req.Body = io.NopCloser(bytes.NewBuffer(buf)) + // + // if i > 0 { + // time.Sleep(5 * time.Second) // no time sleep for the first request + // } + // + // resp, err = dc.client.Do(req) + // + // // stop retries on success and close response body + // if resp != nil { + // defer resp.Body.Close() + // } + // if err == nil { + // break + // } + // + // // retries in case of connection errors with time sleep + // if isConnectionError(err) { + // continue + // } + // + // // return error in case of non-connection error + // return nil, err + //} + + // handle http request errors + if err != nil { return nil, err } - defer resp.Body.Close() + if resp == nil { + return nil, fmt.Errorf("got empty response without error") + } // handle successful request respBytes, _ := io.ReadAll(resp.Body) @@ -171,230 +240,100 @@ func (dc *DecortClient) do(req *http.Request, ctype string) ([]byte, error) { return nil, fmt.Errorf("could not execute request: %w", err) } -func createK8sCloudApi(req k8s_ca.CreateRequest) (*bytes.Buffer, string) { +// isConnectionError checks if given error falls within specific and associated connection errors +//func isConnectionError(err error) bool { +// if strings.Contains(err.Error(), "connection reset by peer") { +// return true +// } +// if errors.Is(err, io.EOF) { +// return true +// } +// +// return false +//} + +// multiPartReq writes the request structure to the request body, and also returns string of the content-type +func multiPartReq(params interface{}) (*bytes.Buffer, string, error) { reqBody := &bytes.Buffer{} writer := multipart.NewWriter(reqBody) - if req.OidcCertificate != "" { - part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") - _, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) - } - - _ = writer.WriteField("name", req.Name) - _ = writer.WriteField("rgId", strconv.FormatUint(req.RGID, 10)) - _ = writer.WriteField("k8ciId", strconv.FormatUint(req.K8SCIID, 10)) - _ = writer.WriteField("workerGroupName", req.WorkerGroupName) - _ = writer.WriteField("networkPlugin", req.NetworkPlugin) - - if req.MasterSEPID != 0 { - _ = writer.WriteField("masterSepId", strconv.FormatUint(req.MasterSEPID, 10)) - } - if req.MasterSEPPool != "" { - _ = writer.WriteField("masterSepPool", req.MasterSEPPool) - } - if req.WorkerSEPID != 0 { - _ = writer.WriteField("workerSepId", strconv.FormatUint(req.WorkerSEPID, 10)) - } - if req.WorkerSEPPool != "" { - _ = writer.WriteField("workerSepPool", req.WorkerSEPPool) - } - - if req.Labels != nil { - for _, v := range req.Labels { - _ = writer.WriteField("labels", v) - } - } - if req.Taints != nil { - for _, v := range req.Taints { - _ = writer.WriteField("taints", v) + values := reflect.ValueOf(params) + types := values.Type() + defer writer.Close() + for i := 0; i < values.NumField(); i++ { + if !values.Field(i).IsValid() { + continue } - } - if req.Annotations != nil { - for _, v := range req.Annotations { - _ = writer.WriteField("annotations", v) + + if values.Field(i).IsZero() { + continue } - } - if req.MasterCPU != 0 { - _ = writer.WriteField("masterCpu", strconv.FormatUint(uint64(req.MasterCPU), 10)) - } - if req.MasterNum != 0 { - _ = writer.WriteField("masterNum", strconv.FormatUint(uint64(req.MasterNum), 10)) - } - if req.MasterRAM != 0 { - _ = writer.WriteField("masterRam", strconv.FormatUint(uint64(req.MasterRAM), 10)) - } - if req.MasterDisk != 0 { - _ = writer.WriteField("masterDisk", strconv.FormatUint(uint64(req.MasterDisk), 10)) - } - if req.WorkerCPU != 0 { - _ = writer.WriteField("workerCpu", strconv.FormatUint(uint64(req.WorkerCPU), 10)) - } - if req.WorkerNum != 0 { - _ = writer.WriteField("workerNum", strconv.FormatUint(uint64(req.WorkerNum), 10)) - } - if req.WorkerRAM != 0 { - _ = writer.WriteField("workerRam", strconv.FormatUint(uint64(req.WorkerRAM), 10)) - } - if req.WorkerDisk != 0 { - _ = writer.WriteField("workerDisk", strconv.FormatUint(uint64(req.WorkerDisk), 10)) - } - if req.ExtNetID != 0 { - _ = writer.WriteField("extnetId", strconv.FormatUint(req.ExtNetID, 10)) - } - if req.VinsId != 0 { - _ = writer.WriteField("vinsId", strconv.FormatUint(req.VinsId, 10)) - } - if !req.WithLB { - _ = writer.WriteField("withLB", strconv.FormatBool(req.WithLB)) - } + if file, ok := constants.FileName[types.Field(i).Name]; ok { + part, err := writer.CreateFormFile(trimString(types.Field(i)), file) + if err != nil { + return &bytes.Buffer{}, "", err + } + _, err = io.Copy(part, strings.NewReader(valueToString(values.Field(i).Interface()))) + if err != nil { + return &bytes.Buffer{}, "", err + } + continue + } - _ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(req.HighlyAvailable)) + if values.Field(i).Type().Kind() == reflect.Slice { + switch slice := values.Field(i).Interface().(type) { + case []string: + for _, val := range slice { + err := writer.WriteField(trimString(types.Field(i)), val) + if err != nil { + return &bytes.Buffer{}, "", err + } + } + case []uint: + for _, val := range slice { + err := writer.WriteField(trimString(types.Field(i)), strconv.FormatUint(uint64(val), 10)) + if err != nil { + return &bytes.Buffer{}, "", err + } + } + case []uint64: + for _, val := range slice { + err := writer.WriteField(trimString(types.Field(i)), strconv.FormatUint(val, 10)) + if err != nil { + return &bytes.Buffer{}, "", err + } + } + default: + return &bytes.Buffer{}, "", fmt.Errorf("unsupported slice type:%T", slice) + } + + continue + } - if req.AdditionalSANs != nil { - for _, v := range req.AdditionalSANs { - _ = writer.WriteField("additionalSANs", v) + err := writer.WriteField(trimString(types.Field(i)), valueToString(values.Field(i).Interface())) + if err != nil { + return &bytes.Buffer{}, "", err } } - if req.InitConfiguration != "" { - _ = writer.WriteField("initConfiguration", req.InitConfiguration) - } - if req.ClusterConfiguration != "" { - _ = writer.WriteField("clusterConfiguration", req.ClusterConfiguration) - } - if req.KubeletConfiguration != "" { - _ = writer.WriteField("kubeletConfiguration", req.KubeletConfiguration) - } - if req.KubeProxyConfiguration != "" { - _ = writer.WriteField("kubeProxyConfiguration", req.KubeProxyConfiguration) - } - if req.JoinConfiguration != "" { - _ = writer.WriteField("joinConfiguration", req.JoinConfiguration) - } - if req.Description != "" { - _ = writer.WriteField("desc", req.Description) - } - if req.UserData != "" { - _ = writer.WriteField("userData", req.UserData) - } - - _ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) - ct := writer.FormDataContentType() - writer.Close() - - return reqBody, ct + return reqBody, ct, nil } -func createK8sCloudBroker(req k8s_cb.CreateRequest) (*bytes.Buffer, string) { - reqBody := &bytes.Buffer{} - writer := multipart.NewWriter(reqBody) - if req.OidcCertificate != "" { - part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") - _, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) - } - - _ = writer.WriteField("name", req.Name) - _ = writer.WriteField("rgId", strconv.FormatUint(req.RGID, 10)) - _ = writer.WriteField("k8ciId", strconv.FormatUint(req.K8CIID, 10)) - _ = writer.WriteField("workerGroupName", req.WorkerGroupName) - _ = writer.WriteField("networkPlugin", req.NetworkPlugin) - - if req.MasterSEPID != 0 { - _ = writer.WriteField("masterSepId", strconv.FormatUint(req.MasterSEPID, 10)) +func valueToString(a any) string { + switch str := a.(type) { + case string: + return str + case uint: + return strconv.FormatUint(uint64(str), 10) + case uint64: + return strconv.FormatUint(str, 10) + case bool: + return strconv.FormatBool(str) + default: + return "" } - if req.MasterSEPPool != "" { - _ = writer.WriteField("masterSepPool", req.MasterSEPPool) - } - if req.WorkerSEPID != 0 { - _ = writer.WriteField("workerSepId", strconv.FormatUint(req.WorkerSEPID, 10)) - } - if req.WorkerSEPPool != "" { - _ = writer.WriteField("workerSepPool", req.WorkerSEPPool) - } - - if req.Labels != nil { - for _, v := range req.Labels { - _ = writer.WriteField("labels", v) - } - } - if req.Taints != nil { - for _, v := range req.Taints { - _ = writer.WriteField("taints", v) - } - } - if req.Annotations != nil { - for _, v := range req.Annotations { - _ = writer.WriteField("annotations", v) - } - } - - if req.MasterCPU != 0 { - _ = writer.WriteField("masterCpu", strconv.FormatUint(req.MasterCPU, 10)) - } - if req.MasterNum != 0 { - _ = writer.WriteField("masterNum", strconv.FormatUint(req.MasterNum, 10)) - } - if req.MasterRAM != 0 { - _ = writer.WriteField("masterRam", strconv.FormatUint(req.MasterRAM, 10)) - } - if req.MasterDisk != 0 { - _ = writer.WriteField("masterDisk", strconv.FormatUint(req.MasterDisk, 10)) - } - if req.WorkerCPU != 0 { - _ = writer.WriteField("workerCpu", strconv.FormatUint(req.WorkerCPU, 10)) - } - if req.WorkerNum != 0 { - _ = writer.WriteField("workerNum", strconv.FormatUint(req.WorkerNum, 10)) - } - if req.WorkerRAM != 0 { - _ = writer.WriteField("workerRam", strconv.FormatUint(req.WorkerRAM, 10)) - } - if req.WorkerDisk != 0 { - _ = writer.WriteField("workerDisk", strconv.FormatUint(req.WorkerDisk, 10)) - } - if req.ExtNetID != 0 { - _ = writer.WriteField("extnetId", strconv.FormatUint(req.ExtNetID, 10)) - } - if req.VinsId != 0 { - _ = writer.WriteField("vinsId", strconv.FormatUint(req.VinsId, 10)) - } - if !req.WithLB { - _ = writer.WriteField("withLB", strconv.FormatBool(req.WithLB)) - } - - _ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(req.HighlyAvailable)) - - if req.AdditionalSANs != nil { - for _, v := range req.AdditionalSANs { - _ = writer.WriteField("additionalSANs", v) - } - } - if req.InitConfiguration != "" { - _ = writer.WriteField("initConfiguration", req.InitConfiguration) - } - if req.ClusterConfiguration != "" { - _ = writer.WriteField("clusterConfiguration", req.ClusterConfiguration) - } - if req.KubeletConfiguration != "" { - _ = writer.WriteField("kubeletConfiguration", req.KubeletConfiguration) - } - if req.KubeProxyConfiguration != "" { - _ = writer.WriteField("kubeProxyConfiguration", req.KubeProxyConfiguration) - } - if req.JoinConfiguration != "" { - _ = writer.WriteField("joinConfiguration", req.JoinConfiguration) - } - if req.Description != "" { - _ = writer.WriteField("desc", req.Description) - } - if req.UserData != "" { - _ = writer.WriteField("userData", req.UserData) - } - - _ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) - - ct := writer.FormDataContentType() +} - writer.Close() - return reqBody, ct +func trimString(el reflect.StructField) string { + return strings.TrimSuffix(el.Tag.Get("url"), ",omitempty") } diff --git a/client_bvs.go b/client_bvs.go new file mode 100644 index 0000000..80c449c --- /dev/null +++ b/client_bvs.go @@ -0,0 +1,380 @@ +package decortsdk + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "sync" + "time" + + "github.com/google/go-querystring/query" + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" + "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" + "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" +) + +// BVSDecortClient is HTTP-client for platform +type BVSDecortClient struct { + client *http.Client + cfg config.BVSConfig + mutex *sync.Mutex + decortURL string +} + +type tokenJSON struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + ExpiresIn uint64 `json:"expires_in"` +} + +// Сlient builder +func NewBVS(cfg config.BVSConfig) *BVSDecortClient { + if cfg.Retries == 0 { + cfg.Retries = 5 + } + if cfg.TimeToRefresh == 0 { + cfg.TimeToRefresh = 1 + } + + return &BVSDecortClient{ + decortURL: cfg.DecortURL, + client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + //nolint:gosec + InsecureSkipVerify: cfg.SSLSkipVerify, + }, + }, + }, + cfg: cfg, + mutex: &sync.Mutex{}, + } +} + +// CloudAPI builder +func (bdc *BVSDecortClient) CloudAPI() *cloudapi.CloudAPI { + return cloudapi.New(bdc) +} + +// CloudBroker builder +func (bdc *BVSDecortClient) CloudBroker() *cloudbroker.CloudBroker { + return cloudbroker.New(bdc) +} + +// DecortApiCall method for sending requests to the platform +func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { + values, err := query.Values(params) + if err != nil { + return nil, err + } + + body := bytes.NewBufferString(values.Encode()) + + req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body) + if err != nil { + return nil, err + } + + // get token + if bdc.cfg.Token.AccessToken == "" { + if _, err = bdc.GetToken(ctx); err != nil { + return nil, err + } + } + + // refresh token + if bdc.cfg.Token.RefreshToken != "" && bdc.cfg.Token.Expiry.Add(-time.Duration(bdc.cfg.TimeToRefresh)*time.Minute).Before(time.Now()) { + if _, err := bdc.RefreshToken(ctx); err != nil { + if _, err = bdc.GetToken(ctx); err != nil { + return nil, err + } + } + } + + // perform request + reqCopy := req.Clone(ctx) + respBytes, err := bdc.do(req, "") + if err == nil { + return respBytes, nil + } + + // get token and retry in case of access denied + if err.Error() == "access is denied" { + _, err = bdc.GetToken(ctx) + if err != nil { + return nil, err + } + + respBytes, err = bdc.do(reqCopy, "") + if err != nil { + return nil, err + } + } + + return respBytes, err +} + +func (bdc *BVSDecortClient) DecortApiCallMP(ctx context.Context, method, url string, params interface{}) ([]byte, error) { + body, ctype, err := multiPartReq(params) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body) + if err != nil { + return nil, err + } + + // get token + if bdc.cfg.Token.AccessToken == "" { + if _, err = bdc.GetToken(ctx); err != nil { + return nil, err + } + } + + // refresh token + if bdc.cfg.Token.RefreshToken != "" && bdc.cfg.Token.Expiry.Add(-time.Duration(bdc.cfg.TimeToRefresh)*time.Minute).Before(time.Now()) { + if _, err := bdc.RefreshToken(ctx); err != nil { + if _, err = bdc.GetToken(ctx); err != nil { + return nil, err + } + } + } + + // perform request + reqCopy := req.Clone(ctx) + respBytes, err := bdc.do(req, ctype) + if err == nil { + return respBytes, nil + } + + // get token and retry in case of access denied + if err.Error() == "access is denied" { + _, err = bdc.GetToken(ctx) + if err != nil { + return nil, err + } + + respBytes, err = bdc.do(reqCopy, ctype) + if err != nil { + return nil, err + } + } + return respBytes, err +} + +// GetToken allows you to get a token and returns the token structure. When specifying the PathCfg variable, +// the token and configuration will be written to a file. +// When specifying the PathToken variable, the token will be written to a file. +func (bdc *BVSDecortClient) GetToken(ctx context.Context) (config.Token, error) { + bdc.mutex.Lock() + defer bdc.mutex.Unlock() + + // set up request headers and body + body := fmt.Sprintf("grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s&response_type=token&scope=openid", bdc.cfg.AppID, bdc.cfg.AppSecret, bdc.cfg.Username, bdc.cfg.Password) + bodyReader := strings.NewReader(body) + + bdc.cfg.SSOURL = strings.TrimSuffix(bdc.cfg.SSOURL, "/") + + req, _ := http.NewRequestWithContext(ctx, "POST", bdc.cfg.SSOURL+"/realms/"+bdc.cfg.Domain+"/protocol/openid-connect/token", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + // request token + resp, err := bdc.client.Do(req) + if err != nil || resp == nil { + return config.Token{}, fmt.Errorf("cannot get token: %w", err) + } + defer resp.Body.Close() + + var tokenBytes []byte + tokenBytes, err = io.ReadAll(resp.Body) + if err != nil { + return config.Token{}, fmt.Errorf("cannot get token: %w", err) + } + + if resp.StatusCode != 200 { + return config.Token{}, fmt.Errorf("cannot get token: %s", tokenBytes) + } + + // save token in config + var tj tokenJSON + if err = json.Unmarshal(tokenBytes, &tj); err != nil { + return config.Token{}, fmt.Errorf("cannot unmarshal token: %w", err) + } + + bdc.cfg.Token = config.Token{ + AccessToken: tj.AccessToken, + TokenType: tj.TokenType, + RefreshToken: tj.RefreshToken, + Expiry: tj.expiry(), + } + + if bdc.cfg.PathCfg != "" { + ser, _ := bdc.cfg.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathCfg) + } + + if bdc.cfg.PathToken != "" { + ser, _ := bdc.cfg.Token.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathToken) + } + + return bdc.cfg.Token, nil +} + +// RefreshToken allows you to refresh a token and returns the token structure. When specifying the PathCfg variable, +// the token and configuration will be written to a file. +// When specifying the PathToken variable, the token will be written to a file +func (bdc *BVSDecortClient) RefreshToken(ctx context.Context) (config.Token, error) { + bdc.mutex.Lock() + defer bdc.mutex.Unlock() + + // set up request headers and body + body := fmt.Sprintf("grant_type=refresh_token&client_id=%s&client_secret=%s&refresh_token=%s&scope=openid", bdc.cfg.AppID, bdc.cfg.AppSecret, bdc.cfg.Token.RefreshToken) + bodyReader := strings.NewReader(body) + + bdc.cfg.SSOURL = strings.TrimSuffix(bdc.cfg.SSOURL, "/") + + req, _ := http.NewRequestWithContext(ctx, "POST", bdc.cfg.SSOURL+"/realms/"+bdc.cfg.Domain+"/protocol/openid-connect/token", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + // refresh token + resp, err := bdc.client.Do(req) + if err != nil || resp == nil { + return config.Token{}, fmt.Errorf("cannot refresh token: %w", err) + } + defer resp.Body.Close() + + var tokenBytes []byte + tokenBytes, err = io.ReadAll(resp.Body) + if err != nil { + return config.Token{}, fmt.Errorf("cannot refresh token: %w", err) + } + + if resp.StatusCode != 200 { + return config.Token{}, fmt.Errorf("cannot refresh token: %s", tokenBytes) + } + + // save token in config + var tj tokenJSON + if err = json.Unmarshal(tokenBytes, &tj); err != nil { + return config.Token{}, fmt.Errorf("cannot unmarshal after refresh token: %w", err) + } + + bdc.cfg.Token = config.Token{ + AccessToken: tj.AccessToken, + TokenType: tj.TokenType, + RefreshToken: tj.RefreshToken, + Expiry: tj.expiry(), + } + + if bdc.cfg.PathCfg != "" { + ser, _ := bdc.cfg.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathCfg) + } + + if bdc.cfg.PathToken != "" { + ser, _ := bdc.cfg.Token.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathToken) + } + + return bdc.cfg.Token, nil +} + +func (e *tokenJSON) expiry() (t time.Time) { + if v := e.ExpiresIn; v != 0 { + return time.Now().Add(time.Duration(v) * time.Second) + } + return +} + +// do method performs request and returns response as an array of bytes and nil error in case of response status code 200. +// In any other cases do returns nil response and error. +// Retries are implemented in case of connection reset errors. +func (bdc *BVSDecortClient) do(req *http.Request, ctype string) ([]byte, error) { + // set up request headers and body + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + if ctype != "" { + req.Header.Set("Content-Type", ctype) + } + + req.Header.Add("Authorization", "bearer "+bdc.cfg.Token.AccessToken) + req.Header.Set("Accept", "application/json") + + buf, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } + + req.Body.Close() + req.Body = io.NopCloser(bytes.NewBuffer(buf)) + + resp, err := bdc.client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + + // retries logic GOES HERE + // get http response + //var resp *http.Response + //for i := uint64(0); i < bdc.cfg.Retries; i++ { + // req := req.Clone(req.Context()) + // req.Body = io.NopCloser(bytes.NewBuffer(buf)) + // + // if i > 0 { + // time.Sleep(5 * time.Second) // no time sleep for the first request + // } + // + // resp, err = bdc.client.Do(req) + // + // // stop retries on success and close response body + // if resp != nil { + // defer resp.Body.Close() + // } + // if err == nil { + // break + // } + // + // // retries in case of connection errors with time sleep + // if isConnectionError(err) { + // continue + // } + // + // // return error in case of non-connection error + // return nil, err + //} + + // handle http request errors + if err != nil { + return nil, err + } + if resp == nil { + return nil, fmt.Errorf("got empty response without error") + } + + var respBytes []byte + respBytes, err = io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // handle access denied and successful request + if resp.StatusCode == 401 { + return respBytes, errors.New("access is denied") + } + if resp.StatusCode == 200 { + return respBytes, nil + } + + // handle errors with other status codes + err = fmt.Errorf("%s", respBytes) + return nil, fmt.Errorf("could not execute request: %w", err) +} diff --git a/config/config_bvs.go b/config/config_bvs.go new file mode 100644 index 0000000..9182fcb --- /dev/null +++ b/config/config_bvs.go @@ -0,0 +1,216 @@ +package config + +import ( + "encoding/json" + "os" + "time" + + "gopkg.in/yaml.v3" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/serialization" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +type BVSConfig struct { + // ServiceAccount username + // Required: true + // Example : "osh_mikoev" + Username string `json:"username" yaml:"username" validate:"required"` + + // ServiceAccount password + // Required: true + // Example: "[1o>hYkjnJr)HI78q7t&#%8Lm" + Password string `json:"password" yaml:"password" validate:"required"` + + // Domain name + // Required: true + // Example: "dynamix" + Domain string `json:"domain" yaml:"domain" validate:"required"` + + // Application (client) identifier for authorization + // in the cloud platform controller in oauth2 mode. + // Required: true + // Example: "ewqfrvea7s890avw804389qwguf234h0otfi3w4eiu" + AppID string `json:"appId" yaml:"appId" validate:"required"` + + // Application (client) secret code for authorization + // in the cloud platform controller in oauth2 mode. + // Example: "frvet09rvesfis0c9erv9fsov0vsdfi09ovds0f" + AppSecret string `json:"appSecret" yaml:"appSecret" validate:"required"` + + // Platform authentication service address + // Required: true + // Example: "https://sso.digitalenergy.online" + SSOURL string `json:"ssoUrl" yaml:"ssoUrl" validate:"url"` + + // The address of the platform on which the actions are planned + // Required: true + // Example: "https://mr4.digitalenergy.online" + DecortURL string `json:"decortUrl" yaml:"decortUrl" validate:"url"` + + // JWT platform token + // Required: false + // Example: "qwqwdfwv68979we0q9bfv7e9sbvd89798qrwv97ff" + Token Token `json:"token" yaml:"token"` + + // Amount platform request attempts + // Default value: 5 + // Required: false + Retries uint64 `json:"retries" yaml:"retries"` + + // Skip verify + // Required: false + SSLSkipVerify bool `json:"sslSkipVerify" yaml:"sslSkipVerify"` + + // HTTP client timeout, unlimited if left empty + // Required: false + Timeout Duration `json:"timeout" yaml:"timeout"` + + // The path of the configuration file entry + // Required: false + PathCfg string `json:"path_cfg" yaml:"path_cfg"` + + // The path of the token file entry + // Required: false + PathToken string `json:"path_token" yaml:"path_token"` + + // The number of minutes before the expiration of the token, a refresh will be made + // Required: false + TimeToRefresh int64 `json:"timeToRefresh" yaml:"timeToRefresh"` +} + +type Token struct { + // AccessToken is the token that authorizes and authenticates + // the requests. + // Required: false + AccessToken string `json:"access_token" yaml:"access_token"` + + // TokenType is the type of token. + // The Type method returns either this or "Bearer", the default. + // Required: false + TokenType string `json:"token_type" yaml:"token_type"` + + // RefreshToken is a token that's used by the application + // (as opposed to the user) to refresh the access token + // if it expires. + // Required: false + RefreshToken string `json:"refresh_token" yaml:"refresh_token"` + + // Expiry is the optional expiration time of the access token. + // Required: false + Expiry time.Time `json:"expiry" yaml:"expiry"` +} + +// SetTimeout is used to set HTTP client timeout. +func (c *BVSConfig) SetTimeout(dur time.Duration) { + c.Timeout = Duration(dur) +} + +// ParseConfigJSON parses Config from specified JSON-formatted file. +func ParseConfigBVSJSON(path string) (BVSConfig, error) { + file, err := os.ReadFile(path) + if err != nil { + return BVSConfig{}, err + } + + var config BVSConfig + + err = json.Unmarshal(file, &config) + if err != nil { + return BVSConfig{}, err + } + + err = validators.ValidateConfig(config) + if err != nil { + return BVSConfig{}, validators.ValidationErrors(validators.GetErrors(err)) + } + + return config, nil +} + +// ParseConfigJSON parses Token from specified JSON-formatted file. +func ParseTokenBVSJSON(path string) (Token, error) { + file, err := os.ReadFile(path) + if err != nil { + return Token{}, err + } + + var token Token + + err = json.Unmarshal(file, &token) + if err != nil { + return Token{}, err + } + + err = validators.ValidateConfig(token) + if err != nil { + return Token{}, validators.ValidationErrors(validators.GetErrors(err)) + } + + return token, nil +} + +// ParseTokenBVSYAML parses Token from specified YAML-formatted file. +func ParseTokenBVSYAML(path string) (Token, error) { + file, err := os.ReadFile(path) + if err != nil { + return Token{}, err + } + + var token Token + + err = yaml.Unmarshal(file, &token) + if err != nil { + return Token{}, err + } + + err = validators.ValidateConfig(token) + if err != nil { + return Token{}, validators.ValidationErrors(validators.GetErrors(err)) + } + + return token, nil +} + +// ParseConfigYAML parses Config from specified YAML-formatted file. +func ParseConfigBVSYAML(path string) (BVSConfig, error) { + file, err := os.ReadFile(path) + if err != nil { + return BVSConfig{}, err + } + + var config BVSConfig + + err = yaml.Unmarshal(file, &config) + if err != nil { + return BVSConfig{}, err + } + + err = validators.ValidateConfig(config) + if err != nil { + return BVSConfig{}, validators.ValidationErrors(validators.GetErrors(err)) + } + + return config, nil +} + +func (t Token) Serialize(params ...string) (serialization.Serialized, error) { + if len(params) > 1 { + prefix := params[0] + indent := params[1] + + return json.MarshalIndent(t, prefix, indent) + } + + return json.Marshal(t) +} + +func (c BVSConfig) Serialize(params ...string) (serialization.Serialized, error) { + if len(params) > 1 { + prefix := params[0] + indent := params[1] + + return json.MarshalIndent(c, prefix, indent) + } + + return json.Marshal(c) +} diff --git a/config/timeouts.go b/config/timeouts.go index 89a12eb..924158a 100644 --- a/config/timeouts.go +++ b/config/timeouts.go @@ -22,6 +22,8 @@ func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { } *d = Duration(tmp) return nil + case float64: + return nil default: return fmt.Errorf("invalid duration %v", value) } @@ -40,6 +42,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error { } *d = Duration(tmp) return nil + case float64: + return nil default: return fmt.Errorf("invalid duration %v", value) } diff --git a/go.mod b/go.mod index beb7e0e..18fb1e0 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,10 @@ require ( require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index a641cd7..52f813c 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,9 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -23,12 +24,12 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/interfaces/caller.go b/interfaces/caller.go index 82e4307..c40b0ba 100644 --- a/interfaces/caller.go +++ b/interfaces/caller.go @@ -6,4 +6,7 @@ import "context" type Caller interface { // DecortApiCall method for sending requests to the platform DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) + + // DecortApiCallMP method for sending requests to the platform + DecortApiCallMP(ctx context.Context, method, url string, params interface{}) ([]byte, error) } diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..b48202e --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,7 @@ +package constants + +const Restmachine = "/restmachine" + +var FileName = map[string]string{ + "OidcCertificate": "ca.crt", +} diff --git a/legacy-client.go b/legacy-client.go index 07b07e4..01293cd 100644 --- a/legacy-client.go +++ b/legacy-client.go @@ -6,23 +6,20 @@ import ( "crypto/tls" "fmt" "io" - "mime/multipart" "net/http" "net/url" - "strconv" "strings" "sync" "time" "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" - k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" - k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s" ) -// Legacy HTTP-client for platform +// LegacyDecortClient is Legacy HTTP-client for platform type LegacyDecortClient struct { decortURL string client *http.Client @@ -71,334 +68,168 @@ func (ldc *LegacyDecortClient) CloudBroker() *cloudbroker.CloudBroker { // DecortApiCall method for sending requests to the platform func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { + // get token if err := ldc.getToken(ctx); err != nil { return nil, err } - k8sCaCreateReq, okCa := params.(k8s_ca.CreateRequest) - k8sCbCreateReq, okCb := params.(k8s_cb.CreateRequest) - - var body *bytes.Buffer - var ctype string - - if okCa { - body, ctype = createK8sCloudApiLegacy(k8sCaCreateReq, ldc.cfg.Token) - } else if okCb { - body, ctype = createK8sCloudBrokerLegacy(k8sCbCreateReq, ldc.cfg.Token) - } else { - values, err := query.Values(params) - if err != nil { - return nil, err - } - body = bytes.NewBufferString(values.Encode() + fmt.Sprintf("&authkey=%s", ldc.cfg.Token)) + values, err := query.Values(params) + if err != nil { + return nil, err } - req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+"/restmachine"+url, body) + body := bytes.NewBufferString(values.Encode() + fmt.Sprintf("&authkey=%s", ldc.cfg.Token)) + + req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } // perform request - var respBytes []byte - respBytes, err = ldc.do(req, ctype) - - return respBytes, err -} - -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) + respBytes, err := ldc.do(req, "") + if err != nil { + return nil, err } - return nil + return respBytes, err } -func (ldc *LegacyDecortClient) do(req *http.Request, ctype string) ([]byte, error) { - if ctype != "" { - req.Header.Add("Content-Type", ctype) - } else { - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") +func (ldc *LegacyDecortClient) DecortApiCallMP(ctx context.Context, method, url string, params interface{}) ([]byte, error) { + body, ctype, err := multiPartReq(params) + if err != nil { + return nil, err } - req.Header.Set("Accept", "application/json") - buf, err := io.ReadAll(req.Body) + req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } - req.Body.Close() - req.Body = io.NopCloser(bytes.NewBuffer(buf)) - resp, err := ldc.client.Do(req) - if err != nil || resp == nil { + // get token + if err = ldc.getToken(ctx); err != nil { return nil, err } - defer resp.Body.Close() - // handle successful request - respBytes, err := io.ReadAll(resp.Body) + // perform request + respBytes, err := ldc.do(req, ctype) if err != nil { return nil, err } - if resp.StatusCode == 200 { - return respBytes, nil - } - // handle errors with status code other than 200 - err = fmt.Errorf("%s", respBytes) - return nil, fmt.Errorf("could not execute request: %w", err) + return respBytes, err } -func createK8sCloudApiLegacy(req k8s_ca.CreateRequest, token string) (*bytes.Buffer, string) { - reqBody := &bytes.Buffer{} - writer := multipart.NewWriter(reqBody) - if req.OidcCertificate != "" { - part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") - _, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) +func (ldc *LegacyDecortClient) getToken(ctx context.Context) error { + ldc.mutex.Lock() + defer ldc.mutex.Unlock() + + // new token is not needed + if ldc.cfg.Token != "" && !time.Now().After(ldc.expiryTime) { + return nil } - _ = writer.WriteField("name", req.Name) - _ = writer.WriteField("rgId", strconv.FormatUint(req.RGID, 10)) - _ = writer.WriteField("k8ciId", strconv.FormatUint(req.K8SCIID, 10)) - _ = writer.WriteField("workerGroupName", req.WorkerGroupName) - _ = writer.WriteField("networkPlugin", req.NetworkPlugin) + // set up request headers and body + body := fmt.Sprintf("username=%s&password=%s", url.QueryEscape(ldc.cfg.Username), url.QueryEscape(ldc.cfg.Password)) + bodyReader := strings.NewReader(body) - if req.MasterSEPID != 0 { - _ = writer.WriteField("masterSepId", strconv.FormatUint(req.MasterSEPID, 10)) - } - if req.MasterSEPPool != "" { - _ = writer.WriteField("masterSepPool", req.MasterSEPPool) - } - if req.WorkerSEPID != 0 { - _ = writer.WriteField("workerSepId", strconv.FormatUint(req.WorkerSEPID, 10)) - } - if req.WorkerSEPPool != "" { - _ = writer.WriteField("workerSepPool", req.WorkerSEPPool) - } + req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - if req.Labels != nil { - for _, v := range req.Labels { - _ = writer.WriteField("labels", v) - } - } - if req.Taints != nil { - for _, v := range req.Taints { - _ = writer.WriteField("taints", v) - } - } - if req.Annotations != nil { - for _, v := range req.Annotations { - _ = writer.WriteField("annotations", v) - } + // request token + resp, err := ldc.client.Do(req) + if err != nil || resp == nil { + return fmt.Errorf("cannot get token: %w", err) } + defer resp.Body.Close() - if req.MasterCPU != 0 { - _ = writer.WriteField("masterCpu", strconv.FormatUint(uint64(req.MasterCPU), 10)) - } - if req.MasterNum != 0 { - _ = writer.WriteField("masterNum", strconv.FormatUint(uint64(req.MasterNum), 10)) - } - if req.MasterRAM != 0 { - _ = writer.WriteField("masterRam", strconv.FormatUint(uint64(req.MasterRAM), 10)) - } - if req.MasterDisk != 0 { - _ = writer.WriteField("masterDisk", strconv.FormatUint(uint64(req.MasterDisk), 10)) - } - if req.WorkerCPU != 0 { - _ = writer.WriteField("workerCpu", strconv.FormatUint(uint64(req.WorkerCPU), 10)) - } - if req.WorkerNum != 0 { - _ = writer.WriteField("workerNum", strconv.FormatUint(uint64(req.WorkerNum), 10)) - } - if req.WorkerRAM != 0 { - _ = writer.WriteField("workerRam", strconv.FormatUint(uint64(req.WorkerRAM), 10)) - } - if req.WorkerDisk != 0 { - _ = writer.WriteField("workerDisk", strconv.FormatUint(uint64(req.WorkerDisk), 10)) - } - if req.ExtNetID != 0 { - _ = writer.WriteField("extnetId", strconv.FormatUint(req.ExtNetID, 10)) - } - if req.VinsId != 0 { - _ = writer.WriteField("vinsId", strconv.FormatUint(req.VinsId, 10)) - } - if !req.WithLB { - _ = writer.WriteField("withLB", strconv.FormatBool(req.WithLB)) + var tokenBytes []byte + tokenBytes, err = io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("cannot get token: %w", err) } - _ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(req.HighlyAvailable)) - - if req.AdditionalSANs != nil { - for _, v := range req.AdditionalSANs { - _ = writer.WriteField("additionalSANs", v) - } - } - if req.InitConfiguration != "" { - _ = writer.WriteField("initConfiguration", req.InitConfiguration) - } - if req.ClusterConfiguration != "" { - _ = writer.WriteField("clusterConfiguration", req.ClusterConfiguration) + if resp.StatusCode != 200 { + return fmt.Errorf("cannot get token: %s", tokenBytes) } - if req.KubeletConfiguration != "" { - _ = writer.WriteField("kubeletConfiguration", req.KubeletConfiguration) - } - if req.KubeProxyConfiguration != "" { - _ = writer.WriteField("kubeProxyConfiguration", req.KubeProxyConfiguration) - } - if req.JoinConfiguration != "" { - _ = writer.WriteField("joinConfiguration", req.JoinConfiguration) - } - if req.Description != "" { - _ = writer.WriteField("desc", req.Description) - } - if req.UserData != "" { - _ = writer.WriteField("userData", req.UserData) - } - - _ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) - - _ = writer.WriteField("authkey", token) - ct := writer.FormDataContentType() - writer.Close() + // save token in config + token := string(tokenBytes) + ldc.cfg.Token = token + ldc.expiryTime = time.Now().AddDate(0, 0, 1) - return reqBody, ct + return nil } -func createK8sCloudBrokerLegacy(req k8s_cb.CreateRequest, token string) (*bytes.Buffer, string) { - reqBody := &bytes.Buffer{} - writer := multipart.NewWriter(reqBody) - if req.OidcCertificate != "" { - part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") - _, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) +// do method performs request and returns response as an array of bytes and nil error in case of response status code 200. +// In any other cases do returns nil response and error. +// Retries are implemented in case of connection reset errors. +func (ldc *LegacyDecortClient) do(req *http.Request, ctype string) ([]byte, error) { + // set up request headers and body + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + if ctype != "" { + req.Header.Set("Content-Type", ctype) } - _ = writer.WriteField("name", req.Name) - _ = writer.WriteField("rgId", strconv.FormatUint(req.RGID, 10)) - _ = writer.WriteField("k8ciId", strconv.FormatUint(req.K8CIID, 10)) - _ = writer.WriteField("workerGroupName", req.WorkerGroupName) - _ = writer.WriteField("networkPlugin", req.NetworkPlugin) + req.Header.Set("Accept", "application/json") - if req.MasterSEPID != 0 { - _ = writer.WriteField("masterSepId", strconv.FormatUint(req.MasterSEPID, 10)) - } - if req.MasterSEPPool != "" { - _ = writer.WriteField("masterSepPool", req.MasterSEPPool) - } - if req.WorkerSEPID != 0 { - _ = writer.WriteField("workerSepId", strconv.FormatUint(req.WorkerSEPID, 10)) - } - if req.WorkerSEPPool != "" { - _ = writer.WriteField("workerSepPool", req.WorkerSEPPool) + buf, err := io.ReadAll(req.Body) + if err != nil { + return nil, err } - if req.Labels != nil { - for _, v := range req.Labels { - _ = writer.WriteField("labels", v) - } - } - if req.Taints != nil { - for _, v := range req.Taints { - _ = writer.WriteField("taints", v) - } - } - if req.Annotations != nil { - for _, v := range req.Annotations { - _ = writer.WriteField("annotations", v) - } - } + req.Body.Close() + req.Body = io.NopCloser(bytes.NewBuffer(buf)) - if req.MasterCPU != 0 { - _ = writer.WriteField("masterCpu", strconv.FormatUint(req.MasterCPU, 10)) - } - if req.MasterNum != 0 { - _ = writer.WriteField("masterNum", strconv.FormatUint(req.MasterNum, 10)) - } - if req.MasterRAM != 0 { - _ = writer.WriteField("masterRam", strconv.FormatUint(req.MasterRAM, 10)) - } - if req.MasterDisk != 0 { - _ = writer.WriteField("masterDisk", strconv.FormatUint(req.MasterDisk, 10)) - } - if req.WorkerCPU != 0 { - _ = writer.WriteField("workerCpu", strconv.FormatUint(req.WorkerCPU, 10)) - } - if req.WorkerNum != 0 { - _ = writer.WriteField("workerNum", strconv.FormatUint(req.WorkerNum, 10)) - } - if req.WorkerRAM != 0 { - _ = writer.WriteField("workerRam", strconv.FormatUint(req.WorkerRAM, 10)) - } - if req.WorkerDisk != 0 { - _ = writer.WriteField("workerDisk", strconv.FormatUint(req.WorkerDisk, 10)) - } - if req.ExtNetID != 0 { - _ = writer.WriteField("extnetId", strconv.FormatUint(req.ExtNetID, 10)) - } - if req.VinsId != 0 { - _ = writer.WriteField("vinsId", strconv.FormatUint(req.VinsId, 10)) + resp, err := ldc.client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + + // retries logic GOES HERE + // get http response + //var resp *http.Response + //for i := uint64(0); i < ldc.cfg.Retries; i++ { + // req := req.Clone(req.Context()) + // req.Body = io.NopCloser(bytes.NewBuffer(buf)) + // + // if i > 0 { + // time.Sleep(5 * time.Second) // no time sleep for the first request + // } + // + // resp, err = ldc.client.Do(req) + // + // // stop retries on success and close response body + // if resp != nil { + // defer resp.Body.Close() + // } + // if err == nil { + // break + // } + // + // // retries in case of connection errors with time sleep + // if isConnectionError(err) { + // continue + // } + // + // // return error in case of non-connection error + // return nil, err + //} + + // handle http request errors + if err != nil { + return nil, err } - if !req.WithLB { - _ = writer.WriteField("withLB", strconv.FormatBool(req.WithLB)) + if resp == nil { + return nil, fmt.Errorf("got empty response without error") } - _ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(req.HighlyAvailable)) - - if req.AdditionalSANs != nil { - for _, v := range req.AdditionalSANs { - _ = writer.WriteField("additionalSANs", v) - } - } - if req.InitConfiguration != "" { - _ = writer.WriteField("initConfiguration", req.InitConfiguration) - } - if req.ClusterConfiguration != "" { - _ = writer.WriteField("clusterConfiguration", req.ClusterConfiguration) - } - if req.KubeletConfiguration != "" { - _ = writer.WriteField("kubeletConfiguration", req.KubeletConfiguration) - } - if req.KubeProxyConfiguration != "" { - _ = writer.WriteField("kubeProxyConfiguration", req.KubeProxyConfiguration) - } - if req.JoinConfiguration != "" { - _ = writer.WriteField("joinConfiguration", req.JoinConfiguration) - } - if req.Description != "" { - _ = writer.WriteField("desc", req.Description) - } - if req.UserData != "" { - _ = writer.WriteField("userData", req.UserData) + // handle successful request + respBytes, _ := io.ReadAll(resp.Body) + if resp.StatusCode == 200 { + return respBytes, nil } - _ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) - - _ = writer.WriteField("authkey", token) - - ct := writer.FormDataContentType() - - writer.Close() - return reqBody, ct + // handle errors with status code other than 200 + err = fmt.Errorf("%s", respBytes) + return nil, fmt.Errorf("could not execute request: %w", err) } diff --git a/pkg/cloudapi/bservice/disable.go b/pkg/cloudapi/bservice/disable.go index a17eb32..f19faf0 100644 --- a/pkg/cloudapi/bservice/disable.go +++ b/pkg/cloudapi/bservice/disable.go @@ -24,7 +24,7 @@ func (b BService) Disable(ctx context.Context, req DisableRequest) (bool, error) return false, validators.ValidationErrors(validators.GetErrors(err)) } - url := "/cloudapi/bservice/disable" + url := "/cloudapi/bservice/delete" res, err := b.client.DecortApiCall(ctx, http.MethodPost, url, req) if err != nil { diff --git a/pkg/cloudapi/bservice/models.go b/pkg/cloudapi/bservice/models.go index 7d6c0a6..ab50f75 100644 --- a/pkg/cloudapi/bservice/models.go +++ b/pkg/cloudapi/bservice/models.go @@ -93,7 +93,7 @@ type RecordBasicService struct { // Main information about Compute type ItemCompute struct { // Account ID - AccountID uint64 + AccountID uint64 `json:"accountId"` // Architecture Architecture string `json:"arch"` @@ -168,6 +168,9 @@ type ItemSnapshot struct { Valid bool `json:"valid"` } +// List of Snapshot +type ListSnapshots []ItemSnapshot + // List of Snapshots type ListInfoSnapshots struct { // Data @@ -177,9 +180,6 @@ type ListInfoSnapshots struct { EntryCount uint64 `json:"entryCount"` } -// List of Snapshots inside RecordBasicService -type ListSnapshots []ItemSnapshot - // Main information about Group type RecordGroup struct { // Account ID diff --git a/pkg/cloudapi/compute/models.go b/pkg/cloudapi/compute/models.go index e7000bb..39ea486 100644 --- a/pkg/cloudapi/compute/models.go +++ b/pkg/cloudapi/compute/models.go @@ -380,6 +380,9 @@ type RecordCompute struct { // Name Name string `json:"name"` + // NeedReboot + NeedReboot bool `json:"needReboot"` + // Natable VINS ID NatableVINSID uint64 `json:"natableVinsId"` @@ -857,6 +860,9 @@ type ItemCompute struct { // Name Name string `json:"name"` + // NeedReboot + NeedReboot bool `json:"needReboot"` + // Pinned or not Pinned bool `json:"pinned"` diff --git a/pkg/cloudapi/image/get.go b/pkg/cloudapi/image/get.go index c530ee4..1b86fce 100644 --- a/pkg/cloudapi/image/get.go +++ b/pkg/cloudapi/image/get.go @@ -16,7 +16,7 @@ type GetRequest struct { // If set to False returns only images in status CREATED // Required: false - ShowAll bool `url:"show_all,omitempty" json:"show_all,omitempty"` + ShowAll bool `url:"showAll,omitempty" json:"showAll,omitempty"` } // Get gets image by ID. diff --git a/pkg/cloudapi/k8ci/list.go b/pkg/cloudapi/k8ci/list.go index e437fe8..09013d8 100644 --- a/pkg/cloudapi/k8ci/list.go +++ b/pkg/cloudapi/k8ci/list.go @@ -10,7 +10,7 @@ import ( type ListRequest struct { // Find by ID // Required: false - ByID uint64 `url:"id,omitempty" json:"id,omitempty"` + ByID uint64 `url:"by_id,omitempty" json:"by_id,omitempty"` // Find by name // Required: false diff --git a/pkg/cloudapi/k8s/create.go b/pkg/cloudapi/k8s/create.go index 6c65d97..932cfdd 100644 --- a/pkg/cloudapi/k8s/create.go +++ b/pkg/cloudapi/k8s/create.go @@ -202,7 +202,7 @@ func (k8s K8S) Create(ctx context.Context, req CreateRequest) (string, error) { url := "/cloudapi/k8s/create" - res, err := k8s.client.DecortApiCall(ctx, http.MethodPost, url, req) + res, err := k8s.client.DecortApiCallMP(ctx, http.MethodPost, url, req) if err != nil { return "", err } diff --git a/pkg/cloudapi/k8s/delete.go b/pkg/cloudapi/k8s/delete.go index 7f9fdfe..03176c0 100644 --- a/pkg/cloudapi/k8s/delete.go +++ b/pkg/cloudapi/k8s/delete.go @@ -17,7 +17,7 @@ type DeleteRequest struct { // True if cluster is destroyed permanently. // Otherwise it can be restored from Recycle Bin // Required: true - Permanently bool `url:"permanently" json:"permanently" validate:"required"` + Permanently bool `url:"permanently" json:"permanently"` } // Delete deletes kubernetes cluster diff --git a/pkg/cloudapi/lb/list.go b/pkg/cloudapi/lb/list.go index b897cf3..9c3daea 100644 --- a/pkg/cloudapi/lb/list.go +++ b/pkg/cloudapi/lb/list.go @@ -18,7 +18,7 @@ type ListRequest struct { // Find by account ID // Required: false - AccountID uint64 `url:"accountID,omitempty" json:"accountID,omitempty"` + AccountID uint64 `url:"accountId,omitempty" json:"accountId,omitempty"` // Find by resource group ID // Required: false diff --git a/pkg/cloudapi/lb/restart.go b/pkg/cloudapi/lb/restart.go index 67e148e..eb25cb2 100644 --- a/pkg/cloudapi/lb/restart.go +++ b/pkg/cloudapi/lb/restart.go @@ -13,6 +13,11 @@ type RestartRequest struct { // ID of the load balancer instance to restart // Required: true LBID uint64 `url:"lbId" json:"lbId" validate:"required"` + + // restart secondary and primary nodes sequentially in HA mode + // Default is true + // Required: false + Safe bool `url:"safe" json:"safe"` } // Restart restarts specified load balancer instance diff --git a/pkg/cloudapi/locations/models.go b/pkg/cloudapi/locations/models.go index 2077d0a..87f7b3c 100644 --- a/pkg/cloudapi/locations/models.go +++ b/pkg/cloudapi/locations/models.go @@ -2,6 +2,9 @@ package locations // Main information about locations type ItemLocation struct { + // AuthBroker + AuthBroker []string `json:"authBroker"` + // Grid ID GID uint64 `json:"gid"` diff --git a/pkg/cloudapi/rg/ids.go b/pkg/cloudapi/rg/ids.go index 3a875af..83da393 100644 --- a/pkg/cloudapi/rg/ids.go +++ b/pkg/cloudapi/rg/ids.go @@ -53,3 +53,14 @@ func (lrc ListResourceConsumption) IDs() []uint64 { } return res } + +// IDs gets array of ResourceGroupIDs from ListAffinityGroup struct +func (lag ListAffinityGroups) IDs() []uint64 { + res := make([]uint64, 0, len(lag.Data)) + for _, ag := range lag.Data { + for _, v := range ag { + res = append(res, v...) + } + } + return res +} diff --git a/pkg/cloudapi/rg/models.go b/pkg/cloudapi/rg/models.go index 887504d..22dc36b 100644 --- a/pkg/cloudapi/rg/models.go +++ b/pkg/cloudapi/rg/models.go @@ -331,18 +331,10 @@ type ItemAffinityGroupComputes struct { // List of affinity groups type ListAffinityGroupsComputes []ItemAffinityGroupComputes -// Main information about -type ItemAffinityGroup struct { - ID uint64 `json:"id"` - NodeID uint64 `json:"node_id"` -} - -// List of affinity group -type ListAffinityGroup []ItemAffinityGroup - +// List of affinity groups type ListAffinityGroups struct { // Data - Data []map[string]ListAffinityGroup `json:"data"` + Data []map[string][]uint64 `json:"data"` // Entry count EntryCount uint64 `json:"entryCount"` @@ -485,6 +477,9 @@ type RecordLoadBalancer struct { // Access Control List ACL interface{} `json:"acl"` + // BackendHAIP + BackendHAIP string `json:"backendHAIP"` + // List of Backends Backends ListBackends `json:"backends"` @@ -509,6 +504,9 @@ type RecordLoadBalancer struct { // External network ID ExtNetID uint64 `json:"extnetId"` + // FrontendHAIP + FrontendHAIP string `json:"frontendHAIP"` + // List of frontends Frontends ListFrontends `json:"frontends"` diff --git a/pkg/cloudapi/vins/create_in_rg.go b/pkg/cloudapi/vins/create_in_rg.go index e24f7d4..8d89fa5 100644 --- a/pkg/cloudapi/vins/create_in_rg.go +++ b/pkg/cloudapi/vins/create_in_rg.go @@ -25,7 +25,8 @@ type CreateInRGRequest struct { // External network ID // Required: false - ExtNetID uint64 `url:"extNetId,omitempty" json:"extNetId,omitempty"` + // -1 - not connect to extnet, 0 - auto select, 1+ - extnet ID + ExtNetID int64 `url:"extNetId" json:"extNetId"` // External IP, related only for extNetId >= 0 // Required: false diff --git a/pkg/cloudbroker/audit.go b/pkg/cloudbroker/audit.go new file mode 100644 index 0000000..cec4931 --- /dev/null +++ b/pkg/cloudbroker/audit.go @@ -0,0 +1,10 @@ +package cloudbroker + +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/audit" +) + +// Accessing the Stack method group +func (cb *CloudBroker) Audit() *audit.Audit { + return audit.New(cb.client) +} diff --git a/pkg/cloudbroker/audit/audit.go b/pkg/cloudbroker/audit/audit.go new file mode 100644 index 0000000..17904f3 --- /dev/null +++ b/pkg/cloudbroker/audit/audit.go @@ -0,0 +1,15 @@ +package audit + +import "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces" + +// Structure for creating request to audit +type Audit struct { + client interfaces.Caller +} + +// Builder for audit endpoint +func New(client interfaces.Caller) *Audit { + return &Audit{ + client: client, + } +} diff --git a/pkg/cloudbroker/audit/get.go b/pkg/cloudbroker/audit/get.go new file mode 100644 index 0000000..1894f85 --- /dev/null +++ b/pkg/cloudbroker/audit/get.go @@ -0,0 +1,46 @@ +package audit + +import ( + "context" + "encoding/json" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// GetRequest struct to get information about account +type GetRequest struct { + // Audit GUID + // Required: true + AuditGuid string `url:"auditGuid" json:"auditGuid" validate:"required"` +} + +// Get gets information about audit as a RecordAudit struct +func (a Audit) Get(ctx context.Context, req GetRequest) (*RecordAudit, error) { + res, err := a.GetRaw(ctx, req) + if err != nil { + return nil, err + } + + info := RecordAudit{} + + err = json.Unmarshal(res, &info) + if err != nil { + return nil, err + } + + return &info, nil +} + +// GetRaw gets information about audit as an array of bytes +func (a Audit) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) { + err := validators.ValidateRequest(req) + if err != nil { + return nil, validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/audit/get" + + res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) + return res, err +} diff --git a/pkg/cloudbroker/audit/linked_jobs.go b/pkg/cloudbroker/audit/linked_jobs.go new file mode 100644 index 0000000..cfe17ba --- /dev/null +++ b/pkg/cloudbroker/audit/linked_jobs.go @@ -0,0 +1,46 @@ +package audit + +import ( + "context" + "encoding/json" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// LinkedJobsRequest struct to get information about jobs linked with Audit +type LinkedJobsRequest struct { + // Audit GUID + // Required: true + AuditGuid string `url:"auditGuid" json:"auditGuid" validate:"required"` +} + +// LinkedJobs gets information about Linked Jobs as a ListLinkedJobs struct +func (a Audit) LinkedJobs(ctx context.Context, req LinkedJobsRequest) (*ListLinkedJobs, error) { + res, err := a.GetRawLinkedJobs(ctx, req) + if err != nil { + return nil, err + } + + info := ListLinkedJobs{} + + err = json.Unmarshal(res, &info) + if err != nil { + return nil, err + } + + return &info, nil +} + +// GetRawLinkedJobs gets information about Linked Jobs as an array of bytes +func (a Audit) GetRawLinkedJobs(ctx context.Context, req LinkedJobsRequest) ([]byte, error) { + err := validators.ValidateRequest(req) + if err != nil { + return nil, validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/audit/linkedJobs" + + res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) + return res, err +} diff --git a/pkg/cloudbroker/audit/list.go b/pkg/cloudbroker/audit/list.go new file mode 100644 index 0000000..e7a7cc6 --- /dev/null +++ b/pkg/cloudbroker/audit/list.go @@ -0,0 +1,64 @@ +package audit + +import ( + "context" + "encoding/json" + "net/http" +) + +// ListRequest struct to give list of account audits +type ListRequest struct { + + // Find all audits after point in time (unixtime) + // Required: false + TimestampAt uint64 `url:"timestampAt,omitempty" json:"timestampAt,omitempty"` + + // Find all audits before point in time (unixtime) + // Required: false + TimestampTo uint64 `url:"timestampTo,omitempty" json:"timestampTo,omitempty"` + + // Find by user (Mongo RegExp supported) + // Required: false + User string `url:"user,omitempty" json:"user,omitempty"` + + // Find by api endpoint (Mongo RegExp supported) + // Required: false + Call string `url:"call,omitempty" json:"call,omitempty"` + + // Find by HTTP status code + // Required: false + StatusCode uint64 `url:"statusCode,omitempty" json:"statusCode,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 audit records for the specified account object +func (a Audit) List(ctx context.Context, req ListRequest) (*ListAudits, error) { + res, err := a.ListRaw(ctx, req) + if err != nil { + return nil, err + } + + list := ListAudits{} + + err = json.Unmarshal(res, &list) + if err != nil { + return nil, err + } + + return &list, nil +} + +// ListRaw gets list of audit records an array of bytes +func (a Audit) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) { + url := "/cloudbroker/audit/list" + + res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) + return res, err +} diff --git a/pkg/cloudbroker/audit/models.go b/pkg/cloudbroker/audit/models.go new file mode 100644 index 0000000..73588f9 --- /dev/null +++ b/pkg/cloudbroker/audit/models.go @@ -0,0 +1,102 @@ +package audit + +// Main info about audit +type ItemAudit struct { + // Call + Call string `json:"call"` + + // GUID + GUID string `json:"guid"` + + // Response time + ResponseTime float64 `json:"responsetime"` + + // Status code + StatusCode uint64 `json:"statuscode"` + + // Timestamp + Timestamp float64 `json:"timestamp"` + + // User + User string `json:"user"` +} + +// List of audits +type ListAudits struct { + // Data + Data []ItemAudit `json:"data"` + + // EntryCount + EntryCount uint64 `json:"entryCount"` +} + +// Main info about audit +type RecordAudit struct { + + // Apitask + Apitask string `json:"apitask"` + + // Arguments + Arguments string `json:"args"` + + // Call + Call string `json:"call"` + + // GUID + GUID string `json:"guid"` + + // Kwargs + Kwargs string `json:"kwargs"` + + // RemoteAddr + RemoteAddr string `json:"remote_addr"` + + // Response time + ResponseTime float64 `json:"responsetime"` + + // Result + Result string `json:"result"` + + // Status code + StatusCode uint64 `json:"statuscode"` + + // Tags + Tags string `json:"tags"` + + // Timestamp + Timestamp float64 `json:"timestamp"` + + // TimestampEnd + TimestampEnd float64 `json:"timestampEnd"` + + // User + User string `json:"user"` +} + +// List of Linked Jobs +type ListLinkedJobs []ItemLinkedJobs + +// Main info about Linked Jobs +type ItemLinkedJobs struct { + + // CMD + CMD string `json:"cmd"` + + // NID + NID uint64 `json:"nid"` + + // state + State string `json:"state"` + + // TimeCreate + TimeCreate uint64 `json:"timeCreate"` + + // TimeStart + TimeStart uint64 `json:"timeStart"` + + // TimeStop + TimeStop uint64 `json:"timeStop"` + + // Timeout + Timeout uint64 `json:"timeout"` +} diff --git a/pkg/cloudbroker/compute/delete_custom_fields.go b/pkg/cloudbroker/compute/delete_custom_fields.go new file mode 100644 index 0000000..33b3d71 --- /dev/null +++ b/pkg/cloudbroker/compute/delete_custom_fields.go @@ -0,0 +1,38 @@ +package compute + +import ( + "context" + "net/http" + "strconv" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// DeleteCustomFieldsRequest struct to delete compute's custom 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 { + return false, validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/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 +} diff --git a/pkg/cloudbroker/compute/ids.go b/pkg/cloudbroker/compute/ids.go index f1a7656..3a6b978 100644 --- a/pkg/cloudbroker/compute/ids.go +++ b/pkg/cloudbroker/compute/ids.go @@ -20,8 +20,8 @@ func (lid ListInfoDisks) IDs() []uint64 { // IDs gets array of PFWsIDs from ListPFW struct func (lp ListPFW) IDs() []uint64 { - res := make([]uint64, 0, len(lp)) - for _, p := range lp { + res := make([]uint64, 0, len(lp.Data)) + for _, p := range lp.Data { res = append(res, p.ID) } return res diff --git a/pkg/cloudbroker/compute/models.go b/pkg/cloudbroker/compute/models.go index c48a639..4b39686 100644 --- a/pkg/cloudbroker/compute/models.go +++ b/pkg/cloudbroker/compute/models.go @@ -12,6 +12,14 @@ type RecordACL struct { RGACL ListACL `json:"rgACL"` } +type ListUsers struct { + // Data + Data RecordACL `json:"data"` + + // Entry count + EntryCount uint64 `json:"entryCount"` +} + // ACL information type ItemACL struct { // Explicit @@ -75,6 +83,15 @@ type ItemSnapshot struct { // List of snapshots type ListSnapshots []ItemSnapshot +// List of snapshots +type ListSnapShot struct { + // Data + Data []ItemSnapshot `json:"data"` + + // Entry count + EntryCount uint64 `json:"entryCount"` +} + // Main information about snapshot usage type ItemSnapshotUsage struct { // Count @@ -232,7 +249,13 @@ type ItemPFW struct { } // List port forwards -type ListPFW []ItemPFW +type ListPFW struct { + // Data + Data []ItemPFW `json:"data"` + + // Entry count + EntryCount uint64 `json:"entryCount"` +} // Main information about rule type ItemRule struct { @@ -569,6 +592,9 @@ type InfoCompute struct { // Boot disk size BootDiskSize uint64 `json:"bootdiskSize"` + // cd Image Id + CdImageId uint64 `json:"cdImageId"` + // Clone reference CloneReference uint64 `json:"cloneReference"` @@ -638,6 +664,9 @@ type InfoCompute struct { // Name Name string `json:"name"` + // Need reboot + NeedReboot bool `json:"needReboot"` + // List OS users OSUsers ListOSUsers `json:"osUsers"` @@ -732,7 +761,7 @@ type ItemCompute struct { InfoCompute // Total disk size - TotalDiskSize uint64 `json:"totalDiskSize"` + TotalDiskSize uint64 `json:"totalDisksSize"` // VINS connected VINSConnected uint64 `json:"vinsConnected"` diff --git a/pkg/cloudbroker/compute/pfw_list.go b/pkg/cloudbroker/compute/pfw_list.go index 7d1751e..b6fcd6c 100644 --- a/pkg/cloudbroker/compute/pfw_list.go +++ b/pkg/cloudbroker/compute/pfw_list.go @@ -20,7 +20,7 @@ type PFWListRequest struct { } // PFWList gets compute port forwards list -func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (ListPFW, error) { +func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (*ListPFW, error) { err := validators.ValidateRequest(req) if err != nil { return nil, validators.ValidationErrors(validators.GetErrors(err)) @@ -40,5 +40,5 @@ func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (ListPFW, erro return nil, err } - return list, nil + return &list, nil } diff --git a/pkg/cloudbroker/compute/snapshot_list.go b/pkg/cloudbroker/compute/snapshot_list.go index 02e78f1..453242a 100644 --- a/pkg/cloudbroker/compute/snapshot_list.go +++ b/pkg/cloudbroker/compute/snapshot_list.go @@ -16,7 +16,7 @@ type SnapshotListRequest struct { } // SnapshotList gets list of compute snapshots -func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (ListSnapshots, error) { +func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (*ListSnapShot, error) { err := validators.ValidateRequest(req) if err != nil { return nil, validators.ValidationErrors(validators.GetErrors(err)) @@ -29,12 +29,12 @@ func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (Lis return nil, err } - list := ListSnapshots{} + list := ListSnapShot{} err = json.Unmarshal(res, &list) if err != nil { return nil, err } - return list, nil + return &list, nil } diff --git a/pkg/cloudbroker/compute/user_list.go b/pkg/cloudbroker/compute/user_list.go index 586d9e9..384bae4 100644 --- a/pkg/cloudbroker/compute/user_list.go +++ b/pkg/cloudbroker/compute/user_list.go @@ -16,7 +16,7 @@ type UserListRequest struct { } // UserList gets users list for compute -func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL, error) { +func (c Compute) UserList(ctx context.Context, req UserListRequest) (*ListUsers, error) { err := validators.ValidateRequest(req) if err != nil { return nil, validators.ValidationErrors(validators.GetErrors(err)) @@ -29,7 +29,7 @@ func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL, return nil, err } - list := RecordACL{} + list := ListUsers{} err = json.Unmarshal(res, &list) if err != nil { diff --git a/pkg/cloudbroker/grid/get_diagnosis.go b/pkg/cloudbroker/grid/get_diagnosis.go index f37f1a8..540f2dd 100644 --- a/pkg/cloudbroker/grid/get_diagnosis.go +++ b/pkg/cloudbroker/grid/get_diagnosis.go @@ -2,6 +2,7 @@ package grid import ( "context" + "fmt" "net/http" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" @@ -38,7 +39,7 @@ func (g Grid) GetDiagnosisGET(ctx context.Context, req GetDiagnosisRequest) (str return "", validators.ValidationErrors(validators.GetErrors(err)) } - url := "/cloudbroker/grid/getDiagnosis" + url := fmt.Sprintf("/cloudbroker/grid/getDiagnosis/?gid=%d", req.GID) res, err := g.client.DecortApiCall(ctx, http.MethodGet, url, req) if err != nil { diff --git a/pkg/cloudbroker/grid/models.go b/pkg/cloudbroker/grid/models.go index 0a6f7c1..731b971 100644 --- a/pkg/cloudbroker/grid/models.go +++ b/pkg/cloudbroker/grid/models.go @@ -67,6 +67,9 @@ type DiskUsage struct { // Detailed information about grid type RecordGrid struct { + // AuthBroker + AuthBroker []interface{} `json:"authBroker"` + // Flag Flag string `json:"flag"` @@ -91,6 +94,9 @@ type ItemGridList struct { // Resource information Resources Resources `json:"Resources"` + // AuthBroker + AuthBroker []interface{} `json:"authBroker"` + // Flag Flag string `json:"flag"` diff --git a/pkg/cloudbroker/k8ci/access_add.go b/pkg/cloudbroker/k8ci/access_add.go new file mode 100644 index 0000000..499895a --- /dev/null +++ b/pkg/cloudbroker/k8ci/access_add.go @@ -0,0 +1,36 @@ +package k8ci + +import ( + "context" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// AccessAddRequest struct for adding permission to access to account for a k8ci +type AccessAddRequest struct { + // ID of the K8 catalog item to add access for + // Required: true + K8CIID uint64 `url:"k8ciId" json:"k8ciId" validate:"required"` + + // Account ID to add to the sharedWith access list + // Required: true + AccountId uint64 `url:"accountId" json:"accountId" validate:"required"` +} + +// Add accountId to sharedWith access list for k8ci. +func (k K8CI) AccessAdd(ctx context.Context, req AccessAddRequest) (string, error) { + err := validators.ValidateRequest(req) + if err != nil { + return "", validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/k8ci/accessAdd" + + res, err := k.client.DecortApiCall(ctx, http.MethodPost, url, req) + if err != nil { + return "", err + } + + return string(res), nil +} diff --git a/pkg/cloudbroker/k8ci/access_remove.go b/pkg/cloudbroker/k8ci/access_remove.go new file mode 100644 index 0000000..8b2c2c9 --- /dev/null +++ b/pkg/cloudbroker/k8ci/access_remove.go @@ -0,0 +1,36 @@ +package k8ci + +import ( + "context" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// AccessRemoveRequest struct for removing permission to access to account for a k8ci +type AccessRemoveRequest struct { + // ID of the K8 catalog item to remove access for + // Required: true + K8CIID uint64 `url:"k8ciId" json:"k8ciId" validate:"required"` + + // Account ID to be removed from the sharedWith access list + // Required: true + AccountId uint64 `url:"accountId" json:"accountId" validate:"required"` +} + +// Remove accountId from sharedWith access list for k8ci. +func (k K8CI) AccessRemove(ctx context.Context, req AccessRemoveRequest) (string, error) { + err := validators.ValidateRequest(req) + if err != nil { + return "", validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/k8ci/accessRemove" + + res, err := k.client.DecortApiCall(ctx, http.MethodPost, url, req) + if err != nil { + return "", err + } + + return string(res), nil +} diff --git a/pkg/cloudbroker/k8s/create.go b/pkg/cloudbroker/k8s/create.go index 55b417b..bc1650f 100644 --- a/pkg/cloudbroker/k8s/create.go +++ b/pkg/cloudbroker/k8s/create.go @@ -29,7 +29,7 @@ type CreateRequest struct { WorkerGroupName string `url:"workerGroupName" json:"workerGroupName" validate:"required,workerGroupName"` // Network plugin - // Must be one of these values: flannel, weavenet, calico + // Must be one of these values: flunnel, weavenet, calico // Required: true NetworkPlugin string `url:"networkPlugin" json:"networkPlugin" validate:"required,networkPlugin"` @@ -204,7 +204,7 @@ func (k K8S) Create(ctx context.Context, req CreateRequest) (string, error) { url := "/cloudbroker/k8s/create" - res, err := k.client.DecortApiCall(ctx, http.MethodPost, url, req) + res, err := k.client.DecortApiCallMP(ctx, http.MethodPost, url, req) if err != nil { return "", err } diff --git a/pkg/cloudbroker/lb/make_highly_available.go b/pkg/cloudbroker/lb/make_highly_available.go index e10463b..c034115 100644 --- a/pkg/cloudbroker/lb/make_highly_available.go +++ b/pkg/cloudbroker/lb/make_highly_available.go @@ -29,10 +29,10 @@ func (l LB) HighlyAvailable(ctx context.Context, req HighlyAvailableRequest) (bo return false, err } - result, err := strconv.ParseBool(string(res)) - if err != nil { + result, err := strconv.ParseUint(string(res), 10, 64) + if err != nil || result != req.LBID { return false, err } - return result, nil + return true, nil } diff --git a/pkg/cloudbroker/rg/affinity_groups_list.go b/pkg/cloudbroker/rg/affinity_groups_list.go index a2e2555..ac6e862 100644 --- a/pkg/cloudbroker/rg/affinity_groups_list.go +++ b/pkg/cloudbroker/rg/affinity_groups_list.go @@ -13,6 +13,14 @@ type AffinityGroupsListRequest struct { // Resource group ID // Required: true RGID uint64 `url:"rgId" json:"rgId" 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"` } // AffinityGroupsList gets all currently defined affinity groups in this resource group with compute IDs diff --git a/pkg/cloudbroker/rg/ids.go b/pkg/cloudbroker/rg/ids.go index 1aa2d69..272570c 100644 --- a/pkg/cloudbroker/rg/ids.go +++ b/pkg/cloudbroker/rg/ids.go @@ -53,3 +53,12 @@ func (lpfw ListPFW) IDs() []uint64 { } return res } + +// IDs gets array of ComputeIDs from ListAffinityGroupItems struct +func (lag ListAffinityGroupItems) IDs() []uint64 { + res := make([]uint64, 0, len(lag)) + for _, ag := range lag { + res = append(res, ag.ID) + } + return res +} diff --git a/pkg/cloudbroker/rg/list.go b/pkg/cloudbroker/rg/list.go index 6d6d3f6..46162af 100644 --- a/pkg/cloudbroker/rg/list.go +++ b/pkg/cloudbroker/rg/list.go @@ -36,6 +36,10 @@ type ListRequest struct { // Required: false Status string `url:"status,omitempty" json:"status,omitempty"` + // Find by status lock + // Required: false + LockStatus string `url:"lockStatus,omitempty" json:"lockStatus,omitempty"` + // Included deleted resource groups // Required: false IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"` diff --git a/pkg/cloudbroker/rg/models.go b/pkg/cloudbroker/rg/models.go index e8628ba..26d7a9a 100644 --- a/pkg/cloudbroker/rg/models.go +++ b/pkg/cloudbroker/rg/models.go @@ -65,6 +65,10 @@ type ItemResourceConsumption struct { // Reserved information Reserved Reservation `json:"Reserved"` + // Resource limits + ResourceLimits ResourceLimits `json:"resourceLimits"` + + // Resource group ID RGID uint64 `json:"rgid"` } @@ -603,6 +607,9 @@ type ItemLB struct { // List ACL ACL ListACL `json:"acl"` + // BackendHAIP + BackendHAIP string `json:"backendHAIP"` + // List backends Backends ListBackends `json:"backends"` @@ -627,6 +634,9 @@ type ItemLB struct { // External network ID ExtNetID uint64 `json:"extnetId"` + // FrontendHAIP + FrontendHAIP string `json:"frontendHAIP"` + // List of frontends Frontends ListFrontends `json:"frontends"` @@ -687,8 +697,15 @@ type ListLB struct { type ListAffinityGroup struct { // Data - Data map[string][]uint64 `json:"data"` + Data []map[string]ListAffinityGroupItems `json:"data"` // Entry count EntryCount uint64 `json:"entryCount"` } + +type ListAffinityGroupItems []ItemAffinityGroup + +type ItemAffinityGroup struct { + ID uint64 `json:"id"` + NodeID uint64 `json:"node_id"` +} diff --git a/pkg/cloudbroker/stack/stack.go b/pkg/cloudbroker/stack/stack.go index 94c5d89..a330f68 100644 --- a/pkg/cloudbroker/stack/stack.go +++ b/pkg/cloudbroker/stack/stack.go @@ -1,4 +1,3 @@ -// Lists all the stack. package stack import "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces" diff --git a/pkg/cloudbroker/vins/create_in_rg.go b/pkg/cloudbroker/vins/create_in_rg.go index 7b90b59..90084e3 100644 --- a/pkg/cloudbroker/vins/create_in_rg.go +++ b/pkg/cloudbroker/vins/create_in_rg.go @@ -25,7 +25,8 @@ type CreateInRGRequest struct { // External network ID // Required: false - ExtNetID uint64 `url:"extNetId,omitempty" json:"extNetId,omitempty"` + // -1 - not connect to extnet, 0 - auto select, 1+ - extnet ID + ExtNetID int64 `url:"extNetId" json:"extNetId"` // External IP, related only for extNetId >= 0 // Required: false diff --git a/samples/config/bvs-config.json b/samples/config/bvs-config.json new file mode 100644 index 0000000..7df369b --- /dev/null +++ b/samples/config/bvs-config.json @@ -0,0 +1,21 @@ +{ + "username": "", + "password": "", + "appId": "", + "appSecret": "", + "ssoUrl": "https://bvs-delta.qa.loc:8443", + "decortUrl": "https://delta.qa.loc", + "domain": "dynamix", + "token": { + "access_token": "string_token", + "token_type": "bearer", + "refresh_token": "string_refresh_token", + "expiry": "2023-11-24T12:40:27.954150524+03:00" + }, + "retries": 5, + "sslSkipVerify": true, + "timeout": "5m", + "path_cfg": "config", + "path_token": "token", + "timeToRefresh": 5 +} diff --git a/samples/config/bvs-config.yml b/samples/config/bvs-config.yml new file mode 100644 index 0000000..a49b8d5 --- /dev/null +++ b/samples/config/bvs-config.yml @@ -0,0 +1,18 @@ +username: +password: +appId: +appSecret: +ssoUrl: https://bvs-delta.qa.loc:8443 +decortUrl: https://delta.qa.loc +domain: dynamix +token": +access_token: string_token +token_type: bearer +refresh_token: string_refresh_token +expiry: 2023-11-24T12:40:27.954150524+03:00 +retries: 5 +sslSkipVerify: true +timeout: 5m +path_cfg: config +path_token: token +timeToRefresh: 5