From 8c19a865b584b8dc40feecd1a26e3d8d9a013230 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 21:55:16 +0000 Subject: [PATCH 01/18] feat(api): disable retries --- .stats.yml | 2 +- README.md | 2 +- client_test.go | 12 ++++++------ internal/requestconfig/requestconfig.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index 54aa04a..d86926c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 9 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-64b3af1a7aa80906205b3369d34afb7c2686ab1c184f4683d81f2e3adffa255e.yml openapi_spec_hash: 4a63ec0585f1f25c42f1a612345f7ab4 -config_hash: b63d685bc4feb4db73f82791193686bd +config_hash: 185d0aef1deabba0abd14e2e3202990c diff --git a/README.md b/README.md index 858a28e..cd6a9bc 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,7 @@ which can be used to wrap any `io.Reader` with the appropriate file name and con ### Retries -Certain errors will be automatically retried 2 times by default, with a short exponential backoff. +Certain errors will be automatically retried 0 times by default, with a short exponential backoff. We retry by default all connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors. diff --git a/client_test.go b/client_test.go index c5a3d51..eebfb89 100644 --- a/client_test.go +++ b/client_test.go @@ -68,11 +68,11 @@ func TestRetryAfter(t *testing.T) { } attempts := len(retryCountHeaders) - if attempts != 3 { - t.Errorf("Expected %d attempts, got %d", 3, attempts) + if attempts != 1 { + t.Errorf("Expected %d attempts, got %d", 1, attempts) } - expectedRetryCountHeaders := []string{"0", "1", "2"} + expectedRetryCountHeaders := []string{"0"} if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) { t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders) } @@ -102,7 +102,7 @@ func TestDeleteRetryCountHeader(t *testing.T) { t.Error("Expected there to be a cancel error") } - expectedRetryCountHeaders := []string{"", "", ""} + expectedRetryCountHeaders := []string{""} if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) { t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders) } @@ -132,7 +132,7 @@ func TestOverwriteRetryCountHeader(t *testing.T) { t.Error("Expected there to be a cancel error") } - expectedRetryCountHeaders := []string{"42", "42", "42"} + expectedRetryCountHeaders := []string{"42"} if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) { t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders) } @@ -160,7 +160,7 @@ func TestRetryAfterMs(t *testing.T) { if err == nil { t.Error("Expected there to be a cancel error") } - if want := 3; attempts != want { + if want := 1; attempts != want { t.Errorf("Expected %d attempts, got %d", want, attempts) } } diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go index 8e5db66..f36e5e7 100644 --- a/internal/requestconfig/requestconfig.go +++ b/internal/requestconfig/requestconfig.go @@ -164,7 +164,7 @@ func NewRequestConfig(ctx context.Context, method string, u string, body any, ds req.Header.Add(k, v) } cfg := RequestConfig{ - MaxRetries: 2, + MaxRetries: 0, Context: ctx, Request: req, HTTPClient: http.DefaultClient, From 2836b6828c5f60e8c0c4e8892bbefa54e54b64b8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 04:26:58 +0000 Subject: [PATCH 02/18] fix(internal): unmarshal correctly when there are multiple discriminators --- internal/apijson/decodeparam_test.go | 88 ++++++++++++++++++++++++++++ internal/apijson/union.go | 48 ++++++++------- 2 files changed, 115 insertions(+), 21 deletions(-) diff --git a/internal/apijson/decodeparam_test.go b/internal/apijson/decodeparam_test.go index 8f08d4e..b6ab607 100644 --- a/internal/apijson/decodeparam_test.go +++ b/internal/apijson/decodeparam_test.go @@ -351,6 +351,36 @@ func init() { }) } +type FooVariant struct { + Type string `json:"type,required"` + Value string `json:"value,required"` +} + +type BarVariant struct { + Type string `json:"type,required"` + Enable bool `json:"enable,required"` +} + +type MultiDiscriminatorUnion struct { + OfFoo *FooVariant `json:",inline"` + OfBar *BarVariant `json:",inline"` + + paramUnion +} + +func init() { + apijson.RegisterDiscriminatedUnion[MultiDiscriminatorUnion]("type", map[string]reflect.Type{ + "foo": reflect.TypeOf(FooVariant{}), + "foo_v2": reflect.TypeOf(FooVariant{}), + "bar": reflect.TypeOf(BarVariant{}), + "bar_legacy": reflect.TypeOf(BarVariant{}), + }) +} + +func (m *MultiDiscriminatorUnion) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, m) +} + func (d *DiscriminatedUnion) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, d) } @@ -408,3 +438,61 @@ func TestDiscriminatedUnion(t *testing.T) { }) } } + +func TestMultiDiscriminatorUnion(t *testing.T) { + tests := map[string]struct { + raw string + target MultiDiscriminatorUnion + shouldFail bool + }{ + "foo_variant": { + raw: `{"type":"foo","value":"test"}`, + target: MultiDiscriminatorUnion{OfFoo: &FooVariant{ + Type: "foo", + Value: "test", + }}, + }, + "foo_v2_variant": { + raw: `{"type":"foo_v2","value":"test_v2"}`, + target: MultiDiscriminatorUnion{OfFoo: &FooVariant{ + Type: "foo_v2", + Value: "test_v2", + }}, + }, + "bar_variant": { + raw: `{"type":"bar","enable":true}`, + target: MultiDiscriminatorUnion{OfBar: &BarVariant{ + Type: "bar", + Enable: true, + }}, + }, + "bar_legacy_variant": { + raw: `{"type":"bar_legacy","enable":false}`, + target: MultiDiscriminatorUnion{OfBar: &BarVariant{ + Type: "bar_legacy", + Enable: false, + }}, + }, + "invalid_type": { + raw: `{"type":"unknown","value":"test"}`, + target: MultiDiscriminatorUnion{}, + shouldFail: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var dst MultiDiscriminatorUnion + err := json.Unmarshal([]byte(test.raw), &dst) + if err != nil && !test.shouldFail { + t.Fatalf("failed unmarshal with err: %v", err) + } + if err == nil && test.shouldFail { + t.Fatalf("expected unmarshal to fail but it succeeded") + } + if !reflect.DeepEqual(dst, test.target) { + t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target) + } + }) + } +} diff --git a/internal/apijson/union.go b/internal/apijson/union.go index 7f3d8dc..c961275 100644 --- a/internal/apijson/union.go +++ b/internal/apijson/union.go @@ -39,12 +39,10 @@ func RegisterDiscriminatedUnion[T any](key string, mappings map[string]reflect.T func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { type variantDecoder struct { - decoder decoderFunc - field reflect.StructField - discriminatorValue any + decoder decoderFunc + field reflect.StructField } - - variants := []variantDecoder{} + decoders := []variantDecoder{} for i := 0; i < t.NumField(); i++ { field := t.Field(i) @@ -53,18 +51,26 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { } decoder := d.typeDecoder(field.Type) - variants = append(variants, variantDecoder{ + decoders = append(decoders, variantDecoder{ decoder: decoder, field: field, }) } + type discriminatedDecoder struct { + variantDecoder + discriminator any + } + discriminatedDecoders := []discriminatedDecoder{} unionEntry, discriminated := unionRegistry[t] - for _, unionVariant := range unionEntry.variants { - for i := 0; i < len(variants); i++ { - variant := &variants[i] - if variant.field.Type.Elem() == unionVariant.Type { - variant.discriminatorValue = unionVariant.DiscriminatorValue + for _, variant := range unionEntry.variants { + // For each union variant, find a matching decoder and save it + for _, decoder := range decoders { + if decoder.field.Type.Elem() == variant.Type { + discriminatedDecoders = append(discriminatedDecoders, discriminatedDecoder{ + decoder, + variant.DiscriminatorValue, + }) break } } @@ -73,10 +79,10 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { return func(n gjson.Result, v reflect.Value, state *decoderState) error { if discriminated && n.Type == gjson.JSON && len(unionEntry.discriminatorKey) != 0 { discriminator := n.Get(unionEntry.discriminatorKey).Value() - for _, variant := range variants { - if discriminator == variant.discriminatorValue { - inner := v.FieldByIndex(variant.field.Index) - return variant.decoder(n, inner, state) + for _, decoder := range discriminatedDecoders { + if discriminator == decoder.discriminator { + inner := v.FieldByIndex(decoder.field.Index) + return decoder.decoder(n, inner, state) } } return errors.New("apijson: was not able to find discriminated union variant") @@ -85,15 +91,15 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { // Set bestExactness to worse than loose bestExactness := loose - 1 bestVariant := -1 - for i, variant := range variants { + for i, decoder := range decoders { // Pointers are used to discern JSON object variants from value variants - if n.Type != gjson.JSON && variant.field.Type.Kind() == reflect.Ptr { + if n.Type != gjson.JSON && decoder.field.Type.Kind() == reflect.Ptr { continue } sub := decoderState{strict: state.strict, exactness: exact} - inner := v.FieldByIndex(variant.field.Index) - err := variant.decoder(n, inner, &sub) + inner := v.FieldByIndex(decoder.field.Index) + err := decoder.decoder(n, inner, &sub) if err != nil { continue } @@ -116,11 +122,11 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { return errors.New("apijson: was not able to coerce type as union strictly") } - for i := 0; i < len(variants); i++ { + for i := 0; i < len(decoders); i++ { if i == bestVariant { continue } - v.FieldByIndex(variants[i].field.Index).SetZero() + v.FieldByIndex(decoders[i].field.Index).SetZero() } return nil From 9719aa4e99d45abd8405f38e891238fc3c487044 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:33:22 +0000 Subject: [PATCH 03/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d86926c..d462d70 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 9 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-64b3af1a7aa80906205b3369d34afb7c2686ab1c184f4683d81f2e3adffa255e.yml -openapi_spec_hash: 4a63ec0585f1f25c42f1a612345f7ab4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-94c657d18a94013c3a191579d7a41e20d5b40861844489a05a0316c74604e61c.yml +openapi_spec_hash: 0ca0c3bdefefb0f150cf193809bb083a config_hash: 185d0aef1deabba0abd14e2e3202990c From 273c45f75e75104ad10eb1eab1bdb7acc3596cce Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:44:10 +0000 Subject: [PATCH 04/18] feat(api): add vm images resources and update formatting --- .stats.yml | 4 +- api.md | 34 ++++++-- client.go | 4 +- node.go | 36 ++++---- vm.go | 72 ++++++++-------- vm_test.go | 12 +-- vmimage.go | 216 +++++++++++++++++++++++++++++++++++++++++++++++ vmimage_test.go | 114 +++++++++++++++++++++++++ vmscript.go | 36 ++++---- vmscript_test.go | 8 +- 10 files changed, 442 insertions(+), 94 deletions(-) create mode 100644 vmimage.go create mode 100644 vmimage_test.go diff --git a/.stats.yml b/.stats.yml index d462d70..f243e7f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 9 +configured_endpoints: 13 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-94c657d18a94013c3a191579d7a41e20d5b40861844489a05a0316c74604e61c.yml openapi_spec_hash: 0ca0c3bdefefb0f150cf193809bb083a -config_hash: 185d0aef1deabba0abd14e2e3202990c +config_hash: 20852160fd697b0edb4b28a75eff4223 diff --git a/api.md b/api.md index eb3ecaa..c72b1cb 100644 --- a/api.md +++ b/api.md @@ -1,14 +1,14 @@ -# Vms +# VMs Response Types: -- sfcnodes.VmLogsResponse -- sfcnodes.VmSSHResponse +- sfcnodes.VMLogsResponse +- sfcnodes.VmsshResponse Methods: -- client.Vms.Logs(ctx context.Context, query sfcnodes.VmLogsParams) (sfcnodes.VmLogsResponse, error) -- client.Vms.SSH(ctx context.Context, query sfcnodes.VmSSHParams) (sfcnodes.VmSSHResponse, error) +- client.VMs.Logs(ctx context.Context, query sfcnodes.VMLogsParams) (sfcnodes.VMLogsResponse, error) +- client.VMs.SSH(ctx context.Context, query sfcnodes.VMSSHParams) (sfcnodes.VmsshResponse, error) ## Script @@ -19,13 +19,29 @@ Params Types: Response Types: - sfcnodes.UserDataUnion -- sfcnodes.VmScriptNewResponse -- sfcnodes.VmScriptGetResponse +- sfcnodes.VMScriptNewResponse +- sfcnodes.VMScriptGetResponse Methods: -- client.Vms.Script.New(ctx context.Context, body sfcnodes.VmScriptNewParams) (sfcnodes.VmScriptNewResponse, error) -- client.Vms.Script.Get(ctx context.Context) (sfcnodes.VmScriptGetResponse, error) +- client.VMs.Script.New(ctx context.Context, body sfcnodes.VMScriptNewParams) (sfcnodes.VMScriptNewResponse, error) +- client.VMs.Script.Get(ctx context.Context) (sfcnodes.VMScriptGetResponse, error) + +## Images + +Response Types: + +- sfcnodes.VMImageCompleteUploadResponse +- sfcnodes.VMImageGetResponse +- sfcnodes.VMImageStartUploadResponse +- sfcnodes.VMImageUploadResponse + +Methods: + +- client.VMs.Images.CompleteUpload(ctx context.Context, imageID string) (sfcnodes.VMImageCompleteUploadResponse, error) +- client.VMs.Images.Get(ctx context.Context, imageID string) (sfcnodes.VMImageGetResponse, error) +- client.VMs.Images.StartUpload(ctx context.Context, body sfcnodes.VMImageStartUploadParams) (sfcnodes.VMImageStartUploadResponse, error) +- client.VMs.Images.Upload(ctx context.Context, imageID string, body sfcnodes.VMImageUploadParams) (sfcnodes.VMImageUploadResponse, error) # Nodes diff --git a/client.go b/client.go index 4fe4421..91bb51e 100644 --- a/client.go +++ b/client.go @@ -16,7 +16,7 @@ import ( // directly, and instead use the [NewClient] method instead. type Client struct { Options []option.RequestOption - Vms VmService + VMs VMService Nodes NodeService } @@ -42,7 +42,7 @@ func NewClient(opts ...option.RequestOption) (r Client) { r = Client{Options: opts} - r.Vms = NewVmService(opts...) + r.VMs = NewVMService(opts...) r.Nodes = NewNodeService(opts...) return diff --git a/node.go b/node.go index c07bfec..6bcabe3 100644 --- a/node.go +++ b/node.go @@ -191,7 +191,7 @@ type ListResponseNodeData struct { StartAt int64 `json:"start_at,nullable"` // Last updated time as Unix timestamp in seconds UpdatedAt int64 `json:"updated_at,nullable"` - Vms ListResponseNodeDataVms `json:"vms,nullable"` + VMs ListResponseNodeDataVMs `json:"vms,nullable"` Zone string `json:"zone,nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -209,7 +209,7 @@ type ListResponseNodeData struct { ProcurementID respjson.Field StartAt respjson.Field UpdatedAt respjson.Field - Vms respjson.Field + VMs respjson.Field Zone respjson.Field ExtraFields map[string]respjson.Field raw string @@ -222,8 +222,8 @@ func (r *ListResponseNodeData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type ListResponseNodeDataVms struct { - Data []ListResponseNodeDataVmsData `json:"data,required"` +type ListResponseNodeDataVMs struct { + Data []ListResponseNodeDataVMsData `json:"data,required"` Object string `json:"object,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -235,12 +235,12 @@ type ListResponseNodeDataVms struct { } // Returns the unmodified JSON received from the API -func (r ListResponseNodeDataVms) RawJSON() string { return r.JSON.raw } -func (r *ListResponseNodeDataVms) UnmarshalJSON(data []byte) error { +func (r ListResponseNodeDataVMs) RawJSON() string { return r.JSON.raw } +func (r *ListResponseNodeDataVMs) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type ListResponseNodeDataVmsData struct { +type ListResponseNodeDataVMsData struct { ID string `json:"id,required"` CreatedAt int64 `json:"created_at,required"` EndAt int64 `json:"end_at,required"` @@ -264,8 +264,8 @@ type ListResponseNodeDataVmsData struct { } // Returns the unmodified JSON received from the API -func (r ListResponseNodeDataVmsData) RawJSON() string { return r.JSON.raw } -func (r *ListResponseNodeDataVmsData) UnmarshalJSON(data []byte) error { +func (r ListResponseNodeDataVMsData) RawJSON() string { return r.JSON.raw } +func (r *ListResponseNodeDataVMsData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } @@ -296,7 +296,7 @@ type Node struct { StartAt int64 `json:"start_at,nullable"` // Last updated time as Unix timestamp in seconds UpdatedAt int64 `json:"updated_at,nullable"` - Vms NodeVms `json:"vms,nullable"` + VMs NodeVMs `json:"vms,nullable"` Zone string `json:"zone,nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -314,7 +314,7 @@ type Node struct { ProcurementID respjson.Field StartAt respjson.Field UpdatedAt respjson.Field - Vms respjson.Field + VMs respjson.Field Zone respjson.Field ExtraFields map[string]respjson.Field raw string @@ -327,8 +327,8 @@ func (r *Node) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type NodeVms struct { - Data []NodeVmsData `json:"data,required"` +type NodeVMs struct { + Data []NodeVMsData `json:"data,required"` Object string `json:"object,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -340,12 +340,12 @@ type NodeVms struct { } // Returns the unmodified JSON received from the API -func (r NodeVms) RawJSON() string { return r.JSON.raw } -func (r *NodeVms) UnmarshalJSON(data []byte) error { +func (r NodeVMs) RawJSON() string { return r.JSON.raw } +func (r *NodeVMs) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type NodeVmsData struct { +type NodeVMsData struct { ID string `json:"id,required"` CreatedAt int64 `json:"created_at,required"` EndAt int64 `json:"end_at,required"` @@ -369,8 +369,8 @@ type NodeVmsData struct { } // Returns the unmodified JSON received from the API -func (r NodeVmsData) RawJSON() string { return r.JSON.raw } -func (r *NodeVmsData) UnmarshalJSON(data []byte) error { +func (r NodeVMsData) RawJSON() string { return r.JSON.raw } +func (r *NodeVMsData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/vm.go b/vm.go index b55da6e..bf9fa86 100644 --- a/vm.go +++ b/vm.go @@ -15,43 +15,45 @@ import ( "github.com/sfcompute/nodes-go/packages/respjson" ) -// VmService contains methods and other services that help with interacting with +// VMService contains methods and other services that help with interacting with // the sfc-nodes API. // // Note, unlike clients, this service does not read variables from the environment // automatically. You should not instantiate this service directly, and instead use -// the [NewVmService] method instead. -type VmService struct { +// the [NewVMService] method instead. +type VMService struct { Options []option.RequestOption - Script VmScriptService + Script VMScriptService + Images VMImageService } -// NewVmService generates a new service that applies the given options to each +// NewVMService generates a new service that applies the given options to each // request. These options are applied after the parent client's options (if there // is one), and before any request-specific options. -func NewVmService(opts ...option.RequestOption) (r VmService) { - r = VmService{} +func NewVMService(opts ...option.RequestOption) (r VMService) { + r = VMService{} r.Options = opts - r.Script = NewVmScriptService(opts...) + r.Script = NewVMScriptService(opts...) + r.Images = NewVMImageService(opts...) return } -func (r *VmService) Logs(ctx context.Context, query VmLogsParams, opts ...option.RequestOption) (res *VmLogsResponse, err error) { +func (r *VMService) Logs(ctx context.Context, query VMLogsParams, opts ...option.RequestOption) (res *VMLogsResponse, err error) { opts = append(r.Options[:], opts...) path := "v0/vms/logs2" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return } -func (r *VmService) SSH(ctx context.Context, query VmSSHParams, opts ...option.RequestOption) (res *VmSSHResponse, err error) { +func (r *VMService) SSH(ctx context.Context, query VMSSHParams, opts ...option.RequestOption) (res *VmsshResponse, err error) { opts = append(r.Options[:], opts...) path := "v0/vms/ssh" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return } -type VmLogsResponse struct { - Data []VmLogsResponseData `json:"data,required"` +type VMLogsResponse struct { + Data []VMLogsResponseData `json:"data,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { Data respjson.Field @@ -61,12 +63,12 @@ type VmLogsResponse struct { } // Returns the unmodified JSON received from the API -func (r VmLogsResponse) RawJSON() string { return r.JSON.raw } -func (r *VmLogsResponse) UnmarshalJSON(data []byte) error { +func (r VMLogsResponse) RawJSON() string { return r.JSON.raw } +func (r *VMLogsResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VmLogsResponseData struct { +type VMLogsResponseData struct { Data []int64 `json:"data,required"` InstanceID string `json:"instance_id,required"` MonotonicTimestampNanoSec int64 `json:"monotonic_timestamp_nano_sec,required"` @@ -88,15 +90,15 @@ type VmLogsResponseData struct { } // Returns the unmodified JSON received from the API -func (r VmLogsResponseData) RawJSON() string { return r.JSON.raw } -func (r *VmLogsResponseData) UnmarshalJSON(data []byte) error { +func (r VMLogsResponseData) RawJSON() string { return r.JSON.raw } +func (r *VMLogsResponseData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VmSSHResponse struct { +type VmsshResponse struct { SSHHostname string `json:"ssh_hostname,required"` SSHPort int64 `json:"ssh_port,required"` - SSHHostKeys []VmSSHResponseSSHHostKey `json:"ssh_host_keys,nullable"` + SSHHostKeys []VmsshResponseSSHHostKey `json:"ssh_host_keys,nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { SSHHostname respjson.Field @@ -108,12 +110,12 @@ type VmSSHResponse struct { } // Returns the unmodified JSON received from the API -func (r VmSSHResponse) RawJSON() string { return r.JSON.raw } -func (r *VmSSHResponse) UnmarshalJSON(data []byte) error { +func (r VmsshResponse) RawJSON() string { return r.JSON.raw } +func (r *VmsshResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VmSSHResponseSSHHostKey struct { +type VmsshResponseSSHHostKey struct { Base64EncodedKey string `json:"base64_encoded_key,required"` KeyType string `json:"key_type,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. @@ -126,15 +128,15 @@ type VmSSHResponseSSHHostKey struct { } // Returns the unmodified JSON received from the API -func (r VmSSHResponseSSHHostKey) RawJSON() string { return r.JSON.raw } -func (r *VmSSHResponseSSHHostKey) UnmarshalJSON(data []byte) error { +func (r VmsshResponseSSHHostKey) RawJSON() string { return r.JSON.raw } +func (r *VmsshResponseSSHHostKey) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VmLogsParams struct { +type VMLogsParams struct { InstanceID string `query:"instance_id,required" json:"-"` // Any of "seqnum_asc", "seqnum_desc". - OrderBy VmLogsParamsOrderBy `query:"order_by,omitzero,required" json:"-"` + OrderBy VMLogsParamsOrderBy `query:"order_by,omitzero,required" json:"-"` BeforeRealtimeTimestamp param.Opt[string] `query:"before_realtime_timestamp,omitzero" json:"-"` BeforeSeqnum param.Opt[int64] `query:"before_seqnum,omitzero" json:"-"` Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` @@ -143,28 +145,28 @@ type VmLogsParams struct { paramObj } -// URLQuery serializes [VmLogsParams]'s query parameters as `url.Values`. -func (r VmLogsParams) URLQuery() (v url.Values, err error) { +// URLQuery serializes [VMLogsParams]'s query parameters as `url.Values`. +func (r VMLogsParams) URLQuery() (v url.Values, err error) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets, }) } -type VmLogsParamsOrderBy string +type VMLogsParamsOrderBy string const ( - VmLogsParamsOrderBySeqnumAsc VmLogsParamsOrderBy = "seqnum_asc" - VmLogsParamsOrderBySeqnumDesc VmLogsParamsOrderBy = "seqnum_desc" + VMLogsParamsOrderBySeqnumAsc VMLogsParamsOrderBy = "seqnum_asc" + VMLogsParamsOrderBySeqnumDesc VMLogsParamsOrderBy = "seqnum_desc" ) -type VmSSHParams struct { - VmID string `query:"vm_id,required" json:"-"` +type VMSSHParams struct { + VMID string `query:"vm_id,required" json:"-"` paramObj } -// URLQuery serializes [VmSSHParams]'s query parameters as `url.Values`. -func (r VmSSHParams) URLQuery() (v url.Values, err error) { +// URLQuery serializes [VMSSHParams]'s query parameters as `url.Values`. +func (r VMSSHParams) URLQuery() (v url.Values, err error) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets, diff --git a/vm_test.go b/vm_test.go index ab9821e..2adcbbd 100644 --- a/vm_test.go +++ b/vm_test.go @@ -13,7 +13,7 @@ import ( "github.com/sfcompute/nodes-go/option" ) -func TestVmLogsWithOptionalParams(t *testing.T) { +func TestVMLogsWithOptionalParams(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -26,9 +26,9 @@ func TestVmLogsWithOptionalParams(t *testing.T) { option.WithBaseURL(baseURL), option.WithBearerToken("My Bearer Token"), ) - _, err := client.Vms.Logs(context.TODO(), sfcnodes.VmLogsParams{ + _, err := client.VMs.Logs(context.TODO(), sfcnodes.VMLogsParams{ InstanceID: "instance_id", - OrderBy: sfcnodes.VmLogsParamsOrderBySeqnumAsc, + OrderBy: sfcnodes.VMLogsParamsOrderBySeqnumAsc, BeforeRealtimeTimestamp: sfcnodes.String("before_realtime_timestamp"), BeforeSeqnum: sfcnodes.Int(0), Limit: sfcnodes.Int(1), @@ -44,7 +44,7 @@ func TestVmLogsWithOptionalParams(t *testing.T) { } } -func TestVmSSH(t *testing.T) { +func TestVMSSH(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -57,8 +57,8 @@ func TestVmSSH(t *testing.T) { option.WithBaseURL(baseURL), option.WithBearerToken("My Bearer Token"), ) - _, err := client.Vms.SSH(context.TODO(), sfcnodes.VmSSHParams{ - VmID: "vm_id", + _, err := client.VMs.SSH(context.TODO(), sfcnodes.VMSSHParams{ + VMID: "vm_id", }) if err != nil { var apierr *sfcnodes.Error diff --git a/vmimage.go b/vmimage.go new file mode 100644 index 0000000..b550291 --- /dev/null +++ b/vmimage.go @@ -0,0 +1,216 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package sfcnodes + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/sfcompute/nodes-go/internal/apijson" + "github.com/sfcompute/nodes-go/internal/requestconfig" + "github.com/sfcompute/nodes-go/option" + "github.com/sfcompute/nodes-go/packages/param" + "github.com/sfcompute/nodes-go/packages/respjson" +) + +// VMImageService contains methods and other services that help with interacting +// with the sfc-nodes API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewVMImageService] method instead. +type VMImageService struct { + Options []option.RequestOption +} + +// NewVMImageService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewVMImageService(opts ...option.RequestOption) (r VMImageService) { + r = VMImageService{} + r.Options = opts + return +} + +func (r *VMImageService) CompleteUpload(ctx context.Context, imageID string, opts ...option.RequestOption) (res *VMImageCompleteUploadResponse, err error) { + opts = append(r.Options[:], opts...) + if imageID == "" { + err = errors.New("missing required image_id parameter") + return + } + path := fmt.Sprintf("v1/vms/images/%s/complete_upload", imageID) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, nil, &res, opts...) + return +} + +func (r *VMImageService) Get(ctx context.Context, imageID string, opts ...option.RequestOption) (res *VMImageGetResponse, err error) { + opts = append(r.Options[:], opts...) + if imageID == "" { + err = errors.New("missing required image_id parameter") + return + } + path := fmt.Sprintf("v1/vms/images/%s", imageID) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +func (r *VMImageService) StartUpload(ctx context.Context, body VMImageStartUploadParams, opts ...option.RequestOption) (res *VMImageStartUploadResponse, err error) { + opts = append(r.Options[:], opts...) + path := "v1/vms/images/start_upload" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +func (r *VMImageService) Upload(ctx context.Context, imageID string, body VMImageUploadParams, opts ...option.RequestOption) (res *VMImageUploadResponse, err error) { + opts = append(r.Options[:], opts...) + if imageID == "" { + err = errors.New("missing required image_id parameter") + return + } + path := fmt.Sprintf("v1/vms/images/%s/upload", imageID) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// Response body for completing a multipart upload +type VMImageCompleteUploadResponse struct { + // The image ID + ImageID string `json:"image_id,required"` + // Any of "image". + Object VMImageCompleteUploadResponseObject `json:"object,required"` + // Status of the upload verification + UploadStatus string `json:"upload_status,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ImageID respjson.Field + Object respjson.Field + UploadStatus respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r VMImageCompleteUploadResponse) RawJSON() string { return r.JSON.raw } +func (r *VMImageCompleteUploadResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type VMImageCompleteUploadResponseObject string + +const ( + VMImageCompleteUploadResponseObjectImage VMImageCompleteUploadResponseObject = "image" +) + +// Response body for image download presigned URL generation +type VMImageGetResponse struct { + // The presigned URL that can be used to download the image + DownloadURL string `json:"download_url,required"` + Etag string `json:"etag,required"` + // Timestamp when the presigned URL expires (RFC 3339 format) + ExpiresAt string `json:"expires_at,required"` + // The image ID + ImageID string `json:"image_id,required"` + // Any of "image". + Object VMImageGetResponseObject `json:"object,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + DownloadURL respjson.Field + Etag respjson.Field + ExpiresAt respjson.Field + ImageID respjson.Field + Object respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r VMImageGetResponse) RawJSON() string { return r.JSON.raw } +func (r *VMImageGetResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type VMImageGetResponseObject string + +const ( + VMImageGetResponseObjectImage VMImageGetResponseObject = "image" +) + +// Response body for starting a multipart upload +type VMImageStartUploadResponse struct { + // The image ID for the created image + ImageID string `json:"image_id,required"` + // Any of "image". + Object VMImageStartUploadResponseObject `json:"object,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ImageID respjson.Field + Object respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r VMImageStartUploadResponse) RawJSON() string { return r.JSON.raw } +func (r *VMImageStartUploadResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type VMImageStartUploadResponseObject string + +const ( + VMImageStartUploadResponseObjectImage VMImageStartUploadResponseObject = "image" +) + +// Response body for image upload presigned URL generation +type VMImageUploadResponse struct { + // Timestamp when the presigned URL expires (RFC 3339 format) + ExpiresAt string `json:"expires_at,required"` + // The presigned URL that can be used to upload the image part + UploadURL string `json:"upload_url,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ExpiresAt respjson.Field + UploadURL respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r VMImageUploadResponse) RawJSON() string { return r.JSON.raw } +func (r *VMImageUploadResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type VMImageStartUploadParams struct { + // Name of the image file + Name string `json:"name,required"` + paramObj +} + +func (r VMImageStartUploadParams) MarshalJSON() (data []byte, err error) { + type shadow VMImageStartUploadParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *VMImageStartUploadParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type VMImageUploadParams struct { + // part idx (1-based) + PartID int64 `json:"part_id,required"` + paramObj +} + +func (r VMImageUploadParams) MarshalJSON() (data []byte, err error) { + type shadow VMImageUploadParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *VMImageUploadParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/vmimage_test.go b/vmimage_test.go new file mode 100644 index 0000000..8de1e2a --- /dev/null +++ b/vmimage_test.go @@ -0,0 +1,114 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package sfcnodes_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sfcompute/nodes-go" + "github.com/sfcompute/nodes-go/internal/testutil" + "github.com/sfcompute/nodes-go/option" +) + +func TestVMImageCompleteUpload(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := sfcnodes.NewClient( + option.WithBaseURL(baseURL), + option.WithBearerToken("My Bearer Token"), + ) + _, err := client.VMs.Images.CompleteUpload(context.TODO(), "image_id") + if err != nil { + var apierr *sfcnodes.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestVMImageGet(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := sfcnodes.NewClient( + option.WithBaseURL(baseURL), + option.WithBearerToken("My Bearer Token"), + ) + _, err := client.VMs.Images.Get(context.TODO(), "image_id") + if err != nil { + var apierr *sfcnodes.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestVMImageStartUpload(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := sfcnodes.NewClient( + option.WithBaseURL(baseURL), + option.WithBearerToken("My Bearer Token"), + ) + _, err := client.VMs.Images.StartUpload(context.TODO(), sfcnodes.VMImageStartUploadParams{ + Name: "name", + }) + if err != nil { + var apierr *sfcnodes.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestVMImageUpload(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := sfcnodes.NewClient( + option.WithBaseURL(baseURL), + option.WithBearerToken("My Bearer Token"), + ) + _, err := client.VMs.Images.Upload( + context.TODO(), + "image_id", + sfcnodes.VMImageUploadParams{ + PartID: 0, + }, + ) + if err != nil { + var apierr *sfcnodes.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/vmscript.go b/vmscript.go index 278d01a..a632270 100644 --- a/vmscript.go +++ b/vmscript.go @@ -14,33 +14,33 @@ import ( "github.com/sfcompute/nodes-go/packages/respjson" ) -// VmScriptService contains methods and other services that help with interacting +// VMScriptService contains methods and other services that help with interacting // with the sfc-nodes API. // // Note, unlike clients, this service does not read variables from the environment // automatically. You should not instantiate this service directly, and instead use -// the [NewVmScriptService] method instead. -type VmScriptService struct { +// the [NewVMScriptService] method instead. +type VMScriptService struct { Options []option.RequestOption } -// NewVmScriptService generates a new service that applies the given options to +// NewVMScriptService generates a new service that applies the given options to // each request. These options are applied after the parent client's options (if // there is one), and before any request-specific options. -func NewVmScriptService(opts ...option.RequestOption) (r VmScriptService) { - r = VmScriptService{} +func NewVMScriptService(opts ...option.RequestOption) (r VMScriptService) { + r = VMScriptService{} r.Options = opts return } -func (r *VmScriptService) New(ctx context.Context, body VmScriptNewParams, opts ...option.RequestOption) (res *VmScriptNewResponse, err error) { +func (r *VMScriptService) New(ctx context.Context, body VMScriptNewParams, opts ...option.RequestOption) (res *VMScriptNewResponse, err error) { opts = append(r.Options[:], opts...) path := "v0/vms/script" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return } -func (r *VmScriptService) Get(ctx context.Context, opts ...option.RequestOption) (res *VmScriptGetResponse, err error) { +func (r *VMScriptService) Get(ctx context.Context, opts ...option.RequestOption) (res *VMScriptGetResponse, err error) { opts = append(r.Options[:], opts...) path := "v0/vms/script" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) @@ -117,7 +117,7 @@ func (u *UserDataUnionParam) asAny() any { return nil } -type VmScriptNewResponse struct { +type VMScriptNewResponse struct { // if the script is valid utf8 then the response may be in either string, or byte // form and the client must handle both Script UserDataUnion `json:"script,required"` @@ -130,12 +130,12 @@ type VmScriptNewResponse struct { } // Returns the unmodified JSON received from the API -func (r VmScriptNewResponse) RawJSON() string { return r.JSON.raw } -func (r *VmScriptNewResponse) UnmarshalJSON(data []byte) error { +func (r VMScriptNewResponse) RawJSON() string { return r.JSON.raw } +func (r *VMScriptNewResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VmScriptGetResponse struct { +type VMScriptGetResponse struct { // if the script is valid utf8 then the response may be in either string, or byte // form and the client must handle both Script UserDataUnion `json:"script,required"` @@ -148,22 +148,22 @@ type VmScriptGetResponse struct { } // Returns the unmodified JSON received from the API -func (r VmScriptGetResponse) RawJSON() string { return r.JSON.raw } -func (r *VmScriptGetResponse) UnmarshalJSON(data []byte) error { +func (r VMScriptGetResponse) RawJSON() string { return r.JSON.raw } +func (r *VMScriptGetResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VmScriptNewParams struct { +type VMScriptNewParams struct { // if the script is valid utf8 then the response may be in either string, or byte // form and the client must handle both Script UserDataUnionParam `json:"script,omitzero,required"` paramObj } -func (r VmScriptNewParams) MarshalJSON() (data []byte, err error) { - type shadow VmScriptNewParams +func (r VMScriptNewParams) MarshalJSON() (data []byte, err error) { + type shadow VMScriptNewParams return param.MarshalObject(r, (*shadow)(&r)) } -func (r *VmScriptNewParams) UnmarshalJSON(data []byte) error { +func (r *VMScriptNewParams) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/vmscript_test.go b/vmscript_test.go index 5a8ffb1..78af7cd 100644 --- a/vmscript_test.go +++ b/vmscript_test.go @@ -13,7 +13,7 @@ import ( "github.com/sfcompute/nodes-go/option" ) -func TestVmScriptNew(t *testing.T) { +func TestVMScriptNew(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -26,7 +26,7 @@ func TestVmScriptNew(t *testing.T) { option.WithBaseURL(baseURL), option.WithBearerToken("My Bearer Token"), ) - _, err := client.Vms.Script.New(context.TODO(), sfcnodes.VmScriptNewParams{ + _, err := client.VMs.Script.New(context.TODO(), sfcnodes.VMScriptNewParams{ Script: sfcnodes.UserDataUnionParam{ OfString: sfcnodes.String("string"), }, @@ -40,7 +40,7 @@ func TestVmScriptNew(t *testing.T) { } } -func TestVmScriptGet(t *testing.T) { +func TestVMScriptGet(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -53,7 +53,7 @@ func TestVmScriptGet(t *testing.T) { option.WithBaseURL(baseURL), option.WithBearerToken("My Bearer Token"), ) - _, err := client.Vms.Script.Get(context.TODO()) + _, err := client.VMs.Script.Get(context.TODO()) if err != nil { var apierr *sfcnodes.Error if errors.As(err, &apierr) { From 2c5c82f2d79e41be8cbdc700286a76496fde98b8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:23:06 +0000 Subject: [PATCH 05/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f243e7f..c287ba5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 13 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-94c657d18a94013c3a191579d7a41e20d5b40861844489a05a0316c74604e61c.yml -openapi_spec_hash: 0ca0c3bdefefb0f150cf193809bb083a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-ce27866182428bb1668a8be71f33264ede4ba0507ba9cafd83a0cd4c49ed91ea.yml +openapi_spec_hash: ff4ca7c27c5f93bbf966d724b9ac71e4 config_hash: 20852160fd697b0edb4b28a75eff4223 From d9c2daa0151d664a5bbff0609d0a6e1e6db3b8c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:21:16 +0000 Subject: [PATCH 06/18] feat(api): api update --- .stats.yml | 4 ++-- vmimage.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index c287ba5..587495b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 13 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-ce27866182428bb1668a8be71f33264ede4ba0507ba9cafd83a0cd4c49ed91ea.yml -openapi_spec_hash: ff4ca7c27c5f93bbf966d724b9ac71e4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-d5d15d4df56a6c1e77d55525d24d75df3c5ab585819f9be1c26a03a380ff839d.yml +openapi_spec_hash: f7147fc2c5803781aedef7f290085690 config_hash: 20852160fd697b0edb4b28a75eff4223 diff --git a/vmimage.go b/vmimage.go index b550291..6597b5e 100644 --- a/vmimage.go +++ b/vmimage.go @@ -45,6 +45,7 @@ func (r *VMImageService) CompleteUpload(ctx context.Context, imageID string, opt return } +// Get the download URL for a VM image by ID func (r *VMImageService) Get(ctx context.Context, imageID string, opts ...option.RequestOption) (res *VMImageGetResponse, err error) { opts = append(r.Options[:], opts...) if imageID == "" { @@ -108,19 +109,20 @@ const ( type VMImageGetResponse struct { // The presigned URL that can be used to download the image DownloadURL string `json:"download_url,required"` - Etag string `json:"etag,required"` // Timestamp when the presigned URL expires (RFC 3339 format) ExpiresAt string `json:"expires_at,required"` // The image ID ImageID string `json:"image_id,required"` + // Human readable name of the image + Name string `json:"name,required"` // Any of "image". Object VMImageGetResponseObject `json:"object,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { DownloadURL respjson.Field - Etag respjson.Field ExpiresAt respjson.Field ImageID respjson.Field + Name respjson.Field Object respjson.Field ExtraFields map[string]respjson.Field raw string From ee795ee93f88b5c379d9ce834356c59fae43e162 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:26:24 +0000 Subject: [PATCH 07/18] fix(api): remove undocumented endpoints, add list endpoint --- .stats.yml | 4 +- api.md | 8 +-- vmimage.go | 142 ++++++++++++------------------------------------ vmimage_test.go | 58 +------------------- 4 files changed, 42 insertions(+), 170 deletions(-) diff --git a/.stats.yml b/.stats.yml index 587495b..40701ca 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 13 +configured_endpoints: 11 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-d5d15d4df56a6c1e77d55525d24d75df3c5ab585819f9be1c26a03a380ff839d.yml openapi_spec_hash: f7147fc2c5803781aedef7f290085690 -config_hash: 20852160fd697b0edb4b28a75eff4223 +config_hash: f29ebc32a45fc14e903cc9881565fcd2 diff --git a/api.md b/api.md index c72b1cb..4a13423 100644 --- a/api.md +++ b/api.md @@ -31,17 +31,13 @@ Methods: Response Types: -- sfcnodes.VMImageCompleteUploadResponse +- sfcnodes.VMImageListResponse - sfcnodes.VMImageGetResponse -- sfcnodes.VMImageStartUploadResponse -- sfcnodes.VMImageUploadResponse Methods: -- client.VMs.Images.CompleteUpload(ctx context.Context, imageID string) (sfcnodes.VMImageCompleteUploadResponse, error) +- client.VMs.Images.List(ctx context.Context) (sfcnodes.VMImageListResponse, error) - client.VMs.Images.Get(ctx context.Context, imageID string) (sfcnodes.VMImageGetResponse, error) -- client.VMs.Images.StartUpload(ctx context.Context, body sfcnodes.VMImageStartUploadParams) (sfcnodes.VMImageStartUploadResponse, error) -- client.VMs.Images.Upload(ctx context.Context, imageID string, body sfcnodes.VMImageUploadParams) (sfcnodes.VMImageUploadResponse, error) # Nodes diff --git a/vmimage.go b/vmimage.go index 6597b5e..6a1820c 100644 --- a/vmimage.go +++ b/vmimage.go @@ -11,7 +11,6 @@ import ( "github.com/sfcompute/nodes-go/internal/apijson" "github.com/sfcompute/nodes-go/internal/requestconfig" "github.com/sfcompute/nodes-go/option" - "github.com/sfcompute/nodes-go/packages/param" "github.com/sfcompute/nodes-go/packages/respjson" ) @@ -34,14 +33,11 @@ func NewVMImageService(opts ...option.RequestOption) (r VMImageService) { return } -func (r *VMImageService) CompleteUpload(ctx context.Context, imageID string, opts ...option.RequestOption) (res *VMImageCompleteUploadResponse, err error) { +// List all VM Images for the authenticated account +func (r *VMImageService) List(ctx context.Context, opts ...option.RequestOption) (res *VMImageListResponse, err error) { opts = append(r.Options[:], opts...) - if imageID == "" { - err = errors.New("missing required image_id parameter") - return - } - path := fmt.Sprintf("v1/vms/images/%s/complete_upload", imageID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, nil, &res, opts...) + path := "v1/vms/images" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return } @@ -57,35 +53,45 @@ func (r *VMImageService) Get(ctx context.Context, imageID string, opts ...option return } -func (r *VMImageService) StartUpload(ctx context.Context, body VMImageStartUploadParams, opts ...option.RequestOption) (res *VMImageStartUploadResponse, err error) { - opts = append(r.Options[:], opts...) - path := "v1/vms/images/start_upload" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return +// Response body for listing images +type VMImageListResponse struct { + Data []VMImageListResponseData `json:"data,required"` + HasMore bool `json:"has_more,required"` + // Any of "list". + Object VMImageListResponseObject `json:"object,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Data respjson.Field + HasMore respjson.Field + Object respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` } -func (r *VMImageService) Upload(ctx context.Context, imageID string, body VMImageUploadParams, opts ...option.RequestOption) (res *VMImageUploadResponse, err error) { - opts = append(r.Options[:], opts...) - if imageID == "" { - err = errors.New("missing required image_id parameter") - return - } - path := fmt.Sprintf("v1/vms/images/%s/upload", imageID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return +// Returns the unmodified JSON received from the API +func (r VMImageListResponse) RawJSON() string { return r.JSON.raw } +func (r *VMImageListResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) } -// Response body for completing a multipart upload -type VMImageCompleteUploadResponse struct { +// Response body for individual image info (used in lists) +type VMImageListResponseData struct { + // Creation timestamp as Unix timestamp in seconds + CreatedAt int64 `json:"created_at,required"` // The image ID ImageID string `json:"image_id,required"` + // Client given name of the image + Name string `json:"name,required"` // Any of "image". - Object VMImageCompleteUploadResponseObject `json:"object,required"` - // Status of the upload verification + Object string `json:"object,required"` + // Upload status of the image UploadStatus string `json:"upload_status,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + CreatedAt respjson.Field ImageID respjson.Field + Name respjson.Field Object respjson.Field UploadStatus respjson.Field ExtraFields map[string]respjson.Field @@ -94,15 +100,15 @@ type VMImageCompleteUploadResponse struct { } // Returns the unmodified JSON received from the API -func (r VMImageCompleteUploadResponse) RawJSON() string { return r.JSON.raw } -func (r *VMImageCompleteUploadResponse) UnmarshalJSON(data []byte) error { +func (r VMImageListResponseData) RawJSON() string { return r.JSON.raw } +func (r *VMImageListResponseData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type VMImageCompleteUploadResponseObject string +type VMImageListResponseObject string const ( - VMImageCompleteUploadResponseObjectImage VMImageCompleteUploadResponseObject = "image" + VMImageListResponseObjectList VMImageListResponseObject = "list" ) // Response body for image download presigned URL generation @@ -140,79 +146,3 @@ type VMImageGetResponseObject string const ( VMImageGetResponseObjectImage VMImageGetResponseObject = "image" ) - -// Response body for starting a multipart upload -type VMImageStartUploadResponse struct { - // The image ID for the created image - ImageID string `json:"image_id,required"` - // Any of "image". - Object VMImageStartUploadResponseObject `json:"object,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ImageID respjson.Field - Object respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r VMImageStartUploadResponse) RawJSON() string { return r.JSON.raw } -func (r *VMImageStartUploadResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type VMImageStartUploadResponseObject string - -const ( - VMImageStartUploadResponseObjectImage VMImageStartUploadResponseObject = "image" -) - -// Response body for image upload presigned URL generation -type VMImageUploadResponse struct { - // Timestamp when the presigned URL expires (RFC 3339 format) - ExpiresAt string `json:"expires_at,required"` - // The presigned URL that can be used to upload the image part - UploadURL string `json:"upload_url,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ExpiresAt respjson.Field - UploadURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r VMImageUploadResponse) RawJSON() string { return r.JSON.raw } -func (r *VMImageUploadResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type VMImageStartUploadParams struct { - // Name of the image file - Name string `json:"name,required"` - paramObj -} - -func (r VMImageStartUploadParams) MarshalJSON() (data []byte, err error) { - type shadow VMImageStartUploadParams - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *VMImageStartUploadParams) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type VMImageUploadParams struct { - // part idx (1-based) - PartID int64 `json:"part_id,required"` - paramObj -} - -func (r VMImageUploadParams) MarshalJSON() (data []byte, err error) { - type shadow VMImageUploadParams - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *VMImageUploadParams) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} diff --git a/vmimage_test.go b/vmimage_test.go index 8de1e2a..d27aa39 100644 --- a/vmimage_test.go +++ b/vmimage_test.go @@ -13,7 +13,7 @@ import ( "github.com/sfcompute/nodes-go/option" ) -func TestVMImageCompleteUpload(t *testing.T) { +func TestVMImageList(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -26,7 +26,7 @@ func TestVMImageCompleteUpload(t *testing.T) { option.WithBaseURL(baseURL), option.WithBearerToken("My Bearer Token"), ) - _, err := client.VMs.Images.CompleteUpload(context.TODO(), "image_id") + _, err := client.VMs.Images.List(context.TODO()) if err != nil { var apierr *sfcnodes.Error if errors.As(err, &apierr) { @@ -58,57 +58,3 @@ func TestVMImageGet(t *testing.T) { t.Fatalf("err should be nil: %s", err.Error()) } } - -func TestVMImageStartUpload(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := sfcnodes.NewClient( - option.WithBaseURL(baseURL), - option.WithBearerToken("My Bearer Token"), - ) - _, err := client.VMs.Images.StartUpload(context.TODO(), sfcnodes.VMImageStartUploadParams{ - Name: "name", - }) - if err != nil { - var apierr *sfcnodes.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} - -func TestVMImageUpload(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := sfcnodes.NewClient( - option.WithBaseURL(baseURL), - option.WithBearerToken("My Bearer Token"), - ) - _, err := client.VMs.Images.Upload( - context.TODO(), - "image_id", - sfcnodes.VMImageUploadParams{ - PartID: 0, - }, - ) - if err != nil { - var apierr *sfcnodes.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} From 59b16bcc748ffea703644d6332f33a79196aeeee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:23:12 +0000 Subject: [PATCH 08/18] feat(api): api update --- .stats.yml | 4 ++-- vmimage.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 40701ca..4d39999 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-d5d15d4df56a6c1e77d55525d24d75df3c5ab585819f9be1c26a03a380ff839d.yml -openapi_spec_hash: f7147fc2c5803781aedef7f290085690 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-0615b3d8a51936970e3e0aa3f5a4b84ce4040fc2af923acad09e145ed3702543.yml +openapi_spec_hash: 15bfdd7a22e1939fc64a3d2a09cce224 config_hash: f29ebc32a45fc14e903cc9881565fcd2 diff --git a/vmimage.go b/vmimage.go index 6a1820c..4a502fc 100644 --- a/vmimage.go +++ b/vmimage.go @@ -87,6 +87,8 @@ type VMImageListResponseData struct { Object string `json:"object,required"` // Upload status of the image UploadStatus string `json:"upload_status,required"` + // SHA256 hash of the image file for integrity verification + Sha256Hash string `json:"sha256_hash,nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { CreatedAt respjson.Field @@ -94,6 +96,7 @@ type VMImageListResponseData struct { Name respjson.Field Object respjson.Field UploadStatus respjson.Field + Sha256Hash respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` @@ -123,6 +126,8 @@ type VMImageGetResponse struct { Name string `json:"name,required"` // Any of "image". Object VMImageGetResponseObject `json:"object,required"` + // SHA256 hash of the image file for integrity verification + Sha256Hash string `json:"sha256_hash,required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { DownloadURL respjson.Field @@ -130,6 +135,7 @@ type VMImageGetResponse struct { ImageID respjson.Field Name respjson.Field Object respjson.Field + Sha256Hash respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` From 5788fb0d297e3c4f79e76e1f2369bd086dad610b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 00:46:53 +0000 Subject: [PATCH 09/18] feat(api): api update --- .stats.yml | 4 ++-- node.go | 2 ++ node_test.go | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4d39999..cdab176 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-0615b3d8a51936970e3e0aa3f5a4b84ce4040fc2af923acad09e145ed3702543.yml -openapi_spec_hash: 15bfdd7a22e1939fc64a3d2a09cce224 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-feb1170aeff2603be5b81d5e47a3687b0c4374d6383f159a4e7600ae82006215.yml +openapi_spec_hash: adefd0c6fbc8ed30486a87b110e03d34 config_hash: f29ebc32a45fc14e903cc9881565fcd2 diff --git a/node.go b/node.go index 6bcabe3..3cec091 100644 --- a/node.go +++ b/node.go @@ -108,6 +108,8 @@ type CreateNodesRequestParam struct { // End time as Unix timestamp in seconds. If provided, end time must be aligned to // the hour. If not provided, the node will be created as an autoreserved node. EndAt param.Opt[int64] `json:"end_at,omitzero"` + // Custom image ID to use for the VM instances + ImageID param.Opt[string] `json:"image_id,omitzero"` // Start time as Unix timestamp in seconds StartAt param.Opt[int64] `json:"start_at,omitzero"` // User script to be executed during the VM's boot process diff --git a/node_test.go b/node_test.go index f7f5011..d5cbd77 100644 --- a/node_test.go +++ b/node_test.go @@ -33,6 +33,7 @@ func TestNodeNewWithOptionalParams(t *testing.T) { Zone: "hayesvalley", CloudInitUserData: []int64{0}, EndAt: sfcnodes.Int(0), + ImageID: sfcnodes.String("vmi_1234567890abcdef"), Names: []string{"cuda-crunch"}, NodeType: sfcnodes.NodeTypeAutoreserved, StartAt: sfcnodes.Int(1640995200), From 97607e02d439b4fe1463a483d362ab5bf65fd708 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 02:03:16 +0000 Subject: [PATCH 10/18] feat(api): api update --- .stats.yml | 4 ++-- node.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index cdab176..dbfe823 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-feb1170aeff2603be5b81d5e47a3687b0c4374d6383f159a4e7600ae82006215.yml -openapi_spec_hash: adefd0c6fbc8ed30486a87b110e03d34 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-b380dba200ffcd01fb245020213f92568032554504940ecb93a361aab9bdcab9.yml +openapi_spec_hash: fd3ac511c392b3a52d718d45e561373c config_hash: f29ebc32a45fc14e903cc9881565fcd2 diff --git a/node.go b/node.go index 3cec091..44bc80b 100644 --- a/node.go +++ b/node.go @@ -251,6 +251,7 @@ type ListResponseNodeDataVMsData struct { // Any of "Pending", "Running", "Destroyed", "NodeFailure", "Unspecified". Status string `json:"status,required"` UpdatedAt int64 `json:"updated_at,required"` + ImageID string `json:"image_id,nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ID respjson.Field @@ -260,6 +261,7 @@ type ListResponseNodeDataVMsData struct { StartAt respjson.Field Status respjson.Field UpdatedAt respjson.Field + ImageID respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` @@ -356,6 +358,7 @@ type NodeVMsData struct { // Any of "Pending", "Running", "Destroyed", "NodeFailure", "Unspecified". Status string `json:"status,required"` UpdatedAt int64 `json:"updated_at,required"` + ImageID string `json:"image_id,nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ID respjson.Field @@ -365,6 +368,7 @@ type NodeVMsData struct { StartAt respjson.Field Status respjson.Field UpdatedAt respjson.Field + ImageID respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` From 4228f538a1ac7cb4285c6bb44d138ee38448eaf5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:23:19 +0000 Subject: [PATCH 11/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index dbfe823..b113186 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-b380dba200ffcd01fb245020213f92568032554504940ecb93a361aab9bdcab9.yml -openapi_spec_hash: fd3ac511c392b3a52d718d45e561373c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-1f04dab33fdb09eea786722a8c56bed4ffc0badf6d0d4812993bcd007136750e.yml +openapi_spec_hash: 82aabe435b5d4253006f3980ce93a003 config_hash: f29ebc32a45fc14e903cc9881565fcd2 From 764ef1bb988db82dc7997b0c40c0ec2fa070f2f6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 02:23:18 +0000 Subject: [PATCH 12/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b113186..d849a47 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-1f04dab33fdb09eea786722a8c56bed4ffc0badf6d0d4812993bcd007136750e.yml -openapi_spec_hash: 82aabe435b5d4253006f3980ce93a003 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-1415d59edad5400306534d25db6248cde21829a841de00c2b16a2ec53a5660bb.yml +openapi_spec_hash: 897f7c44cbf1d6fcb39e3b9ee9df3f2e config_hash: f29ebc32a45fc14e903cc9881565fcd2 From 90c566457cad4e6964d6217feeb21eb699d2c385 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 03:23:42 +0000 Subject: [PATCH 13/18] chore: bump minimum go version to 1.22 --- go.mod | 2 +- internal/encoding/json/shims/shims.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index f115e1a..3332421 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sfcompute/nodes-go -go 1.21 +go 1.22 require ( github.com/tidwall/gjson v1.14.4 diff --git a/internal/encoding/json/shims/shims.go b/internal/encoding/json/shims/shims.go index b65a016..fe9a71a 100644 --- a/internal/encoding/json/shims/shims.go +++ b/internal/encoding/json/shims/shims.go @@ -1,5 +1,5 @@ // This package provides shims over Go 1.2{2,3} APIs -// which are missing from Go 1.21, and used by the Go 1.24 encoding/json package. +// which are missing from Go 1.22, and used by the Go 1.24 encoding/json package. // // Inside the vendored package, all shim code has comments that begin look like // // SHIM(...): ... From 82733b2eff62084fa0b0e415e8a3ad6e1f8f546c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 03:24:44 +0000 Subject: [PATCH 14/18] chore: update more docs for 1.22 --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 536f5f7..090e94c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ $ ./scripts/lint This will install all the required dependencies and build the SDK. -You can also [install go 1.18+ manually](https://go.dev/doc/install). +You can also [install go 1.22+ manually](https://go.dev/doc/install). ## Modifying/Adding code diff --git a/README.md b/README.md index cd6a9bc..9b731f7 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ go get -u 'github.com/sfcompute/nodes-go@v0.1.0-alpha.2' ## Requirements -This library requires Go 1.18+. +This library requires Go 1.22+. ## Usage From 6adf0a14ecd1b5d20924b1b26816af8c5ed270ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 03:25:23 +0000 Subject: [PATCH 15/18] fix: use slices.Concat instead of sometimes modifying r.Options --- client.go | 3 ++- node.go | 11 ++++++----- vm.go | 5 +++-- vmimage.go | 5 +++-- vmscript.go | 5 +++-- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 91bb51e..8c783b6 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,7 @@ import ( "context" "net/http" "os" + "slices" "github.com/sfcompute/nodes-go/internal/requestconfig" "github.com/sfcompute/nodes-go/option" @@ -80,7 +81,7 @@ func NewClient(opts ...option.RequestOption) (r Client) { // For even greater flexibility, see [option.WithResponseInto] and // [option.WithResponseBodyInto]. func (r *Client) Execute(ctx context.Context, method string, path string, params any, res any, opts ...option.RequestOption) error { - opts = append(r.Options, opts...) + opts = slices.Concat(r.Options, opts) return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...) } diff --git a/node.go b/node.go index 44bc80b..646088c 100644 --- a/node.go +++ b/node.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "slices" "github.com/sfcompute/nodes-go/internal/apijson" "github.com/sfcompute/nodes-go/internal/apiquery" @@ -40,7 +41,7 @@ func NewNodeService(opts ...option.RequestOption) (r NodeService) { // Create VM nodes func (r *NodeService) New(ctx context.Context, body NodeNewParams, opts ...option.RequestOption) (res *ListResponseNode, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v1/nodes" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return @@ -48,7 +49,7 @@ func (r *NodeService) New(ctx context.Context, body NodeNewParams, opts ...optio // List all VM nodes for the authenticated account func (r *NodeService) List(ctx context.Context, query NodeListParams, opts ...option.RequestOption) (res *ListResponseNode, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v1/nodes" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return @@ -56,7 +57,7 @@ func (r *NodeService) List(ctx context.Context, query NodeListParams, opts ...op // Purchase additional time to extend the end time of a reserved VM node func (r *NodeService) Extend(ctx context.Context, id string, body NodeExtendParams, opts ...option.RequestOption) (res *Node, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") return @@ -68,7 +69,7 @@ func (r *NodeService) Extend(ctx context.Context, id string, body NodeExtendPara // Retrieve details of a specific node by its ID or name func (r *NodeService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Node, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") return @@ -81,7 +82,7 @@ func (r *NodeService) Get(ctx context.Context, id string, opts ...option.Request // Release an auto reserved VM node from its procurement, reducing the // procurement's desired quantity by 1 func (r *NodeService) Release(ctx context.Context, id string, opts ...option.RequestOption) (res *Node, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") return diff --git a/vm.go b/vm.go index bf9fa86..675fc64 100644 --- a/vm.go +++ b/vm.go @@ -6,6 +6,7 @@ import ( "context" "net/http" "net/url" + "slices" "github.com/sfcompute/nodes-go/internal/apijson" "github.com/sfcompute/nodes-go/internal/apiquery" @@ -39,14 +40,14 @@ func NewVMService(opts ...option.RequestOption) (r VMService) { } func (r *VMService) Logs(ctx context.Context, query VMLogsParams, opts ...option.RequestOption) (res *VMLogsResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v0/vms/logs2" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return } func (r *VMService) SSH(ctx context.Context, query VMSSHParams, opts ...option.RequestOption) (res *VmsshResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v0/vms/ssh" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return diff --git a/vmimage.go b/vmimage.go index 4a502fc..90fcc19 100644 --- a/vmimage.go +++ b/vmimage.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "slices" "github.com/sfcompute/nodes-go/internal/apijson" "github.com/sfcompute/nodes-go/internal/requestconfig" @@ -35,7 +36,7 @@ func NewVMImageService(opts ...option.RequestOption) (r VMImageService) { // List all VM Images for the authenticated account func (r *VMImageService) List(ctx context.Context, opts ...option.RequestOption) (res *VMImageListResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v1/vms/images" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return @@ -43,7 +44,7 @@ func (r *VMImageService) List(ctx context.Context, opts ...option.RequestOption) // Get the download URL for a VM image by ID func (r *VMImageService) Get(ctx context.Context, imageID string, opts ...option.RequestOption) (res *VMImageGetResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) if imageID == "" { err = errors.New("missing required image_id parameter") return diff --git a/vmscript.go b/vmscript.go index a632270..b44b255 100644 --- a/vmscript.go +++ b/vmscript.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "net/http" + "slices" "github.com/sfcompute/nodes-go/internal/apijson" "github.com/sfcompute/nodes-go/internal/requestconfig" @@ -34,14 +35,14 @@ func NewVMScriptService(opts ...option.RequestOption) (r VMScriptService) { } func (r *VMScriptService) New(ctx context.Context, body VMScriptNewParams, opts ...option.RequestOption) (res *VMScriptNewResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v0/vms/script" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return } func (r *VMScriptService) Get(ctx context.Context, opts ...option.RequestOption) (res *VMScriptGetResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v0/vms/script" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return From 5983889bfd8681434b39372a0d58fe60115ff267 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 03:25:52 +0000 Subject: [PATCH 16/18] chore: do not install brew dependencies in ./scripts/bootstrap by default --- scripts/bootstrap | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index d6ac165..5ab3066 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi From 42f31df1c143e56ca36cea4b5de2b937ac000c7c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:59:10 +0000 Subject: [PATCH 17/18] chore: configure new SDK language --- .stats.yml | 8 +-- api.md | 2 + node.go | 145 ++++++++++++++++++++++++++++++++++++++++++++++----- node_test.go | 57 +++++++++++++++++++- 4 files changed, 195 insertions(+), 17 deletions(-) diff --git a/.stats.yml b/.stats.yml index d849a47..a2e8f7c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-1415d59edad5400306534d25db6248cde21829a841de00c2b16a2ec53a5660bb.yml -openapi_spec_hash: 897f7c44cbf1d6fcb39e3b9ee9df3f2e -config_hash: f29ebc32a45fc14e903cc9881565fcd2 +configured_endpoints: 13 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-92d29637c0ceda06c752ad70b078ff8dad505843c16b923130774376890baaa5.yml +openapi_spec_hash: 1f20f7d76aa5575ee0601ece537af956 +config_hash: cf202573c712b5d91a4d496f35f0ff57 diff --git a/api.md b/api.md index 4a13423..bb64d71 100644 --- a/api.md +++ b/api.md @@ -59,6 +59,8 @@ Methods: - client.Nodes.New(ctx context.Context, body sfcnodes.NodeNewParams) (sfcnodes.ListResponseNode, error) - client.Nodes.List(ctx context.Context, query sfcnodes.NodeListParams) (sfcnodes.ListResponseNode, error) +- client.Nodes.Delete(ctx context.Context, id string) error - client.Nodes.Extend(ctx context.Context, id string, body sfcnodes.NodeExtendParams) (sfcnodes.Node, error) - client.Nodes.Get(ctx context.Context, id string) (sfcnodes.Node, error) +- client.Nodes.Redeploy(ctx context.Context, id string, body sfcnodes.NodeRedeployParams) (sfcnodes.Node, error) - client.Nodes.Release(ctx context.Context, id string) (sfcnodes.Node, error) diff --git a/node.go b/node.go index 646088c..73f0ea9 100644 --- a/node.go +++ b/node.go @@ -47,7 +47,7 @@ func (r *NodeService) New(ctx context.Context, body NodeNewParams, opts ...optio return } -// List all VM nodes for the authenticated account +// List all nodes for the authenticated account func (r *NodeService) List(ctx context.Context, query NodeListParams, opts ...option.RequestOption) (res *ListResponseNode, err error) { opts = slices.Concat(r.Options, opts) path := "v1/nodes" @@ -55,6 +55,19 @@ func (r *NodeService) List(ctx context.Context, query NodeListParams, opts ...op return } +// Delete a node by id. The node cannot be deleted if it has active or pending VMs. +func (r *NodeService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("v1/nodes/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) + return +} + // Purchase additional time to extend the end time of a reserved VM node func (r *NodeService) Extend(ctx context.Context, id string, body NodeExtendParams, opts ...option.RequestOption) (res *Node, err error) { opts = slices.Concat(r.Options, opts) @@ -79,6 +92,19 @@ func (r *NodeService) Get(ctx context.Context, id string, opts ...option.Request return } +// Redeploy a node by replacing its current VM with a new one. Optionally update +// the VM image and cloud init user data. +func (r *NodeService) Redeploy(ctx context.Context, id string, body NodeRedeployParams, opts ...option.RequestOption) (res *Node, err error) { + opts = slices.Concat(r.Options, opts) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("v1/nodes/%s/redeploy", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) + return +} + // Release an auto reserved VM node from its procurement, reducing the // procurement's desired quantity by 1 func (r *NodeService) Release(ctx context.Context, id string, opts ...option.RequestOption) (res *Node, err error) { @@ -106,17 +132,18 @@ type CreateNodesRequestParam struct { MaxPricePerNodeHour int64 `json:"max_price_per_node_hour,required"` // Zone to create the nodes in Zone string `json:"zone,required"` - // End time as Unix timestamp in seconds. If provided, end time must be aligned to - // the hour. If not provided, the node will be created as an autoreserved node. + // End time as Unix timestamp in seconds If provided, end time must be aligned to + // the hour If not provided, the node will be created as an autoreserved node EndAt param.Opt[int64] `json:"end_at,omitzero"` + // User script to be executed during the VM's boot process Data should be base64 + // encoded + CloudInitUserData param.Opt[string] `json:"cloud_init_user_data,omitzero" format:"byte"` // Custom image ID to use for the VM instances ImageID param.Opt[string] `json:"image_id,omitzero"` - // Start time as Unix timestamp in seconds + // Start time as Unix timestamp in seconds Required for reserved nodes StartAt param.Opt[int64] `json:"start_at,omitzero"` - // User script to be executed during the VM's boot process - CloudInitUserData []int64 `json:"cloud_init_user_data,omitzero"` - // Custom node names. Names cannot follow the vm\_{alpha_numeric_chars} as this is - // reserved for system-generated IDs. Names cannot be numeric strings. + // Custom node names Names cannot follow the vm\_{alpha_numeric_chars} as this is + // reserved for system-generated IDs Names cannot be numeric strings Names []string `json:"names,omitzero"` // Any of "autoreserved", "reserved". NodeType NodeType `json:"node_type,omitzero"` @@ -182,7 +209,8 @@ type ListResponseNodeData struct { // "deleted", "failed", "unknown". Status Status `json:"status,required"` // Creation time as Unix timestamp in seconds - CreatedAt int64 `json:"created_at,nullable"` + CreatedAt int64 `json:"created_at,nullable"` + CurrentVM ListResponseNodeDataCurrentVM `json:"current_vm,nullable"` // Deletion time as Unix timestamp in seconds DeletedAt int64 `json:"deleted_at,nullable"` // End time as Unix timestamp in seconds @@ -206,6 +234,7 @@ type ListResponseNodeData struct { Owner respjson.Field Status respjson.Field CreatedAt respjson.Field + CurrentVM respjson.Field DeletedAt respjson.Field EndAt respjson.Field MaxPricePerNodeHour respjson.Field @@ -225,6 +254,37 @@ func (r *ListResponseNodeData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +type ListResponseNodeDataCurrentVM struct { + ID string `json:"id,required"` + CreatedAt int64 `json:"created_at,required"` + EndAt int64 `json:"end_at,required"` + Object string `json:"object,required"` + StartAt int64 `json:"start_at,required"` + // Any of "Pending", "Running", "Destroyed", "NodeFailure", "Unspecified". + Status string `json:"status,required"` + UpdatedAt int64 `json:"updated_at,required"` + ImageID string `json:"image_id,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ID respjson.Field + CreatedAt respjson.Field + EndAt respjson.Field + Object respjson.Field + StartAt respjson.Field + Status respjson.Field + UpdatedAt respjson.Field + ImageID respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r ListResponseNodeDataCurrentVM) RawJSON() string { return r.JSON.raw } +func (r *ListResponseNodeDataCurrentVM) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type ListResponseNodeDataVMs struct { Data []ListResponseNodeDataVMsData `json:"data,required"` Object string `json:"object,required"` @@ -289,7 +349,8 @@ type Node struct { // "deleted", "failed", "unknown". Status Status `json:"status,required"` // Creation time as Unix timestamp in seconds - CreatedAt int64 `json:"created_at,nullable"` + CreatedAt int64 `json:"created_at,nullable"` + CurrentVM NodeCurrentVM `json:"current_vm,nullable"` // Deletion time as Unix timestamp in seconds DeletedAt int64 `json:"deleted_at,nullable"` // End time as Unix timestamp in seconds @@ -313,6 +374,7 @@ type Node struct { Owner respjson.Field Status respjson.Field CreatedAt respjson.Field + CurrentVM respjson.Field DeletedAt respjson.Field EndAt respjson.Field MaxPricePerNodeHour respjson.Field @@ -332,6 +394,37 @@ func (r *Node) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +type NodeCurrentVM struct { + ID string `json:"id,required"` + CreatedAt int64 `json:"created_at,required"` + EndAt int64 `json:"end_at,required"` + Object string `json:"object,required"` + StartAt int64 `json:"start_at,required"` + // Any of "Pending", "Running", "Destroyed", "NodeFailure", "Unspecified". + Status string `json:"status,required"` + UpdatedAt int64 `json:"updated_at,required"` + ImageID string `json:"image_id,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ID respjson.Field + CreatedAt respjson.Field + EndAt respjson.Field + Object respjson.Field + StartAt respjson.Field + Status respjson.Field + UpdatedAt respjson.Field + ImageID respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r NodeCurrentVM) RawJSON() string { return r.JSON.raw } +func (r *NodeCurrentVM) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type NodeVMs struct { Data []NodeVMsData `json:"data,required"` Object string `json:"object,required"` @@ -416,11 +509,15 @@ func (r *NodeNewParams) UnmarshalJSON(data []byte) error { type NodeListParams struct { // Filter nodes by node_id Use ?id=n_b1dc52505c6db142&id=n_b1dc52505c6db133 to - // specify multiple IDs. Cannot be used with name + // specify multiple IDs. Cannot combine with name or node_type ID []string `query:"id,omitzero" json:"-"` // Filter nodes by their names Use ?name=val1&name=val2 to specify multiple names. - // Cannot be used with id + // Cannot combine with id or node_type Name []string `query:"name,omitzero" json:"-"` + // Filter nodes by their type Cannot combine with id or name + // + // Any of "autoreserved", "reserved". + Type NodeType `query:"type,omitzero" json:"-"` paramObj } @@ -443,3 +540,27 @@ func (r NodeExtendParams) MarshalJSON() (data []byte, err error) { func (r *NodeExtendParams) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &r.ExtendNodeRequest) } + +type NodeRedeployParams struct { + // Update the cloud init user data for VMs running on this node Data should be + // base64 encoded + CloudInitUserData param.Opt[string] `json:"cloud_init_user_data,omitzero" format:"byte"` + // Redeploy node with this VM image ID + ImageID param.Opt[string] `json:"image_id,omitzero"` + // If false, then the new VM will inherit any configuration (like image_id, + // cloud_init_user_data) that is left empty in this request from the current VM. + // + // If true, then any configuration left empty will be set as empty in the new VM. + // E.g if cloud_init_user_data is left unset and override_empty is true, then the + // new VM will not have any cloud init user data. override_empty defaults to false. + OverrideEmpty param.Opt[bool] `json:"override_empty,omitzero"` + paramObj +} + +func (r NodeRedeployParams) MarshalJSON() (data []byte, err error) { + type shadow NodeRedeployParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *NodeRedeployParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/node_test.go b/node_test.go index d5cbd77..88ca793 100644 --- a/node_test.go +++ b/node_test.go @@ -31,7 +31,7 @@ func TestNodeNewWithOptionalParams(t *testing.T) { DesiredCount: 1, MaxPricePerNodeHour: 1000, Zone: "hayesvalley", - CloudInitUserData: []int64{0}, + CloudInitUserData: sfcnodes.String("aGVsbG8gd29ybGQ="), EndAt: sfcnodes.Int(0), ImageID: sfcnodes.String("vmi_1234567890abcdef"), Names: []string{"cuda-crunch"}, @@ -64,6 +64,7 @@ func TestNodeListWithOptionalParams(t *testing.T) { _, err := client.Nodes.List(context.TODO(), sfcnodes.NodeListParams{ ID: []string{"string"}, Name: []string{"string"}, + Type: sfcnodes.NodeTypeAutoreserved, }) if err != nil { var apierr *sfcnodes.Error @@ -74,6 +75,29 @@ func TestNodeListWithOptionalParams(t *testing.T) { } } +func TestNodeDelete(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := sfcnodes.NewClient( + option.WithBaseURL(baseURL), + option.WithBearerToken("My Bearer Token"), + ) + err := client.Nodes.Delete(context.TODO(), "id") + if err != nil { + var apierr *sfcnodes.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + func TestNodeExtend(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" @@ -129,6 +153,37 @@ func TestNodeGet(t *testing.T) { } } +func TestNodeRedeployWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := sfcnodes.NewClient( + option.WithBaseURL(baseURL), + option.WithBearerToken("My Bearer Token"), + ) + _, err := client.Nodes.Redeploy( + context.TODO(), + "id", + sfcnodes.NodeRedeployParams{ + CloudInitUserData: sfcnodes.String("aGVsbG8gd29ybGQ="), + ImageID: sfcnodes.String("vmi_1234567890abcdef"), + OverrideEmpty: sfcnodes.Bool(true), + }, + ) + if err != nil { + var apierr *sfcnodes.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + func TestNodeRelease(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" From fb1973b6adb73c59e6b3b4aedaecf20d5f90ff94 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:59:26 +0000 Subject: [PATCH 18/18] release: 0.1.0-alpha.3 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ README.md | 2 +- internal/version.go | 2 +- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f14b480..aaf968a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.2" + ".": "0.1.0-alpha.3" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3547ab3..783e72f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## 0.1.0-alpha.3 (2025-10-13) + +Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sfcompute/nodes-go/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) + +### Features + +* **api:** add vm images resources and update formatting ([273c45f](https://github.com/sfcompute/nodes-go/commit/273c45f75e75104ad10eb1eab1bdb7acc3596cce)) +* **api:** api update ([97607e0](https://github.com/sfcompute/nodes-go/commit/97607e02d439b4fe1463a483d362ab5bf65fd708)) +* **api:** api update ([5788fb0](https://github.com/sfcompute/nodes-go/commit/5788fb0d297e3c4f79e76e1f2369bd086dad610b)) +* **api:** api update ([59b16bc](https://github.com/sfcompute/nodes-go/commit/59b16bcc748ffea703644d6332f33a79196aeeee)) +* **api:** api update ([d9c2daa](https://github.com/sfcompute/nodes-go/commit/d9c2daa0151d664a5bbff0609d0a6e1e6db3b8c8)) +* **api:** disable retries ([8c19a86](https://github.com/sfcompute/nodes-go/commit/8c19a865b584b8dc40feecd1a26e3d8d9a013230)) + + +### Bug Fixes + +* **api:** remove undocumented endpoints, add list endpoint ([ee795ee](https://github.com/sfcompute/nodes-go/commit/ee795ee93f88b5c379d9ce834356c59fae43e162)) +* **internal:** unmarshal correctly when there are multiple discriminators ([2836b68](https://github.com/sfcompute/nodes-go/commit/2836b6828c5f60e8c0c4e8892bbefa54e54b64b8)) +* use slices.Concat instead of sometimes modifying r.Options ([6adf0a1](https://github.com/sfcompute/nodes-go/commit/6adf0a14ecd1b5d20924b1b26816af8c5ed270ad)) + + +### Chores + +* bump minimum go version to 1.22 ([90c5664](https://github.com/sfcompute/nodes-go/commit/90c566457cad4e6964d6217feeb21eb699d2c385)) +* configure new SDK language ([42f31df](https://github.com/sfcompute/nodes-go/commit/42f31df1c143e56ca36cea4b5de2b937ac000c7c)) +* do not install brew dependencies in ./scripts/bootstrap by default ([5983889](https://github.com/sfcompute/nodes-go/commit/5983889bfd8681434b39372a0d58fe60115ff267)) +* update more docs for 1.22 ([82733b2](https://github.com/sfcompute/nodes-go/commit/82733b2eff62084fa0b0e415e8a3ad6e1f8f546c)) + ## 0.1.0-alpha.2 (2025-09-05) Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sfcompute/nodes-go/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) diff --git a/README.md b/README.md index 9b731f7..dbad93d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or to pin the version: ```sh -go get -u 'github.com/sfcompute/nodes-go@v0.1.0-alpha.2' +go get -u 'github.com/sfcompute/nodes-go@v0.1.0-alpha.3' ``` diff --git a/internal/version.go b/internal/version.go index d6f40b3..2d1d85e 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.1.0-alpha.2" // x-release-please-version +const PackageVersion = "0.1.0-alpha.3" // x-release-please-version