diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ebf220..ea923d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,30 @@ -## Version 1.7.1 +## Version 1.7.2 ## 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 diff --git a/README.md b/README.md index 2d84a42..7ef8619 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ import ( func main() { // Парсинг конфигурации из JSON-файла - cfg := config.ParseConfigJSON("") + cfg, _ := config.ParseConfigJSON("") } ``` @@ -635,7 +635,7 @@ filtered := resp. ```go func main() { // Чтение конфигурации из файла - cfg := config.ParseConfigJSON("") + cfg, _ := config.ParseConfigJSON("") // Создание клиента client := decort.New(cfg) @@ -864,7 +864,7 @@ import ( func main() { // Парсинг конфигурации из YAML-файла - legacyCfg := config.ParseLegacyConfigYAML("") + legacyCfg, _ := config.ParseLegacyConfigYAML("") } ``` @@ -969,21 +969,43 @@ func main() { Работа с BVS клиентом применяется для пользователей, которые используют для авторизации BVS. +### Настройка параметров BVS в кабинете администратора + +Для корректной работы функции обновления токена необходимо соблюдать следующие условия: + +на странице администратора по следующему пути: домены-<имя вашего домена>-(символ i)-токены +- параметр "Время жизни токена доступа" - устанавливается для всего домена. Не применяется есть по следующему пути: безопасность-клиентские_системы-(символ i)-токены-"Время жизни токена доступа" не выставлено иное время, которое имеет больший приоритет для конкретной клиентской системы +- параметр "Время простоя сессии" - время жизни токена обновления. В случае указания количества минут меньше, чем время жизни токена, то обновление токена будет работать некорректно. Редомендуется указывать время или равное или больше времени жизни токена +- параметр "Максимальное время жизни сессии" - время в течение которого возможно производить обновление токена. Если данный параметр будет равен времени жизни токена, то обновление токена будет работать некорректно. Редомендуется указывать время больше времени жизни токена + ### Настройка конфигурации BVS клиента Сначала, необходимо создать переменную конфигурации клиента. Конфигурация состоит как из обязательных, так и необязательных полей. -| Поле | Тип | Обязательный | Описание | -| ------------- | ------ | ------------ | ------------------------------------------------------------------ | -| Username | string | Да | username legacy пользователя | -| Password | string | Да | пароль legacy пользователя | -| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | -| SSOURL | string | Да | URL адрес сервиса аутентификации и авторизации | -| Domain | string | Да | Имя домена | -| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | -| Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений | -| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата | - +| Поле | Тип | Обязательный | Описание | +| ------------- | --------------------------------------------- | ------------ | ------------------------------------------------------------------ | +| Username | string | Да | username пользователя | +| Password | string | Да | пароль пользователя | +| AppID | string | Да | app_id ключа для выполнения запросов | +| AppSecret | string | Да | app_secret ключ для выполнения запроса | +| DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | +| SSOURL | string | Да | URL адрес сервиса аутентификации и авторизации | +| Domain | string | Да | Имя домена | +| Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | +| Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений | +| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата | +| Token | struct{} [см. ниже](#описание-структуры-token)| Нет | JWT токен | +| PathCfg | string | Нет | Путь записи конфигурации в файл | +| PathToken | string | Нет | Путь записи токена в файл | +| TimeToRefresh | uint | Нет | Количество минут, за сколько до истечения срока действия токена выполнится его обновление, по умолчанию - 1 минута | + +### Описание структуры token +| Параметр | Тип | Описание | +| ------------ | ------ | ------------------------------- | +| AccessToken | string | Токен | +| TokenType | string | Тип токена | +| RefreshToken | string | Токен для запроса на обновление | +| Expiry | time | Время жизни токена | #### Пример конфигурации BVS клиента @@ -1022,7 +1044,24 @@ import ( func main() { // Парсинг конфигурации из YAML-файла - BVSCfg := config.ParseConfigBVSYAML("") + BVSCfg, _ := config.ParseConfigBVSYAML("") +} +``` + +#### Парсинг BVS токена из файла + +Также возможно создать переменную токена из JSON или YAML файла, используя функцию `ParseTokenBVSJSON` (или `ParseTokenBVSYAML`) из пакета config. +
+*См. пример файлов конфигурации ниже и в директории `samples/`.* + +```go +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/config" +) + +func main() { + // Парсинг токена из json-файла + BVSToken, _ := config.ParseTokenBVSJSON("") } ``` @@ -1037,9 +1076,18 @@ func main() { "ssoUrl": "https://bvs-delta.qa.loc:8443", "decortUrl": "https://delta.qa.loc", "domain": "dynamix", + "token": { + "access_token": "string_token", + "token_type": "bearer", + "refresh_token": "string_refresh_token", + "expiry": "2023-11-24T12:40:27.954150524+03:00" + }, "retries": 5, + "sslSkipVerify": true, "timeout": "5m", - "sslSkipVerify": false + "path_cfg": "config", + "path_token": "token", + "timeToRefresh": 5 } ``` @@ -1051,10 +1099,18 @@ func main() { appSecret: ssoUrl: https://bvs-delta.qa.loc:8443 decortUrl: https://delta.qa.loc - domain: dynamix, + domain: dynamix + token": + access_token: string_token + token_type: bearer + refresh_token: string_refresh_token + expiry: 2023-11-24T12:40:27.954150524+03:00 retries: 5 + sslSkipVerify: true timeout: 5m - sslSkipVerify: false + path_cfg: config + path_token: token + timeToRefresh: 5 ``` ### Создание BVS клиента @@ -1090,6 +1146,88 @@ func main() { } ``` +#### Пример получения BVS токена + +В случае указания значения в переменной конфигурации `PathCfg` токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` + +```go +package main + +import ( + "fmt" + + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + decort "repository.basistech.ru/BASIS/decort-golang-sdk" +) + +func main() { + // Настройка конфигурации + BVSCfg := config.config.BVSConfig{ + Username: "", + Password: "", + AppID: "", + AppSecret: "", + SSOURL: "https://bvs-delta.qa.loc:8443" + DecortURL: "https://mr4.digitalenergy.online", + Domain: "dynamix", + PathCfg: "config", + Retries: 5, + } + + // Создание клиента + BVSClient := decort.NewBVS(cfg) + + // Выполнение запроса на получение токена + token, err := client.GetToken(context.Background()) + if err != nil { + log.Fatal(err) + } + + fmt.Println(token) +} +``` + +#### Пример обновления BVS токена + +В случае указания значения в переменной конфигурации `PathCfg` обновленный токен и конфигурация будут записаны в файл в формате `json`, переменная `PathToken` служит для записи токена в файл в формате `json` + +```go +package main + +import ( + "fmt" + + "repository.basistech.ru/BASIS/decort-golang-sdk/config" + decort "repository.basistech.ru/BASIS/decort-golang-sdk" +) + +func main() { + // Настройка конфигурации + BVSCfg := config.config.BVSConfig{ + Username: "", + Password: "", + AppID: "", + AppSecret: "", + SSOURL: "https://bvs-delta.qa.loc:8443" + DecortURL: "https://mr4.digitalenergy.online", + Domain: "dynamix", + PathToken: "token", + Retries: 5, + } + + // Создание клиента + BVSClient := decort.NewBVS(cfg) + + // Выполнение запроса на обновление токена + token, err := client.RefreshToken(context.Background()) + if err != nil { + log.Fatal(err) + } + + fmt.Println(token) +} +``` + #### Пример выполнения запроса ```go diff --git a/client.go b/client.go index 156eb90..c4d4e78 100644 --- a/client.go +++ b/client.go @@ -16,14 +16,13 @@ import ( "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s" ) -const restmachine = "/restmachine" - // HTTP-client for platform type DecortClient struct { decortURL string @@ -90,7 +89,7 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p body = bytes.NewBufferString(values.Encode()) } - req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+restmachine+url, body) + req, err := http.NewRequestWithContext(ctx, method, dc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } @@ -169,17 +168,20 @@ func (dc *DecortClient) do(req *http.Request, ctype string) (*http.Response, err // req = req.Clone(req.Context()) req.Body = io.NopCloser(bytes.NewBuffer(buf)) resp, err := dc.client.Do(req) - if err == nil { - if resp.StatusCode == 200 { - return resp, err - } + if err != nil || resp == nil { + return resp, err + } + + if resp.StatusCode == 200 { + return resp, err } - // } - respBytes, _ := io.ReadAll(resp.Body) - err = fmt.Errorf("%s", respBytes) - resp.Body.Close() - return nil, fmt.Errorf("could not execute request: %w", err) + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body.Close() + return resp, errors.New(string(respBytes)) } func createK8sCloudApi(req k8s_ca.CreateRequest) (*bytes.Buffer, string) { diff --git a/client_bvs.go b/client_bvs.go index 1e74660..5516e6b 100644 --- a/client_bvs.go +++ b/client_bvs.go @@ -13,9 +13,11 @@ import ( "strconv" "strings" "sync" + "time" "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" @@ -30,11 +32,21 @@ type BVSDecortClient struct { decortURL string } +type tokenJSON struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + ExpiresIn uint64 `json:"expires_in"` +} + // Сlient builder func NewBVS(cfg config.BVSConfig) *BVSDecortClient { if cfg.Retries == 0 { cfg.Retries = 5 } + if cfg.TimeToRefresh == 0 { + cfg.TimeToRefresh = 1 + } return &BVSDecortClient{ decortURL: cfg.DecortURL, @@ -80,18 +92,42 @@ func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url strin body = bytes.NewBufferString(values.Encode()) } - req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+restmachine+url, body) + req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } - if err = bdc.getToken(ctx); err != nil { - return nil, err + if bdc.cfg.Token.AccessToken == "" { + if _, err = bdc.GetToken(ctx); err != nil { + return nil, err + } } + 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 + } + } + } + reqCopy := req.Clone(ctx) + //nolint:bodyclose + //work defer, error lint resp, err := bdc.do(req, ctype) if err != nil { - return nil, err + 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 { + return nil, err + } } defer resp.Body.Close() @@ -107,39 +143,117 @@ func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url strin return respBytes, nil } -func (bdc *BVSDecortClient) getToken(ctx context.Context) error { +// 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() - if !bdc.cfg.Token.Valid() { + 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) - body := fmt.Sprintf("grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s&response_type=token&scope=openid", bdc.cfg.AppID, bdc.cfg.AppSecret, bdc.cfg.Username, bdc.cfg.Password) - bodyReader := strings.NewReader(body) + bdc.cfg.SSOURL = strings.TrimSuffix(bdc.cfg.SSOURL, "/") - bdc.cfg.SSOURL = strings.TrimSuffix(bdc.cfg.SSOURL, "/") + req, _ := http.NewRequestWithContext(ctx, "POST", bdc.cfg.SSOURL+"/realms/"+bdc.cfg.Domain+"/protocol/openid-connect/token", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - 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") + resp, err := bdc.client.Do(req) + if err != nil { + return config.Token{}, fmt.Errorf("cannot get token: %w", err) + } - resp, err := bdc.client.Do(req) - if err != nil { - return fmt.Errorf("cannot get token: %w", err) - } + tokenBytes, _ := io.ReadAll(resp.Body) + resp.Body.Close() - tokenBytes, _ := io.ReadAll(resp.Body) - resp.Body.Close() + if resp.StatusCode != 200 { + return config.Token{}, fmt.Errorf("cannot get token: %s", tokenBytes) + } - if resp.StatusCode != 200 { - return fmt.Errorf("cannot get token: %s", tokenBytes) - } + var tj tokenJSON - err = json.Unmarshal(tokenBytes, &bdc.cfg.Token) - if err != nil { - return fmt.Errorf("cannot unmarshal token: %s", tokenBytes) - } + if err = json.Unmarshal(tokenBytes, &tj); err != nil { + return config.Token{}, fmt.Errorf("cannot unmarshal token: %w", err) + } + + bdc.cfg.Token = config.Token{ + AccessToken: tj.AccessToken, + TokenType: tj.TokenType, + RefreshToken: tj.RefreshToken, + Expiry: tj.expiry(), + } + + if bdc.cfg.PathCfg != "" { + ser, _ := bdc.cfg.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathCfg) + } + + if bdc.cfg.PathToken != "" { + ser, _ := bdc.cfg.Token.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathToken) } - return nil + return bdc.cfg.Token, nil +} + +// RefreshToken allows you to refresh a token and returns the token structure, when specifying the PathCfg variable, +// the token and configuration will be written to a file, +// when specifying the PathToken variable, the token will be written to a file +func (bdc *BVSDecortClient) RefreshToken(ctx context.Context) (config.Token, error) { + bdc.mutex.Lock() + defer bdc.mutex.Unlock() + + body := fmt.Sprintf("grant_type=refresh_token&client_id=%s&client_secret=%s&refresh_token=%s&scope=openid", bdc.cfg.AppID, bdc.cfg.AppSecret, bdc.cfg.Token.RefreshToken) + bodyReader := strings.NewReader(body) + + bdc.cfg.SSOURL = strings.TrimSuffix(bdc.cfg.SSOURL, "/") + + req, _ := http.NewRequestWithContext(ctx, "POST", bdc.cfg.SSOURL+"/realms/"+bdc.cfg.Domain+"/protocol/openid-connect/token", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := bdc.client.Do(req) + if err != nil { + return config.Token{}, fmt.Errorf("cannot refresh token: %w", err) + } + + tokenBytes, _ := io.ReadAll(resp.Body) + resp.Body.Close() + + if resp.StatusCode != 200 { + return config.Token{}, fmt.Errorf("cannot refresh token: %s", tokenBytes) + } + + var tj tokenJSON + + if err = json.Unmarshal(tokenBytes, &tj); err != nil { + return config.Token{}, fmt.Errorf("cannot unmarshal after refresh token: %w", err) + } + + bdc.cfg.Token = config.Token{ + AccessToken: tj.AccessToken, + TokenType: tj.TokenType, + RefreshToken: tj.RefreshToken, + Expiry: tj.expiry(), + } + + if bdc.cfg.PathCfg != "" { + ser, _ := bdc.cfg.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathCfg) + } + + if bdc.cfg.PathToken != "" { + ser, _ := bdc.cfg.Token.Serialize("", " ") + _ = ser.WriteToFile(bdc.cfg.PathToken) + } + + return bdc.cfg.Token, nil +} + +func (e *tokenJSON) expiry() (t time.Time) { + if v := e.ExpiresIn; v != 0 { + return time.Now().Add(time.Duration(v) * time.Second) + } + return } func (bdc *BVSDecortClient) do(req *http.Request, ctype string) (*http.Response, error) { @@ -148,28 +262,32 @@ func (bdc *BVSDecortClient) do(req *http.Request, ctype string) (*http.Response, } else { req.Header.Add("Content-Type", "application/x-www-form-urlencoded") } - bdc.cfg.Token.SetAuthHeader(req) + req.Header.Add("Authorization", "bearer "+bdc.cfg.Token.AccessToken) req.Header.Set("Accept", "application/json") - // var resp *http.Response - // var err error buf, _ := io.ReadAll(req.Body) - // for i := uint64(0); i < bdc.cfg.Retries; i++ { - // req = req.Clone(req.Context()) req.Body = io.NopCloser(bytes.NewBuffer(buf)) resp, err := bdc.client.Do(req) - if err == nil { - if resp.StatusCode == 200 { - return resp, err - } + if err != nil || resp == nil { + return resp, err } - // } - respBytes, _ := io.ReadAll(resp.Body) - err = fmt.Errorf("%s", respBytes) - resp.Body.Close() - return nil, fmt.Errorf("could not execute request: %w", err) + 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 { + return nil, err + } + resp.Body.Close() + return resp, errors.New(string(respBytes)) } func createK8sCloudApiBVS(req k8s_ca.CreateRequest) (*bytes.Buffer, string) { diff --git a/config/config_bvs.go b/config/config_bvs.go index 6b568bb..9182fcb 100644 --- a/config/config_bvs.go +++ b/config/config_bvs.go @@ -5,8 +5,8 @@ import ( "os" "time" - "golang.org/x/oauth2" "gopkg.in/yaml.v3" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/serialization" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" ) @@ -24,7 +24,7 @@ type BVSConfig struct { // Domain name // Required: true // Example: "dynamix" - Domain string `json:"domain" yaml:"domain"` + Domain string `json:"domain" yaml:"domain" validate:"required"` // Application (client) identifier for authorization // in the cloud platform controller in oauth2 mode. @@ -50,7 +50,7 @@ type BVSConfig struct { // JWT platform token // Required: false // Example: "qwqwdfwv68979we0q9bfv7e9sbvd89798qrwv97ff" - Token oauth2.Token `json:"token" yaml:"token"` + Token Token `json:"token" yaml:"token"` // Amount platform request attempts // Default value: 5 @@ -64,6 +64,40 @@ type BVSConfig struct { // HTTP client timeout, unlimited if left empty // Required: false Timeout Duration `json:"timeout" yaml:"timeout"` + + // The path of the configuration file entry + // Required: false + PathCfg string `json:"path_cfg" yaml:"path_cfg"` + + // The path of the token file entry + // Required: false + PathToken string `json:"path_token" yaml:"path_token"` + + // The number of minutes before the expiration of the token, a refresh will be made + // Required: false + TimeToRefresh int64 `json:"timeToRefresh" yaml:"timeToRefresh"` +} + +type Token struct { + // AccessToken is the token that authorizes and authenticates + // the requests. + // Required: false + AccessToken string `json:"access_token" yaml:"access_token"` + + // TokenType is the type of token. + // The Type method returns either this or "Bearer", the default. + // Required: false + TokenType string `json:"token_type" yaml:"token_type"` + + // RefreshToken is a token that's used by the application + // (as opposed to the user) to refresh the access token + // if it expires. + // Required: false + RefreshToken string `json:"refresh_token" yaml:"refresh_token"` + + // Expiry is the optional expiration time of the access token. + // Required: false + Expiry time.Time `json:"expiry" yaml:"expiry"` } // SetTimeout is used to set HTTP client timeout. @@ -93,6 +127,50 @@ func ParseConfigBVSJSON(path string) (BVSConfig, error) { return config, nil } +// ParseConfigJSON parses Token from specified JSON-formatted file. +func ParseTokenBVSJSON(path string) (Token, error) { + file, err := os.ReadFile(path) + if err != nil { + return Token{}, err + } + + var token Token + + err = json.Unmarshal(file, &token) + if err != nil { + return Token{}, err + } + + err = validators.ValidateConfig(token) + if err != nil { + return Token{}, validators.ValidationErrors(validators.GetErrors(err)) + } + + return token, nil +} + +// ParseTokenBVSYAML parses Token from specified YAML-formatted file. +func ParseTokenBVSYAML(path string) (Token, error) { + file, err := os.ReadFile(path) + if err != nil { + return Token{}, err + } + + var token Token + + err = yaml.Unmarshal(file, &token) + if err != nil { + return Token{}, err + } + + err = validators.ValidateConfig(token) + if err != nil { + return Token{}, validators.ValidationErrors(validators.GetErrors(err)) + } + + return token, nil +} + // ParseConfigYAML parses Config from specified YAML-formatted file. func ParseConfigBVSYAML(path string) (BVSConfig, error) { file, err := os.ReadFile(path) @@ -114,3 +192,25 @@ func ParseConfigBVSYAML(path string) (BVSConfig, error) { return config, nil } + +func (t Token) Serialize(params ...string) (serialization.Serialized, error) { + if len(params) > 1 { + prefix := params[0] + indent := params[1] + + return json.MarshalIndent(t, prefix, indent) + } + + return json.Marshal(t) +} + +func (c BVSConfig) Serialize(params ...string) (serialization.Serialized, error) { + if len(params) > 1 { + prefix := params[0] + indent := params[1] + + return json.MarshalIndent(c, prefix, indent) + } + + return json.Marshal(c) +} diff --git a/config/timeouts.go b/config/timeouts.go index 89a12eb..924158a 100644 --- a/config/timeouts.go +++ b/config/timeouts.go @@ -22,6 +22,8 @@ func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { } *d = Duration(tmp) return nil + case float64: + return nil default: return fmt.Errorf("invalid duration %v", value) } @@ -40,6 +42,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error { } *d = Duration(tmp) return nil + case float64: + return nil default: return fmt.Errorf("invalid duration %v", value) } diff --git a/go.mod b/go.mod index 7588a3e..18fb1e0 100644 --- a/go.mod +++ b/go.mod @@ -5,20 +5,16 @@ go 1.20 require ( github.com/go-playground/validator/v10 v10.11.2 github.com/google/go-querystring v1.1.0 - golang.org/x/oauth2 v0.13.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/net v0.16.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 95dee0d..52f813c 100644 --- a/go.sum +++ b/go.sum @@ -8,13 +8,9 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -28,29 +24,13 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..66d9840 --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,5 @@ +package constants + +const Restmachine = "/restmachine" + + diff --git a/legacy-client.go b/legacy-client.go index 0bd7a46..3bc8930 100644 --- a/legacy-client.go +++ b/legacy-client.go @@ -17,6 +17,7 @@ import ( "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" k8s_ca "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" @@ -94,7 +95,7 @@ func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url st body = bytes.NewBufferString(values.Encode() + fmt.Sprintf("&authkey=%s", ldc.cfg.Token)) } - req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+restmachine+url, body) + req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } @@ -164,17 +165,20 @@ func (ldc *LegacyDecortClient) do(req *http.Request, ctype string) (*http.Respon // req = req.Clone(req.Context()) req.Body = io.NopCloser(bytes.NewBuffer(buf)) resp, err := ldc.client.Do(req) - if err == nil { - if resp.StatusCode == 200 { - return resp, err - } + if err != nil || resp == nil { + return resp, err } - // } - respBytes, _ := io.ReadAll(resp.Body) - err = fmt.Errorf("%s", respBytes) - resp.Body.Close() - return nil, fmt.Errorf("could not execute request: %w", err) + if resp.StatusCode == 200 { + return resp, err + } + + respBytes, 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) { diff --git a/pkg/cloudapi/k8s/delete.go b/pkg/cloudapi/k8s/delete.go index 7f9fdfe..03176c0 100644 --- a/pkg/cloudapi/k8s/delete.go +++ b/pkg/cloudapi/k8s/delete.go @@ -17,7 +17,7 @@ type DeleteRequest struct { // True if cluster is destroyed permanently. // Otherwise it can be restored from Recycle Bin // Required: true - Permanently bool `url:"permanently" json:"permanently" validate:"required"` + Permanently bool `url:"permanently" json:"permanently"` } // Delete deletes kubernetes cluster diff --git a/pkg/cloudbroker/audit.go b/pkg/cloudbroker/audit.go new file mode 100644 index 0000000..cec4931 --- /dev/null +++ b/pkg/cloudbroker/audit.go @@ -0,0 +1,10 @@ +package cloudbroker + +import ( + "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/audit" +) + +// Accessing the Stack method group +func (cb *CloudBroker) Audit() *audit.Audit { + return audit.New(cb.client) +} diff --git a/pkg/cloudbroker/audit/audit.go b/pkg/cloudbroker/audit/audit.go new file mode 100644 index 0000000..26f69ef --- /dev/null +++ b/pkg/cloudbroker/audit/audit.go @@ -0,0 +1,15 @@ +package audit + +import "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces" + +// Structure for creating request to audit +type Audit struct { + client interfaces.Caller +} + +// Builder for audit endpoint +func New(client interfaces.Caller) *Audit{ + return &Audit{ + client: client, + } +} \ No newline at end of file diff --git a/pkg/cloudbroker/audit/get.go b/pkg/cloudbroker/audit/get.go new file mode 100644 index 0000000..1894f85 --- /dev/null +++ b/pkg/cloudbroker/audit/get.go @@ -0,0 +1,46 @@ +package audit + +import ( + "context" + "encoding/json" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// GetRequest struct to get information about account +type GetRequest struct { + // Audit GUID + // Required: true + AuditGuid string `url:"auditGuid" json:"auditGuid" validate:"required"` +} + +// Get gets information about audit as a RecordAudit struct +func (a Audit) Get(ctx context.Context, req GetRequest) (*RecordAudit, error) { + res, err := a.GetRaw(ctx, req) + if err != nil { + return nil, err + } + + info := RecordAudit{} + + err = json.Unmarshal(res, &info) + if err != nil { + return nil, err + } + + return &info, nil +} + +// GetRaw gets information about audit as an array of bytes +func (a Audit) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) { + err := validators.ValidateRequest(req) + if err != nil { + return nil, validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/audit/get" + + res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) + return res, err +} diff --git a/pkg/cloudbroker/audit/linked_jobs.go b/pkg/cloudbroker/audit/linked_jobs.go new file mode 100644 index 0000000..ebf773e --- /dev/null +++ b/pkg/cloudbroker/audit/linked_jobs.go @@ -0,0 +1,46 @@ +package audit + +import ( + "context" + "encoding/json" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// LinkedJobsRequest struct to get information about jobs linked with Audit +type LinkedJobsRequest struct { + // Audit GUID + // Required: true + AuditGuid string `url:"auditGuid" json:"auditGuid" validate:"required"` +} + +// LinkedJobs gets information about Linked Jobs as a ListLinkedJobs struct +func (a Audit) LinkedJobs(ctx context.Context, req LinkedJobsRequest) (*ListLinkedJobs, error) { + res, err := a.GetRawLinkedJobs(ctx, req) + if err != nil { + return nil, err + } + + info := ListLinkedJobs{} + + err = json.Unmarshal(res, &info) + if err != nil { + return nil, err + } + + return &info, nil +} + +// GetRawLinkedJobs gets information about Linked Jobs as an array of bytes +func (a Audit) GetRawLinkedJobs(ctx context.Context, req LinkedJobsRequest) ([]byte, error) { + err := validators.ValidateRequest(req) + if err != nil { + return nil, validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/audit/linkedJobs" + + res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) + return res, err +} \ No newline at end of file diff --git a/pkg/cloudbroker/audit/list.go b/pkg/cloudbroker/audit/list.go new file mode 100644 index 0000000..e7a7cc6 --- /dev/null +++ b/pkg/cloudbroker/audit/list.go @@ -0,0 +1,64 @@ +package audit + +import ( + "context" + "encoding/json" + "net/http" +) + +// ListRequest struct to give list of account audits +type ListRequest struct { + + // Find all audits after point in time (unixtime) + // Required: false + TimestampAt uint64 `url:"timestampAt,omitempty" json:"timestampAt,omitempty"` + + // Find all audits before point in time (unixtime) + // Required: false + TimestampTo uint64 `url:"timestampTo,omitempty" json:"timestampTo,omitempty"` + + // Find by user (Mongo RegExp supported) + // Required: false + User string `url:"user,omitempty" json:"user,omitempty"` + + // Find by api endpoint (Mongo RegExp supported) + // Required: false + Call string `url:"call,omitempty" json:"call,omitempty"` + + // Find by HTTP status code + // Required: false + StatusCode uint64 `url:"statusCode,omitempty" json:"statusCode,omitempty"` + + // Page number + // Required: false + Page uint64 `url:"page,omitempty" json:"page,omitempty"` + + // Page size + // Required: false + Size uint64 `url:"size,omitempty" json:"size,omitempty"` +} + +// List gets audit records for the specified account object +func (a Audit) List(ctx context.Context, req ListRequest) (*ListAudits, error) { + res, err := a.ListRaw(ctx, req) + if err != nil { + return nil, err + } + + list := ListAudits{} + + err = json.Unmarshal(res, &list) + if err != nil { + return nil, err + } + + return &list, nil +} + +// ListRaw gets list of audit records an array of bytes +func (a Audit) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) { + url := "/cloudbroker/audit/list" + + res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req) + return res, err +} diff --git a/pkg/cloudbroker/audit/models.go b/pkg/cloudbroker/audit/models.go new file mode 100644 index 0000000..73588f9 --- /dev/null +++ b/pkg/cloudbroker/audit/models.go @@ -0,0 +1,102 @@ +package audit + +// Main info about audit +type ItemAudit struct { + // Call + Call string `json:"call"` + + // GUID + GUID string `json:"guid"` + + // Response time + ResponseTime float64 `json:"responsetime"` + + // Status code + StatusCode uint64 `json:"statuscode"` + + // Timestamp + Timestamp float64 `json:"timestamp"` + + // User + User string `json:"user"` +} + +// List of audits +type ListAudits struct { + // Data + Data []ItemAudit `json:"data"` + + // EntryCount + EntryCount uint64 `json:"entryCount"` +} + +// Main info about audit +type RecordAudit struct { + + // Apitask + Apitask string `json:"apitask"` + + // Arguments + Arguments string `json:"args"` + + // Call + Call string `json:"call"` + + // GUID + GUID string `json:"guid"` + + // Kwargs + Kwargs string `json:"kwargs"` + + // RemoteAddr + RemoteAddr string `json:"remote_addr"` + + // Response time + ResponseTime float64 `json:"responsetime"` + + // Result + Result string `json:"result"` + + // Status code + StatusCode uint64 `json:"statuscode"` + + // Tags + Tags string `json:"tags"` + + // Timestamp + Timestamp float64 `json:"timestamp"` + + // TimestampEnd + TimestampEnd float64 `json:"timestampEnd"` + + // User + User string `json:"user"` +} + +// List of Linked Jobs +type ListLinkedJobs []ItemLinkedJobs + +// Main info about Linked Jobs +type ItemLinkedJobs struct { + + // CMD + CMD string `json:"cmd"` + + // NID + NID uint64 `json:"nid"` + + // state + State string `json:"state"` + + // TimeCreate + TimeCreate uint64 `json:"timeCreate"` + + // TimeStart + TimeStart uint64 `json:"timeStart"` + + // TimeStop + TimeStop uint64 `json:"timeStop"` + + // Timeout + Timeout uint64 `json:"timeout"` +} diff --git a/pkg/cloudbroker/compute/delete_custom_fields.go b/pkg/cloudbroker/compute/delete_custom_fields.go new file mode 100644 index 0000000..33b3d71 --- /dev/null +++ b/pkg/cloudbroker/compute/delete_custom_fields.go @@ -0,0 +1,38 @@ +package compute + +import ( + "context" + "net/http" + "strconv" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// DeleteCustomFieldsRequest struct to delete compute's custom fields +type DeleteCustomFieldsRequest struct { + // ID of the compute + // Required: true + ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"` +} + +// DeleteCustomFields deletes computes custom fields +func (c Compute) DeleteCustomFields(ctx context.Context, req DeleteCustomFieldsRequest) (bool, error) { + err := validators.ValidateRequest(req) + if err != nil { + return false, validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/compute/deleteCustomFields" + + res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req) + if err != nil { + return false, err + } + + result, err := strconv.ParseBool(string(res)) + if err != nil { + return false, err + } + + return result, nil +} diff --git a/pkg/cloudbroker/compute/ids.go b/pkg/cloudbroker/compute/ids.go index 62e4c2f..3a6b978 100644 --- a/pkg/cloudbroker/compute/ids.go +++ b/pkg/cloudbroker/compute/ids.go @@ -20,8 +20,8 @@ func (lid ListInfoDisks) IDs() []uint64 { // IDs gets array of PFWsIDs from ListPFW struct func (lp ListPFW) IDs() []uint64 { - res := make([]uint64, 0, len(lp)) - for _, p := range lp { + res := make([]uint64, 0, len(lp.Data)) + for _, p := range lp.Data { res = append(res, p.ID) } return res @@ -43,4 +43,4 @@ func (lpd ListPCIDevices) IDs() []uint64 { res = append(res, pd.ID) } return res -} \ No newline at end of file +} diff --git a/pkg/cloudbroker/compute/models.go b/pkg/cloudbroker/compute/models.go index cb29be0..4b39686 100644 --- a/pkg/cloudbroker/compute/models.go +++ b/pkg/cloudbroker/compute/models.go @@ -12,6 +12,14 @@ type RecordACL struct { RGACL ListACL `json:"rgACL"` } +type ListUsers struct { + // Data + Data RecordACL `json:"data"` + + // Entry count + EntryCount uint64 `json:"entryCount"` +} + // ACL information type ItemACL struct { // Explicit @@ -75,6 +83,15 @@ type ItemSnapshot struct { // List of snapshots type ListSnapshots []ItemSnapshot +// List of snapshots +type ListSnapShot struct { + // Data + Data []ItemSnapshot `json:"data"` + + // Entry count + EntryCount uint64 `json:"entryCount"` +} + // Main information about snapshot usage type ItemSnapshotUsage struct { // Count @@ -232,7 +249,13 @@ type ItemPFW struct { } // List port forwards -type ListPFW []ItemPFW +type ListPFW struct { + // Data + Data []ItemPFW `json:"data"` + + // Entry count + EntryCount uint64 `json:"entryCount"` +} // Main information about rule type ItemRule struct { @@ -569,6 +592,9 @@ type InfoCompute struct { // Boot disk size BootDiskSize uint64 `json:"bootdiskSize"` + // cd Image Id + CdImageId uint64 `json:"cdImageId"` + // Clone reference CloneReference uint64 `json:"cloneReference"` @@ -638,6 +664,9 @@ type InfoCompute struct { // Name Name string `json:"name"` + // Need reboot + NeedReboot bool `json:"needReboot"` + // List OS users OSUsers ListOSUsers `json:"osUsers"` @@ -721,7 +750,7 @@ type RecordCompute struct { } // Information about of disk IDs -type ListInfoDisks []InfoDisk +type ListInfoDisks []InfoDisk // Main information about compute for list type ItemCompute struct { @@ -732,7 +761,7 @@ type ItemCompute struct { InfoCompute // Total disk size - TotalDiskSize uint64 `json:"totalDiskSize"` + TotalDiskSize uint64 `json:"totalDisksSize"` // VINS connected VINSConnected uint64 `json:"vinsConnected"` diff --git a/pkg/cloudbroker/compute/pfw_list.go b/pkg/cloudbroker/compute/pfw_list.go index 7d1751e..b6fcd6c 100644 --- a/pkg/cloudbroker/compute/pfw_list.go +++ b/pkg/cloudbroker/compute/pfw_list.go @@ -20,7 +20,7 @@ type PFWListRequest struct { } // PFWList gets compute port forwards list -func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (ListPFW, error) { +func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (*ListPFW, error) { err := validators.ValidateRequest(req) if err != nil { return nil, validators.ValidationErrors(validators.GetErrors(err)) @@ -40,5 +40,5 @@ func (c Compute) PFWList(ctx context.Context, req PFWListRequest) (ListPFW, erro return nil, err } - return list, nil + return &list, nil } diff --git a/pkg/cloudbroker/compute/snapshot_list.go b/pkg/cloudbroker/compute/snapshot_list.go index 02e78f1..453242a 100644 --- a/pkg/cloudbroker/compute/snapshot_list.go +++ b/pkg/cloudbroker/compute/snapshot_list.go @@ -16,7 +16,7 @@ type SnapshotListRequest struct { } // SnapshotList gets list of compute snapshots -func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (ListSnapshots, error) { +func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (*ListSnapShot, error) { err := validators.ValidateRequest(req) if err != nil { return nil, validators.ValidationErrors(validators.GetErrors(err)) @@ -29,12 +29,12 @@ func (c Compute) SnapshotList(ctx context.Context, req SnapshotListRequest) (Lis return nil, err } - list := ListSnapshots{} + list := ListSnapShot{} err = json.Unmarshal(res, &list) if err != nil { return nil, err } - return list, nil + return &list, nil } diff --git a/pkg/cloudbroker/compute/user_list.go b/pkg/cloudbroker/compute/user_list.go index 586d9e9..384bae4 100644 --- a/pkg/cloudbroker/compute/user_list.go +++ b/pkg/cloudbroker/compute/user_list.go @@ -16,7 +16,7 @@ type UserListRequest struct { } // UserList gets users list for compute -func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL, error) { +func (c Compute) UserList(ctx context.Context, req UserListRequest) (*ListUsers, error) { err := validators.ValidateRequest(req) if err != nil { return nil, validators.ValidationErrors(validators.GetErrors(err)) @@ -29,7 +29,7 @@ func (c Compute) UserList(ctx context.Context, req UserListRequest) (*RecordACL, return nil, err } - list := RecordACL{} + list := ListUsers{} err = json.Unmarshal(res, &list) if err != nil { diff --git a/pkg/cloudbroker/grid/get_diagnosis.go b/pkg/cloudbroker/grid/get_diagnosis.go index f37f1a8..540f2dd 100644 --- a/pkg/cloudbroker/grid/get_diagnosis.go +++ b/pkg/cloudbroker/grid/get_diagnosis.go @@ -2,6 +2,7 @@ package grid import ( "context" + "fmt" "net/http" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" @@ -38,7 +39,7 @@ func (g Grid) GetDiagnosisGET(ctx context.Context, req GetDiagnosisRequest) (str return "", validators.ValidationErrors(validators.GetErrors(err)) } - url := "/cloudbroker/grid/getDiagnosis" + url := fmt.Sprintf("/cloudbroker/grid/getDiagnosis/?gid=%d", req.GID) res, err := g.client.DecortApiCall(ctx, http.MethodGet, url, req) if err != nil { diff --git a/pkg/cloudbroker/grid/models.go b/pkg/cloudbroker/grid/models.go index 0a6f7c1..731b971 100644 --- a/pkg/cloudbroker/grid/models.go +++ b/pkg/cloudbroker/grid/models.go @@ -67,6 +67,9 @@ type DiskUsage struct { // Detailed information about grid type RecordGrid struct { + // AuthBroker + AuthBroker []interface{} `json:"authBroker"` + // Flag Flag string `json:"flag"` @@ -91,6 +94,9 @@ type ItemGridList struct { // Resource information Resources Resources `json:"Resources"` + // AuthBroker + AuthBroker []interface{} `json:"authBroker"` + // Flag Flag string `json:"flag"` diff --git a/pkg/cloudbroker/k8ci/access_add.go b/pkg/cloudbroker/k8ci/access_add.go new file mode 100644 index 0000000..a134f80 --- /dev/null +++ b/pkg/cloudbroker/k8ci/access_add.go @@ -0,0 +1,36 @@ +package k8ci + +import ( + "context" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// AccessAddRequest struct for adding permission to access to account for a k8ci +type AccessAddRequest struct { + // ID of the K8 catalog item to add access for + // Required: true + K8CIID uint64 `url:"k8ciId" json:"k8ciId" validate:"required"` + + // Account ID to add to the sharedWith access list + // Required: true + AccountId uint64 `url:"accountId" json:"accountId" validate:"required"` +} + +// Add accountId to sharedWith access list for k8ci. +func (k K8CI) AccessAdd (ctx context.Context, req AccessAddRequest) (string, error) { + err := validators.ValidateRequest(req) + if err != nil { + return "", validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/k8ci/accessAdd" + + res, err := k.client.DecortApiCall(ctx, http.MethodPost, url, req) + if err != nil { + return "", err + } + + return string(res), nil +} diff --git a/pkg/cloudbroker/k8ci/access_remove.go b/pkg/cloudbroker/k8ci/access_remove.go new file mode 100644 index 0000000..c1a675f --- /dev/null +++ b/pkg/cloudbroker/k8ci/access_remove.go @@ -0,0 +1,36 @@ +package k8ci + +import ( + "context" + "net/http" + + "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" +) + +// AccessRemoveRequest struct for removing permission to access to account for a k8ci +type AccessRemoveRequest struct { + // ID of the K8 catalog item to remove access for + // Required: true + K8CIID uint64 `url:"k8ciId" json:"k8ciId" validate:"required"` + + // Account ID to be removed from the sharedWith access list + // Required: true + AccountId uint64 `url:"accountId" json:"accountId" validate:"required"` +} + +// Remove accountId from sharedWith access list for k8ci. +func (k K8CI) AccessRemove (ctx context.Context, req AccessRemoveRequest) (string, error) { + err := validators.ValidateRequest(req) + if err != nil { + return "", validators.ValidationErrors(validators.GetErrors(err)) + } + + url := "/cloudbroker/k8ci/accessRemove" + + res, err := k.client.DecortApiCall(ctx, http.MethodPost, url, req) + if err != nil { + return "", err + } + + return string(res), nil +} diff --git a/pkg/cloudbroker/k8s/delete.go b/pkg/cloudbroker/k8s/delete.go index af3a2ca..e05ba0c 100644 --- a/pkg/cloudbroker/k8s/delete.go +++ b/pkg/cloudbroker/k8s/delete.go @@ -17,7 +17,7 @@ type DeleteRequest struct { // True if cluster is destroyed permanently. // Otherwise it can be restored from recycle bin // Required: false - Permanently bool `url:"permanently,omitempty" json:"permanently,omitempty"` + Permanently bool `url:"permanently" json:"permanently"` } // Delete deletes kubernetes cluster diff --git a/pkg/cloudbroker/lb/make_highly_available.go b/pkg/cloudbroker/lb/make_highly_available.go index e10463b..c034115 100644 --- a/pkg/cloudbroker/lb/make_highly_available.go +++ b/pkg/cloudbroker/lb/make_highly_available.go @@ -29,10 +29,10 @@ func (l LB) HighlyAvailable(ctx context.Context, req HighlyAvailableRequest) (bo return false, err } - result, err := strconv.ParseBool(string(res)) - if err != nil { + result, err := strconv.ParseUint(string(res), 10, 64) + if err != nil || result != req.LBID { return false, err } - return result, nil + return true, nil } diff --git a/pkg/cloudbroker/rg/affinity_groups_list.go b/pkg/cloudbroker/rg/affinity_groups_list.go index a2e2555..ac6e862 100644 --- a/pkg/cloudbroker/rg/affinity_groups_list.go +++ b/pkg/cloudbroker/rg/affinity_groups_list.go @@ -13,6 +13,14 @@ type AffinityGroupsListRequest struct { // Resource group ID // Required: true RGID uint64 `url:"rgId" json:"rgId" validate:"required"` + + // Page number + // Required: false + Page uint64 `url:"page,omitempty" json:"page,omitempty"` + + // Page size + // Required: false + Size uint64 `url:"size,omitempty" json:"size,omitempty"` } // AffinityGroupsList gets all currently defined affinity groups in this resource group with compute IDs diff --git a/pkg/cloudbroker/rg/ids.go b/pkg/cloudbroker/rg/ids.go index 1aa2d69..272570c 100644 --- a/pkg/cloudbroker/rg/ids.go +++ b/pkg/cloudbroker/rg/ids.go @@ -53,3 +53,12 @@ func (lpfw ListPFW) IDs() []uint64 { } return res } + +// IDs gets array of ComputeIDs from ListAffinityGroupItems struct +func (lag ListAffinityGroupItems) IDs() []uint64 { + res := make([]uint64, 0, len(lag)) + for _, ag := range lag { + res = append(res, ag.ID) + } + return res +} diff --git a/pkg/cloudbroker/rg/list.go b/pkg/cloudbroker/rg/list.go index 6d6d3f6..46162af 100644 --- a/pkg/cloudbroker/rg/list.go +++ b/pkg/cloudbroker/rg/list.go @@ -36,6 +36,10 @@ type ListRequest struct { // Required: false Status string `url:"status,omitempty" json:"status,omitempty"` + // Find by status lock + // Required: false + LockStatus string `url:"lockStatus,omitempty" json:"lockStatus,omitempty"` + // Included deleted resource groups // Required: false IncludeDeleted bool `url:"includedeleted,omitempty" json:"includedeleted,omitempty"` diff --git a/pkg/cloudbroker/rg/models.go b/pkg/cloudbroker/rg/models.go index e8628ba..1a5f074 100644 --- a/pkg/cloudbroker/rg/models.go +++ b/pkg/cloudbroker/rg/models.go @@ -65,6 +65,10 @@ type ItemResourceConsumption struct { // Reserved information Reserved Reservation `json:"Reserved"` + // Resource limits + ResourceLimits ResourceLimits `json:"resourceLimits"` + + // Resource group ID RGID uint64 `json:"rgid"` } @@ -687,8 +691,15 @@ type ListLB struct { type ListAffinityGroup struct { // Data - Data map[string][]uint64 `json:"data"` + Data []map[string]ListAffinityGroupItems `json:"data"` // Entry count EntryCount uint64 `json:"entryCount"` } + +type ListAffinityGroupItems []ItemAffinityGroup + +type ItemAffinityGroup struct { + ID uint64 `json:"id"` + NodeID uint64 `json:"node_id"` +} diff --git a/pkg/cloudbroker/stack/stack.go b/pkg/cloudbroker/stack/stack.go index 94c5d89..a330f68 100644 --- a/pkg/cloudbroker/stack/stack.go +++ b/pkg/cloudbroker/stack/stack.go @@ -1,4 +1,3 @@ -// Lists all the stack. package stack import "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces" diff --git a/samples/config/bvs-config.json b/samples/config/bvs-config.json index b57fc25..7df369b 100644 --- a/samples/config/bvs-config.json +++ b/samples/config/bvs-config.json @@ -6,7 +6,16 @@ "ssoUrl": "https://bvs-delta.qa.loc:8443", "decortUrl": "https://delta.qa.loc", "domain": "dynamix", + "token": { + "access_token": "string_token", + "token_type": "bearer", + "refresh_token": "string_refresh_token", + "expiry": "2023-11-24T12:40:27.954150524+03:00" + }, "retries": 5, + "sslSkipVerify": true, "timeout": "5m", - "sslSkipVerify": false + "path_cfg": "config", + "path_token": "token", + "timeToRefresh": 5 } diff --git a/samples/config/bvs-config.yml b/samples/config/bvs-config.yml index 520a983..a49b8d5 100644 --- a/samples/config/bvs-config.yml +++ b/samples/config/bvs-config.yml @@ -4,7 +4,15 @@ appId: appSecret: ssoUrl: https://bvs-delta.qa.loc:8443 decortUrl: https://delta.qa.loc -domain: dynamix, +domain: dynamix +token": +access_token: string_token +token_type: bearer +refresh_token: string_refresh_token +expiry: 2023-11-24T12:40:27.954150524+03:00 retries: 5 +sslSkipVerify: true timeout: 5m -sslSkipVerify: false +path_cfg: config +path_token: token +timeToRefresh: 5