You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
decort-golang-sdk/client_bvs.go

520 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}