Skip to content
Merged
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
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.9.0"
".": "0.9.6"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 36
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-fbe706932aedef16a8bb04fe361b1e8659ae074ff9122f4e9e28914087a677b8.yml
openapi_spec_hash: 6ba4858b44cb317676c58f33bfa1426a
config_hash: 542144891263a3626eabcb2fd4f4f3e5
configured_endpoints: 37
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-cf9805637aaf9bf590b5ebc1e7430414850aff0c8ad727a017df290ea8df9913.yml
openapi_spec_hash: e27144cf0b24dc74fbdb77d8e24d818f
config_hash: d67a314ba1fda8242041000fc9160e92
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.9.6 (2026-01-30)

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

### Features

* add boot time optimizations for faster VM startup ([3992761](https://github.com/kernel/hypeman-go/commit/3992761e3ad8ebb0cc22fb7408199b068e9d8013))
* Add to stainless config new API endpoints ([de008e8](https://github.com/kernel/hypeman-go/commit/de008e89fadbaedde6554181618fb03c71b49465))
* **api:** manual updates ([f60e600](https://github.com/kernel/hypeman-go/commit/f60e60015bb9ce18c7083963d9ecd11c980de495))
* **builds:** implement two-tier build cache with per-repo token scopes ([0e29d03](https://github.com/kernel/hypeman-go/commit/0e29d03d94cf50a0d0e83c323f7ed9f2e15f3e61))
* **client:** add a convenient param.SetJSON helper ([7fea166](https://github.com/kernel/hypeman-go/commit/7fea1660f3d17d8a35f5d2f6aa352b553785624b))
* Use resources module for input validation ([af678e8](https://github.com/kernel/hypeman-go/commit/af678e8c794307a6bd47476acff3ca42a7a52546))

## 0.9.0 (2026-01-05)

Full Changelog: [v0.8.0...v0.9.0](https://github.com/kernel/hypeman-go/compare/v0.8.0...v0.9.0)
Expand Down
2 changes: 1 addition & 1 deletion 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/kernel/hypeman-go@v0.8.0'
go get -u 'github.com/kernel/hypeman-go@v0.9.6'
```

<!-- x-release-please-end -->
Expand Down
1 change: 1 addition & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Methods:
- <code title="post /volumes">client.Volumes.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#VolumeService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</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#VolumeNewParams">VolumeNewParams</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#Volume">Volume</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /volumes">client.Volumes.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#VolumeService.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/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#Volume">Volume</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /volumes/{id}">client.Volumes.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#VolumeService.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 /volumes/from-archive">client.Volumes.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#VolumeService.NewFromArchive">NewFromArchive</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/builtin#io.Reader">io.Reader</a>, params <a href="https://pkg.go.dev/github.com/kernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#VolumeNewFromArchiveParams">VolumeNewFromArchiveParams</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#Volume">Volume</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /volumes/{id}">client.Volumes.<a href="https://pkg.go.dev/github.com/kernel/hypeman-go#VolumeService.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#Volume">Volume</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>

# Devices
Expand Down
2 changes: 1 addition & 1 deletion internal/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package internal

const PackageVersion = "0.9.0" // x-release-please-version
const PackageVersion = "0.9.6" // x-release-please-version
5 changes: 5 additions & 0 deletions resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ type ResourceAllocation struct {
CPU int64 `json:"cpu"`
// Disk allocated in bytes (overlay + volumes)
DiskBytes int64 `json:"disk_bytes"`
// Disk I/O bandwidth limit in bytes/sec
DiskIoBps int64 `json:"disk_io_bps"`
// Instance identifier
InstanceID string `json:"instance_id"`
// Instance name
Expand All @@ -172,6 +174,7 @@ type ResourceAllocation struct {
JSON struct {
CPU respjson.Field
DiskBytes respjson.Field
DiskIoBps respjson.Field
InstanceID respjson.Field
InstanceName respjson.Field
MemoryBytes respjson.Field
Expand Down Expand Up @@ -230,6 +233,7 @@ type Resources struct {
Memory ResourceStatus `json:"memory,required"`
Network ResourceStatus `json:"network,required"`
DiskBreakdown DiskBreakdown `json:"disk_breakdown"`
DiskIo ResourceStatus `json:"disk_io"`
// GPU resource status. Null if no GPUs available.
GPU GPUResourceStatus `json:"gpu,nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
Expand All @@ -240,6 +244,7 @@ type Resources struct {
Memory respjson.Field
Network respjson.Field
DiskBreakdown respjson.Field
DiskIo respjson.Field
GPU respjson.Field
ExtraFields map[string]respjson.Field
raw string
Expand Down
59 changes: 54 additions & 5 deletions volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
package hypeman

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"slices"
"time"

"github.com/kernel/hypeman-go/internal/apiform"
"github.com/kernel/hypeman-go/internal/apijson"
"github.com/kernel/hypeman-go/internal/apiquery"
"github.com/kernel/hypeman-go/internal/requestconfig"
"github.com/kernel/hypeman-go/option"
"github.com/kernel/hypeman-go/packages/param"
Expand All @@ -36,11 +42,7 @@ func NewVolumeService(opts ...option.RequestOption) (r VolumeService) {
return
}

// Creates a new volume. Supports two modes:
//
// - JSON body: Creates an empty volume of the specified size
// - Multipart form: Creates a volume pre-populated with content from a tar.gz
// archive
// Creates a new empty volume of the specified size.
func (r *VolumeService) New(ctx context.Context, body VolumeNewParams, opts ...option.RequestOption) (res *Volume, err error) {
opts = slices.Concat(r.Options, opts)
path := "volumes"
Expand Down Expand Up @@ -69,6 +71,16 @@ func (r *VolumeService) Delete(ctx context.Context, id string, opts ...option.Re
return
}

// Creates a new volume pre-populated with content from a tar.gz archive. The
// archive is streamed directly into the volume's root directory.
func (r *VolumeService) NewFromArchive(ctx context.Context, body io.Reader, params VolumeNewFromArchiveParams, opts ...option.RequestOption) (res *Volume, err error) {
opts = slices.Concat(r.Options, opts)
opts = append([]option.RequestOption{option.WithRequestBody("application/gzip", body)}, opts...)
path := "volumes/from-archive"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
return
}

// Get volume details
func (r *VolumeService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Volume, err error) {
opts = slices.Concat(r.Options, opts)
Expand Down Expand Up @@ -150,3 +162,40 @@ func (r VolumeNewParams) MarshalJSON() (data []byte, err error) {
func (r *VolumeNewParams) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}

type VolumeNewFromArchiveParams struct {
// Volume name
Name string `query:"name,required" json:"-"`
// Maximum size in GB (extraction fails if content exceeds this)
SizeGB int64 `query:"size_gb,required" json:"-"`
// Optional custom volume ID (auto-generated if not provided)
ID param.Opt[string] `query:"id,omitzero" json:"-"`
paramObj
}

func (r VolumeNewFromArchiveParams) MarshalMultipart() (data []byte, contentType string, err error) {
buf := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buf)
err = apiform.MarshalRoot(r, writer)
if err == nil {
err = apiform.WriteExtras(writer, r.ExtraFields())
}
if err != nil {
writer.Close()
return nil, "", err
}
err = writer.Close()
if err != nil {
return nil, "", err
}
return buf.Bytes(), writer.FormDataContentType(), nil
}

// URLQuery serializes [VolumeNewFromArchiveParams]'s query parameters as
// `url.Values`.
func (r VolumeNewFromArchiveParams) URLQuery() (v url.Values, err error) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
33 changes: 33 additions & 0 deletions volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package hypeman_test

import (
"bytes"
"context"
"errors"
"io"
"os"
"testing"

Expand Down Expand Up @@ -86,6 +88,37 @@ func TestVolumeDelete(t *testing.T) {
}
}

func TestVolumeNewFromArchiveWithOptionalParams(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 := hypeman.NewClient(
option.WithBaseURL(baseURL),
option.WithAPIKey("My API Key"),
)
_, err := client.Volumes.NewFromArchive(
context.TODO(),
io.Reader(bytes.NewBuffer([]byte("some file contents"))),
hypeman.VolumeNewFromArchiveParams{
Name: "name",
SizeGB: 0,
ID: hypeman.String("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 TestVolumeGet(t *testing.T) {
t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
Expand Down