v1.9.3 v1.7.3
loskutovanl 2 years ago
parent cd741b7f11
commit d4065938ac

@ -1,30 +1,7 @@
## Version 1.7.2 ## Version 1.7.3
## Bugfix ## Bugfix
- Fix panic in clients
- Add refresh Token in BVS authorization mode and refactoring bvs_client
- Delete tag validate and omitempty in Permanently field, DeleteRequest model in cloudApi/k8s/delete cloudBroker/k8s/delete
- Add Page and Size fields into model AffinityGroupsListRequest in cloudbroker/rg/affinity_groups_list;
- Change type of Data field from for model ListAffinityGroup in cloudbroker/rg/affinity_groups_list for correct parsing;
- Add new models ListAffinityGroupItems and ItemAffinityGroup to support correct parsing for model ListAffinityGroup in cloudbroker/rg/affinity_groups_list;
- Add missing IDs() method for model ListAffinityGroupItems in cloudbroker/rg/affinity_groups_list;
- Add ResourceLimits field into model ItemResourceConsumption in cloudbroker/rg/models;
- Add LockStatus field into model ListRequest in cloudbroker/rg/list;
- Fix url for cloudbroker/grid/getDiagnosisGet
- Create ListUsers model with Data and EntryCount fields in cloudbroker/compute/models;
- Change type of return structure for UserList method from RecordACL to ListUsers in cloudbroker/compute/user_list;
- Create ListSnapShot model with Data and EntryCount fields in cloudbroker/compute/models;
- Change type of return structure for SnapshotList from ListSnapshots to *ListSnapShot in cloudbroker/compute/snapshot_list;
- Change json tag for TotalDiskSize field for model ItemCompute in cloudbroker/compute/models;
- Add NeedReboot and CdImageId fields for model InfoCompute in cloudbroker/compute/models;
- Reorganize ListPFW model by adding Data and EntryCount fields with json tags in cloudbroker/compute/models;
- Fix IDs() method for ListPFW model in cloudbroker/compute/ids;
- Change type of return structure for PFWList method from ListPFW to *ListPFW in cloudbroker/compute/pfw_list.
- Refactoring clients
## Feature - Add fields FrontendHAIP and BackendHAIP in model ItemLB in cloudbroker/rg and RecordLoadBalancer in cloudapi/rg
- Add endpoints AccessAdd AccessRemote in cloudBroker/k8ci - Add field AuthBroker in ItemLocation model in cloudapi/locations
- Add endpoints Audit in cloudBroker
- Add AuthBroker field to model RecordGrid in cloudbroker/grid/models
- Add AuthBroker field to model ItemGridList in cloudbroker/grid/models
- Add endpoints DeleteCustomFields in cloudbroker/compute

