Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6905ec5
chore(internal): codegen related update
stainless-app[bot] Mar 3, 2026
0efeaca
chore(internal): codegen related update
stainless-app[bot] Mar 6, 2026
d5f51e8
chore(ci): skip uploading artifacts on stainless-internal branches
stainless-app[bot] Mar 7, 2026
d4332e4
chore(internal): minor cleanup
stainless-app[bot] Mar 11, 2026
f740ed8
chore(internal): use explicit returns
stainless-app[bot] Mar 11, 2026
f8f3570
chore(internal): use explicit returns in more places
stainless-app[bot] Mar 11, 2026
f1ab274
chore(internal): tweak CI branches
stainless-app[bot] Mar 17, 2026
c997311
chore(internal): update gitignore
stainless-app[bot] Mar 24, 2026
59072fa
chore(ci): skip lint on metadata-only changes
stainless-app[bot] Mar 25, 2026
9be56ed
chore(internal): support default value struct tag
stainless-app[bot] Mar 26, 2026
68d8f8e
chore(client): fix multipart serialisation of Default() fields
stainless-app[bot] Mar 26, 2026
179abd8
fix: prevent duplicate ? in query params
stainless-app[bot] Mar 27, 2026
9a31c22
chore: remove unnecessary error check for url parsing
stainless-app[bot] Mar 27, 2026
ecfc0ce
feat(internal): support comma format in multipart form encoding
stainless-app[bot] Mar 27, 2026
046b6e1
chore(ci): support opting out of skipping builds on metadata-only com…
stainless-app[bot] Mar 28, 2026
afd6c27
chore: update docs for api:"required"
stainless-app[bot] Mar 28, 2026
fa94f5f
fix: fix issue with unmarshaling in some cases
stainless-app[bot] Apr 1, 2026
0b4c0ec
fix: better respect format tags from the spec
stainless-app[bot] Apr 9, 2026
2f9af5c
codegen metadata
stainless-app[bot] Apr 20, 2026
1aad79a
chore(internal): more robust bootstrap script
stainless-app[bot] Apr 23, 2026
e8e7fff
feat(go): add default http client with timeout
stainless-app[bot] Apr 28, 2026
c14c4ea
feat: support setting headers via env
stainless-app[bot] Apr 28, 2026
9730d4f
codegen metadata
stainless-app[bot] May 1, 2026
bedfc12
codegen metadata
stainless-app[bot] May 1, 2026
851e07b
chore: avoid embedding reflect.Type for dead code elimination
stainless-app[bot] May 1, 2026
ce03df6
chore: redact api-key headers in debug logs
stainless-app[bot] May 8, 2026
57cc619
fix(go): avoid panic when http.DefaultTransport is wrapped
stainless-app[bot] May 8, 2026
44eaa76
ci: pin GitHub Actions to commit SHAs
stainless-app[bot] May 13, 2026
77a27ee
feat(client): optimize json encoder for internal types
stainless-app[bot] May 14, 2026
0ae17e1
feat(api): api update
stainless-app[bot] May 15, 2026
eb93c6d
feat(api): api update
stainless-app[bot] May 15, 2026
57ca4f4
codegen metadata
stainless-app[bot] May 17, 2026
bca26e2
feat(api): update config to account for breaking changes
stainless-app[bot] May 18, 2026
9916726
codegen metadata
stainless-app[bot] May 18, 2026
3510626
codegen metadata
stainless-app[bot] May 18, 2026
8594097
feat(api): api update
stainless-app[bot] May 19, 2026
5c53052
feat(api): api update
stainless-app[bot] May 21, 2026
dcaca0e
release: 0.1.0-alpha.6
stainless-app[bot] May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
name: CI
on:
push:
branches-ignore:
- 'generated'
- 'codegen/**'
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
branches:
- '**'
- '!integrated/**'
- '!stl-preview-head/**'
- '!stl-preview-base/**'
- '!generated'
- '!codegen/**'
- 'codegen/stl/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
- 'stl-preview-base/**'

jobs:
build:
timeout-minutes: 10
name: build
permissions:
contents: read
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/sfc-nodes-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: |-
github.repository == 'stainless-sdks/sfc-nodes-go' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Get GitHub OIDC Token
if: |-
github.repository == 'stainless-sdks/sfc-nodes-go' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: core.setOutput('github_token', await core.getIDToken());

