diff --git a/CHANGELOG.md b/CHANGELOG.md index ea923d4..e4cc7a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,7 @@ -## Version 1.7.2 +## Version 1.7.3 ## 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. - -## Feature -- Add endpoints AccessAdd AccessRemote in cloudBroker/k8ci -- 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 +- Refactoring clients +- Add fields FrontendHAIP and BackendHAIP in model ItemLB in cloudbroker/rg and RecordLoadBalancer in cloudapi/rg +- Add field AuthBroker in ItemLocation model in cloudapi/locations diff --git a/README.md b/README.md index 7ef8619..bbba2f2 100644 --- a/README.md +++ b/README.md @@ -17,29 +17,44 @@ Decort SDK - это библиотека, написанная на языке G - [Установка](#установка) - [Список 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-токена) + - [Пример выполнения запроса](#пример-выполнения-запроса) ## Установка @@ -114,8 +129,8 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk Алгоритм работы с библиотекой выглядит следующим образом: 1. Выполнение одного из действий: - - настройка конфигурации клиента; - - парсинг конфигурации из файла. +- настройка конфигурации клиента; +- парсинг конфигурации из файла. 2. Создание клиента. 3. Создание структуры запроса. 4. Выполнение запроса. @@ -157,7 +172,7 @@ func main(){ #### Парсинг конфигурации из файла -Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigJSON` (или `ParseConfigYAML`) из пакета config. +Также возможно создать переменную конфигурации из JSON или YAML файла, используя функцию `ParseConfigJSON` (или `ParseConfigYAML`) из пакета config.
*См. пример файлов конфигурации ниже и в директории `samples/`.* @@ -239,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` Все поля структуры имеют описание, в которых содержится: @@ -421,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. Вызвать метод, отвечающий за выполнение запроса и передать в него: @@ -767,7 +782,7 @@ func main() { ``` -### Получение списка уникальных идентификаторов (ID) объекта +### Получение списка уникальных идентификаторов ID объекта Для всех структур, имеющих поля со списками объектов с уникальными числовыми идентификаторами (ID), добавлены методы IDs(), возвращающие массивы уникальных идентификаторов объектов в этих списках. @@ -916,7 +931,7 @@ func main() { 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 - реквест на создание виртуальной машины @@ -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 { log.Fatal(err) } @@ -1021,9 +1036,9 @@ func main(){ AppSecret: "", Username: "", Password: "", - SSOURL: "https://bvs-delta.qa.loc:8443" + SSOURL: "https://bvs-delta.qa.loc:8443", DecortURL: "https://delta.qa.loc", - Domain: "dynamix" + Domain: "dynamix", Retries: 5, } @@ -1133,7 +1148,7 @@ func main() { Password: "", AppID: "", AppSecret: "", - SSOURL: "https://bvs-delta.qa.loc:8443" + SSOURL: "https://bvs-delta.qa.loc:8443", DecortURL: "https://mr4.digitalenergy.online", Domain: "dynamix", Retries: 5, @@ -1142,11 +1157,11 @@ func main() { BVSCfg.SetTimeout(5 * time.Minute) // Создание клиента - BVSClient := decort.NewBVS(cfg) + BVSClient := decort.NewBVS(BVSCfg) } ``` -#### Пример получения BVS токена +#### Пример получения BVS токена В случае указания значения в переменной конфигурации `PathCfg` токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` @@ -1162,12 +1177,12 @@ import ( func main() { // Настройка конфигурации - BVSCfg := config.config.BVSConfig{ + BVSCfg := config.BVSConfig{ Username: "", Password: "", AppID: "", AppSecret: "", - SSOURL: "https://bvs-delta.qa.loc:8443" + SSOURL: "https://bvs-delta.qa.loc:8443", DecortURL: "https://mr4.digitalenergy.online", Domain: "dynamix", 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 { log.Fatal(err) } @@ -1187,7 +1202,7 @@ func main() { } ``` -#### Пример обновления BVS токена +#### Пример обновления BVS токена В случае указания значения в переменной конфигурации `PathCfg` обновленный токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` @@ -1203,12 +1218,12 @@ import ( func main() { // Настройка конфигурации - BVSCfg := config.config.BVSConfig{ + BVSCfg := config.BVSConfig{ Username: "", Password: "", AppID: "", AppSecret: "", - SSOURL: "https://bvs-delta.qa.loc:8443" + SSOURL: "https://bvs-delta.qa.loc:8443", DecortURL: "https://mr4.digitalenergy.online", Domain: "dynamix", 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 { log.Fatal(err) } @@ -1242,19 +1257,19 @@ import ( func main() { // Настройка конфигурации - BVSCfg := config.config.BVSConfig{ + BVSCfg := config.BVSConfig{ Username: "", Password: "", AppID: "", AppSecret: "", - SSOURL: "https://bvs-delta.qa.loc:8443" + SSOURL: "https://bvs-delta.qa.loc:8443", DecortURL: "https://mr4.digitalenergy.online", Domain: "dynamix", Retries: 5, } // Создание клиента - BVSClient := decort.NewBVS(cfg) + BVSClient := decort.NewBVS(BVSCfg) // Создание структуры запроса // 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 { log.Fatal(err) } fmt.Println(res) -} \ No newline at end of file +} +``` diff --git a/client.go b/client.go index c4d4e78..730d923 100644 --- a/client.go +++ b/client.go @@ -4,11 +4,11 @@ import ( "bytes" "context" "crypto/tls" - "errors" "fmt" "io" "mime/multipart" "net/http" + "reflect" "strconv" "strings" "sync" @@ -18,12 +18,10 @@ import ( "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 @@ -72,342 +70,270 @@ 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 } + 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 } - resp, err := dc.do(req, ctype) + // perform request + respBytes, err := dc.do(req, "") if err != nil { 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 { return nil, err } - if resp.StatusCode != 200 { - return nil, errors.New(string(respBytes)) + 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 } - 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 { 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 } -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 != "" { - 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) req.Header.Set("Accept", "application/json") - // var resp *http.Response - // var err error - buf, _ := io.ReadAll(req.Body) + buf, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } - // for i := uint64(0); i < dc.cfg.Retries; i++ { - // req = req.Clone(req.Context()) + req.Body.Close() req.Body = io.NopCloser(bytes.NewBuffer(buf)) + resp, err := dc.client.Do(req) - if err != nil || resp == nil { - return resp, err + 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 + } + if resp == nil { + return nil, fmt.Errorf("got empty response without error") } + // handle successful request + respBytes, _ := io.ReadAll(resp.Body) if resp.StatusCode == 200 { - return resp, err + return respBytes, nil } - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - resp.Body.Close() - return resp, errors.New(string(respBytes)) + // handle errors with status code other than 200 + err = fmt.Errorf("%s", respBytes) + 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 index 5516e6b..80c449c 100644 --- a/client_bvs.go +++ b/client_bvs.go @@ -8,9 +8,7 @@ import ( "errors" "fmt" "io" - "mime/multipart" "net/http" - "strconv" "strings" "sync" "time" @@ -19,12 +17,10 @@ import ( "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 +// BVSDecortClient is HTTP-client for platform type BVSDecortClient struct { client *http.Client cfg config.BVSConfig @@ -75,34 +71,26 @@ func (bdc *BVSDecortClient) CloudBroker() *cloudbroker.CloudBroker { // DecortApiCall method for sending requests to the platform func (bdc *BVSDecortClient) 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 = 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()) + 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 { @@ -110,46 +98,87 @@ func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url strin } } } + + // perform request reqCopy := req.Clone(ctx) - //nolint:bodyclose - //work defer, error lint - resp, err := bdc.do(req, ctype) - if err != nil { - if err.Error() == "access is denied" { - if _, err = bdc.GetToken(ctx); err != nil { - return nil, err - } - //nolint:bodyclose - //we close the body in case of any error - resp, err = bdc.do(reqCopy, ctype) - if err != nil { - return nil, err - } - } else { + 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 } } - 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 { return nil, err } - if resp.StatusCode != 200 { - return nil, errors.New(string(respBytes)) + 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 + } + } } - 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, -// the token and configuration will be written to a file, -// when specifying the PathToken variable, the token will be written to a file +// 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) @@ -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.Header.Add("Content-Type", "application/x-www-form-urlencoded") + // request token resp, err := bdc.client.Do(req) - if err != nil { + if err != nil || resp == nil { return config.Token{}, fmt.Errorf("cannot get token: %w", err) } + defer resp.Body.Close() - tokenBytes, _ := io.ReadAll(resp.Body) - 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) } @@ -196,13 +230,14 @@ func (bdc *BVSDecortClient) GetToken(ctx context.Context) (config.Token, error) 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 +// 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) @@ -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.Header.Add("Content-Type", "application/x-www-form-urlencoded") + // refresh token resp, err := bdc.client.Do(req) - if err != nil { + if err != nil || resp == nil { return config.Token{}, fmt.Errorf("cannot refresh token: %w", err) } + defer resp.Body.Close() - tokenBytes, _ := io.ReadAll(resp.Body) - 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) } @@ -256,264 +296,85 @@ func (e *tokenJSON) expiry() (t time.Time) { 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 != "" { - 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 "+bdc.cfg.Token.AccessToken) req.Header.Set("Accept", "application/json") - buf, _ := 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) + buf, err := io.ReadAll(req.Body) if err != nil { 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 { - _ = 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 { - 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) - } + req.Body.Close() + req.Body = io.NopCloser(bytes.NewBuffer(buf)) - 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) - } + 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 req.Annotations != nil { - for _, v := range req.Annotations { - _ = writer.WriteField("annotations", v) - } + if resp == nil { + return nil, fmt.Errorf("got empty response without error") } - 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)) + var respBytes []byte + respBytes, err = io.ReadAll(resp.Body) + if err != nil { + return nil, 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 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) + // handle access denied and successful request + if resp.StatusCode == 401 { + return respBytes, errors.New("access is denied") } - if req.UserData != "" { - _ = writer.WriteField("userData", req.UserData) + if resp.StatusCode == 200 { + return respBytes, nil } - _ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) - - ct := writer.FormDataContentType() - - writer.Close() - return reqBody, ct + // handle errors with other status codes + err = fmt.Errorf("%s", respBytes) + return nil, fmt.Errorf("could not execute request: %w", err) } 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 index 66d9840..b48202e 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -2,4 +2,6 @@ package constants const Restmachine = "/restmachine" - +var FileName = map[string]string{ + "OidcCertificate": "ca.crt", +} diff --git a/legacy-client.go b/legacy-client.go index 3bc8930..01293cd 100644 --- a/legacy-client.go +++ b/legacy-client.go @@ -4,13 +4,10 @@ import ( "bytes" "context" "crypto/tls" - "errors" "fmt" "io" - "mime/multipart" "net/http" "net/url" - "strconv" "strings" "sync" "time" @@ -19,12 +16,10 @@ import ( "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 @@ -73,342 +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 } + 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 } - resp, err := ldc.do(req, ctype) + // perform request + respBytes, err := ldc.do(req, "") if err != nil { 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 { return nil, err } - if resp.StatusCode != 200 { - return nil, errors.New(string(respBytes)) + req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body) + 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 { 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) + // new token is not needed + if ldc.cfg.Token != "" && !time.Now().After(ldc.expiryTime) { + return nil } - 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) { - if ctype != "" { - req.Header.Add("Content-Type", ctype) - } else { - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - } - req.Header.Set("Accept", "application/json") + req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - // var resp *http.Response - // 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)) + // request token resp, err := ldc.client.Do(req) if err != nil || resp == nil { - return resp, err - } - - if resp.StatusCode == 200 { - return resp, err + return fmt.Errorf("cannot get token: %w", err) } + defer resp.Body.Close() - respBytes, err := io.ReadAll(resp.Body) + var tokenBytes []byte + tokenBytes, err = io.ReadAll(resp.Body) if err != nil { - return nil, 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)) + return fmt.Errorf("cannot get token: %w", err) } - 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) + if resp.StatusCode != 200 { + return fmt.Errorf("cannot get token: %s", tokenBytes) } - _ = writer.WriteField("extnetOnly", strconv.FormatBool(req.ExtNetOnly)) - - _ = writer.WriteField("authkey", token) + // save token in config + token := string(tokenBytes) + ldc.cfg.Token = token + ldc.expiryTime = time.Now().AddDate(0, 0, 1) - ct := writer.FormDataContentType() - writer.Close() - - 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/k8s/create.go b/pkg/cloudapi/k8s/create.go index 8f42d25..f32aa93 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/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/models.go b/pkg/cloudapi/rg/models.go index cf1bef4..1f11e58 100644 --- a/pkg/cloudapi/rg/models.go +++ b/pkg/cloudapi/rg/models.go @@ -486,6 +486,9 @@ type RecordLoadBalancer struct { // Access Control List ACL interface{} `json:"acl"` + // BackendHAIP + BackendHAIP string `json:"backendHAIP"` + // List of Backends Backends ListBackends `json:"backends"` @@ -510,6 +513,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/cloudbroker/k8s/create.go b/pkg/cloudbroker/k8s/create.go index 5b02abc..b29dc16 100644 --- a/pkg/cloudbroker/k8s/create.go +++ b/pkg/cloudbroker/k8s/create.go @@ -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/rg/models.go b/pkg/cloudbroker/rg/models.go index 1a5f074..26d7a9a 100644 --- a/pkg/cloudbroker/rg/models.go +++ b/pkg/cloudbroker/rg/models.go @@ -607,6 +607,9 @@ type ItemLB struct { // List ACL ACL ListACL `json:"acl"` + // BackendHAIP + BackendHAIP string `json:"backendHAIP"` + // List backends Backends ListBackends `json:"backends"` @@ -631,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"`