package decortsdk import ( "bytes" "context" "crypto/tls" "fmt" "io" "net/http" "net/url" "strings" "sync" "time" "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" ) // LegacyDecortClient is Legacy HTTP-client for platform type LegacyDecortClient struct { decortURL string client *http.Client cfg config.LegacyConfig expiryTime time.Time mutex *sync.Mutex } // Legacy client builder func NewLegacy(cfg config.LegacyConfig) *LegacyDecortClient { if cfg.Retries == 0 { cfg.Retries = 5 } var expiryTime time.Time if cfg.Token != "" { expiryTime = time.Now().AddDate(0, 0, 1) } return &LegacyDecortClient{ decortURL: cfg.DecortURL, client: &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ //nolint:gosec InsecureSkipVerify: cfg.SSLSkipVerify, }, }, }, cfg: cfg, expiryTime: expiryTime, mutex: &sync.Mutex{}, } } // CloudAPI builder func (ldc *LegacyDecortClient) CloudAPI() *cloudapi.CloudAPI { return cloudapi.New(ldc) } // CloudBroker builder func (ldc *LegacyDecortClient) CloudBroker() *cloudbroker.CloudBroker { return cloudbroker.New(ldc) } // 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 } 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 } // perform request respBytes, err := ldc.do(req, "") if err != nil { return nil, err } 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 } req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+constants.Restmachine+url, body) if err != nil { return nil, err } // 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() // new token is not needed if ldc.cfg.Token != "" && !time.Now().After(ldc.expiryTime) { 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) req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") // request token resp, err := ldc.client.Do(req) if err != nil || resp == nil { return fmt.Errorf("cannot get token: %w", err) } defer resp.Body.Close() var tokenBytes []byte tokenBytes, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("cannot get token: %w", err) } if resp.StatusCode != 200 { return fmt.Errorf("cannot get token: %s", tokenBytes) } // save token in config token := string(tokenBytes) ldc.cfg.Token = token ldc.expiryTime = time.Now().AddDate(0, 0, 1) return nil } // do method performs request and returns response as an array of bytes and nil error in case of response status code 200. // In any other cases do returns nil response and error. // Retries are implemented in case of connection reset errors. func (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) } req.Header.Set("Accept", "application/json") buf, err := io.ReadAll(req.Body) if err != nil { return nil, err } req.Body.Close() req.Body = io.NopCloser(bytes.NewBuffer(buf)) resp, err := 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 resp == nil { return nil, fmt.Errorf("got empty response without error") } // handle successful request respBytes, _ := io.ReadAll(resp.Body) if resp.StatusCode == 200 { return respBytes, nil } // handle errors with status code other than 200 err = fmt.Errorf("%s", respBytes) return nil, fmt.Errorf("could not execute request: %w", err) }