- name: Upload tarball
if: |-
github.repository == 'stainless-sdks/sfc-nodes-go' &&
!startsWith(github.ref, 'refs/heads/stl/')
env:
URL: https://pkg.stainless.com/s
AUTH: ${{ steps.github-oidc.outputs.github_token }}
SHA: ${{ github.sha }}
run: ./scripts/utils/upload-artifact.sh
lint:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/sfc-nodes-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: ./go.mod

Expand All @@ -35,10 +68,10 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/sfc-nodes-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: ./go.mod

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.prism.log
.stdy.log
codegen.log
Brewfile.lock.json
.idea/
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.0-alpha.5"
".": "0.1.0-alpha.6"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 15
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-d786973209f42e6ca35f318f4d5bf01e4abd77205e210409c6a3fb371a99a4c5.yml
openapi_spec_hash: 03857ab189ed9fcd889e7b3fe1cc2f2f
config_hash: a187153315a646ecf95709ee4a223df5
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company/sfc-nodes-ac16f44d89080c75848b7db501921b159820ec69b23cac750448c429df12abdb.yml
openapi_spec_hash: e58f2d7597976dca2450667819526e74
config_hash: 8457a42ab599fb499cdacdb3ff40cfe9
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# Changelog

## 0.1.0-alpha.6 (2026-05-21)

Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sfcompute/nodes-go/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)

### Features

