diff --git a/catalog.go b/catalog.go index 65300c4..10e0999 100644 --- a/catalog.go +++ b/catalog.go @@ -45,6 +45,11 @@ func (s *PricingService) EstimateInstance(ctx context.Context, region string, in err := s.client.do(ctx, http.MethodPost, "/regions/"+escaped(region)+"/price-estimate", input, &result, newID()) return &result, err } +func (s *PricingService) EstimateExistingInstance(ctx context.Context, region, instanceID string) (*PriceEstimate, error) { + var result PriceEstimate + err := s.client.do(ctx, http.MethodPost, instancesPath(region, instanceID)+"/price-estimate", map[string]any{}, &result, "") + return &result, err +} func catalogPath(region, resource string, options ListOptions) string { values := url.Values{} if options.Query != "" { diff --git a/client_test.go b/client_test.go index 08f81d5..0b3247b 100644 --- a/client_test.go +++ b/client_test.go @@ -106,3 +106,35 @@ func TestListQueryParametersAreNotEscapedIntoPath(t *testing.T) { t.Fatal(err) } } + +func TestRuntimePricingAndInstanceEIPOperations(t *testing.T) { + paths := []string{} + client, _ := testClient(t, func(w http.ResponseWriter, r *http.Request) { + paths = append(paths, r.Method+" "+r.URL.Path) + w.Header().Set("Content-Type", "application/json") + if r.URL.Path == "/api/sdk/v1/regions/us-east-1/instances/local-1/price-estimate" { + _, _ = w.Write([]byte(`{"code":200,"message":"Success","data":{"currency":"USD","monthly_price":12.5,"configuration_fingerprint":"fingerprint"}}`)) + return + } + _, _ = w.Write([]byte(`{"code":202,"message":"Accepted","data":{"task_id":"task-1","status":"queued"}}`)) + }) + estimate, err := client.Pricing.EstimateExistingInstance(context.Background(), "us-east-1", "local-1") + if err != nil || estimate.ConfigurationFingerprint != "fingerprint" || estimate.MonthlyPrice != 12.5 { + t.Fatalf("estimate=%#v err=%v", estimate, err) + } + if _, err := client.EIPs.Disassociate(context.Background(), "us-east-1", "local-1", WithIdempotencyKey("disassociate")); err != nil { + t.Fatal(err) + } + if _, err := client.EIPs.Release(context.Background(), "us-east-1", "local-1", WithIdempotencyKey("release")); err != nil { + t.Fatal(err) + } + want := []string{"POST /api/sdk/v1/regions/us-east-1/instances/local-1/price-estimate", "POST /api/sdk/v1/regions/us-east-1/instances/local-1/eip/disassociate", "DELETE /api/sdk/v1/regions/us-east-1/instances/local-1/eip"} + if len(paths) != len(want) { + t.Fatalf("paths=%v", paths) + } + for i := range want { + if paths[i] != want[i] { + t.Fatalf("paths=%v want=%v", paths, want) + } + } +} diff --git a/resources.go b/resources.go index b7fa839..d10761d 100644 --- a/resources.go +++ b/resources.go @@ -51,6 +51,12 @@ func (s *EIPService) Get(ctx context.Context, region, instanceID string) (*EIPIn func (s *EIPService) Ensure(ctx context.Context, region, instanceID string, input EnsureEIPRequest, options ...RequestOption) (*Operation, error) { return (&InstancesService{client: s.client}).operation(ctx, http.MethodPost, instancesPath(region, instanceID)+"/eip", input, options...) } +func (s *EIPService) Disassociate(ctx context.Context, region, instanceID string, options ...RequestOption) (*Operation, error) { + return (&InstancesService{client: s.client}).operation(ctx, http.MethodPost, instancesPath(region, instanceID)+"/eip/disassociate", map[string]any{}, options...) +} +func (s *EIPService) Release(ctx context.Context, region, instanceID string, options ...RequestOption) (*Operation, error) { + return (&InstancesService{client: s.client}).operation(ctx, http.MethodDelete, instancesPath(region, instanceID)+"/eip", map[string]any{}, options...) +} func (s *EIPService) SetReverseDNS(ctx context.Context, region, instanceID string, input ReverseDNSRequest, options ...RequestOption) (*Operation, error) { return (&InstancesService{client: s.client}).operation(ctx, http.MethodPatch, instancesPath(region, instanceID)+"/eip/reverse-dns", input, options...) } @@ -79,9 +85,11 @@ type ModifyVolumeRequest struct { } func (s *VolumeService) List(ctx context.Context, region, instanceID string) ([]Volume, error) { - var result []Volume + var result struct { + Items []Volume `json:"items"` + } err := s.client.do(ctx, http.MethodGet, instancesPath(region, instanceID)+"/volumes", nil, &result, "") - return result, err + return result.Items, err } func (s *VolumeService) Create(ctx context.Context, region, instanceID string, input CreateVolumeRequest, options ...RequestOption) (*Operation, error) { return (&InstancesService{client: s.client}).operation(ctx, http.MethodPost, instancesPath(region, instanceID)+"/volumes", input, options...) diff --git a/types.go b/types.go index fa36f2b..ef3ddcf 100644 --- a/types.go +++ b/types.go @@ -112,12 +112,14 @@ type CreateInstanceRequest struct { TrafficLimitGiB *float64 `json:"traffic_limit_gib,omitempty"` } type PriceEstimate struct { - Currency string `json:"currency"` - HourlyPrice float64 `json:"hourly_price"` - MonthlyPrice float64 `json:"monthly_price"` - CalendarMonthPrice float64 `json:"calendar_month_price"` - Breakdown map[string]any `json:"breakdown"` - Raw map[string]any `json:"-"` + Currency string `json:"currency"` + HourlyPrice float64 `json:"hourly_price"` + MonthlyPrice float64 `json:"monthly_price"` + CalendarMonthPrice float64 `json:"calendar_month_price"` + Breakdown map[string]any `json:"breakdown"` + ConfigurationFingerprint string `json:"configuration_fingerprint,omitempty"` + ResourceSyncedAt time.Time `json:"resource_synced_at,omitempty"` + Raw map[string]any `json:"-"` } type Instance struct { ID string `json:"id"`