Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ jobs:
github.repository == 'stainless-sdks/hypeman-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@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

Expand All @@ -53,10 +53,10 @@ jobs:
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 @@ -68,10 +68,10 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/hypeman-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
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.19.0"
".": "0.20.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 52
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/hypeman-0aececd4fa79c47cb7222167d6746064c53b69eb70ee14252be71ccc31e6d2a2.yml
openapi_spec_hash: c514624af74c74835e3187b857184ff2
config_hash: ed668fae8826ff533f38df16c9664f44
configured_endpoints: 54
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/hypeman-48b300e51c488f55e3f36024515bdb78a8fa5fe44c6fbde99d3f8a34c16df1cb.yml
openapi_spec_hash: 1b53b5b26006f74ccbbb8facc57a9d44
config_hash: dfacce742631adb22616891760f57888
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.20.0 (2026-05-14)

Full Changelog: [v0.19.0...v0.20.0](https://github.com/kernel/hypeman-go/compare/v0.19.0...v0.20.0)

### Features

* **client:** optimize json encoder for internal types ([d8bd64d](https://github.com/kernel/hypeman-go/commit/d8bd64d9ec2cd00f3c219e1540cb18c1bc43ecb8))
* Model template as an instance state instead of a separate registry ([8ea492b](https://github.com/kernel/hypeman-go/commit/8ea492b994dffbea6d99f1897d87aaa627835d1b))

## 0.19.0 (2026-05-12)

Full Changelog: [v0.18.0...v0.19.0](https://github.com/kernel/hypeman-go/compare/v0.18.0...v0.19.0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/kernel/hypeman-go@v0.19.0'
go get -u 'github.com/kernel/hypeman-go@v0.20.0'
```

<!-- x-release-please-end -->
Expand Down
2 changes: 2 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ Methods:
- <code title="patch /instances/{id}">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Update">Update</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>, body <a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceUpdateParams">InstanceUpdateParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /instances">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.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/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceListParams">InstanceListParams</a>) (\*[]<a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /instances/{id}">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Delete">Delete</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/builtin#error">error</a></code>
- <code title="post /instances/{id}/demote-template">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.DemoteTemplate">DemoteTemplate</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/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /instances/{id}/fork">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Fork">Fork</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>, body <a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceForkParams">InstanceForkParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /instances/{id}">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.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/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /instances/{id}/logs">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Logs">Logs</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>, query <a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceLogsParams">InstanceLogsParams</a>) (\*<a href="https://pkg.go.dev/builtin#string">string</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /instances/{id}/promote-template">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.PromoteTemplate">PromoteTemplate</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/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /instances/{id}/restore">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Restore">Restore</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/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /instances/{id}/standby">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Standby">Standby</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>, body <a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceStandbyParams">InstanceStandbyParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /instances/{id}/start">client.Instances.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceService.Start">Start</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>, body <a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#InstanceStartParams">InstanceStartParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
Expand Down
72 changes: 52 additions & 20 deletions instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ func (r *InstanceService) Delete(ctx context.Context, id string, opts ...option.
return err
}

// Demote a template back to standby so it can be restored or deleted
func (r *InstanceService) DemoteTemplate(ctx context.Context, id string, opts ...option.RequestOption) (res *Instance, err error) {
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return nil, err
}
path := fmt.Sprintf("instances/%s/demote-template", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
return res, err
}

// Fork an instance from stopped, standby, or running (with from_running=true)
func (r *InstanceService) Fork(ctx context.Context, id string, body InstanceForkParams, opts ...option.RequestOption) (res *Instance, err error) {
opts = slices.Concat(r.Options, opts)
Expand Down Expand Up @@ -142,6 +154,18 @@ func (r *InstanceService) LogsStreaming(ctx context.Context, id string, query In
return ssestream.NewStream[string](ssestream.NewDecoder(raw), err)
}

// Promote a standby instance into a fork-only template
func (r *InstanceService) PromoteTemplate(ctx context.Context, id string, opts ...option.RequestOption) (res *Instance, err error) {
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return nil, err
}
path := fmt.Sprintf("instances/%s/promote-template", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
return res, err
}

// Restore instance from standby
func (r *InstanceService) Restore(ctx context.Context, id string, opts ...option.RequestOption) (res *Instance, err error) {
opts = slices.Concat(r.Options, opts)
Expand Down Expand Up @@ -391,17 +415,19 @@ type Instance struct {
Name string `json:"name" api:"required"`
// Instance state:
//
// - Created: VMM created but not started (Cloud Hypervisor native)
// - Initializing: VM is running while guest init is still in progress
// - Running: Guest program has started and instance is ready
// - Paused: VM is paused (Cloud Hypervisor native)
// - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native)
// - Stopped: No VMM running, no snapshot exists
// - Standby: No VMM running, snapshot exists (can be restored)
// - Unknown: Failed to determine state (see state_error for details)
// - Created: VMM created but not started (Cloud Hypervisor native)
// - Initializing: VM is running while guest init is still in progress
// - Running: Guest program has started and instance is ready
// - Paused: VM is paused (Cloud Hypervisor native)
// - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native)
// - Stopped: No VMM running, no snapshot exists
// - Standby: No VMM running, snapshot exists (can be restored)
// - Template: Standby snapshot promoted to a fork-only parent; cannot wake while
// forks exist
// - Unknown: Failed to determine state (see state_error for details)
//
// Any of "Created", "Initializing", "Running", "Paused", "Shutdown", "Stopped",
// "Standby", "Unknown".
// "Standby", "Template", "Unknown".
State InstanceState `json:"state" api:"required"`
// Linux-only automatic standby policy based on active inbound TCP connections
// observed from the host conntrack table.
Expand Down Expand Up @@ -496,14 +522,16 @@ func (r *Instance) UnmarshalJSON(data []byte) error {

// Instance state:
//
// - Created: VMM created but not started (Cloud Hypervisor native)
// - Initializing: VM is running while guest init is still in progress
// - Running: Guest program has started and instance is ready
// - Paused: VM is paused (Cloud Hypervisor native)
// - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native)
// - Stopped: No VMM running, no snapshot exists
// - Standby: No VMM running, snapshot exists (can be restored)
// - Unknown: Failed to determine state (see state_error for details)
// - Created: VMM created but not started (Cloud Hypervisor native)
// - Initializing: VM is running while guest init is still in progress
// - Running: Guest program has started and instance is ready
// - Paused: VM is paused (Cloud Hypervisor native)
// - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native)
// - Stopped: No VMM running, no snapshot exists
// - Standby: No VMM running, snapshot exists (can be restored)
// - Template: Standby snapshot promoted to a fork-only parent; cannot wake while
// forks exist
// - Unknown: Failed to determine state (see state_error for details)
type InstanceState string

const (
Expand All @@ -514,6 +542,7 @@ const (
InstanceStateShutdown InstanceState = "Shutdown"
InstanceStateStopped InstanceState = "Stopped"
InstanceStateStandby InstanceState = "Standby"
InstanceStateTemplate InstanceState = "Template"
InstanceStateUnknown InstanceState = "Unknown"
)

Expand Down Expand Up @@ -916,7 +945,7 @@ type WaitForStateResponse struct {
// Current instance state when the wait completed
//
// Any of "Created", "Initializing", "Running", "Paused", "Shutdown", "Stopped",
// "Standby", "Unknown".
// "Standby", "Template", "Unknown".
State WaitForStateResponseState `json:"state" api:"required"`
// Whether the timeout expired before the target state was reached
TimedOut bool `json:"timed_out" api:"required"`
Expand Down Expand Up @@ -949,6 +978,7 @@ const (
WaitForStateResponseStateShutdown WaitForStateResponseState = "Shutdown"
WaitForStateResponseStateStopped WaitForStateResponseState = "Stopped"
WaitForStateResponseStateStandby WaitForStateResponseState = "Standby"
WaitForStateResponseStateTemplate WaitForStateResponseState = "Template"
WaitForStateResponseStateUnknown WaitForStateResponseState = "Unknown"
)

Expand Down Expand Up @@ -1213,7 +1243,7 @@ type InstanceListParams struct {
// Filter instances by state (e.g., Running, Stopped)
//
// Any of "Created", "Initializing", "Running", "Paused", "Shutdown", "Stopped",
// "Standby", "Unknown".
// "Standby", "Template", "Unknown".
State InstanceListParamsState `query:"state,omitzero" json:"-"`
// Filter instances by tag key-value pairs. Uses deepObject style:
// ?tags[team]=backend&tags[env]=staging Multiple entries are ANDed together. All
Expand Down Expand Up @@ -1241,6 +1271,7 @@ const (
InstanceListParamsStateShutdown InstanceListParamsState = "Shutdown"
InstanceListParamsStateStopped InstanceListParamsState = "Stopped"
InstanceListParamsStateStandby InstanceListParamsState = "Standby"
InstanceListParamsStateTemplate InstanceListParamsState = "Template"
InstanceListParamsStateUnknown InstanceListParamsState = "Unknown"
)

Expand Down Expand Up @@ -1364,7 +1395,7 @@ type InstanceWaitParams struct {
// Target state to wait for
//
// Any of "Created", "Initializing", "Running", "Paused", "Shutdown", "Stopped",
// "Standby", "Unknown".
// "Standby", "Template", "Unknown".
State InstanceWaitParamsState `query:"state,omitzero" api:"required" json:"-"`
// Maximum duration to wait (Go duration format, e.g. "30s", "2m"). Capped at 5
// minutes. Defaults to 60 seconds.
Expand All @@ -1391,5 +1422,6 @@ const (
InstanceWaitParamsStateShutdown InstanceWaitParamsState = "Shutdown"
InstanceWaitParamsStateStopped InstanceWaitParamsState = "Stopped"
InstanceWaitParamsStateStandby InstanceWaitParamsState = "Standby"
InstanceWaitParamsStateTemplate InstanceWaitParamsState = "Template"
InstanceWaitParamsStateUnknown InstanceWaitParamsState = "Unknown"
)
46 changes: 46 additions & 0 deletions instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,29 @@ func TestInstanceDelete(t *testing.T) {
}
}

func TestInstanceDemoteTemplate(t *testing.T) {
t.Skip("Mock server 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 := hypeman.NewClient(
option.WithBaseURL(baseURL),
option.WithAPIKey("My API Key"),
)
_, err := client.Instances.DemoteTemplate(context.TODO(), "id")
if err != nil {
var apierr *hypeman.Error
if errors.As(err, &apierr) {
t.Log(string(apierr.DumpRequest(true)))
}
t.Fatalf("err should be nil: %s", err.Error())
}
}

func TestInstanceForkWithOptionalParams(t *testing.T) {
t.Skip("Mock server tests are disabled")
baseURL := "http://localhost:4010"
Expand Down Expand Up @@ -251,6 +274,29 @@ func TestInstanceGet(t *testing.T) {
}
}

func TestInstancePromoteTemplate(t *testing.T) {
t.Skip("Mock server 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 := hypeman.NewClient(
option.WithBaseURL(baseURL),
option.WithAPIKey("My API Key"),
)
_, err := client.Instances.PromoteTemplate(context.TODO(), "id")
if err != nil {
var apierr *hypeman.Error
if errors.As(err, &apierr) {
t.Log(string(apierr.DumpRequest(true)))
}
t.Fatalf("err should be nil: %s", err.Error())
}
}

func TestInstanceRestore(t *testing.T) {
t.Skip("Mock server tests are disabled")
baseURL := "http://localhost:4010"
Expand Down
Loading
Loading