package utils
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type CreateInstanceRequest struct {
AppRef string `json:"app_ref"`
PgVersion int `json:"pg_version"`
Resources InstanceResources `json:"resources"`
}
type ResizeInstanceRequest struct {
Containers []ContainerResource `json:"containers"`
}
type ContainerResource struct {
Name string `json:"name"`
Resources InstanceResources `json:"resources"`
}
type InstanceResources struct {
Cpu string `json:"cpu,omitempty"`
Memory string `json:"memory,omitempty"`
CpuLimit string `json:"cpu_limit,omitempty"`
MemoryLimit string `json:"memory_limit,omitempty"`
}
type InstanceResponse struct {
PodID string `json:"pod_id"`
PodName string `json:"pod_name"`
Endpoint string `json:"endpoint"`
}
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
type APIClient struct {
baseURL string
token string
http *http.Client
}
func NewAPIClient(baseURL, token string) *APIClient {
return &APIClient{
baseURL: baseURL,
token: token,
http: &http.Client{Timeout: 30 * time.Second},
}
}
func NewAPIClientWithTimeout(baseURL, token string, timeout time.Duration) *APIClient {
return &APIClient{
baseURL: baseURL,
token: token,
http: &http.Client{Timeout: timeout},
}
}
func (c *APIClient) WithToken(token string) *APIClient {
return NewAPIClient(c.baseURL, token)
}
func (c *APIClient) Allocate(ctx context.Context, ns string, req *CreateInstanceRequest) (*InstanceResponse, int, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, 0, fmt.Errorf("marshal allocate request: %w", err)
}
respBody, status, err := c.do(ctx, http.MethodPost, "/api/v1/instances", ns, body)
if err != nil {
return nil, status, err
}
if status >= 300 {
return nil, status, nil
}
var resp InstanceResponse
if err := json.Unmarshal(respBody, &resp); err != nil {
return nil, status, fmt.Errorf("unmarshal allocate response: %w", err)
}
return &resp, status, nil
}
func (c *APIClient) Resize(ctx context.Context, ns, appRef string, req *ResizeInstanceRequest) (*InstanceResponse, int, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, 0, fmt.Errorf("marshal resize request: %w", err)
}
respBody, status, err := c.do(ctx, http.MethodPatch, "/api/v1/instances/"+appRef, ns, body)
if err != nil {
return nil, status, err
}
if status >= 300 {
return nil, status, nil
}
var resp InstanceResponse
if err := json.Unmarshal(respBody, &resp); err != nil {
return nil, status, fmt.Errorf("unmarshal resize response: %w", err)
}
return &resp, status, nil
}
func (c *APIClient) Release(ctx context.Context, ns, appRef string) (int, error) {
_, status, err := c.do(ctx, http.MethodDelete, "/api/v1/instances/"+appRef, ns, nil)
return status, err
}
func (c *APIClient) do(ctx context.Context, method, path, ns string, body []byte) ([]byte, int, error) {
url := fmt.Sprintf("%s%s?namespace=%s", c.baseURL, path, ns)
var bodyReader io.Reader
if body != nil {
bodyReader = bytes.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return nil, 0, fmt.Errorf("build request: %w", err)
}
if c.token != "" {
req.Header.Set("Authorization", "Bearer "+c.token)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.http.Do(req)
if err != nil {
return nil, 0, fmt.Errorf("http %s %s: %w", method, path, err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, resp.StatusCode, fmt.Errorf("read response: %w", err)
}
return respBody, resp.StatusCode, nil
}