From de008e89fadbaedde6554181618fb03c71b49465 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:41:09 +0000 Subject: [PATCH 1/2] feat: Add to stainless config new API endpoints --- .stats.yml | 4 +- README.md | 18 +++ api.md | 32 ++++++ build.go | 287 +++++++++++++++++++++++++++++++++++++++++++++++ build_test.go | 115 +++++++++++++++++++ client.go | 4 + resource.go | 253 +++++++++++++++++++++++++++++++++++++++++ resource_test.go | 37 ++++++ 8 files changed, 748 insertions(+), 2 deletions(-) create mode 100644 build.go create mode 100644 build_test.go create mode 100644 resource.go create mode 100644 resource_test.go diff --git a/.stats.yml b/.stats.yml index d6e9571..55ad565 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 30 +configured_endpoints: 36 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-e052ac01c788e7e3e46c96bf3c42be7ae57f9dd046129add8012d0eeb388e884.yml openapi_spec_hash: fd805921c0162d63405f5feb7e8c7082 -config_hash: 170041ea532e81620e0f6a73f6b31d44 +config_hash: 14135b7c88169a15762c8defb0bdfd16 diff --git a/README.md b/README.md index 20c5d75..4dc556c 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,24 @@ file returned by `os.Open` will be sent with the file name on disk. We also provide a helper `hypeman.File(reader io.Reader, filename string, contentType string)` which can be used to wrap any `io.Reader` with the appropriate file name and content type. +```go +// A file from the file system +file, err := os.Open("/path/to/file") +hypeman.BuildNewParams{ + Source: file, +} + +// A file from a string +hypeman.BuildNewParams{ + Source: strings.NewReader("my file contents"), +} + +// With a custom filename and contentType +hypeman.BuildNewParams{ + Source: hypeman.File(strings.NewReader(`{"hello": "foo"}`), "file.go", "application/json"), +} +``` + ### Retries Certain errors will be automatically retried 2 times by default, with a short exponential backoff. diff --git a/api.md b/api.md index 4b1b164..4310415 100644 --- a/api.md +++ b/api.md @@ -104,3 +104,35 @@ Methods: - client.Ingresses.List(ctx context.Context) ([]hypeman.Ingress, error) - client.Ingresses.Delete(ctx context.Context, id string) error - client.Ingresses.Get(ctx context.Context, id string) (hypeman.Ingress, error) +# Resources + +Response Types: + +- hypeman.DiskBreakdown +- hypeman.GPUProfile +- hypeman.GPUResourceStatus +- hypeman.PassthroughDevice +- hypeman.ResourceAllocation +- hypeman.ResourceStatus +- hypeman.Resources + +Methods: + +- client.Resources.Get(ctx context.Context) (\*hypeman.Resources, error) + +# Builds + +Response Types: + +- hypeman.Build +- hypeman.BuildEvent +- hypeman.BuildProvenance +- hypeman.BuildStatus + +Methods: + +- client.Builds.New(ctx context.Context, body hypeman.BuildNewParams) (\*hypeman.Build, error) +- client.Builds.List(ctx context.Context) (\*[]hypeman.Build, error) +- client.Builds.Cancel(ctx context.Context, id string) error +- client.Builds.Events(ctx context.Context, id string, query hypeman.BuildEventsParams) (\*hypeman.BuildEvent, error) +- client.Builds.Get(ctx context.Context, id string) (\*hypeman.Build, error) diff --git a/build.go b/build.go new file mode 100644 index 0000000..34d9c4d --- /dev/null +++ b/build.go @@ -0,0 +1,287 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package hypeman + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "slices" + "time" + + "github.com/onkernel/hypeman-go/internal/apiform" + "github.com/onkernel/hypeman-go/internal/apijson" + "github.com/onkernel/hypeman-go/internal/apiquery" + "github.com/onkernel/hypeman-go/internal/requestconfig" + "github.com/onkernel/hypeman-go/option" + "github.com/onkernel/hypeman-go/packages/param" + "github.com/onkernel/hypeman-go/packages/respjson" + "github.com/onkernel/hypeman-go/packages/ssestream" +) + +// BuildService 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 [NewBuildService] method instead. +type BuildService struct { + Options []option.RequestOption +} + +// NewBuildService 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 NewBuildService(opts ...option.RequestOption) (r BuildService) { + r = BuildService{} + r.Options = opts + return +} + +// Creates a new build job. Source code should be uploaded as a tar.gz archive in +// the multipart form data. +func (r *BuildService) New(ctx context.Context, body BuildNewParams, opts ...option.RequestOption) (res *Build, err error) { + opts = slices.Concat(r.Options, opts) + path := "builds" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// List builds +func (r *BuildService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Build, err error) { + opts = slices.Concat(r.Options, opts) + path := "builds" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +// Cancel build +func (r *BuildService) Cancel(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 + } + path := fmt.Sprintf("builds/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) + return +} + +// Streams build events as Server-Sent Events. Events include: +// +// - `log`: Build log lines with timestamp and content +// - `status`: Build status changes (queued→building→pushing→ready/failed) +// - `heartbeat`: Keep-alive events sent every 30s to prevent connection timeouts +// +// Returns existing logs as events, then continues streaming if follow=true. +func (r *BuildService) EventsStreaming(ctx context.Context, id string, query BuildEventsParams, opts ...option.RequestOption) (stream *ssestream.Stream[BuildEvent]) { + var ( + raw *http.Response + err error + ) + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("builds/%s/events", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...) + return ssestream.NewStream[BuildEvent](ssestream.NewDecoder(raw), err) +} + +// Get build details +func (r *BuildService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Build, err error) { + opts = slices.Concat(r.Options, opts) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("builds/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +type Build struct { + // Build job identifier + ID string `json:"id,required"` + // Build creation timestamp + CreatedAt time.Time `json:"created_at,required" format:"date-time"` + // Build job status + // + // Any of "queued", "building", "pushing", "ready", "failed", "cancelled". + Status BuildStatus `json:"status,required"` + // Build completion timestamp + CompletedAt time.Time `json:"completed_at,nullable" format:"date-time"` + // Build duration in milliseconds + DurationMs int64 `json:"duration_ms,nullable"` + // Error message (only when status is failed) + Error string `json:"error,nullable"` + // Digest of built image (only when status is ready) + ImageDigest string `json:"image_digest,nullable"` + // Full image reference (only when status is ready) + ImageRef string `json:"image_ref,nullable"` + Provenance BuildProvenance `json:"provenance"` + // Position in build queue (only when status is queued) + QueuePosition int64 `json:"queue_position,nullable"` + // Build start timestamp + StartedAt time.Time `json:"started_at,nullable" format:"date-time"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ID respjson.Field + CreatedAt respjson.Field + Status respjson.Field + CompletedAt respjson.Field + DurationMs respjson.Field + Error respjson.Field + ImageDigest respjson.Field + ImageRef respjson.Field + Provenance respjson.Field + QueuePosition respjson.Field + StartedAt respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r Build) RawJSON() string { return r.JSON.raw } +func (r *Build) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type BuildEvent struct { + // Event timestamp + Timestamp time.Time `json:"timestamp,required" format:"date-time"` + // Event type + // + // Any of "log", "status", "heartbeat". + Type BuildEventType `json:"type,required"` + // Log line content (only for type=log) + Content string `json:"content"` + // New build status (only for type=status) + // + // Any of "queued", "building", "pushing", "ready", "failed", "cancelled". + Status BuildStatus `json:"status"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Timestamp respjson.Field + Type respjson.Field + Content respjson.Field + Status respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r BuildEvent) RawJSON() string { return r.JSON.raw } +func (r *BuildEvent) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Event type +type BuildEventType string + +const ( + BuildEventTypeLog BuildEventType = "log" + BuildEventTypeStatus BuildEventType = "status" + BuildEventTypeHeartbeat BuildEventType = "heartbeat" +) + +type BuildProvenance struct { + // Pinned base image digest used + BaseImageDigest string `json:"base_image_digest"` + // BuildKit version used + BuildkitVersion string `json:"buildkit_version"` + // Map of lockfile names to SHA256 hashes + LockfileHashes map[string]string `json:"lockfile_hashes"` + // SHA256 hash of source tarball + SourceHash string `json:"source_hash"` + // Build completion timestamp + Timestamp time.Time `json:"timestamp" format:"date-time"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + BaseImageDigest respjson.Field + BuildkitVersion respjson.Field + LockfileHashes respjson.Field + SourceHash respjson.Field + Timestamp respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r BuildProvenance) RawJSON() string { return r.JSON.raw } +func (r *BuildProvenance) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Build job status +type BuildStatus string + +const ( + BuildStatusQueued BuildStatus = "queued" + BuildStatusBuilding BuildStatus = "building" + BuildStatusPushing BuildStatus = "pushing" + BuildStatusReady BuildStatus = "ready" + BuildStatusFailed BuildStatus = "failed" + BuildStatusCancelled BuildStatus = "cancelled" +) + +type BuildNewParams struct { + // Source tarball (tar.gz) containing application code and optionally a Dockerfile + Source io.Reader `json:"source,omitzero,required" format:"binary"` + // Optional pinned base image digest + BaseImageDigest param.Opt[string] `json:"base_image_digest,omitzero"` + // Tenant-specific cache key prefix + CacheScope param.Opt[string] `json:"cache_scope,omitzero"` + // Dockerfile content. Required if not included in the source tarball. + Dockerfile param.Opt[string] `json:"dockerfile,omitzero"` + // JSON array of secret references to inject during build. Each object has "id" + // (required) for use with --mount=type=secret,id=... Example: [{"id": + // "npm_token"}, {"id": "github_token"}] + Secrets param.Opt[string] `json:"secrets,omitzero"` + // Build timeout (default 600) + TimeoutSeconds param.Opt[int64] `json:"timeout_seconds,omitzero"` + paramObj +} + +func (r BuildNewParams) 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 +} + +type BuildEventsParams struct { + // Continue streaming new events after initial output + Follow param.Opt[bool] `query:"follow,omitzero" json:"-"` + paramObj +} + +// URLQuery serializes [BuildEventsParams]'s query parameters as `url.Values`. +func (r BuildEventsParams) URLQuery() (v url.Values, err error) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/build_test.go b/build_test.go new file mode 100644 index 0000000..63f6253 --- /dev/null +++ b/build_test.go @@ -0,0 +1,115 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package hypeman_test + +import ( + "bytes" + "context" + "errors" + "io" + "os" + "testing" + + "github.com/onkernel/hypeman-go" + "github.com/onkernel/hypeman-go/internal/testutil" + "github.com/onkernel/hypeman-go/option" +) + +func TestBuildNewWithOptionalParams(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.Builds.New(context.TODO(), hypeman.BuildNewParams{ + Source: io.Reader(bytes.NewBuffer([]byte("some file contents"))), + BaseImageDigest: hypeman.String("base_image_digest"), + CacheScope: hypeman.String("cache_scope"), + Dockerfile: hypeman.String("dockerfile"), + Secrets: hypeman.String("secrets"), + TimeoutSeconds: hypeman.Int(0), + }) + 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 TestBuildList(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.Builds.List(context.TODO()) + 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 TestBuildCancel(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.Builds.Cancel(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 TestBuildGet(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.Builds.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()) + } +} diff --git a/client.go b/client.go index c583e82..eeef91e 100644 --- a/client.go +++ b/client.go @@ -23,6 +23,8 @@ type Client struct { Volumes VolumeService Devices DeviceService Ingresses IngressService + Resources ResourceService + Builds BuildService } // DefaultClientOptions read from the environment (HYPEMAN_API_KEY, @@ -53,6 +55,8 @@ func NewClient(opts ...option.RequestOption) (r Client) { r.Volumes = NewVolumeService(opts...) r.Devices = NewDeviceService(opts...) r.Ingresses = NewIngressService(opts...) + r.Resources = NewResourceService(opts...) + r.Builds = NewBuildService(opts...) return } diff --git a/resource.go b/resource.go new file mode 100644 index 0000000..fd42899 --- /dev/null +++ b/resource.go @@ -0,0 +1,253 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package hypeman + +import ( + "context" + "net/http" + "slices" + + "github.com/onkernel/hypeman-go/internal/apijson" + "github.com/onkernel/hypeman-go/internal/requestconfig" + "github.com/onkernel/hypeman-go/option" + "github.com/onkernel/hypeman-go/packages/respjson" +) + +// ResourceService 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 [NewResourceService] method instead. +type ResourceService struct { + Options []option.RequestOption +} + +// NewResourceService 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 NewResourceService(opts ...option.RequestOption) (r ResourceService) { + r = ResourceService{} + r.Options = opts + return +} + +// Returns current host resource capacity, allocation status, and per-instance +// breakdown. Resources include CPU, memory, disk, and network. Oversubscription +// ratios are applied to calculate effective limits. +func (r *ResourceService) Get(ctx context.Context, opts ...option.RequestOption) (res *Resources, err error) { + opts = slices.Concat(r.Options, opts) + path := "resources" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +type DiskBreakdown struct { + // Disk used by exported rootfs images + ImagesBytes int64 `json:"images_bytes"` + // Disk used by OCI layer cache (shared blobs) + OciCacheBytes int64 `json:"oci_cache_bytes"` + // Disk used by instance overlays (rootfs + volume overlays) + OverlaysBytes int64 `json:"overlays_bytes"` + // Disk used by volumes + VolumesBytes int64 `json:"volumes_bytes"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ImagesBytes respjson.Field + OciCacheBytes respjson.Field + OverlaysBytes respjson.Field + VolumesBytes respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r DiskBreakdown) RawJSON() string { return r.JSON.raw } +func (r *DiskBreakdown) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Available vGPU profile +type GPUProfile struct { + // Number of instances that can be created with this profile + Available int64 `json:"available,required"` + // Frame buffer size in MB + FramebufferMB int64 `json:"framebuffer_mb,required"` + // Profile name (user-facing) + Name string `json:"name,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Available respjson.Field + FramebufferMB respjson.Field + Name respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r GPUProfile) RawJSON() string { return r.JSON.raw } +func (r *GPUProfile) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// GPU resource status. Null if no GPUs available. +type GPUResourceStatus struct { + // GPU mode (vgpu for SR-IOV/mdev, passthrough for whole GPU) + // + // Any of "vgpu", "passthrough". + Mode GPUResourceStatusMode `json:"mode,required"` + // Total slots (VFs for vGPU, physical GPUs for passthrough) + TotalSlots int64 `json:"total_slots,required"` + // Slots currently in use + UsedSlots int64 `json:"used_slots,required"` + // Physical GPUs (only in passthrough mode) + Devices []PassthroughDevice `json:"devices"` + // Available vGPU profiles (only in vGPU mode) + Profiles []GPUProfile `json:"profiles"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Mode respjson.Field + TotalSlots respjson.Field + UsedSlots respjson.Field + Devices respjson.Field + Profiles respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r GPUResourceStatus) RawJSON() string { return r.JSON.raw } +func (r *GPUResourceStatus) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// GPU mode (vgpu for SR-IOV/mdev, passthrough for whole GPU) +type GPUResourceStatusMode string + +const ( + GPUResourceStatusModeVgpu GPUResourceStatusMode = "vgpu" + GPUResourceStatusModePassthrough GPUResourceStatusMode = "passthrough" +) + +// Physical GPU available for passthrough +type PassthroughDevice struct { + // Whether this GPU is available (not attached to an instance) + Available bool `json:"available,required"` + // GPU name + Name string `json:"name,required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Available respjson.Field + Name respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r PassthroughDevice) RawJSON() string { return r.JSON.raw } +func (r *PassthroughDevice) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type ResourceAllocation struct { + // vCPUs allocated + CPU int64 `json:"cpu"` + // Disk allocated in bytes (overlay + volumes) + DiskBytes int64 `json:"disk_bytes"` + // Instance identifier + InstanceID string `json:"instance_id"` + // Instance name + InstanceName string `json:"instance_name"` + // Memory allocated in bytes + MemoryBytes int64 `json:"memory_bytes"` + // Download bandwidth limit in bytes/sec (external→VM) + NetworkDownloadBps int64 `json:"network_download_bps"` + // Upload bandwidth limit in bytes/sec (VM→external) + NetworkUploadBps int64 `json:"network_upload_bps"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CPU respjson.Field + DiskBytes respjson.Field + InstanceID respjson.Field + InstanceName respjson.Field + MemoryBytes respjson.Field + NetworkDownloadBps respjson.Field + NetworkUploadBps respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r ResourceAllocation) RawJSON() string { return r.JSON.raw } +func (r *ResourceAllocation) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type ResourceStatus struct { + // Currently allocated resources + Allocated int64 `json:"allocated,required"` + // Available for allocation (effective_limit - allocated) + Available int64 `json:"available,required"` + // Raw host capacity + Capacity int64 `json:"capacity,required"` + // Capacity after oversubscription (capacity \* ratio) + EffectiveLimit int64 `json:"effective_limit,required"` + // Oversubscription ratio applied + OversubRatio float64 `json:"oversub_ratio,required"` + // Resource type + Type string `json:"type,required"` + // How capacity was determined (detected, configured) + Source string `json:"source"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Allocated respjson.Field + Available respjson.Field + Capacity respjson.Field + EffectiveLimit respjson.Field + OversubRatio respjson.Field + Type respjson.Field + Source respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r ResourceStatus) RawJSON() string { return r.JSON.raw } +func (r *ResourceStatus) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type Resources struct { + Allocations []ResourceAllocation `json:"allocations,required"` + CPU ResourceStatus `json:"cpu,required"` + Disk ResourceStatus `json:"disk,required"` + Memory ResourceStatus `json:"memory,required"` + Network ResourceStatus `json:"network,required"` + DiskBreakdown DiskBreakdown `json:"disk_breakdown"` + // GPU resource status. Null if no GPUs available. + GPU GPUResourceStatus `json:"gpu,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Allocations respjson.Field + CPU respjson.Field + Disk respjson.Field + Memory respjson.Field + Network respjson.Field + DiskBreakdown respjson.Field + GPU respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r Resources) RawJSON() string { return r.JSON.raw } +func (r *Resources) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/resource_test.go b/resource_test.go new file mode 100644 index 0000000..dfca0cd --- /dev/null +++ b/resource_test.go @@ -0,0 +1,37 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package hypeman_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/onkernel/hypeman-go" + "github.com/onkernel/hypeman-go/internal/testutil" + "github.com/onkernel/hypeman-go/option" +) + +func TestResourceGet(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.Resources.Get(context.TODO()) + 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 8fe6bd3b53f5ea3b41c4a1257e5697b849d48bb3 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Tue, 20 Jan 2026 11:50:53 -0500 Subject: [PATCH 2/2] onkernel -> kernel to fix build --- CHANGELOG.md | 6 +++--- README.md | 2 +- api.md | 34 +++++++++++++++++----------------- build.go | 16 ++++++++-------- build_test.go | 6 +++--- resource.go | 8 ++++---- resource_test.go | 6 +++--- 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58bf980..4405152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ ## 0.9.0 (2026-01-05) -Full Changelog: [v0.8.0...v0.9.0](https://github.com/onkernel/hypeman-go/compare/v0.8.0...v0.9.0) +Full Changelog: [v0.8.0...v0.9.0](https://github.com/kernel/hypeman-go/compare/v0.8.0...v0.9.0) ### Features -* QEMU support ([d708091](https://github.com/onkernel/hypeman-go/commit/d70809169d136df3f1efbf961f2a90084e1f9fa5)) -* Resource accounting ([4141287](https://github.com/onkernel/hypeman-go/commit/414128770e8137ed2a40d404f0f4ac06ea1a0731)) +* QEMU support ([d708091](https://github.com/kernel/hypeman-go/commit/d70809169d136df3f1efbf961f2a90084e1f9fa5)) +* Resource accounting ([4141287](https://github.com/kernel/hypeman-go/commit/414128770e8137ed2a40d404f0f4ac06ea1a0731)) ## 0.8.0 (2025-12-23) diff --git a/README.md b/README.md index 4dc556c..d20968d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or to pin the version: ```sh -go get -u 'github.com/onkernel/hypeman-go@v0.9.0' +go get -u 'github.com/kernel/hypeman-go@v0.9.0' ``` diff --git a/api.md b/api.md index 4310415..ae9d5bb 100644 --- a/api.md +++ b/api.md @@ -108,31 +108,31 @@ Methods: Response Types: -- hypeman.DiskBreakdown -- hypeman.GPUProfile -- hypeman.GPUResourceStatus -- hypeman.PassthroughDevice -- hypeman.ResourceAllocation -- hypeman.ResourceStatus -- hypeman.Resources +- hypeman.DiskBreakdown +- hypeman.GPUProfile +- hypeman.GPUResourceStatus +- hypeman.PassthroughDevice +- hypeman.ResourceAllocation +- hypeman.ResourceStatus +- hypeman.Resources Methods: -- client.Resources.Get(ctx context.Context) (\*hypeman.Resources, error) +- client.Resources.Get(ctx context.Context) (\*hypeman.Resources, error) # Builds Response Types: -- hypeman.Build -- hypeman.BuildEvent -- hypeman.BuildProvenance -- hypeman.BuildStatus +- hypeman.Build +- hypeman.BuildEvent +- hypeman.BuildProvenance +- hypeman.BuildStatus Methods: -- client.Builds.New(ctx context.Context, body hypeman.BuildNewParams) (\*hypeman.Build, error) -- client.Builds.List(ctx context.Context) (\*[]hypeman.Build, error) -- client.Builds.Cancel(ctx context.Context, id string) error -- client.Builds.Events(ctx context.Context, id string, query hypeman.BuildEventsParams) (\*hypeman.BuildEvent, error) -- client.Builds.Get(ctx context.Context, id string) (\*hypeman.Build, error) +- client.Builds.New(ctx context.Context, body hypeman.BuildNewParams) (\*hypeman.Build, error) +- client.Builds.List(ctx context.Context) (\*[]hypeman.Build, error) +- client.Builds.Cancel(ctx context.Context, id string) error +- client.Builds.Events(ctx context.Context, id string, query hypeman.BuildEventsParams) (\*hypeman.BuildEvent, error) +- client.Builds.Get(ctx context.Context, id string) (\*hypeman.Build, error) diff --git a/build.go b/build.go index 34d9c4d..b10833e 100644 --- a/build.go +++ b/build.go @@ -14,14 +14,14 @@ import ( "slices" "time" - "github.com/onkernel/hypeman-go/internal/apiform" - "github.com/onkernel/hypeman-go/internal/apijson" - "github.com/onkernel/hypeman-go/internal/apiquery" - "github.com/onkernel/hypeman-go/internal/requestconfig" - "github.com/onkernel/hypeman-go/option" - "github.com/onkernel/hypeman-go/packages/param" - "github.com/onkernel/hypeman-go/packages/respjson" - "github.com/onkernel/hypeman-go/packages/ssestream" + "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" + "github.com/kernel/hypeman-go/packages/respjson" + "github.com/kernel/hypeman-go/packages/ssestream" ) // BuildService contains methods and other services that help with interacting with diff --git a/build_test.go b/build_test.go index 63f6253..a76dbb6 100644 --- a/build_test.go +++ b/build_test.go @@ -10,9 +10,9 @@ import ( "os" "testing" - "github.com/onkernel/hypeman-go" - "github.com/onkernel/hypeman-go/internal/testutil" - "github.com/onkernel/hypeman-go/option" + "github.com/kernel/hypeman-go" + "github.com/kernel/hypeman-go/internal/testutil" + "github.com/kernel/hypeman-go/option" ) func TestBuildNewWithOptionalParams(t *testing.T) { diff --git a/resource.go b/resource.go index fd42899..7806701 100644 --- a/resource.go +++ b/resource.go @@ -7,10 +7,10 @@ import ( "net/http" "slices" - "github.com/onkernel/hypeman-go/internal/apijson" - "github.com/onkernel/hypeman-go/internal/requestconfig" - "github.com/onkernel/hypeman-go/option" - "github.com/onkernel/hypeman-go/packages/respjson" + "github.com/kernel/hypeman-go/internal/apijson" + "github.com/kernel/hypeman-go/internal/requestconfig" + "github.com/kernel/hypeman-go/option" + "github.com/kernel/hypeman-go/packages/respjson" ) // ResourceService contains methods and other services that help with interacting diff --git a/resource_test.go b/resource_test.go index dfca0cd..4566d8d 100644 --- a/resource_test.go +++ b/resource_test.go @@ -8,9 +8,9 @@ import ( "os" "testing" - "github.com/onkernel/hypeman-go" - "github.com/onkernel/hypeman-go/internal/testutil" - "github.com/onkernel/hypeman-go/option" + "github.com/kernel/hypeman-go" + "github.com/kernel/hypeman-go/internal/testutil" + "github.com/kernel/hypeman-go/option" ) func TestResourceGet(t *testing.T) {