@ -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 ( ) )
}
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 {
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" {
if _ , err = bdc . GetToken ( ctx ) ; err != nil {
_ , err = bdc . GetToken ( ctx )
if err != nil {
return nil , err
}
//nolint:bodyclose
//we close the body in case of any error
resp , err = bdc . do ( reqCopy , ctype )
respBytes , err = bdc . do ( reqCopy , "" )
if err != nil {
return nil , err
}
} else {
return nil , err
}
return respBytes , err
}
defer resp . Body . Close ( )
respBytes , err := io . ReadAll ( resp . Body )
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
}
}
}
// perform request
reqCopy := req . Clone ( ctx )
respBytes , err := bdc . do ( req , ctype )
if err == nil {
return respBytes , nil
}
// 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
// get token and retry in case of access denied
if err . Error ( ) == "access is denied" {
_ , err = bdc . GetToken ( ctx )
if err != nil {
return nil , err
}
respBytes , err = bdc . do ( reqCopy , ctype )
if err != nil {
return nil , err
}
}
return respBytes , err
}
// GetToken allows you to get a token and returns the token structure. When specifying the PathCfg variable,
// the token and configuration will be written to a file.
// When specifying the PathToken variable, the token will be written to a file.
func ( bdc * BVSDecortClient ) GetToken ( ctx context . Context ) ( config . Token , error ) {
bdc . mutex . Lock ( )
defer bdc . mutex . Unlock ( )
// set up request headers and body
body := fmt . Sprintf ( "grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s&response_type=token&scope=openid" , bdc . cfg . AppID , bdc . cfg . AppSecret , bdc . cfg . Username , bdc . cfg . Password )
bodyReader := strings . NewReader ( body )
@ -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 , w hen specifying the PathCfg variable,
// the token and configuration will be written to a file ,
// w hen specifying the PathToken variable, the token will be written to a file
// RefreshToken allows you to refresh a token and returns the token structure . W hen specifying the PathCfg variable,
// the token and configuration will be written to a file .
// W hen 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 ) {
if ctype != "" {
req . Header . Add ( "Content-Type" , ctype )
} else {
// do method performs request and returns response as an array of bytes and nil error in case of response status code 200.
// In any other cases do returns nil response and error.
// Retries are implemented in case of connection reset errors.
func ( bdc * BVSDecortClient ) do ( req * http . Request , ctype string ) ( [ ] byte , error ) {
// set up request headers and body
req . Header . Add ( "Content-Type" , "application/x-www-form-urlencoded" )
if ctype != "" {
req . Header . Set ( "Content-Type" , ctype )
}
req . Header . Add ( "Authorization" , "bearer " + bdc . cfg . Token . AccessToken )
req . Header . Set ( "Accept" , "application/json" )
buf , _ := 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 )
req . Body . Close ( )
req . Body = io . NopCloser ( bytes . NewBuffer ( buf ) )
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 )
resp , err := bdc . client . Do ( req )
if resp != nil {
defer resp . Body . Close ( )
}
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 )
// retries logic GOES HERE
// get http response
//var resp *http.Response
//for i := uint64(0); i < bdc.cfg.Retries; i++ {
// req := req.Clone(req.Context())
// req.Body = io.NopCloser(bytes.NewBuffer(buf))
//
// if i > 0 {
// time.Sleep(5 * time.Second) // no time sleep for the first request
// }
//
// resp, err = bdc.client.Do(req)
//
// // stop retries on success and close response body
// if resp != nil {
// defer resp.Body.Close()
// }
// if err == nil {
// break
// }
//
// // retries in case of connection errors with time sleep
// if isConnectionError(err) {
// continue
// }
//
// // return error in case of non-connection error
// return nil, err
//}
// handle http request errors
if err != nil {
return nil , err
}
if resp == nil {
return nil , fmt . Errorf ( "got empty response without error" )
}
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 re q. 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 )
}