* **api:** api update ([5c53052](https://github.com/sfcompute/nodes-go/commit/5c530522281441ba93e17b182dcde27e30fb43cc))
* **api:** api update ([8594097](https://github.com/sfcompute/nodes-go/commit/85940976fa7a1fad13d0ee62e4325f7b17a2bfb2))
* **api:** api update ([eb93c6d](https://github.com/sfcompute/nodes-go/commit/eb93c6dcb89945517c8d3e506d80bec1531a8fbd))
* **api:** api update ([0ae17e1](https://github.com/sfcompute/nodes-go/commit/0ae17e1a7c4894100cf2bc6c3a8098eba4e17e35))
* **api:** update config to account for breaking changes ([bca26e2](https://github.com/sfcompute/nodes-go/commit/bca26e229a3a6120f77df90f693989b54d26a7b1))
* **client:** optimize json encoder for internal types ([77a27ee](https://github.com/sfcompute/nodes-go/commit/77a27ee3287988b67c53a7a08111b91a5e08d2de))
* **go:** add default http client with timeout ([e8e7fff](https://github.com/sfcompute/nodes-go/commit/e8e7fff59aeb8bcd5b44703b55c59665e47dde46))
* **internal:** support comma format in multipart form encoding ([ecfc0ce](https://github.com/sfcompute/nodes-go/commit/ecfc0ceb5a993acb87fc73731ba19c0e24349ea9))
* support setting headers via env ([c14c4ea](https://github.com/sfcompute/nodes-go/commit/c14c4ead498de87c9cc80bba147fbc6f285d64a4))


### Bug Fixes

* better respect format tags from the spec ([0b4c0ec](https://github.com/sfcompute/nodes-go/commit/0b4c0ecae518042d6ceecb8525718fb141e3e1c8))
* fix issue with unmarshaling in some cases ([fa94f5f](https://github.com/sfcompute/nodes-go/commit/fa94f5f06356b76992a572891c762c3fe3559dc0))
* **go:** avoid panic when http.DefaultTransport is wrapped ([57cc619](https://github.com/sfcompute/nodes-go/commit/57cc619fd65be59bbf0d85a3f25a3a9c1a07843a))
* prevent duplicate ? in query params ([179abd8](https://github.com/sfcompute/nodes-go/commit/179abd873e3776a78b5f465b2878981f684c0001))


### Chores

* avoid embedding reflect.Type for dead code elimination ([851e07b](https://github.com/sfcompute/nodes-go/commit/851e07b60b7b90623fb34c9d8e947677806c55ae))
* **ci:** skip lint on metadata-only changes ([59072fa](https://github.com/sfcompute/nodes-go/commit/59072fa7b6eda168b36e5702c27e85cd99ab84cc))
* **ci:** skip uploading artifacts on stainless-internal branches ([d5f51e8](https://github.com/sfcompute/nodes-go/commit/d5f51e8e5330f29e7c05a58fea234a90d3c28a86))
* **ci:** support opting out of skipping builds on metadata-only commits ([046b6e1](https://github.com/sfcompute/nodes-go/commit/046b6e1a7cb29da1c78c90944644f50e5629f304))
* **client:** fix multipart serialisation of Default() fields ([68d8f8e](https://github.com/sfcompute/nodes-go/commit/68d8f8e87b235485f65c079a52cd3c381d9936be))
* **internal:** codegen related update ([0efeaca](https://github.com/sfcompute/nodes-go/commit/0efeacad5ecdbea83c326a17bb7a2f593afdcdcb))
* **internal:** codegen related update ([6905ec5](https://github.com/sfcompute/nodes-go/commit/6905ec5c3e4361acdbb1eda50ebcb869330c0504))
* **internal:** minor cleanup ([d4332e4](https://github.com/sfcompute/nodes-go/commit/d4332e4aeae3e5a569fc586ab430182cb705de17))
* **internal:** more robust bootstrap script ([1aad79a](https://github.com/sfcompute/nodes-go/commit/1aad79aad4eb6f35fc6a72077c7f4f4ac2299ba4))
* **internal:** support default value struct tag ([9be56ed](https://github.com/sfcompute/nodes-go/commit/9be56edd00c8e78ee7d3cd56604eeebe63df376b))
* **internal:** tweak CI branches ([f1ab274](https://github.com/sfcompute/nodes-go/commit/f1ab27477b86808c8942b6ce899803ac150c8b16))
* **internal:** update gitignore ([c997311](https://github.com/sfcompute/nodes-go/commit/c9973115ba8dcfb4eb10f7cab9c438c7729779fb))
* **internal:** use explicit returns ([f740ed8](https://github.com/sfcompute/nodes-go/commit/f740ed8beb5a3e52d0c879e31a9ed47f85af233d))
* **internal:** use explicit returns in more places ([f8f3570](https://github.com/sfcompute/nodes-go/commit/f8f3570dcb58aaecc9139b4bf4e93c1a77f4a711))
* redact api-key headers in debug logs ([ce03df6](https://github.com/sfcompute/nodes-go/commit/ce03df6b12d63a0711fd15049397c4e6f69c4071))
* remove unnecessary error check for url parsing ([9a31c22](https://github.com/sfcompute/nodes-go/commit/9a31c22ba410290e150a66b0f7bec72c9ae131be))
* update docs for api:"required" ([afd6c27](https://github.com/sfcompute/nodes-go/commit/afd6c27ac6b3ae51794c4098293b9f1a676cf2a2))

## 0.1.0-alpha.5 (2026-02-25)

Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sfcompute/nodes-go/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/sfcompute/nodes-go@v0.1.0-alpha.5'
go get -u 'github.com/sfcompute/nodes-go@v0.1.0-alpha.6'
```

<!-- x-release-please-end -->
Expand Down Expand Up @@ -70,7 +70,7 @@ func main() {
The sfcnodes library uses the [`omitzero`](https://tip.golang.org/doc/go1.24#encodingjsonpkgencodingjson)
semantics from the Go 1.24+ `encoding/json` release for request fields.

Required primitive fields (`int64`, `string`, etc.) feature the tag <code>\`json:"...,required"\`</code>. These
Required primitive fields (`int64`, `string`, etc.) feature the tag <code>\`api:"required"\`</code>. These
fields are always serialized, even their zero values.

Optional primitive types are wrapped in a `param.Opt[T]`. These fields can be set with the provided constructors, `sfcnodes.String(string)`, `sfcnodes.Int(int64)`, etc.
Expand Down
4 changes: 2 additions & 2 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Response Types:

Methods:

- <code title="get /v1/vms/images">client.VMs.Images.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (\*<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go">sfcnodes</a>.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageListResponse">VMImageListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /v1/vms/images/{image_id}">client.VMs.Images.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, imageID <a href="https://pkg.go.dev/builtin#string">string</a>) (\*<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go">sfcnodes</a>.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageGetResponse">VMImageGetResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /preview/v2/images">client.VMs.Images.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sfcompute/nodes-go">sfcnodes</a>.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageListParams">VMImageListParams</a>) (\*<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go">sfcnodes</a>.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageListResponse">VMImageListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /preview/v2/images/{id}">client.VMs.Images.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (\*<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go">sfcnodes</a>.<a href="https://pkg.go.dev/github.com/sfcompute/nodes-go#VMImageGetResponse">VMImageGetResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>

# Nodes

Expand Down
23 changes: 19 additions & 4 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"slices"
"strings"

"github.com/sfcompute/nodes-go/internal/requestconfig"
"github.com/sfcompute/nodes-go/option"
Expand All @@ -17,21 +18,35 @@ import (
// directly, and instead use the [NewClient] method instead.
type Client struct {
Options []option.RequestOption
VMs VMService
Nodes NodeService
Zones ZoneService
// Manage your Virtual Machines.
VMs VMService
// Manage compute nodes. Create, list, extend, and release nodes for your
// workloads.
Nodes NodeService
// Zones represent physically colocated datacenters. Use these endpoints to
// discover available zones and their capacity, hardware specifications, and
// regional information.
Zones ZoneService
}

// DefaultClientOptions read from the environment (SFC_NODES_BEARER_TOKEN,
// SFC_NODES_BASE_URL). This should be used to initialize new clients.
func DefaultClientOptions() []option.RequestOption {
defaults := []option.RequestOption{option.WithEnvironmentProduction()}
defaults := []option.RequestOption{option.WithHTTPClient(defaultHTTPClient()), option.WithEnvironmentProduction()}
if o, ok := os.LookupEnv("SFC_NODES_BASE_URL"); ok {
defaults = append(defaults, option.WithBaseURL(o))
}
if o, ok := os.LookupEnv("SFC_NODES_BEARER_TOKEN"); ok {
defaults = append(defaults, option.WithBearerToken(o))
}
if o, ok := os.LookupEnv("SFC_NODES_CUSTOM_HEADERS"); ok {
for _, line := range strings.Split(o, "\n") {
colon := strings.Index(line, ":")
if colon >= 0 {
defaults = append(defaults, option.WithHeader(strings.TrimSpace(line[:colon]), strings.TrimSpace(line[colon+1:])))
}
}
}
return defaults
}

Expand Down
2 changes: 1 addition & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestUserAgentHeader(t *testing.T) {
},
}),
)
client.Nodes.List(context.Background(), sfcnodes.NodeListParams{})
_, _ = client.Nodes.List(context.Background(), sfcnodes.NodeListParams{})
if userAgent != fmt.Sprintf("SFCNodes/Go %s", internal.PackageVersion) {
t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
}
Expand Down
30 changes: 30 additions & 0 deletions default_http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

package sfcnodes

import (
"net/http"
"time"
)

// defaultResponseHeaderTimeout bounds the time between a fully written request
// and the server's response headers. It does not apply to the response body,
// so long-running streams are unaffected. Without this, a server that accepts
// the connection but never responds would hang the request indefinitely.
const defaultResponseHeaderTimeout = 10 * time.Minute

// defaultHTTPClient returns an [*http.Client] used when the caller does not
// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the
// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout]
// is set so stuck connections fail fast instead of compounding across retries.
// If [http.DefaultTransport] has been wrapped (for example by otelhttp for
// distributed tracing), the wrapping is preserved and the header timeout is
// skipped.
func defaultHTTPClient() *http.Client {
if t, ok := http.DefaultTransport.(*http.Transport); ok {
t = t.Clone()
t.ResponseHeaderTimeout = defaultResponseHeaderTimeout
return &http.Client{Transport: t}
}
return &http.Client{Transport: http.DefaultTransport}
}
26 changes: 23 additions & 3 deletions internal/apiform/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type encoderField struct {
}

type encoderEntry struct {
reflect.Type
typ reflect.Type
dateFormat string
arrayFmt string
root bool
Expand All @@ -76,7 +76,7 @@ func (e *encoder) marshal(value any, writer *multipart.Writer) error {

func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
typ: t,
dateFormat: e.dateFormat,
arrayFmt: e.arrayFmt,
root: e.root,
Expand Down Expand Up @@ -183,6 +183,18 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
itemEncoder := e.typeEncoder(t.Elem())
keyFn := e.arrayKeyEncoder()
if e.arrayFmt == "comma" {
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if v.Len() == 0 {
return nil
}
elements := make([]string, v.Len())
for i := 0; i < v.Len(); i++ {
elements[i] = fmt.Sprint(v.Index(i).Interface())
}
return writer.WriteField(key, strings.Join(elements, ","))
}
}
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if keyFn == nil {
return fmt.Errorf("apiform: unsupported array format")
Expand Down Expand Up @@ -265,6 +277,14 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
}
return typeEncoderFn(key, value, writer)
}
} else if ptag.defaultValue != nil {
typeEncoderFn := e.typeEncoder(field.Type)
encoderFn = func(key string, value reflect.Value, writer *multipart.Writer) error {
if value.IsZero() {
return typeEncoderFn(key, reflect.ValueOf(ptag.defaultValue), writer)
}
return typeEncoderFn(key, value, writer)
}
} else {
encoderFn = e.typeEncoder(field.Type)
}
Expand Down Expand Up @@ -469,5 +489,5 @@ func WriteExtras(writer *multipart.Writer, extras map[string]any) (err error) {
break
}
}
return
return err
}
Loading
Loading