From 83363a5275d88e131d0736516fea215faaab84a7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:11:31 +0000 Subject: [PATCH 1/3] chore(internal): codegen related update --- .github/workflows/ci.yml | 27 +++++++++++++++++ scripts/utils/upload-artifact.sh | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100755 scripts/utils/upload-artifact.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa2972c..ab1f59c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,33 @@ on: - 'stl-preview-base/**' jobs: + build: + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + 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) + steps: + - uses: actions/checkout@v6 + + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/hypeman-go' + id: github-oidc + uses: actions/github-script@v8 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: github.repository == 'stainless-sdks/hypeman-go' + 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 diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 0000000..37f5561 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -exuo pipefail + +DIST_DIR="dist" +FILENAME="source.zip" + +mapfile -d '' files < <( + find . -type f \ + \( -name '*.go' -o -name 'go.mod' -o -name 'go.sum' \) \ + ! -path "./${DIST_DIR}/*" \ + -print0 +) + +if [[ ${#files[@]} -eq 0 ]]; then + echo -e "\033[31mNo Go source files found for packaging.\033[0m" + exit 1 +fi + +mkdir -p "$DIST_DIR" +rm -f "${DIST_DIR}/${FILENAME}" + +relative_files=() +for file in "${files[@]}"; do + relative_files+=("${file#./}") +done + +zip "${DIST_DIR}/${FILENAME}" "${relative_files[@]}" + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: application/zip" \ + --data-binary "@${DIST_DIR}/${FILENAME}" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: Download and unzip: 'https://pkg.stainless.com/s/hypeman-go/$SHA'. Run 'go mod edit -replace github.com/kernel/hypeman-go=/path/to/unzipped_directory'.\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi From 5ab52b7c74031eb7e874615aa42c00090cb00b51 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:26:59 +0000 Subject: [PATCH 2/3] feat: Add fork operation to stainless config --- .stats.yml | 4 +- api.md | 3 ++ instance.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++ instance_test.go | 54 ++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c9ace89..511176d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 37 +configured_endpoints: 39 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-68bd472fc1704fc7ff7ed01b4213dda068a0865d42693d47ecef90651526febb.yml openapi_spec_hash: 18ec995954b05d8dfb1e9e3254cf579a -config_hash: d452c139da1e46a44a68b91e8a40de72 +config_hash: 368f8c9248e41f12124ab83b6f5b2eec diff --git a/api.md b/api.md index dfd33e2..3fa23c1 100644 --- a/api.md +++ b/api.md @@ -30,6 +30,7 @@ Params Types: Response Types: - hypeman.Instance +- hypeman.InstanceStats - hypeman.PathInfo - hypeman.VolumeMount @@ -38,12 +39,14 @@ Methods: - client.Instances.New(ctx context.Context, body hypeman.InstanceNewParams) (\*hypeman.Instance, error) - client.Instances.List(ctx context.Context, query hypeman.InstanceListParams) (\*[]hypeman.Instance, error) - client.Instances.Delete(ctx context.Context, id string) error +- client.Instances.Fork(ctx context.Context, id string, body hypeman.InstanceForkParams) (\*hypeman.Instance, error) - client.Instances.Get(ctx context.Context, id string) (\*hypeman.Instance, error) - client.Instances.Logs(ctx context.Context, id string, query hypeman.InstanceLogsParams) (\*string, error) - client.Instances.Restore(ctx context.Context, id string) (\*hypeman.Instance, error) - client.Instances.Standby(ctx context.Context, id string) (\*hypeman.Instance, error) - client.Instances.Start(ctx context.Context, id string, body hypeman.InstanceStartParams) (\*hypeman.Instance, error) - client.Instances.Stat(ctx context.Context, id string, query hypeman.InstanceStatParams) (\*hypeman.PathInfo, error) +- client.Instances.Stats(ctx context.Context, id string) (\*hypeman.InstanceStats, error) - client.Instances.Stop(ctx context.Context, id string) (\*hypeman.Instance, error) ## Volumes diff --git a/instance.go b/instance.go index 28cbf5b..cfe3b54 100644 --- a/instance.go +++ b/instance.go @@ -71,6 +71,18 @@ func (r *InstanceService) Delete(ctx context.Context, id string, opts ...option. return } +// 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) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("instances/%s/fork", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + // Get instance details func (r *InstanceService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Instance, err error) { opts = slices.Concat(r.Options, opts) @@ -157,6 +169,20 @@ func (r *InstanceService) Stat(ctx context.Context, id string, query InstanceSta return } +// Returns real-time resource utilization statistics for a running VM instance. +// Metrics are collected from /proc//stat and /proc//statm for CPU and +// memory, and from TAP interface statistics for network I/O. +func (r *InstanceService) Stats(ctx context.Context, id string, opts ...option.RequestOption) (res *InstanceStats, err error) { + opts = slices.Concat(r.Options, opts) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("instances/%s/stats", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + // Stop instance (graceful shutdown) func (r *InstanceService) Stop(ctx context.Context, id string, opts ...option.RequestOption) (res *Instance, err error) { opts = slices.Concat(r.Options, opts) @@ -348,6 +374,52 @@ func (r *InstanceNetwork) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// Real-time resource utilization statistics for a VM instance +type InstanceStats struct { + // Total memory allocated to the VM (Size + HotplugSize) in bytes + AllocatedMemoryBytes int64 `json:"allocated_memory_bytes" api:"required"` + // Number of vCPUs allocated to the VM + AllocatedVcpus int64 `json:"allocated_vcpus" api:"required"` + // Total CPU time consumed by the VM hypervisor process in seconds + CPUSeconds float64 `json:"cpu_seconds" api:"required"` + // Instance identifier + InstanceID string `json:"instance_id" api:"required"` + // Instance name + InstanceName string `json:"instance_name" api:"required"` + // Resident Set Size - actual physical memory used by the VM in bytes + MemoryRssBytes int64 `json:"memory_rss_bytes" api:"required"` + // Virtual Memory Size - total virtual memory allocated in bytes + MemoryVmsBytes int64 `json:"memory_vms_bytes" api:"required"` + // Total network bytes received by the VM (from TAP interface) + NetworkRxBytes int64 `json:"network_rx_bytes" api:"required"` + // Total network bytes transmitted by the VM (from TAP interface) + NetworkTxBytes int64 `json:"network_tx_bytes" api:"required"` + // Memory utilization ratio (RSS / allocated memory). Only present when + // allocated_memory_bytes > 0. + MemoryUtilizationRatio float64 `json:"memory_utilization_ratio" api:"nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AllocatedMemoryBytes respjson.Field + AllocatedVcpus respjson.Field + CPUSeconds respjson.Field + InstanceID respjson.Field + InstanceName respjson.Field + MemoryRssBytes respjson.Field + MemoryVmsBytes respjson.Field + NetworkRxBytes respjson.Field + NetworkTxBytes respjson.Field + MemoryUtilizationRatio respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r InstanceStats) RawJSON() string { return r.JSON.raw } +func (r *InstanceStats) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type PathInfo struct { // Whether the path exists Exists bool `json:"exists" api:"required"` @@ -590,6 +662,41 @@ const ( InstanceListParamsStateUnknown InstanceListParamsState = "Unknown" ) +type InstanceForkParams struct { + // Name for the forked instance (lowercase letters, digits, and dashes only; cannot + // start or end with a dash) + Name string `json:"name" api:"required"` + // Allow forking from a running source instance. When true and source is Running, + // the source is put into standby, forked, then restored back to Running. + FromRunning param.Opt[bool] `json:"from_running,omitzero"` + // Optional final state for the forked instance. Default is the source instance + // state at fork time. For example, forking from Running defaults the fork result + // to Running. + // + // Any of "Stopped", "Standby", "Running". + TargetState InstanceForkParamsTargetState `json:"target_state,omitzero"` + paramObj +} + +func (r InstanceForkParams) MarshalJSON() (data []byte, err error) { + type shadow InstanceForkParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *InstanceForkParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Optional final state for the forked instance. Default is the source instance +// state at fork time. For example, forking from Running defaults the fork result +// to Running. +type InstanceForkParamsTargetState string + +const ( + InstanceForkParamsTargetStateStopped InstanceForkParamsTargetState = "Stopped" + InstanceForkParamsTargetStateStandby InstanceForkParamsTargetState = "Standby" + InstanceForkParamsTargetStateRunning InstanceForkParamsTargetState = "Running" +) + type InstanceLogsParams struct { // Continue streaming new lines after initial output Follow param.Opt[bool] `query:"follow,omitzero" json:"-"` diff --git a/instance_test.go b/instance_test.go index 4434e31..92f99ae 100644 --- a/instance_test.go +++ b/instance_test.go @@ -124,6 +124,37 @@ func TestInstanceDelete(t *testing.T) { } } +func TestInstanceForkWithOptionalParams(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.Fork( + context.TODO(), + "id", + hypeman.InstanceForkParams{ + Name: "my-workload-1-fork", + FromRunning: hypeman.Bool(false), + TargetState: hypeman.InstanceForkParamsTargetStateRunning, + }, + ) + 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 TestInstanceGet(t *testing.T) { t.Skip("Mock server tests are disabled") baseURL := "http://localhost:4010" @@ -253,6 +284,29 @@ func TestInstanceStatWithOptionalParams(t *testing.T) { } } +func TestInstanceStats(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.Stats(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 TestInstanceStop(t *testing.T) { t.Skip("Mock server tests are disabled") baseURL := "http://localhost:4010" From 1c955f010833fbbb52024f6754987e1ae078fc52 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:27:39 +0000 Subject: [PATCH 3/3] release: 0.15.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ README.md | 2 +- internal/version.go | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a26ebfc..8f3e0a4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.14.0" + ".": "0.15.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fa4c8..646e8f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.15.0 (2026-03-04) + +Full Changelog: [v0.14.0...v0.15.0](https://github.com/kernel/hypeman-go/compare/v0.14.0...v0.15.0) + +### Features + +* Add fork operation to stainless config ([5ab52b7](https://github.com/kernel/hypeman-go/commit/5ab52b7c74031eb7e874615aa42c00090cb00b51)) + + +### Chores + +* **internal:** codegen related update ([83363a5](https://github.com/kernel/hypeman-go/commit/83363a5275d88e131d0736516fea215faaab84a7)) + ## 0.14.0 (2026-03-02) Full Changelog: [v0.13.0...v0.14.0](https://github.com/kernel/hypeman-go/compare/v0.13.0...v0.14.0) diff --git a/README.md b/README.md index 5543274..47f44f5 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.14.0' +go get -u 'github.com/kernel/hypeman-go@v0.15.0' ``` diff --git a/internal/version.go b/internal/version.go index 870e575..1f338c3 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.14.0" // x-release-please-version +const PackageVersion = "0.15.0" // x-release-please-version