diff --git a/.release-please-manifest.json b/.release-please-manifest.json index da59f99..2aca35a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.4.0" + ".": "0.5.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index e97e7e3..2410f40 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 18 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-5058541cc0e298a6fc3e9cda1af0e32586d1f39c5666946e15f546c1aedc18ea.yml -openapi_spec_hash: 7f572ac0c7f9dc4f5fc7d9883a53d6c7 -config_hash: 35db4c99791f175865381f13a8ad6075 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-ce51f144a3d2de556750203edbaa5bfeefe874660737c35a4fc37dfb30057dd5.yml +openapi_spec_hash: 27663b6503056317abcb578ac7b67c06 +config_hash: b4e65d240d7bca1ba6162ee2098c8ac2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7886e3d..2b7f5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.5.0 (2025-11-26) + +Full Changelog: [v0.4.0...v0.5.0](https://github.com/onkernel/hypeman-cli/compare/v0.4.0...v0.5.0) + +### Features + +* Generate log streaming ([31951c5](https://github.com/onkernel/hypeman-cli/commit/31951c5caf65c008f9811ffd023f54a10c3f1474)) + ## 0.4.0 (2025-11-26) Full Changelog: [v0.3.0...v0.4.0](https://github.com/onkernel/hypeman-cli/compare/v0.3.0...v0.4.0) diff --git a/go.mod b/go.mod index 806f926..7699c66 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,10 @@ require ( github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/x/term v0.2.1 + github.com/gorilla/websocket v1.5.3 github.com/itchyny/json2yaml v0.1.4 github.com/muesli/reflow v0.3.0 - github.com/onkernel/hypeman-go v0.2.0 + github.com/onkernel/hypeman-go v0.4.0 github.com/tidwall/gjson v1.18.0 github.com/tidwall/pretty v1.2.1 github.com/tidwall/sjson v1.2.5 @@ -25,7 +26,6 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/gorilla/websocket v1.5.3 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect diff --git a/go.sum b/go.sum index 38f32b0..401e851 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/onkernel/hypeman-go v0.2.0 h1:wiDMSi7eGTKfVfdxhCg8vcFKa6xbXjWG2sSHk7EXi4Y= -github.com/onkernel/hypeman-go v0.2.0/go.mod h1:pxRRFfVcLvafZpDD1O6IjwHnem3hKEuZTCClrnGiIKA= +github.com/onkernel/hypeman-go v0.4.0 h1:3zDpB/WOPhkdJ/Lug3DmBDUcGR5zHwpOTzLEbsO4AnE= +github.com/onkernel/hypeman-go v0.4.0/go.mod h1:pxRRFfVcLvafZpDD1O6IjwHnem3hKEuZTCClrnGiIKA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/pkg/cmd/instance.go b/pkg/cmd/instance.go index 3e099af..54294af 100644 --- a/pkg/cmd/instance.go +++ b/pkg/cmd/instance.go @@ -81,48 +81,48 @@ var instancesDelete = cli.Command{ HideHelpCommand: true, } -var instancesPutInStandby = cli.Command{ - Name: "put-in-standby", - Usage: "Put instance in standby (pause, snapshot, delete VMM)", +var instancesLogs = cli.Command{ + Name: "logs", + Usage: "Streams instance console logs as Server-Sent Events. Returns the last N lines\n(controlled by `tail` parameter), then optionally continues streaming new lines\nif `follow=true`.", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", }, + &cli.BoolFlag{ + Name: "follow", + Usage: "Continue streaming new lines after initial output", + }, + &cli.Int64Flag{ + Name: "tail", + Usage: "Number of lines to return from end", + Value: 100, + }, }, - Action: handleInstancesPutInStandby, + Action: handleInstancesLogs, HideHelpCommand: true, } -var instancesRestoreFromStandby = cli.Command{ - Name: "restore-from-standby", - Usage: "Restore instance from standby", +var instancesPutInStandby = cli.Command{ + Name: "put-in-standby", + Usage: "Put instance in standby (pause, snapshot, delete VMM)", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", }, }, - Action: handleInstancesRestoreFromStandby, + Action: handleInstancesPutInStandby, HideHelpCommand: true, } -var instancesStreamLogs = cli.Command{ - Name: "stream-logs", - Usage: "Stream instance logs (SSE)", +var instancesRestoreFromStandby = cli.Command{ + Name: "restore-from-standby", + Usage: "Restore instance from standby", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", }, - &cli.BoolFlag{ - Name: "follow", - Usage: "Follow logs (stream with SSE)", - }, - &cli.Int64Flag{ - Name: "tail", - Usage: "Number of lines to return from end", - Value: 100, - }, }, - Action: handleInstancesStreamLogs, + Action: handleInstancesRestoreFromStandby, HideHelpCommand: true, } @@ -226,7 +226,7 @@ func handleInstancesDelete(ctx context.Context, cmd *cli.Command) error { ) } -func handleInstancesPutInStandby(ctx context.Context, cmd *cli.Command) error { +func handleInstancesLogs(ctx context.Context, cmd *cli.Command) error { client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("id") && len(unusedArgs) > 0 { @@ -236,24 +236,27 @@ func handleInstancesPutInStandby(ctx context.Context, cmd *cli.Command) error { if len(unusedArgs) > 0 { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - var res []byte - _, err := client.Instances.PutInStandby( + params := hypeman.InstanceLogsParams{} + if cmd.IsSet("follow") { + params.Follow = hypeman.Opt(cmd.Value("follow").(bool)) + } + if cmd.IsSet("tail") { + params.Tail = hypeman.Opt(cmd.Value("tail").(int64)) + } + stream := client.Instances.LogsStreaming( ctx, cmd.Value("id").(string), + params, option.WithMiddleware(debugMiddleware(cmd.Bool("debug"))), - option.WithResponseBodyInto(&res), ) - if err != nil { - return err + defer stream.Close() + for stream.Next() { + fmt.Printf("%s\n", stream.Current()) } - - json := gjson.Parse(string(res)) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON("instances put-in-standby", json, format, transform) + return stream.Err() } -func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) error { +func handleInstancesPutInStandby(ctx context.Context, cmd *cli.Command) error { client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("id") && len(unusedArgs) > 0 { @@ -264,7 +267,7 @@ func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) er return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } var res []byte - _, err := client.Instances.RestoreFromStandby( + _, err := client.Instances.PutInStandby( ctx, cmd.Value("id").(string), option.WithMiddleware(debugMiddleware(cmd.Bool("debug"))), @@ -277,10 +280,10 @@ func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) er json := gjson.Parse(string(res)) format := cmd.Root().String("format") transform := cmd.Root().String("transform") - return ShowJSON("instances restore-from-standby", json, format, transform) + return ShowJSON("instances put-in-standby", json, format, transform) } -func handleInstancesStreamLogs(ctx context.Context, cmd *cli.Command) error { +func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) error { client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("id") && len(unusedArgs) > 0 { @@ -290,22 +293,19 @@ func handleInstancesStreamLogs(ctx context.Context, cmd *cli.Command) error { if len(unusedArgs) > 0 { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := hypeman.InstanceStreamLogsParams{} - if cmd.IsSet("follow") { - params.Follow = hypeman.Opt(cmd.Value("follow").(bool)) - } - if cmd.IsSet("tail") { - params.Tail = hypeman.Opt(cmd.Value("tail").(int64)) - } - stream := client.Instances.StreamLogsStreaming( + var res []byte + _, err := client.Instances.RestoreFromStandby( ctx, cmd.Value("id").(string), - params, option.WithMiddleware(debugMiddleware(cmd.Bool("debug"))), + option.WithResponseBodyInto(&res), ) - defer stream.Close() - for stream.Next() { - fmt.Printf("%s\n", stream.Current()) + if err != nil { + return err } - return stream.Err() + + json := gjson.Parse(string(res)) + format := cmd.Root().String("format") + transform := cmd.Root().String("transform") + return ShowJSON("instances restore-from-standby", json, format, transform) } diff --git a/pkg/cmd/logs.go b/pkg/cmd/logs.go index ebb9635..1713913 100644 --- a/pkg/cmd/logs.go +++ b/pkg/cmd/logs.go @@ -3,12 +3,9 @@ package cmd import ( "context" "fmt" - "io" - "net/http" - "net/url" - "os" "github.com/onkernel/hypeman-go" + "github.com/onkernel/hypeman-go/option" "github.com/urfave/cli/v3" ) @@ -46,54 +43,27 @@ func handleLogs(ctx context.Context, cmd *cli.Command) error { return err } - // Build URL for logs endpoint - baseURL := cmd.Root().String("base-url") - if baseURL == "" { - baseURL = os.Getenv("HYPEMAN_BASE_URL") + params := hypeman.InstanceLogsParams{} + if cmd.IsSet("follow") { + params.Follow = hypeman.Opt(cmd.Bool("follow")) } - if baseURL == "" { - baseURL = "http://localhost:8080" + if cmd.IsSet("tail") { + params.Tail = hypeman.Opt(int64(cmd.Int("tail"))) } - u, err := url.Parse(baseURL) - if err != nil { - return fmt.Errorf("invalid base URL: %w", err) - } - u.Path = fmt.Sprintf("/instances/%s/logs", instanceID) - - // Add query parameters - q := u.Query() - q.Set("tail", fmt.Sprintf("%d", cmd.Int("tail"))) - if cmd.Bool("follow") { - q.Set("follow", "true") - } - u.RawQuery = q.Encode() - - // Make HTTP request - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) - } - - apiKey := os.Getenv("HYPEMAN_API_KEY") - if apiKey != "" { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed to fetch logs: %w", err) - } - defer resp.Body.Close() + stream := client.Instances.LogsStreaming( + ctx, + instanceID, + params, + option.WithMiddleware(debugMiddleware(cmd.Root().Bool("debug"))), + ) + defer stream.Close() - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to fetch logs (HTTP %d): %s", resp.StatusCode, string(body)) + for stream.Next() { + fmt.Println(stream.Current()) } - // Stream the response to stdout - _, err = io.Copy(os.Stdout, resp.Body) - return err + return stream.Err() } diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index b113456..86845a8 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -2,4 +2,4 @@ package cmd -const Version = "0.4.0" // x-release-please-version +const Version = "0.5.0" // x-release-please-version