@ -17,29 +17,44 @@ Decort SDK - это библиотека, написанная на языке G
- [Установка](#установка) - [Установка](#установка)
- [Список API](#список-api) - [Список API](#список-api)
- [Cloudapi](#cloudapi) - [Cloudapi](#cloudapi)
- [Cloudbroker](#cloudbroker) - [Cloudbroker](#cloudbroker)
- [Работа с библиотекой](#работа-с-библиотекой) - [Работа с библиотекой](#работа-с-библиотекой)
- [Настройка конфигурации клиента](#настройка-конфигурации-клиента) - [Настройка конфигурации клиента](#настройка-конфигурации-клиента)
- [Пример конфигурации клиента](#пример-конфигурации-клиента) - [Пример конфигурации клиента](#пример-конфигурации-клиента)
- [Парсинг конфигурации из файла](#парсинг-конфигурации-из-файла) - [Парсинг конфигурации из файла](#парсинг-конфигурации-из-файла)
- [Пример JSON конфигурации](#пример-json-конфигурации) - [Пример JSON конфигурации](#пример-json-конфигурации)
- [Пример YAML конфигурации](#пример-yaml-конфигурации) - [Пример YAML конфигурации](#пример-yaml-конфигурации)
- [Создание клиента](#создание-клиента) - [Создание клиента](#создание-клиента)
- [Создание структуры запроса](#cоздание-структуры-запроса) - [Создание структуры запроса](#cоздание-структуры-запроса)
- [Выполнение запроса](#выполнение-запроса) - [Выполнение запроса](#выполнение-запроса)
- [Фильтрация](#фильтрация) - [Фильтрация](#фильтрация)
- [Сортировка](#сортировка) - [Сортировка](#сортировка)
- [Сериализация](#сериализация) - [Сериализация](#сериализация)
- [Получение списка уникальных идентификаторов (ID) объекта](#получение-списка-уникальных-идентификаторов-id-объекта)
- [Работа с legacy клиентом](#работа-с-legacy-клиентом) - [Работа с legacy клиентом](#работа-с-legacy-клиентом)
- [Настройка конфигурации legacy клиента](#настройка-конфигурации-legacy-клиента) - [Настройка конфигурации legacy клиента](#настройка-конфигурации-legacy-клиента)
- [Пример конфигурации legacy клиента](#пример-конфигурации-legacy-клиента) - [Пример конфигурации legacy клиента](#пример-конфигурации-legacy-клиента)
- [Парсинг legacy конфигурации из файла](#парсинг-legacy-конфигурации-из-файла) - [Парсинг legacy конфигурации из файла](#парсинг-legacy-конфигурации-из-файла)
- [Пример legacy JSON конфигурации](#пример-legacy-json-конфигурации) - [Пример legacy JSON конфигурации](#пример-legacy-json-конфигурации)
- [Пример legacy YAML конфигурации](#пример-legacy-yaml-конфигурации) - [Пример legacy YAML конфигурации](#пример-legacy-yaml-конфигурации)
- [Создание legacy клиента](#создание-legacy-клиента) - [Создание legacy клиента](#создание-legacy-клиента)
- [Создание структуры запроса](#cоздание-структуры-запроса) - [Создание структуры запроса](#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-токена)
- [Пример выполнения запроса](#пример-выполнения-запроса)
## Установка ## Установка
@ -114,8 +129,8 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
Алгоритм работы с библиотекой выглядит следующим образом: Алгоритм работы с библиотекой выглядит следующим образом:
1. Выполнение одного из действий: 1. Выполнение одного из действий:
- настройка конфигурации клиента; - настройка конфигурации клиента;
- парсинг конфигурации из файла. - парсинг конфигурации из файла.
2. Создание клиента. 2. Создание клиента.
3. Создание структуры запроса. 3. Создание структуры запроса.
4. Выполнение запроса. 4. Выполнение запроса.
@ -157,7 +172,7 @@ func main(){
#### Парсинг конфигурации из файла #### Парсинг конфигурации из файла
Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigJSON` (или `ParseConfigYAML`) из пакета config. Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigJSON` (или `ParseConfigYAML`) из пакета config.
<br> <br>
*См. пример файлов конфигурации ниже и в директории `samples/`.* *См. пример файлов конфигурации ниже и в директории `samples/`.*
@ -239,48 +254,48 @@ func main() {
В каждом пакете находятся пакеты групп API: В каждом пакете находятся пакеты групп API:
- **cloudapi**: - **cloudapi**:
- `pkg/cloudapi/account` - для `Account` - `pkg/cloudapi/account` - для `Account`
- `pkg/cloudapi/bservice` - для `Basic Service` - `pkg/cloudapi/bservice` - для `Basic Service`
- `pkg/cloudapi/compute` - для `Compute` - `pkg/cloudapi/compute` - для `Compute`
- `pkg/cloudapi/disks` - для `Disks` - `pkg/cloudapi/disks` - для `Disks`
- `pkg/cloudapi/extnet` - для `ExtNet` - `pkg/cloudapi/extnet` - для `ExtNet`
- `pkg/cloudapi/flipgroup` - для `FLIPGroup` - `pkg/cloudapi/flipgroup` - для `FLIPGroup`
- `pkg/cloudapi/image` - для `Image` - `pkg/cloudapi/image` - для `Image`
- `pkg/cloudapi/k8ci` - для `K8CI` - `pkg/cloudapi/k8ci` - для `K8CI`
- `pkg/cloudapi/k8s` - для `K8S` - `pkg/cloudapi/k8s` - для `K8S`
- `pkg/cloudapi/kvmppc` - для `KVMPPC` - `pkg/cloudapi/kvmppc` - для `KVMPPC`
- `pkg/cloudapi/kvmx86` - для `KVMX86` - `pkg/cloudapi/kvmx86` - для `KVMX86`
- `pkg/cloudapi/lb` - для `LB` - `pkg/cloudapi/lb` - для `LB`
- `pkg/cloudapi/locations` - для `Locations` - `pkg/cloudapi/locations` - для `Locations`
- `pkg/cloudapi/rg` - для `RG` - `pkg/cloudapi/rg` - для `RG`
- `pkg/cloudapi/sizes` - для `Sizes` - `pkg/cloudapi/sizes` - для `Sizes`
- `pkg/cloudapi/stack` - для `Stack` - `pkg/cloudapi/stack` - для `Stack`
- `pkg/cloudapi/tasks` - для `Tasks` - `pkg/cloudapi/tasks` - для `Tasks`
- `pkg/cloudapi/vins` - для `VINS` - `pkg/cloudapi/vins` - для `VINS`
- **cloudbroker**: - **cloudbroker**:
- `pkg/cloudbroker/account` - для `Account` - `pkg/cloudbroker/account` - для `Account`
- `pkg/cloudbroker/apiaccess` - для `APIAccess` - `pkg/cloudbroker/apiaccess` - для `APIAccess`
- `pkg/cloudbroker/backup` - для `Backup` - `pkg/cloudbroker/backup` - для `Backup`
- `pkg/cloudbroker/compute` - для `Compute` - `pkg/cloudbroker/compute` - для `Compute`
- `pkg/cloudbroker/disks` - для `Disks` - `pkg/cloudbroker/disks` - для `Disks`
- `pkg/cloudbroker/extnet` - для `ExtNet` - `pkg/cloudbroker/extnet` - для `ExtNet`
- `pkg/cloudbroker/flipgroup` - для `FLIPGroup` - `pkg/cloudbroker/flipgroup` - для `FLIPGroup`
- `pkg/cloudbroker/grid` - для `Grid` - `pkg/cloudbroker/grid` - для `Grid`
- `pkg/cloudbroker/group` - для `Group` - `pkg/cloudbroker/group` - для `Group`
- `pkg/cloudbroker/image` - для `Image` - `pkg/cloudbroker/image` - для `Image`
- `pkg/cloudbroker/k8ci` - для `K8CI` - `pkg/cloudbroker/k8ci` - для `K8CI`
- `pkg/cloudbroker/k8s` - для `K8S` - `pkg/cloudbroker/k8s` - для `K8S`
- `pkg/cloudbroker/kvmppc` - для `KVMPPC` - `pkg/cloudbroker/kvmppc` - для `KVMPPC`
- `pkg/cloudbroker/kvmx86` - для `KVMX86` - `pkg/cloudbroker/kvmx86` - для `KVMX86`
- `pkg/cloudbroker/lb` - для `LB` - `pkg/cloudbroker/lb` - для `LB`
- `pkg/cloudbroker/pcidevice` - для `PCIDevice` - `pkg/cloudbroker/pcidevice` - для `PCIDevice`
- `pkg/cloudbroker/rg` - для `RG` - `pkg/cloudbroker/rg` - для `RG`
- `pkg/cloudbroker/sep` - для `SEP` - `pkg/cloudbroker/sep` - для `SEP`
- `pkg/cloudbroker/stack` - для `Stack` - `pkg/cloudbroker/stack` - для `Stack`
- `pkg/cloudbroker/tasks` - для `Tasks` - `pkg/cloudbroker/tasks` - для `Tasks`
- `pkg/cloudbroker/user` - для `User` - `pkg/cloudbroker/user` - для `User`
- `pkg/cloudbroker/vgpu` - для `VGPU` - `pkg/cloudbroker/vgpu` - для `VGPU`
- `pkg/cloudbroker/vins` - для `VINS` - `pkg/cloudbroker/vins` - для `VINS`
Все поля структуры имеют описание, в которых содержится: Все поля структуры имеют описание, в которых содержится:
@ -421,50 +436,50 @@ func main() {
Доступные методы для `.CloudAPI()`: Доступные методы для `.CloudAPI()`:
- `.Account()` - для работы с `Account` - `.Account()` - для работы с `Account`
- `.BService()` - для работы с `BService` - `.BService()` - для работы с `BService`
- `.Compute()` - для работы с `Compute` - `.Compute()` - для работы с `Compute`
- `.Disks()` - для работы с `Disks` - `.Disks()` - для работы с `Disks`
- `.ExtNet()` - для работы с `ExtNet` - `.ExtNet()` - для работы с `ExtNet`
- `.FLIPgroup()` - для работы с `FLIPGroup` - `.FLIPgroup()` - для работы с `FLIPGroup`
- `.Image()` - для работы с `Image` - `.Image()` - для работы с `Image`
- `.K8CI()` - для работы с `K8CI` - `.K8CI()` - для работы с `K8CI`
- `.K8S()` - для работы с `K8S` - `.K8S()` - для работы с `K8S`
- `.KVMPPC()` - для работы с `KVMPPC` - `.KVMPPC()` - для работы с `KVMPPC`
- `.KVMx86()` - для работы с `KVMX86` - `.KVMx86()` - для работы с `KVMX86`
- `.LB()` - для работы с `LB` - `.LB()` - для работы с `LB`
- `.Locations()` - для работы с `Locations` - `.Locations()` - для работы с `Locations`
- `.RG()` - для работы с `RG` - `.RG()` - для работы с `RG`
- `.Sizes()` - для работы с `Sizes` - `.Sizes()` - для работы с `Sizes`
- `.Stack()` - для работы с `Stack` - `.Stack()` - для работы с `Stack`
- `.Tasks()` - для работы с `Tasks` - `.Tasks()` - для работы с `Tasks`
- `.VINS()` - для работы с `VINS` - `.VINS()` - для работы с `VINS`
Доступные методы для `.CloudBroker()`: Доступные методы для `.CloudBroker()`:
- `.Account()` - для работы с `Account` - `.Account()` - для работы с `Account`
- `.APIAccess()` - для работы с `APIAccess` - `.APIAccess()` - для работы с `APIAccess`
- `.Backup()` - для работы с `Backup` - `.Backup()` - для работы с `Backup`
- `.Compute()` - для работы с `Compute` - `.Compute()` - для работы с `Compute`
- `.Disks()` - для работы с `Disks` - `.Disks()` - для работы с `Disks`
- `.ExtNet()` - для работы с `ExtNet` - `.ExtNet()` - для работы с `ExtNet`
- `.FLIPGroup()` - для работы с `FLIPGroup` - `.FLIPGroup()` - для работы с `FLIPGroup`
- `.Grid()` - для работы с `Grid` - `.Grid()` - для работы с `Grid`
- `.Group()` - для работы с `Group` - `.Group()` - для работы с `Group`
- `.Image()` - для работы с `Image` - `.Image()` - для работы с `Image`
- `.K8CI()` - для работы с `K8CI` - `.K8CI()` - для работы с `K8CI`
- `.K8S()` - для работы с `K8S` - `.K8S()` - для работы с `K8S`
- `.KVMPPC()` - для работы с `KVMPPC` - `.KVMPPC()` - для работы с `KVMPPC`
- `.KVMx86()` - для работы с `KVMX86` - `.KVMx86()` - для работы с `KVMX86`
- `.LB()` - для работы с `LB` - `.LB()` - для работы с `LB`
- `.PCIDevice()` - для работы с `PCIDevice` - `.PCIDevice()` - для работы с `PCIDevice`
- `.RG()` - для работы с `RG` - `.RG()` - для работы с `RG`
- `.SEP()` - для работы с `SEP` - `.SEP()` - для работы с `SEP`
- `.Stack()` - для работы с `Stack` - `.Stack()` - для работы с `Stack`
- `.Tasks()` - для работы с `Tasks` - `.Tasks()` - для работы с `Tasks`
- `.User()` - для работы с `User` - `.User()` - для работы с `User`
- `.VGPU()` - для работы с `VGPU` - `.VGPU()` - для работы с `VGPU`
- `.VINS()` - для работы с `VINS` - `.VINS()` - для работы с `VINS`
3. Вызвать метод, отвечающий за выполнение запроса и передать в него: 3. Вызвать метод, отвечающий за выполнение запроса и передать в него:
@ -767,7 +782,7 @@ func main() {
``` ```
### Получение списка уникальных идентификаторов (ID) объекта ### Получение списка уникальных идентификаторов ID объекта
Для всех структур, имеющих поля со списками объектов с уникальными числовыми идентификаторами (ID), добавлены методы IDs(), возвращающие массивы уникальных идентификаторов объектов в этих списках. Для всех структур, имеющих поля со списками объектов с уникальными числовыми идентификаторами (ID), добавлены методы IDs(), возвращающие массивы уникальных идентификаторов объектов в этих списках.
@ -916,7 +931,7 @@ func main() {
legacyCfg.SetTimeout(5 * time.Minute) legacyCfg.SetTimeout(5 * time.Minute)
// Создание клиента // Создание клиента
legacyClient := decort.NewLegacy(cfg) legacyClient := decort.NewLegacy(legacyCfg)
} }
``` ```
@ -943,7 +958,7 @@ func main() {
} }
// Создание клиента // Создание клиента
legacyClient := decort.NewLegacy(cfg) legacyClient := decort.NewLegacy(legacyCfg)
// Создание структуры запроса // Создание структуры запроса
// CreateRequest - реквест на создание виртуальной машины // CreateRequest - реквест на создание виртуальной машины
@ -956,7 +971,7 @@ func main() {
} }
// Выполнение запроса // Выполнение запроса
res, err := client.CloudAPI().KVMX86().Create(context.Background(), req) res, err := legacyClient.CloudAPI().KVMX86().Create(context.Background(), req)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -1021,9 +1036,9 @@ func main(){
AppSecret: "<APP_SECRET>", AppSecret: "<APP_SECRET>",
Username: "<USERNAME>", Username: "<USERNAME>",
Password: "<PASSWORD>", Password: "<PASSWORD>",
SSOURL: "https://bvs-delta.qa.loc:8443" SSOURL: "https://bvs-delta.qa.loc:8443",
DecortURL: "https://delta.qa.loc", DecortURL: "https://delta.qa.loc",
Domain: "dynamix" Domain: "dynamix",
Retries: 5, Retries: 5,
} }
@ -1133,7 +1148,7 @@ func main() {
Password: "<PASSWORD>", Password: "<PASSWORD>",
AppID: "<APPID>", AppID: "<APPID>",
AppSecret: "<APPSECRET>", AppSecret: "<APPSECRET>",
SSOURL: "https://bvs-delta.qa.loc:8443" SSOURL: "https://bvs-delta.qa.loc:8443",
DecortURL: "https://mr4.digitalenergy.online", DecortURL: "https://mr4.digitalenergy.online",
Domain: "dynamix", Domain: "dynamix",
Retries: 5, Retries: 5,
@ -1142,11 +1157,11 @@ func main() {
BVSCfg.SetTimeout(5 * time.Minute) BVSCfg.SetTimeout(5 * time.Minute)
// Создание клиента // Создание клиента
BVSClient := decort.NewBVS(cfg) BVSClient := decort.NewBVS(BVSCfg)
} }
``` ```
#### Пример получения BVS токена #### Пример получения BVS токена
В случае указания значения в переменной конфигурации `PathCfg` токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` В случае указания значения в переменной конфигурации `PathCfg` токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json`
@ -1162,12 +1177,12 @@ import (
func main() { func main() {
// Настройка конфигурации // Настройка конфигурации
BVSCfg := config.config.BVSConfig{ BVSCfg := config.BVSConfig{
Username: "<USERNAME>", Username: "<USERNAME>",
Password: "<PASSWORD>", Password: "<PASSWORD>",
AppID: "<APPID>", AppID: "<APPID>",
AppSecret: "<APPSECRET>", AppSecret: "<APPSECRET>",
SSOURL: "https://bvs-delta.qa.loc:8443" SSOURL: "https://bvs-delta.qa.loc:8443",
DecortURL: "https://mr4.digitalenergy.online", DecortURL: "https://mr4.digitalenergy.online",
Domain: "dynamix", Domain: "dynamix",
PathCfg: "config", PathCfg: "config",
@ -1175,10 +1190,10 @@ func main() {
} }
// Создание клиента // Создание клиента
BVSClient := decort.NewBVS(cfg) BVSClient := decort.NewBVS(BVSCfg)
// Выполнение запроса на получение токена // Выполнение запроса на получение токена
token, err := client.GetToken(context.Background()) token, err := BVSClient.GetToken(context.Background())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -1187,7 +1202,7 @@ func main() {
} }
``` ```
#### Пример обновления BVS токена #### Пример обновления BVS токена
В случае указания значения в переменной конфигурации `PathCfg` обновленный токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` В случае указания значения в переменной конфигурации `PathCfg` обновленный токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json`
@ -1203,12 +1218,12 @@ import (
func main() { func main() {
// Настройка конфигурации // Настройка конфигурации
BVSCfg := config.config.BVSConfig{ BVSCfg := config.BVSConfig{
Username: "<USERNAME>", Username: "<USERNAME>",
Password: "<PASSWORD>", Password: "<PASSWORD>",
AppID: "<APPID>", AppID: "<APPID>",
AppSecret: "<APPSECRET>", AppSecret: "<APPSECRET>",
SSOURL: "https://bvs-delta.qa.loc:8443" SSOURL: "https://bvs-delta.qa.loc:8443",
DecortURL: "https://mr4.digitalenergy.online", DecortURL: "https://mr4.digitalenergy.online",
Domain: "dynamix", Domain: "dynamix",
PathToken: "token", PathToken: "token",
@ -1216,10 +1231,10 @@ func main() {
} }
// Создание клиента // Создание клиента
BVSClient := decort.NewBVS(cfg) BVSClient := decort.NewBVS(BVSCfg)
// Выполнение запроса на обновление токена // Выполнение запроса на обновление токена
token, err := client.RefreshToken(context.Background()) token, err := BVSClient.RefreshToken(context.Background())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -1242,19 +1257,19 @@ import (
func main() { func main() {
// Настройка конфигурации // Настройка конфигурации
BVSCfg := config.config.BVSConfig{ BVSCfg := config.BVSConfig{
Username: "<USERNAME>", Username: "<USERNAME>",
Password: "<PASSWORD>", Password: "<PASSWORD>",
AppID: "<APPID>", AppID: "<APPID>",
AppSecret: "<APPSECRET>", AppSecret: "<APPSECRET>",
SSOURL: "https://bvs-delta.qa.loc:8443" SSOURL: "https://bvs-delta.qa.loc:8443",
DecortURL: "https://mr4.digitalenergy.online", DecortURL: "https://mr4.digitalenergy.online",
Domain: "dynamix", Domain: "dynamix",
Retries: 5, Retries: 5,
} }
// Создание клиента // Создание клиента
BVSClient := decort.NewBVS(cfg) BVSClient := decort.NewBVS(BVSCfg)
// Создание структуры запроса // Создание структуры запроса
// CreateRequest - реквест на создание виртуальной машины // CreateRequest - реквест на создание виртуальной машины
@ -1267,10 +1282,11 @@ func main() {
} }
// Выполнение запроса // Выполнение запроса
res, err := client.CloudAPI().KVMX86().Create(context.Background(), req) res, err := BVSClient.CloudAPI().KVMX86().Create(context.Background(), req)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
fmt.Println(res) fmt.Println(res)
} }
```

@ -4,11 +4,11 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -18,12 +18,10 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/config" "repository.basistech.ru/BASIS/decort-golang-sdk/config"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "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/cloudapi"
k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s"
) )
// HTTP-client for platform // DecortClient is HTTP-client for platform
type DecortClient struct { type DecortClient struct {
decortURL string decortURL string
client *http.Client client *http.Client
@ -72,342 +70,270 @@ func (dc *DecortClient) CloudBroker() *cloudbroker.CloudBroker {
// DecortApiCall method for sending requests to the platform // DecortApiCall method for sending requests to the platform
func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
k8sCaCreateReq, okCa := params.(k8s_ca.CreateRequest) values, err := query.Values(params)
k8sCbCreateReq, okCb := params.(k8s_cb.CreateRequest) if err != nil {
var body *bytes.Buffer return nil, err
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())
} }
body := bytes.NewBufferString(values.Encode())
req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+constants.Restmachine+url, body) req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+constants.Restmachine+url, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// get token
if err = dc.getToken(ctx); err != nil { if err = dc.getToken(ctx); err != nil {
return nil, err return nil, err
} }
resp, err := dc.do(req, ctype) // perform request
respBytes, err := dc.do(req, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body) 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 { if err != nil {
return nil, err return nil, err
} }
if resp.StatusCode != 200 { req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+constants.Restmachine+url, body)
return nil, errors.New(string(respBytes)) if err != nil {
return nil, err
}
// get token
if err = dc.getToken(ctx); err != nil {
return nil, err
} }
return respBytes, nil // perform request
respBytes, err := dc.do(req, ctype)
if err != nil {
return nil, err
}
return respBytes, err
} }
func (dc *DecortClient) getToken(ctx context.Context) error { func (dc *DecortClient) getToken(ctx context.Context) error {
dc.mutex.Lock() dc.mutex.Lock()
defer dc.mutex.Unlock() defer dc.mutex.Unlock()
if dc.cfg.Token == "" || time.Now().After(dc.expiryTime) { // new token is not needed
body := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&response_type=id_token", dc.cfg.AppID, dc.cfg.AppSecret) if dc.cfg.Token != "" && !time.Now().After(dc.expiryTime) {
bodyReader := strings.NewReader(body) return nil
}
dc.cfg.SSOURL = strings.TrimSuffix(dc.cfg.SSOURL, "/")
req, _ := http.NewRequestWithContext(ctx, "POST", dc.cfg.SSOURL+"/v1/oauth/access_token", bodyReader) // set up request headers and body
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 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) dc.cfg.SSOURL = strings.TrimSuffix(dc.cfg.SSOURL, "/")
if err != nil {
return fmt.Errorf("cannot get token: %w", err)
}
tokenBytes, _ := io.ReadAll(resp.Body) req, _ := http.NewRequestWithContext(ctx, "POST", dc.cfg.SSOURL+"/v1/oauth/access_token", bodyReader)
resp.Body.Close() req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if resp.StatusCode != 200 { // request token
return fmt.Errorf("cannot get token: %s", tokenBytes) 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 if resp.StatusCode != 200 {
dc.expiryTime = time.Now().AddDate(0, 0, 1) 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 return nil
} }
func (dc *DecortClient) do(req *http.Request, ctype string) (*http.Response, error) { // 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 != "" { if ctype != "" {
req.Header.Add("Content-Type", ctype) req.Header.Set("Content-Type", ctype)
} else {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
} }
req.Header.Add("Authorization", "bearer "+dc.cfg.Token) req.Header.Add("Authorization", "bearer "+dc.cfg.Token)
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
// var resp *http.Response buf, err := io.ReadAll(req.Body)
// var err error if err != nil {
buf, _ := io.ReadAll(req.Body) return nil, err
}
// for i := uint64(0); i < dc.cfg.Retries; i++ { req.Body.Close()
// req = req.Clone(req.Context())
req.Body = io.NopCloser(bytes.NewBuffer(buf)) req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := dc.client.Do(req) resp, err := dc.client.Do(req)
if err != nil || resp == nil { if resp != nil {
return resp, err 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
}
if resp == nil {
return nil, fmt.Errorf("got empty response without error")
} }
// handle successful request
respBytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 200 { if resp.StatusCode == 200 {
return resp, err return respBytes, nil
} }
respBytes, err := io.ReadAll(resp.Body) // handle errors with status code other than 200
if err != nil { err = fmt.Errorf("%s", respBytes)
return nil, err return nil, fmt.Errorf("could not execute request: %w", err)
}
resp.Body.Close()
return resp, errors.New(string(respBytes))
} }
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{} reqBody := &bytes.Buffer{}
writer := multipart.NewWriter(reqBody) writer := multipart.NewWriter(reqBody)
if req.OidcCertificate != "" { values := reflect.ValueOf(params)
part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") types := values.Type()
_, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) defer writer.Close()
} for i := 0; i < values.NumField(); i++ {
if !values.Field(i).IsValid() {
_ = writer.WriteField("name", req.Name) continue
_ = 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)
} }
}
if req.Annotations != nil { if values.Field(i).IsZero() {
for _, v := range req.Annotations { continue
_ = writer.WriteField("annotations", v)
} }
}
if req.MasterCPU != 0 { if file, ok := constants.FileName[types.Field(i).Name]; ok {
_ = writer.WriteField("masterCpu", strconv.FormatUint(uint64(req.MasterCPU), 10)) part, err := writer.CreateFormFile(trimString(types.Field(i)), file)
} if err != nil {
if req.MasterNum != 0 { return &bytes.Buffer{}, "", err
_ = writer.WriteField("masterNum", strconv.FormatUint(uint64(req.MasterNum), 10)) }
} _, err = io.Copy(part, strings.NewReader(valueToString(values.Field(i).Interface())))
if req.MasterRAM != 0 { if err != nil {
_ = writer.WriteField("masterRam", strconv.FormatUint(uint64(req.MasterRAM), 10)) return &bytes.Buffer{}, "", err
} }
if req.MasterDisk != 0 { continue
_ = 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))
}
_ = 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 { err := writer.WriteField(trimString(types.Field(i)), valueToString(values.Field(i).Interface()))
for _, v := range req.AdditionalSANs { if err != nil {
_ = writer.WriteField("additionalSANs", v) 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() ct := writer.FormDataContentType()
writer.Close() return reqBody, ct, nil
return reqBody, ct
} }
func createK8sCloudBroker(req k8s_cb.CreateRequest) (*bytes.Buffer, string) { func valueToString(a any) string {
reqBody := &bytes.Buffer{} switch str := a.(type) {
writer := multipart.NewWriter(reqBody) case string:
if req.OidcCertificate != "" { return str
part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") case uint:
_, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) return strconv.FormatUint(uint64(str), 10)
} case uint64:
return strconv.FormatUint(str, 10)
_ = writer.WriteField("name", req.Name) case bool:
_ = writer.WriteField("rgId", strconv.FormatUint(req.RGID, 10)) return strconv.FormatBool(str)
_ = writer.WriteField("k8ciId", strconv.FormatUint(req.K8CIID, 10)) default:
_ = writer.WriteField("workerGroupName", req.WorkerGroupName) return ""
_ = 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)
}
}
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() func trimString(el reflect.StructField) string {
return reqBody, ct return strings.TrimSuffix(el.Tag.Get("url"), ",omitempty")
} }

@ -8,9 +8,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"mime/multipart"
"net/http" "net/http"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -19,12 +17,10 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/config" "repository.basistech.ru/BASIS/decort-golang-sdk/config"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "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/cloudapi"
k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s"
) )
// HTTP-client for platform // BVSDecortClient is HTTP-client for platform
type BVSDecortClient struct { type BVSDecortClient struct {
client *http.Client client *http.Client
cfg config.BVSConfig cfg config.BVSConfig
@ -75,34 +71,26 @@ func (bdc *BVSDecortClient) CloudBroker() *cloudbroker.CloudBroker {
// DecortApiCall method for sending requests to the platform // DecortApiCall method for sending requests to the platform
func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
k8sCaCreateReq, okCa := params.(k8s_ca.CreateRequest) values, err := query.Values(params)
k8sCbCreateReq, okCb := params.(k8s_cb.CreateRequest) if err != nil {
var body *bytes.Buffer return nil, err
var ctype string
if okCa {
body, ctype = createK8sCloudApiBVS(k8sCaCreateReq)
} else if okCb {
body, ctype = createK8sCloudBrokerBVS(k8sCbCreateReq)
} else {
values, err := query.Values(params)
if err != nil {
return nil, err
}
body = bytes.NewBufferString(values.Encode())
} }
body := bytes.NewBufferString(values.Encode())
req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body) req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// get token
if bdc.cfg.Token.AccessToken == "" { if bdc.cfg.Token.AccessToken == "" {
if _, err = bdc.GetToken(ctx); err != nil { if _, err = bdc.GetToken(ctx); err != nil {
return nil, err 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 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.RefreshToken(ctx); err != nil {
if _, err = bdc.GetToken(ctx); err != nil { if _, err = bdc.GetToken(ctx); err != nil {
@ -110,46 +98,87 @@ func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url strin
} }
} }
} }
// perform request
reqCopy := req.Clone(ctx) reqCopy := req.Clone(ctx)
//nolint:bodyclose respBytes, err := bdc.do(req, "")
//work defer, error lint if err == nil {
resp, err := bdc.do(req, ctype) return respBytes, nil
if err != nil { }
if err.Error() == "access is denied" {
if _, err = bdc.GetToken(ctx); err != nil { // get token and retry in case of access denied
return nil, err if err.Error() == "access is denied" {
} _, err = bdc.GetToken(ctx)
//nolint:bodyclose if err != nil {
//we close the body in case of any error return nil, err
resp, err = bdc.do(reqCopy, ctype) }
if err != nil {
return nil, err respBytes, err = bdc.do(reqCopy, "")
} if err != nil {
} else {
return nil, err return nil, err
} }
} }
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body) 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 { if err != nil {
return nil, err return nil, err
} }
if resp.StatusCode != 200 { req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body)
return nil, errors.New(string(respBytes)) 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
}
}
} }
return respBytes, nil // 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, // 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, // the token and configuration will be written to a file.
// when specifying the PathToken variable, the token 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) { func (bdc *BVSDecortClient) GetToken(ctx context.Context) (config.Token, error) {
bdc.mutex.Lock() bdc.mutex.Lock()
defer bdc.mutex.Unlock() 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) 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) bodyReader := strings.NewReader(body)
@ -158,20 +187,25 @@ func (bdc *BVSDecortClient) GetToken(ctx context.Context) (config.Token, error)
req, _ := http.NewRequestWithContext(ctx, "POST", bdc.cfg.SSOURL+"/realms/"+bdc.cfg.Domain+"/protocol/openid-connect/token", bodyReader) 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") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
// request token
resp, err := bdc.client.Do(req) resp, err := bdc.client.Do(req)
if err != nil { if err != nil || resp == nil {
return config.Token{}, fmt.Errorf("cannot get token: %w", err) return config.Token{}, fmt.Errorf("cannot get token: %w", err)
} }
defer resp.Body.Close()
tokenBytes, _ := io.ReadAll(resp.Body) var tokenBytes []byte
resp.Body.Close() tokenBytes, err = io.ReadAll(resp.Body)
if err != nil {
return config.Token{}, fmt.Errorf("cannot get token: %w", err)
}
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return config.Token{}, fmt.Errorf("cannot get token: %s", tokenBytes) return config.Token{}, fmt.Errorf("cannot get token: %s", tokenBytes)
} }
// save token in config
var tj tokenJSON var tj tokenJSON
if err = json.Unmarshal(tokenBytes, &tj); err != nil { if err = json.Unmarshal(tokenBytes, &tj); err != nil {
return config.Token{}, fmt.Errorf("cannot unmarshal token: %w", err) return config.Token{}, fmt.Errorf("cannot unmarshal token: %w", err)
} }
@ -196,13 +230,14 @@ func (bdc *BVSDecortClient) GetToken(ctx context.Context) (config.Token, error)
return bdc.cfg.Token, nil return bdc.cfg.Token, nil
} }
// RefreshToken allows you to refresh a token and returns the token structure, when specifying the PathCfg variable, // 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, // the token and configuration will be written to a file.
// when specifying the PathToken variable, the token 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) { func (bdc *BVSDecortClient) RefreshToken(ctx context.Context) (config.Token, error) {
bdc.mutex.Lock() bdc.mutex.Lock()
defer bdc.mutex.Unlock() 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) 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) bodyReader := strings.NewReader(body)
@ -211,20 +246,25 @@ func (bdc *BVSDecortClient) RefreshToken(ctx context.Context) (config.Token, err
req, _ := http.NewRequestWithContext(ctx, "POST", bdc.cfg.SSOURL+"/realms/"+bdc.cfg.Domain+"/protocol/openid-connect/token", bodyReader) 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") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
// refresh token
resp, err := bdc.client.Do(req) resp, err := bdc.client.Do(req)
if err != nil { if err != nil || resp == nil {
return config.Token{}, fmt.Errorf("cannot refresh token: %w", err) return config.Token{}, fmt.Errorf("cannot refresh token: %w", err)
} }
defer resp.Body.Close()
tokenBytes, _ := io.ReadAll(resp.Body) var tokenBytes []byte
resp.Body.Close() tokenBytes, err = io.ReadAll(resp.Body)
if err != nil {
return config.Token{}, fmt.Errorf("cannot refresh token: %w", err)
}
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return config.Token{}, fmt.Errorf("cannot refresh token: %s", tokenBytes) return config.Token{}, fmt.Errorf("cannot refresh token: %s", tokenBytes)
} }
// save token in config
var tj tokenJSON var tj tokenJSON
if err = json.Unmarshal(tokenBytes, &tj); err != nil { if err = json.Unmarshal(tokenBytes, &tj); err != nil {
return config.Token{}, fmt.Errorf("cannot unmarshal after refresh token: %w", err) return config.Token{}, fmt.Errorf("cannot unmarshal after refresh token: %w", err)
} }
@ -256,264 +296,85 @@ func (e *tokenJSON) expiry() (t time.Time) {
return return
} }
func (bdc *BVSDecortClient) do(req *http.Request, ctype string) (*http.Response, error) { // 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 != "" { if ctype != "" {
req.Header.Add("Content-Type", ctype) req.Header.Set("Content-Type", ctype)
} else {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
} }
req.Header.Add("Authorization", "bearer "+bdc.cfg.Token.AccessToken) req.Header.Add("Authorization", "bearer "+bdc.cfg.Token.AccessToken)
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
buf, _ := io.ReadAll(req.Body) buf, err := io.ReadAll(req.Body)
req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := bdc.client.Do(req)
if err != nil || resp == nil {
return resp, err
}
if resp.StatusCode == 401 {
resp.Body.Close()
return resp, errors.New("access is denied")
}
if resp.StatusCode == 200 {
return resp, err
}
respBytes, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp.Body.Close()
return resp, errors.New(string(respBytes))
}
func createK8sCloudApiBVS(req k8s_ca.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.K8SCIID, 10))
_ = writer.WriteField("workerGroupName", req.WorkerGroupName)
_ = writer.WriteField("networkPlugin", req.NetworkPlugin)
if req.MasterSEPID != 0 { req.Body.Close()
_ = writer.WriteField("masterSepId", strconv.FormatUint(req.MasterSEPID, 10)) req.Body = io.NopCloser(bytes.NewBuffer(buf))
}
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(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))
}
_ = 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 createK8sCloudBrokerBVS(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))
}
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 { resp, err := bdc.client.Do(req)
for _, v := range req.Labels { if resp != nil {
_ = writer.WriteField("labels", v) defer resp.Body.Close()
} }
}
if req.Taints != nil { // retries logic GOES HERE
for _, v := range req.Taints { // get http response
_ = writer.WriteField("taints", v) //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 req.Annotations != nil { if resp == nil {
for _, v := range req.Annotations { return nil, fmt.Errorf("got empty response without error")
_ = writer.WriteField("annotations", v)
}
} }
if req.MasterCPU != 0 { var respBytes []byte
_ = writer.WriteField("masterCpu", strconv.FormatUint(req.MasterCPU, 10)) respBytes, err = io.ReadAll(resp.Body)
} if err != nil {
if req.MasterNum != 0 { return nil, err
_ = 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)) // handle access denied and successful request
if resp.StatusCode == 401 {
if req.AdditionalSANs != nil { return respBytes, errors.New("access is denied")
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 != "" { if resp.StatusCode == 200 {
_ = writer.WriteField("userData", req.UserData) return respBytes, nil
} }
_ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) // handle errors with other status codes
err = fmt.Errorf("%s", respBytes)
ct := writer.FormDataContentType() return nil, fmt.Errorf("could not execute request: %w", err)
writer.Close()
return reqBody, ct
} }

@ -6,4 +6,7 @@ import "context"
type Caller interface { type Caller interface {
// DecortApiCall method for sending requests to the platform // DecortApiCall method for sending requests to the platform
DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) 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)
} }

@ -2,4 +2,6 @@ package constants
const Restmachine = "/restmachine" const Restmachine = "/restmachine"
var FileName = map[string]string{
"OidcCertificate": "ca.crt",
}

@ -4,13 +4,10 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io" "io"
"mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -19,12 +16,10 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/config" "repository.basistech.ru/BASIS/decort-golang-sdk/config"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "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/cloudapi"
k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s"
) )
// Legacy HTTP-client for platform // LegacyDecortClient is Legacy HTTP-client for platform
type LegacyDecortClient struct { type LegacyDecortClient struct {
decortURL string decortURL string
client *http.Client client *http.Client
@ -73,342 +68,168 @@ func (ldc *LegacyDecortClient) CloudBroker() *cloudbroker.CloudBroker {
// DecortApiCall method for sending requests to the platform // DecortApiCall method for sending requests to the platform
func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
// get token
if err := ldc.getToken(ctx); err != nil { if err := ldc.getToken(ctx); err != nil {
return nil, err return nil, err
} }
k8sCaCreateReq, okCa := params.(k8s_ca.CreateRequest) values, err := query.Values(params)
k8sCbCreateReq, okCb := params.(k8s_cb.CreateRequest) if err != nil {
return nil, err
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))
} }
body := bytes.NewBufferString(values.Encode() + fmt.Sprintf("&authkey=%s", ldc.cfg.Token))
req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body) req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp, err := ldc.do(req, ctype) // perform request
respBytes, err := ldc.do(req, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body) return respBytes, err
}
func (ldc *LegacyDecortClient) DecortApiCallMP(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
body, ctype, err := multiPartReq(params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.StatusCode != 200 { req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body)
return nil, errors.New(string(respBytes)) if err != nil {
return nil, err
} }
return respBytes, nil // get token
if err = ldc.getToken(ctx); err != nil {
return nil, err
}
// perform request
respBytes, err := ldc.do(req, ctype)
if err != nil {
return nil, err
}
return respBytes, err
} }
func (ldc *LegacyDecortClient) getToken(ctx context.Context) error { func (ldc *LegacyDecortClient) getToken(ctx context.Context) error {
ldc.mutex.Lock() ldc.mutex.Lock()
defer ldc.mutex.Unlock() defer ldc.mutex.Unlock()
if ldc.cfg.Token == "" || time.Now().After(ldc.expiryTime) { // new token is not needed
body := fmt.Sprintf("username=%s&password=%s", url.QueryEscape(ldc.cfg.Username), url.QueryEscape(ldc.cfg.Password)) if ldc.cfg.Token != "" && !time.Now().After(ldc.expiryTime) {
bodyReader := strings.NewReader(body) return nil
req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := ldc.client.Do(req)
if err != nil {
return fmt.Errorf("unable to get token: %w", err)
}
tokenBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("unable to get token: %s", tokenBytes)
}
token := string(tokenBytes)
ldc.cfg.Token = token
ldc.expiryTime = time.Now().AddDate(0, 0, 1)
} }
return nil // 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)
func (ldc *LegacyDecortClient) do(req *http.Request, ctype string) (*http.Response, error) { req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader)
if ctype != "" { req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Type", ctype)
} else {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
}
req.Header.Set("Accept", "application/json")
// var resp *http.Response // request token
// var err error
buf, _ := io.ReadAll(req.Body)
// for i := uint64(0); i < ldc.cfg.Retries; i++ {
// req = req.Clone(req.Context())
req.Body = io.NopCloser(bytes.NewBuffer(buf))
resp, err := ldc.client.Do(req) resp, err := ldc.client.Do(req)
if err != nil || resp == nil { if err != nil || resp == nil {
return resp, err return fmt.Errorf("cannot get token: %w", err)
}
if resp.StatusCode == 200 {
return resp, err
} }
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body) var tokenBytes []byte
tokenBytes, err = io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return fmt.Errorf("cannot get token: %w", err)
}
resp.Body.Close()
return resp, errors.New(string(respBytes))
}
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))
}
_ = 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)
}
}
if req.Annotations != nil {
for _, v := range req.Annotations {
_ = writer.WriteField("annotations", v)
}
}
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))
}
_ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(req.HighlyAvailable))
if req.AdditionalSANs != nil { if resp.StatusCode != 200 {
for _, v := range req.AdditionalSANs { return fmt.Errorf("cannot get token: %s", tokenBytes)
_ = 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)) // save token in config
token := string(tokenBytes)
_ = writer.WriteField("authkey", token) ldc.cfg.Token = token
ldc.expiryTime = time.Now().AddDate(0, 0, 1)
ct := writer.FormDataContentType() return nil
writer.Close()
return reqBody, ct
} }
func createK8sCloudBrokerLegacy(req k8s_cb.CreateRequest, token string) (*bytes.Buffer, string) { // do method performs request and returns response as an array of bytes and nil error in case of response status code 200.
reqBody := &bytes.Buffer{} // In any other cases do returns nil response and error.
writer := multipart.NewWriter(reqBody) // Retries are implemented in case of connection reset errors.
if req.OidcCertificate != "" { func (ldc *LegacyDecortClient) do(req *http.Request, ctype string) ([]byte, error) {
part, _ := writer.CreateFormFile("oidcCertificate", "ca.crt") // set up request headers and body
_, _ = io.Copy(part, strings.NewReader(req.OidcCertificate)) req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if ctype != "" {
req.Header.Set("Content-Type", ctype)
} }
_ = writer.WriteField("name", req.Name) req.Header.Set("Accept", "application/json")
_ = 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 { buf, err := io.ReadAll(req.Body)
_ = writer.WriteField("masterSepId", strconv.FormatUint(req.MasterSEPID, 10)) if err != nil {
} return nil, err
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 { req.Body.Close()
for _, v := range req.Labels { req.Body = io.NopCloser(bytes.NewBuffer(buf))
_ = 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 { resp, err := ldc.client.Do(req)
_ = writer.WriteField("masterCpu", strconv.FormatUint(req.MasterCPU, 10)) if resp != nil {
} defer resp.Body.Close()
if req.MasterNum != 0 { }
_ = writer.WriteField("masterNum", strconv.FormatUint(req.MasterNum, 10))
} // retries logic GOES HERE
if req.MasterRAM != 0 { // get http response
_ = writer.WriteField("masterRam", strconv.FormatUint(req.MasterRAM, 10)) //var resp *http.Response
} //for i := uint64(0); i < ldc.cfg.Retries; i++ {
if req.MasterDisk != 0 { // req := req.Clone(req.Context())
_ = writer.WriteField("masterDisk", strconv.FormatUint(req.MasterDisk, 10)) // req.Body = io.NopCloser(bytes.NewBuffer(buf))
} //
if req.WorkerCPU != 0 { // if i > 0 {
_ = writer.WriteField("workerCpu", strconv.FormatUint(req.WorkerCPU, 10)) // time.Sleep(5 * time.Second) // no time sleep for the first request
} // }
if req.WorkerNum != 0 { //
_ = writer.WriteField("workerNum", strconv.FormatUint(req.WorkerNum, 10)) // resp, err = ldc.client.Do(req)
} //
if req.WorkerRAM != 0 { // // stop retries on success and close response body
_ = writer.WriteField("workerRam", strconv.FormatUint(req.WorkerRAM, 10)) // if resp != nil {
} // defer resp.Body.Close()
if req.WorkerDisk != 0 { // }
_ = writer.WriteField("workerDisk", strconv.FormatUint(req.WorkerDisk, 10)) // if err == nil {
} // break
if req.ExtNetID != 0 { // }
_ = writer.WriteField("extnetId", strconv.FormatUint(req.ExtNetID, 10)) //
} // // retries in case of connection errors with time sleep
if req.VinsId != 0 { // if isConnectionError(err) {
_ = writer.WriteField("vinsId", strconv.FormatUint(req.VinsId, 10)) // 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 { if resp == nil {
_ = writer.WriteField("withLB", strconv.FormatBool(req.WithLB)) return nil, fmt.Errorf("got empty response without error")
} }
_ = writer.WriteField("highlyAvailableLB", strconv.FormatBool(req.HighlyAvailable)) // handle successful request
respBytes, _ := io.ReadAll(resp.Body)
if req.AdditionalSANs != nil { if resp.StatusCode == 200 {
for _, v := range req.AdditionalSANs { return respBytes, nil
_ = 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)) // handle errors with status code other than 200
err = fmt.Errorf("%s", respBytes)
_ = writer.WriteField("authkey", token) return nil, fmt.Errorf("could not execute request: %w", err)
ct := writer.FormDataContentType()
writer.Close()
return reqBody, ct
} }

@ -202,7 +202,7 @@ func (k8s K8S) Create(ctx context.Context, req CreateRequest) (string, error) {
url := "/cloudapi/k8s/create" 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 { if err != nil {
return "", err return "", err
} }

@ -2,6 +2,9 @@ package locations
// Main information about locations // Main information about locations
type ItemLocation struct { type ItemLocation struct {
// AuthBroker
AuthBroker []string `json:"authBroker"`
// Grid ID // Grid ID
GID uint64 `json:"gid"` GID uint64 `json:"gid"`

@ -486,6 +486,9 @@ type RecordLoadBalancer struct {
// Access Control List // Access Control List
ACL interface{} `json:"acl"` ACL interface{} `json:"acl"`
// BackendHAIP
BackendHAIP string `json:"backendHAIP"`
// List of Backends // List of Backends
Backends ListBackends `json:"backends"` Backends ListBackends `json:"backends"`
@ -510,6 +513,9 @@ type RecordLoadBalancer struct {
// External network ID // External network ID
ExtNetID uint64 `json:"extnetId"` ExtNetID uint64 `json:"extnetId"`
// FrontendHAIP
FrontendHAIP string `json:"frontendHAIP"`
// List of frontends // List of frontends
Frontends ListFrontends `json:"frontends"` Frontends ListFrontends `json:"frontends"`

@ -204,7 +204,7 @@ func (k K8S) Create(ctx context.Context, req CreateRequest) (string, error) {
url := "/cloudbroker/k8s/create" 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 { if err != nil {
return "", err return "", err
} }

@ -607,6 +607,9 @@ type ItemLB struct {
// List ACL // List ACL
ACL ListACL `json:"acl"` ACL ListACL `json:"acl"`
// BackendHAIP
BackendHAIP string `json:"backendHAIP"`
// List backends // List backends
Backends ListBackends `json:"backends"` Backends ListBackends `json:"backends"`
@ -631,6 +634,9 @@ type ItemLB struct {
// External network ID // External network ID
ExtNetID uint64 `json:"extnetId"` ExtNetID uint64 `json:"extnetId"`
// FrontendHAIP
FrontendHAIP string `json:"frontendHAIP"`
// List of frontends // List of frontends
Frontends ListFrontends `json:"frontends"` Frontends ListFrontends `json:"frontends"`

Loading…
Cancel
Save