Skip to content

Commit 49ea898

Browse files
feat: add hypeman cp for file copy to/from running VMs
1 parent bd30aba commit 49ea898

File tree

4 files changed

+104
-4
lines changed

4 files changed

+104
-4
lines changed

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 29
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-95df8b193133def744aa61dc372f286663ffc20d833488d242fa288af65adc39.yml
3-
openapi_spec_hash: 833120a235ecb298688c2fb1122b3574
4-
config_hash: 01911ae3d5491505b04f842d7edff72a
1+
configured_endpoints: 30
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-28e78b73c796f9ee866671ed946402b5d569e683c3207d57c9143eb7d6f83fb6.yml
3+
openapi_spec_hash: fce0ac8713369a5f048bac684ed34fc8
4+
config_hash: f65a6a2bcef49a9f623212f9de6d6f6f

api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Params Types:
3030
Response Types:
3131

3232
- <a href="https://pkg.go.dev/github.com/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#Instance">Instance</a>
33+
- <a href="https://pkg.go.dev/github.com/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#PathInfo">PathInfo</a>
3334
- <a href="https://pkg.go.dev/github.com/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#VolumeMount">VolumeMount</a>
3435

3536
Methods:
@@ -42,6 +43,7 @@ Methods:
4243
- <code title="post /instances/{id}/restore">client.Instances.<a href="https://pkg.go.dev/github.com/onkernel/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/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
4344
- <code title="post /instances/{id}/standby">client.Instances.<a href="https://pkg.go.dev/github.com/onkernel/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>) (<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
4445
- <code title="post /instances/{id}/start">client.Instances.<a href="https://pkg.go.dev/github.com/onkernel/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>) (<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
46+
- <code title="get /instances/{id}/stat">client.Instances.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#InstanceService.Stat">Stat</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/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#InstanceStatParams">InstanceStatParams</a>) (<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#PathInfo">PathInfo</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
4547
- <code title="post /instances/{id}/stop">client.Instances.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#InstanceService.Stop">Stop</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/onkernel/hypeman-go">hypeman</a>.<a href="https://pkg.go.dev/github.com/onkernel/hypeman-go#Instance">Instance</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
4648

4749
## Volumes

instance.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ func (r *InstanceService) Start(ctx context.Context, id string, opts ...option.R
144144
return
145145
}
146146

147+
// Returns information about a path in the guest filesystem. Useful for checking if
148+
// a path exists, its type, and permissions before performing file operations.
149+
func (r *InstanceService) Stat(ctx context.Context, id string, query InstanceStatParams, opts ...option.RequestOption) (res *PathInfo, err error) {
150+
opts = slices.Concat(r.Options, opts)
151+
if id == "" {
152+
err = errors.New("missing required id parameter")
153+
return
154+
}
155+
path := fmt.Sprintf("instances/%s/stat", id)
156+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
157+
return
158+
}
159+
147160
// Stop instance (graceful shutdown)
148161
func (r *InstanceService) Stop(ctx context.Context, id string, opts ...option.RequestOption) (res *Instance, err error) {
149162
opts = slices.Concat(r.Options, opts)
@@ -277,6 +290,45 @@ func (r *InstanceNetwork) UnmarshalJSON(data []byte) error {
277290
return apijson.UnmarshalRoot(data, r)
278291
}
279292

293+
type PathInfo struct {
294+
// Whether the path exists
295+
Exists bool `json:"exists,required"`
296+
// Error message if stat failed (e.g., permission denied). Only set when exists is
297+
// false due to an error rather than the path not existing.
298+
Error string `json:"error,nullable"`
299+
// True if this is a directory
300+
IsDir bool `json:"is_dir"`
301+
// True if this is a regular file
302+
IsFile bool `json:"is_file"`
303+
// True if this is a symbolic link (only set when follow_links=false)
304+
IsSymlink bool `json:"is_symlink"`
305+
// Symlink target path (only set when is_symlink=true)
306+
LinkTarget string `json:"link_target,nullable"`
307+
// File mode (Unix permissions)
308+
Mode int64 `json:"mode"`
309+
// File size in bytes
310+
Size int64 `json:"size"`
311+
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
312+
JSON struct {
313+
Exists respjson.Field
314+
Error respjson.Field
315+
IsDir respjson.Field
316+
IsFile respjson.Field
317+
IsSymlink respjson.Field
318+
LinkTarget respjson.Field
319+
Mode respjson.Field
320+
Size respjson.Field
321+
ExtraFields map[string]respjson.Field
322+
raw string
323+
} `json:"-"`
324+
}
325+
326+
// Returns the unmodified JSON received from the API
327+
func (r PathInfo) RawJSON() string { return r.JSON.raw }
328+
func (r *PathInfo) UnmarshalJSON(data []byte) error {
329+
return apijson.UnmarshalRoot(data, r)
330+
}
331+
280332
type VolumeMount struct {
281333
// Path where volume is mounted in the guest
282334
MountPath string `json:"mount_path,required"`
@@ -424,3 +476,19 @@ const (
424476
InstanceLogsParamsSourceVmm InstanceLogsParamsSource = "vmm"
425477
InstanceLogsParamsSourceHypeman InstanceLogsParamsSource = "hypeman"
426478
)
479+
480+
type InstanceStatParams struct {
481+
// Path to stat in the guest filesystem
482+
Path string `query:"path,required" json:"-"`
483+
// Follow symbolic links (like stat vs lstat)
484+
FollowLinks param.Opt[bool] `query:"follow_links,omitzero" json:"-"`
485+
paramObj
486+
}
487+
488+
// URLQuery serializes [InstanceStatParams]'s query parameters as `url.Values`.
489+
func (r InstanceStatParams) URLQuery() (v url.Values, err error) {
490+
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
491+
ArrayFormat: apiquery.ArrayQueryFormatComma,
492+
NestedFormat: apiquery.NestedQueryFormatBrackets,
493+
})
494+
}

instance_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,36 @@ func TestInstanceStart(t *testing.T) {
196196
}
197197
}
198198

199+
func TestInstanceStatWithOptionalParams(t *testing.T) {
200+
t.Skip("Prism tests are disabled")
201+
baseURL := "http://localhost:4010"
202+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
203+
baseURL = envURL
204+
}
205+
if !testutil.CheckTestServer(t, baseURL) {
206+
return
207+
}
208+
client := hypeman.NewClient(
209+
option.WithBaseURL(baseURL),
210+
option.WithAPIKey("My API Key"),
211+
)
212+
_, err := client.Instances.Stat(
213+
context.TODO(),
214+
"id",
215+
hypeman.InstanceStatParams{
216+
Path: "path",
217+
FollowLinks: hypeman.Bool(true),
218+
},
219+
)
220+
if err != nil {
221+
var apierr *hypeman.Error
222+
if errors.As(err, &apierr) {
223+
t.Log(string(apierr.DumpRequest(true)))
224+
}
225+
t.Fatalf("err should be nil: %s", err.Error())
226+
}
227+
}
228+
199229
func TestInstanceStop(t *testing.T) {
200230
t.Skip("Prism tests are disabled")
201231
baseURL := "http://localhost:4010"

0 commit comments

Comments
 (0)