Files
decort-golang-sdk/client_bvs.go
2023-11-29 15:57:26 +03:00

520 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package decortsdk
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"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"
k8s_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8s"
)
// HTTP-client for platform
type BVSDecortClient struct {
client *http.Client
cfg config.BVSConfig
mutex *sync.Mutex
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,
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
//nolint:gosec
InsecureSkipVerify: cfg.SSLSkipVerify,
},
},
},
cfg: cfg,
mutex: &sync.Mutex{},
}
}
// CloudAPI builder
func (bdc *BVSDecortClient) CloudAPI() *cloudapi.CloudAPI {
return cloudapi.New(bdc)
}
// CloudBroker builder
func (bdc *BVSDecortClient) CloudBroker() *cloudbroker.CloudBroker {
return cloudbroker.New(bdc)
}
// 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())
}
req, err := http.NewRequestWithContext(ctx, method, bdc.decortURL+constants.Restmachine+url, body)
if 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 {
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()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New(string(respBytes))
}
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
func (bdc *BVSDecortClient) GetToken(ctx context.Context) (config.Token, error) {
bdc.mutex.Lock()
defer bdc.mutex.Unlock()
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, "/")
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)
}
tokenBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
return config.Token{}, fmt.Errorf("cannot get token: %s", tokenBytes)
}
var tj tokenJSON
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 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) {
if ctype != "" {
req.Header.Add("Content-Type", ctype)
} else {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
}
req.Header.Add("Authorization", "bearer "+bdc.cfg.Token.AccessToken)
req.Header.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)
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)
}
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
}