From 3dbe64e2bdb18744c58d30a8a9df44107fed6a61 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 24 Mar 2026 04:55:04 +0000
Subject: [PATCH 1/4] chore(internal): update gitignore
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index c6d0501..8554aff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.prism.log
+.stdy.log
codegen.log
Brewfile.lock.json
.idea/
From f0d4d52bf563ca828cba5f31f6a0c913d4dba4f8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 24 Mar 2026 16:59:40 +0000
Subject: [PATCH 2/4] feat: Add scheduled instance snapshots with retention
cleanup
---
.stats.yml | 8 +-
api.md | 12 +++
instance.go | 126 ++++++++++++++++++++++++++++++-
instancesnapshotschedule.go | 93 +++++++++++++++++++++++
instancesnapshotschedule_test.go | 100 ++++++++++++++++++++++++
5 files changed, 332 insertions(+), 7 deletions(-)
create mode 100644 instancesnapshotschedule.go
create mode 100644 instancesnapshotschedule_test.go
diff --git a/.stats.yml b/.stats.yml
index 4d77a66..327b3e8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 47
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-8251e13b6a8ec8965b5cb0b5ab33d4a16e274a4be7cd6d9fa36642878108797c.yml
-openapi_spec_hash: 5c1c0d21d430074ffa76ae62ea137f0b
-config_hash: 47cce606a7f8af4dac9c2a8dbc822484
+configured_endpoints: 50
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-c642f2375b218db533c8ae0ff0695b85c048c72fe19400788ecd2c9d992def46.yml
+openapi_spec_hash: 505818e6d24972006fa869dd6ed90757
+config_hash: 32c7f371b192b03881971de768ebb07d
diff --git a/api.md b/api.md
index 01ab836..4c97e0f 100644
--- a/api.md
+++ b/api.md
@@ -33,7 +33,9 @@ Methods:
Params Types:
+- hypeman.SetSnapshotScheduleRequestParam
- hypeman.SnapshotPolicyParam
+- hypeman.SnapshotScheduleRetentionParam
- hypeman.VolumeMountParam
Response Types:
@@ -42,6 +44,8 @@ Response Types:
- hypeman.InstanceStats
- hypeman.PathInfo
- hypeman.SnapshotPolicy
+- hypeman.SnapshotSchedule
+- hypeman.SnapshotScheduleRetention
- hypeman.VolumeMount
Methods:
@@ -74,6 +78,14 @@ Methods:
- client.Instances.Snapshots.New(ctx context.Context, id string, body hypeman.InstanceSnapshotNewParams) (\*hypeman.Snapshot, error)
- client.Instances.Snapshots.Restore(ctx context.Context, snapshotID string, params hypeman.InstanceSnapshotRestoreParams) (\*hypeman.Instance, error)
+## SnapshotSchedule
+
+Methods:
+
+- client.Instances.SnapshotSchedule.Update(ctx context.Context, id string, body hypeman.InstanceSnapshotScheduleUpdateParams) (\*hypeman.SnapshotSchedule, error)
+- client.Instances.SnapshotSchedule.Delete(ctx context.Context, id string) error
+- client.Instances.SnapshotSchedule.Get(ctx context.Context, id string) (\*hypeman.SnapshotSchedule, error)
+
# Snapshots
Params Types:
diff --git a/instance.go b/instance.go
index a2c146f..a9244e4 100644
--- a/instance.go
+++ b/instance.go
@@ -29,9 +29,10 @@ import (
// automatically. You should not instantiate this service directly, and instead use
// the [NewInstanceService] method instead.
type InstanceService struct {
- Options []option.RequestOption
- Volumes InstanceVolumeService
- Snapshots InstanceSnapshotService
+ Options []option.RequestOption
+ Volumes InstanceVolumeService
+ Snapshots InstanceSnapshotService
+ SnapshotSchedule InstanceSnapshotScheduleService
}
// NewInstanceService generates a new service that applies the given options to
@@ -42,6 +43,7 @@ func NewInstanceService(opts ...option.RequestOption) (r InstanceService) {
r.Options = opts
r.Volumes = NewInstanceVolumeService(opts...)
r.Snapshots = NewInstanceSnapshotService(opts...)
+ r.SnapshotSchedule = NewInstanceSnapshotScheduleService(opts...)
return
}
@@ -481,6 +483,27 @@ func (r *PathInfo) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+// The properties Interval, Retention are required.
+type SetSnapshotScheduleRequestParam struct {
+ // Snapshot interval (Go duration format, minimum 1m).
+ Interval string `json:"interval" api:"required"`
+ // At least one of max_count or max_age must be provided.
+ Retention SnapshotScheduleRetentionParam `json:"retention,omitzero" api:"required"`
+ // Optional prefix for auto-generated scheduled snapshot names (max 47 chars).
+ NamePrefix param.Opt[string] `json:"name_prefix,omitzero"`
+ // User-defined key-value tags.
+ Metadata map[string]string `json:"metadata,omitzero"`
+ paramObj
+}
+
+func (r SetSnapshotScheduleRequestParam) MarshalJSON() (data []byte, err error) {
+ type shadow SetSnapshotScheduleRequestParam
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *SetSnapshotScheduleRequestParam) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
type SnapshotPolicy struct {
Compression shared.SnapshotCompressionConfig `json:"compression"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
@@ -519,6 +542,103 @@ func (r *SnapshotPolicyParam) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+type SnapshotSchedule struct {
+ // Schedule creation timestamp.
+ CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"`
+ // Source instance ID.
+ InstanceID string `json:"instance_id" api:"required"`
+ // Snapshot interval (Go duration format).
+ Interval string `json:"interval" api:"required"`
+ // Next scheduled run time.
+ NextRunAt time.Time `json:"next_run_at" api:"required" format:"date-time"`
+ // Automatic cleanup policy for scheduled snapshots.
+ Retention SnapshotScheduleRetention `json:"retention" api:"required"`
+ // Schedule update timestamp.
+ UpdatedAt time.Time `json:"updated_at" api:"required" format:"date-time"`
+ // Last schedule run error, if any.
+ LastError string `json:"last_error" api:"nullable"`
+ // Last schedule execution time.
+ LastRunAt time.Time `json:"last_run_at" api:"nullable" format:"date-time"`
+ // Snapshot ID produced by the last successful run.
+ LastSnapshotID string `json:"last_snapshot_id" api:"nullable"`
+ // User-defined key-value tags.
+ Metadata map[string]string `json:"metadata"`
+ // Optional prefix used for generated scheduled snapshot names.
+ NamePrefix string `json:"name_prefix" api:"nullable"`
+ // JSON contains metadata for fields, check presence with [respjson.Field.Valid].
+ JSON struct {
+ CreatedAt respjson.Field
+ InstanceID respjson.Field
+ Interval respjson.Field
+ NextRunAt respjson.Field
+ Retention respjson.Field
+ UpdatedAt respjson.Field
+ LastError respjson.Field
+ LastRunAt respjson.Field
+ LastSnapshotID respjson.Field
+ Metadata respjson.Field
+ NamePrefix respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
+ } `json:"-"`
+}
+
+// Returns the unmodified JSON received from the API
+func (r SnapshotSchedule) RawJSON() string { return r.JSON.raw }
+func (r *SnapshotSchedule) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+// Automatic cleanup policy for scheduled snapshots.
+type SnapshotScheduleRetention struct {
+ // Delete scheduled snapshots older than this duration (Go duration format).
+ MaxAge string `json:"max_age"`
+ // Keep at most this many scheduled snapshots for the instance (0 disables
+ // count-based cleanup).
+ MaxCount int64 `json:"max_count"`
+ // JSON contains metadata for fields, check presence with [respjson.Field.Valid].
+ JSON struct {
+ MaxAge respjson.Field
+ MaxCount respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
+ } `json:"-"`
+}
+
+// Returns the unmodified JSON received from the API
+func (r SnapshotScheduleRetention) RawJSON() string { return r.JSON.raw }
+func (r *SnapshotScheduleRetention) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+// ToParam converts this SnapshotScheduleRetention to a
+// SnapshotScheduleRetentionParam.
+//
+// Warning: the fields of the param type will not be present. ToParam should only
+// be used at the last possible moment before sending a request. Test for this with
+// SnapshotScheduleRetentionParam.Overrides()
+func (r SnapshotScheduleRetention) ToParam() SnapshotScheduleRetentionParam {
+ return param.Override[SnapshotScheduleRetentionParam](json.RawMessage(r.RawJSON()))
+}
+
+// Automatic cleanup policy for scheduled snapshots.
+type SnapshotScheduleRetentionParam struct {
+ // Delete scheduled snapshots older than this duration (Go duration format).
+ MaxAge param.Opt[string] `json:"max_age,omitzero"`
+ // Keep at most this many scheduled snapshots for the instance (0 disables
+ // count-based cleanup).
+ MaxCount param.Opt[int64] `json:"max_count,omitzero"`
+ paramObj
+}
+
+func (r SnapshotScheduleRetentionParam) MarshalJSON() (data []byte, err error) {
+ type shadow SnapshotScheduleRetentionParam
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *SnapshotScheduleRetentionParam) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
type VolumeMount struct {
// Path where volume is mounted in the guest
MountPath string `json:"mount_path" api:"required"`
diff --git a/instancesnapshotschedule.go b/instancesnapshotschedule.go
new file mode 100644
index 0000000..44d9966
--- /dev/null
+++ b/instancesnapshotschedule.go
@@ -0,0 +1,93 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package hypeman
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "slices"
+
+ shimjson "github.com/kernel/hypeman-go/internal/encoding/json"
+ "github.com/kernel/hypeman-go/internal/requestconfig"
+ "github.com/kernel/hypeman-go/option"
+)
+
+// InstanceSnapshotScheduleService contains methods and other services that help
+// with interacting with the hypeman 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 [NewInstanceSnapshotScheduleService] method instead.
+type InstanceSnapshotScheduleService struct {
+ Options []option.RequestOption
+}
+
+// NewInstanceSnapshotScheduleService 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 NewInstanceSnapshotScheduleService(opts ...option.RequestOption) (r InstanceSnapshotScheduleService) {
+ r = InstanceSnapshotScheduleService{}
+ r.Options = opts
+ return
+}
+
+// Scheduled runs automatically choose snapshot behavior from current instance
+// state:
+//
+// - `Running` or `Standby` source: create a `Standby` snapshot.
+// - `Stopped` source: create a `Stopped` snapshot. For running instances, this
+// includes a brief pause/resume cycle during each capture. The minimum supported
+// interval is `1m`, but larger intervals are recommended for heavier or
+// latency-sensitive workloads. Updating only retention, metadata, or
+// `name_prefix` preserves the next scheduled run; changing `interval`
+// establishes a new cadence.
+func (r *InstanceSnapshotScheduleService) Update(ctx context.Context, id string, body InstanceSnapshotScheduleUpdateParams, opts ...option.RequestOption) (res *SnapshotSchedule, 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/snapshot-schedule", id)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...)
+ return res, err
+}
+
+// Delete snapshot schedule for an instance
+func (r *InstanceSnapshotScheduleService) 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 err
+ }
+ path := fmt.Sprintf("instances/%s/snapshot-schedule", id)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
+ return err
+}
+
+// Get snapshot schedule for an instance
+func (r *InstanceSnapshotScheduleService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *SnapshotSchedule, 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/snapshot-schedule", id)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ return res, err
+}
+
+type InstanceSnapshotScheduleUpdateParams struct {
+ SetSnapshotScheduleRequest SetSnapshotScheduleRequestParam
+ paramObj
+}
+
+func (r InstanceSnapshotScheduleUpdateParams) MarshalJSON() (data []byte, err error) {
+ return shimjson.Marshal(r.SetSnapshotScheduleRequest)
+}
+func (r *InstanceSnapshotScheduleUpdateParams) UnmarshalJSON(data []byte) error {
+ return json.Unmarshal(data, &r.SetSnapshotScheduleRequest)
+}
diff --git a/instancesnapshotschedule_test.go b/instancesnapshotschedule_test.go
new file mode 100644
index 0000000..13337ec
--- /dev/null
+++ b/instancesnapshotschedule_test.go
@@ -0,0 +1,100 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package hypeman_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/kernel/hypeman-go"
+ "github.com/kernel/hypeman-go/internal/testutil"
+ "github.com/kernel/hypeman-go/option"
+)
+
+func TestInstanceSnapshotScheduleUpdateWithOptionalParams(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.SnapshotSchedule.Update(
+ context.TODO(),
+ "id",
+ hypeman.InstanceSnapshotScheduleUpdateParams{
+ SetSnapshotScheduleRequest: hypeman.SetSnapshotScheduleRequestParam{
+ Interval: "24h",
+ Retention: hypeman.SnapshotScheduleRetentionParam{
+ MaxAge: hypeman.String("168h"),
+ MaxCount: hypeman.Int(7),
+ },
+ Metadata: map[string]string{
+ "team": "backend",
+ "env": "staging",
+ },
+ NamePrefix: hypeman.String("nightly"),
+ },
+ },
+ )
+ 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 TestInstanceSnapshotScheduleDelete(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.SnapshotSchedule.Delete(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 TestInstanceSnapshotScheduleGet(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.SnapshotSchedule.Get(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())
+ }
+}
From e3c8b1f0943858b91f196db4889a27f0d626a6e4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 25 Mar 2026 03:22:59 +0000
Subject: [PATCH 3/4] chore(ci): skip lint on metadata-only changes
Note that we still want to run tests, as these depend on the metadata.
---
.github/workflows/ci.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5fb9003..a99f28f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,7 +24,8 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/hypeman-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: |-
github.repository == 'stainless-sdks/hypeman-go' &&
- (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
+ (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
From 82a59380dedfa66d1429bb2769454bba4824d19b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 25 Mar 2026 03:23:41 +0000
Subject: [PATCH 4/4] release: 0.17.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 14 ++++++++++++++
README.md | 2 +-
internal/version.go | 2 +-
4 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b4e9013..6db19b9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.16.0"
+ ".": "0.17.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e2ab44..2c100e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 0.17.0 (2026-03-25)
+
+Full Changelog: [v0.16.0...v0.17.0](https://github.com/kernel/hypeman-go/compare/v0.16.0...v0.17.0)
+
+### Features
+
+* Add scheduled instance snapshots with retention cleanup ([f0d4d52](https://github.com/kernel/hypeman-go/commit/f0d4d52bf563ca828cba5f31f6a0c913d4dba4f8))
+
+
+### Chores
+
+* **ci:** skip lint on metadata-only changes ([e3c8b1f](https://github.com/kernel/hypeman-go/commit/e3c8b1f0943858b91f196db4889a27f0d626a6e4))
+* **internal:** update gitignore ([3dbe64e](https://github.com/kernel/hypeman-go/commit/3dbe64e2bdb18744c58d30a8a9df44107fed6a61))
+
## 0.16.0 (2026-03-23)
Full Changelog: [v0.15.0...v0.16.0](https://github.com/kernel/hypeman-go/compare/v0.15.0...v0.16.0)
diff --git a/README.md b/README.md
index 62dd1ed..e3d4666 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Or to pin the version:
```sh
-go get -u 'github.com/kernel/hypeman-go@v0.16.0'
+go get -u 'github.com/kernel/hypeman-go@v0.17.0'
```
diff --git a/internal/version.go b/internal/version.go
index 16d4c3d..c265c64 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.16.0" // x-release-please-version
+const PackageVersion = "0.17.0" // x-release-please-version