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