| package ionet |
|
|
| import ( |
| "encoding/json" |
| "fmt" |
| "strings" |
|
|
| "github.com/samber/lo" |
| ) |
|
|
| |
| func (c *Client) DeployContainer(req *DeploymentRequest) (*DeploymentResponse, error) { |
| if req == nil { |
| return nil, fmt.Errorf("deployment request cannot be nil") |
| } |
|
|
| |
| if req.ResourcePrivateName == "" { |
| return nil, fmt.Errorf("resource_private_name is required") |
| } |
| if len(req.LocationIDs) == 0 { |
| return nil, fmt.Errorf("location_ids is required") |
| } |
| if req.HardwareID <= 0 { |
| return nil, fmt.Errorf("hardware_id is required") |
| } |
| if req.RegistryConfig.ImageURL == "" { |
| return nil, fmt.Errorf("registry_config.image_url is required") |
| } |
| if req.GPUsPerContainer < 1 { |
| return nil, fmt.Errorf("gpus_per_container must be at least 1") |
| } |
| if req.DurationHours < 1 { |
| return nil, fmt.Errorf("duration_hours must be at least 1") |
| } |
| if req.ContainerConfig.ReplicaCount < 1 { |
| return nil, fmt.Errorf("container_config.replica_count must be at least 1") |
| } |
|
|
| resp, err := c.makeRequest("POST", "/deploy", req) |
| if err != nil { |
| return nil, fmt.Errorf("failed to deploy container: %w", err) |
| } |
|
|
| |
| |
| var deployResp DeploymentResponse |
| if err := json.Unmarshal(resp.Body, &deployResp); err != nil { |
| return nil, fmt.Errorf("failed to parse deployment response: %w", err) |
| } |
|
|
| return &deployResp, nil |
| } |
|
|
| |
| func (c *Client) ListDeployments(opts *ListDeploymentsOptions) (*DeploymentList, error) { |
| params := make(map[string]interface{}) |
|
|
| if opts != nil { |
| params["status"] = opts.Status |
| params["location_id"] = opts.LocationID |
| params["page"] = opts.Page |
| params["page_size"] = opts.PageSize |
| params["sort_by"] = opts.SortBy |
| params["sort_order"] = opts.SortOrder |
| } |
|
|
| endpoint := "/deployments" + buildQueryParams(params) |
|
|
| resp, err := c.makeRequest("GET", endpoint, nil) |
| if err != nil { |
| return nil, fmt.Errorf("failed to list deployments: %w", err) |
| } |
|
|
| var deploymentList DeploymentList |
| if err := decodeData(resp.Body, &deploymentList); err != nil { |
| return nil, fmt.Errorf("failed to parse deployments list: %w", err) |
| } |
|
|
| deploymentList.Deployments = lo.Map(deploymentList.Deployments, func(deployment Deployment, _ int) Deployment { |
| deployment.GPUCount = deployment.HardwareQuantity |
| deployment.Replicas = deployment.HardwareQuantity |
| return deployment |
| }) |
|
|
| return &deploymentList, nil |
| } |
|
|
| |
| func (c *Client) GetDeployment(deploymentID string) (*DeploymentDetail, error) { |
| if deploymentID == "" { |
| return nil, fmt.Errorf("deployment ID cannot be empty") |
| } |
|
|
| endpoint := fmt.Sprintf("/deployment/%s", deploymentID) |
|
|
| resp, err := c.makeRequest("GET", endpoint, nil) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get deployment details: %w", err) |
| } |
|
|
| var deploymentDetail DeploymentDetail |
| if err := decodeDataWithFlexibleTimes(resp.Body, &deploymentDetail); err != nil { |
| return nil, fmt.Errorf("failed to parse deployment details: %w", err) |
| } |
|
|
| return &deploymentDetail, nil |
| } |
|
|
| |
| func (c *Client) UpdateDeployment(deploymentID string, req *UpdateDeploymentRequest) (*UpdateDeploymentResponse, error) { |
| if deploymentID == "" { |
| return nil, fmt.Errorf("deployment ID cannot be empty") |
| } |
| if req == nil { |
| return nil, fmt.Errorf("update request cannot be nil") |
| } |
|
|
| endpoint := fmt.Sprintf("/deployment/%s", deploymentID) |
|
|
| resp, err := c.makeRequest("PATCH", endpoint, req) |
| if err != nil { |
| return nil, fmt.Errorf("failed to update deployment: %w", err) |
| } |
|
|
| |
| |
| var updateResp UpdateDeploymentResponse |
| if err := json.Unmarshal(resp.Body, &updateResp); err != nil { |
| return nil, fmt.Errorf("failed to parse update deployment response: %w", err) |
| } |
|
|
| return &updateResp, nil |
| } |
|
|
| |
| func (c *Client) ExtendDeployment(deploymentID string, req *ExtendDurationRequest) (*DeploymentDetail, error) { |
| if deploymentID == "" { |
| return nil, fmt.Errorf("deployment ID cannot be empty") |
| } |
| if req == nil { |
| return nil, fmt.Errorf("extend request cannot be nil") |
| } |
| if req.DurationHours < 1 { |
| return nil, fmt.Errorf("duration_hours must be at least 1") |
| } |
|
|
| endpoint := fmt.Sprintf("/deployment/%s/extend", deploymentID) |
|
|
| resp, err := c.makeRequest("POST", endpoint, req) |
| if err != nil { |
| return nil, fmt.Errorf("failed to extend deployment: %w", err) |
| } |
|
|
| var deploymentDetail DeploymentDetail |
| if err := decodeDataWithFlexibleTimes(resp.Body, &deploymentDetail); err != nil { |
| return nil, fmt.Errorf("failed to parse extended deployment details: %w", err) |
| } |
|
|
| return &deploymentDetail, nil |
| } |
|
|
| |
| func (c *Client) DeleteDeployment(deploymentID string) (*UpdateDeploymentResponse, error) { |
| if deploymentID == "" { |
| return nil, fmt.Errorf("deployment ID cannot be empty") |
| } |
|
|
| endpoint := fmt.Sprintf("/deployment/%s", deploymentID) |
|
|
| resp, err := c.makeRequest("DELETE", endpoint, nil) |
| if err != nil { |
| return nil, fmt.Errorf("failed to delete deployment: %w", err) |
| } |
|
|
| |
| |
| var deleteResp UpdateDeploymentResponse |
| if err := json.Unmarshal(resp.Body, &deleteResp); err != nil { |
| return nil, fmt.Errorf("failed to parse delete deployment response: %w", err) |
| } |
|
|
| return &deleteResp, nil |
| } |
|
|
| |
| func (c *Client) GetPriceEstimation(req *PriceEstimationRequest) (*PriceEstimationResponse, error) { |
| if req == nil { |
| return nil, fmt.Errorf("price estimation request cannot be nil") |
| } |
|
|
| |
| if len(req.LocationIDs) == 0 { |
| return nil, fmt.Errorf("location_ids is required") |
| } |
| if req.HardwareID == 0 { |
| return nil, fmt.Errorf("hardware_id is required") |
| } |
| if req.ReplicaCount < 1 { |
| return nil, fmt.Errorf("replica_count must be at least 1") |
| } |
|
|
| currency := strings.TrimSpace(req.Currency) |
| if currency == "" { |
| currency = "usdc" |
| } |
|
|
| durationType := strings.TrimSpace(req.DurationType) |
| if durationType == "" { |
| durationType = "hour" |
| } |
| durationType = strings.ToLower(durationType) |
|
|
| apiDurationType := "" |
|
|
| durationQty := req.DurationQty |
| if durationQty < 1 { |
| durationQty = req.DurationHours |
| } |
| if durationQty < 1 { |
| return nil, fmt.Errorf("duration_qty must be at least 1") |
| } |
|
|
| hardwareQty := req.HardwareQty |
| if hardwareQty < 1 { |
| hardwareQty = req.GPUsPerContainer |
| } |
| if hardwareQty < 1 { |
| return nil, fmt.Errorf("hardware_qty must be at least 1") |
| } |
|
|
| durationHoursForRate := req.DurationHours |
| if durationHoursForRate < 1 { |
| durationHoursForRate = durationQty |
| } |
| switch durationType { |
| case "hour", "hours", "hourly": |
| durationHoursForRate = durationQty |
| apiDurationType = "hourly" |
| case "day", "days", "daily": |
| durationHoursForRate = durationQty * 24 |
| apiDurationType = "daily" |
| case "week", "weeks", "weekly": |
| durationHoursForRate = durationQty * 24 * 7 |
| apiDurationType = "weekly" |
| case "month", "months", "monthly": |
| durationHoursForRate = durationQty * 24 * 30 |
| apiDurationType = "monthly" |
| } |
| if durationHoursForRate < 1 { |
| durationHoursForRate = 1 |
| } |
| if apiDurationType == "" { |
| apiDurationType = "hourly" |
| } |
|
|
| params := map[string]interface{}{ |
| "location_ids": req.LocationIDs, |
| "hardware_id": req.HardwareID, |
| "hardware_qty": hardwareQty, |
| "gpus_per_container": req.GPUsPerContainer, |
| "duration_type": apiDurationType, |
| "duration_qty": durationQty, |
| "duration_hours": req.DurationHours, |
| "replica_count": req.ReplicaCount, |
| "currency": currency, |
| } |
|
|
| endpoint := "/price" + buildQueryParams(params) |
|
|
| resp, err := c.makeRequest("GET", endpoint, nil) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get price estimation: %w", err) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| var pricingData struct { |
| ReplicaCount int `json:"replica_count"` |
| GPUsPerContainer int `json:"gpus_per_container"` |
| AvailableReplicaCount []int `json:"available_replica_count"` |
| Discount float64 `json:"discount"` |
| IonetFee float64 `json:"ionet_fee"` |
| IonetFeePercent float64 `json:"ionet_fee_percent"` |
| CurrencyConversionFee float64 `json:"currency_conversion_fee"` |
| CurrencyConversionFeePercent float64 `json:"currency_conversion_fee_percent"` |
| TotalCostUSDC float64 `json:"total_cost_usdc"` |
| } |
|
|
| if err := decodeData(resp.Body, &pricingData); err != nil { |
| return nil, fmt.Errorf("failed to parse price estimation response: %w", err) |
| } |
|
|
| |
| durationHoursFloat := float64(durationHoursForRate) |
| if durationHoursFloat <= 0 { |
| durationHoursFloat = 1 |
| } |
|
|
| priceResp := &PriceEstimationResponse{ |
| EstimatedCost: pricingData.TotalCostUSDC, |
| Currency: strings.ToUpper(currency), |
| EstimationValid: true, |
| PriceBreakdown: PriceBreakdown{ |
| ComputeCost: pricingData.TotalCostUSDC - pricingData.IonetFee - pricingData.CurrencyConversionFee, |
| TotalCost: pricingData.TotalCostUSDC, |
| HourlyRate: pricingData.TotalCostUSDC / durationHoursFloat, |
| }, |
| } |
|
|
| return priceResp, nil |
| } |
|
|
| |
| func (c *Client) CheckClusterNameAvailability(clusterName string) (bool, error) { |
| if clusterName == "" { |
| return false, fmt.Errorf("cluster name cannot be empty") |
| } |
|
|
| params := map[string]interface{}{ |
| "cluster_name": clusterName, |
| } |
|
|
| endpoint := "/clusters/check_cluster_name_availability" + buildQueryParams(params) |
|
|
| resp, err := c.makeRequest("GET", endpoint, nil) |
| if err != nil { |
| return false, fmt.Errorf("failed to check cluster name availability: %w", err) |
| } |
|
|
| var availabilityResp bool |
| if err := json.Unmarshal(resp.Body, &availabilityResp); err != nil { |
| return false, fmt.Errorf("failed to parse cluster name availability response: %w", err) |
| } |
|
|
| return availabilityResp, nil |
| } |
|
|
| |
| func (c *Client) UpdateClusterName(clusterID string, req *UpdateClusterNameRequest) (*UpdateClusterNameResponse, error) { |
| if clusterID == "" { |
| return nil, fmt.Errorf("cluster ID cannot be empty") |
| } |
| if req == nil { |
| return nil, fmt.Errorf("update cluster name request cannot be nil") |
| } |
| if req.Name == "" { |
| return nil, fmt.Errorf("cluster name cannot be empty") |
| } |
|
|
| endpoint := fmt.Sprintf("/clusters/%s/update-name", clusterID) |
|
|
| resp, err := c.makeRequest("PUT", endpoint, req) |
| if err != nil { |
| return nil, fmt.Errorf("failed to update cluster name: %w", err) |
| } |
|
|
| |
| var updateResp UpdateClusterNameResponse |
| if err := json.Unmarshal(resp.Body, &updateResp); err != nil { |
| return nil, fmt.Errorf("failed to parse update cluster name response: %w", err) |
| } |
|
|
| return &updateResp, nil |
| } |
|
|