From 3d8c544aeaabd4b861e454da0cea74eab9962a13 Mon Sep 17 00:00:00 2001 From: Mike Han Date: Fri, 31 Mar 2023 15:43:07 -0600 Subject: [PATCH 1/6] Add saucectl hto logs command --- cmd/saucectl/saucectl.go | 2 ++ internal/cmd/hto/cmd.go | 48 +++++++++++++++++++++++++++++ internal/cmd/hto/logs.go | 29 +++++++++++++++++ internal/http/imagerunner.go | 10 ++++++ internal/imagerunner/imagerunner.go | 13 +++++++- 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 internal/cmd/hto/cmd.go create mode 100644 internal/cmd/hto/logs.go diff --git a/cmd/saucectl/saucectl.go b/cmd/saucectl/saucectl.go index a8b92309b..ea3fedc51 100644 --- a/cmd/saucectl/saucectl.go +++ b/cmd/saucectl/saucectl.go @@ -13,6 +13,7 @@ import ( "github.com/saucelabs/saucectl/internal/cmd/completion" "github.com/saucelabs/saucectl/internal/cmd/configure" "github.com/saucelabs/saucectl/internal/cmd/doctor" + "github.com/saucelabs/saucectl/internal/cmd/hto" "github.com/saucelabs/saucectl/internal/cmd/ini" "github.com/saucelabs/saucectl/internal/cmd/jobs" "github.com/saucelabs/saucectl/internal/cmd/new" @@ -71,6 +72,7 @@ func main() { storage.Command(cmd.PersistentPreRun), artifacts.Command(cmd.PersistentPreRun), jobs.Command(cmd.PersistentPreRun), + hto.Command(cmd.PersistentPreRun), ) if err := cmd.Execute(); err != nil { diff --git a/internal/cmd/hto/cmd.go b/internal/cmd/hto/cmd.go new file mode 100644 index 000000000..197d0961c --- /dev/null +++ b/internal/cmd/hto/cmd.go @@ -0,0 +1,48 @@ +package hto + +import ( + "errors" + "time" + + "github.com/saucelabs/saucectl/internal/credentials" + "github.com/saucelabs/saucectl/internal/http" + "github.com/saucelabs/saucectl/internal/region" + "github.com/spf13/cobra" +) + +var ( + imagerunnerClient http.ImageRunner +) + +func Command(preRun func(cmd *cobra.Command, args []string)) *cobra.Command { + var regio string + + cmd := &cobra.Command{ + Use: "hto", + Short: "Commands for interacting with Hosted Test Orchestration (HTO) runs", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if preRun != nil { + preRun(cmd, args) + } + + if region.FromString(regio) == region.None { + return errors.New("invalid region") + } + + creds := credentials.Get() + url := region.FromString(regio).APIBaseURL() + + imagerunnerClient = http.NewImageRunner(url, creds, 15*time.Minute) + + return nil + }, + } + + flags := cmd.PersistentFlags() + flags.StringVarP(®io, "region", "r", "us-west-1", "The Sauce Labs region. Options: us-west-1, eu-central-1.") + + cmd.AddCommand( + LogsCommand(), + ) + return cmd +} diff --git a/internal/cmd/hto/logs.go b/internal/cmd/hto/logs.go new file mode 100644 index 000000000..954c9c054 --- /dev/null +++ b/internal/cmd/hto/logs.go @@ -0,0 +1,29 @@ +package hto + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" +) + +func LogsCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "logs ", + Short: "Fetch the logs for an HTO run", + RunE: func(cmd *cobra.Command, args []string) error { + return exec(args[0]) + }, + } + + return cmd +} + +func exec(runID string) error { + log, err := imagerunnerClient.GetLogs(context.Background(), runID) + if err != nil { + return err + } + fmt.Println(log) + return nil +} diff --git a/internal/http/imagerunner.go b/internal/http/imagerunner.go index 92fc7f4e8..dc892e89c 100644 --- a/internal/http/imagerunner.go +++ b/internal/http/imagerunner.go @@ -127,6 +127,11 @@ func (c *ImageRunner) DownloadArtifacts(ctx context.Context, id string) (io.Read } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return nil, imagerunner.LogURLNotFoundError{ + RunID: id, + } + } if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected server response (%d): %s", resp.StatusCode, b) @@ -169,6 +174,11 @@ func (c *ImageRunner) GetLogs(ctx context.Context, id string) (string, error) { } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return "", imagerunner.LogURLNotFoundError{ + RunID: id, + } + } if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("unexpected server response (%d): %s", resp.StatusCode, b) diff --git a/internal/imagerunner/imagerunner.go b/internal/imagerunner/imagerunner.go index 82f801c30..e6725facc 100644 --- a/internal/imagerunner/imagerunner.go +++ b/internal/imagerunner/imagerunner.go @@ -1,6 +1,9 @@ package imagerunner -import "errors" +import ( + "errors" + "fmt" +) // The different states that a runner can be in. const ( @@ -31,6 +34,14 @@ func Done(status string) bool { var ErrResourceNotFound = errors.New("resource not found") +type LogURLNotFoundError struct { + RunID string +} + +func (e LogURLNotFoundError) Error() string { + return fmt.Sprintf("Could not find log URL for run with ID %s", e.RunID) +} + type RunnerSpec struct { Container Container `json:"container,omitempty"` EntryPoint string `json:"entrypoint,omitempty"` From 0f65225e0c5217a30bce222d91c313d22374cfb3 Mon Sep 17 00:00:00 2001 From: Mike Han Date: Fri, 31 Mar 2023 15:51:27 -0600 Subject: [PATCH 2/6] Consistent -h messages (no trailing periods seems to be the norm) --- internal/cmd/storage/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/storage/cmd.go b/internal/cmd/storage/cmd.go index 4c5d2fcc6..7a68f8f93 100644 --- a/internal/cmd/storage/cmd.go +++ b/internal/cmd/storage/cmd.go @@ -19,7 +19,7 @@ func Command(preRun func(cmd *cobra.Command, args []string)) *cobra.Command { cmd := &cobra.Command{ Use: "storage", - Short: "Interact with Sauce Storage.", + Short: "Interact with Sauce Storage", TraverseChildren: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if preRun != nil { From 5dc5a8ada0b92ea4f6af6b46a3e49244f7b9e41b Mon Sep 17 00:00:00 2001 From: Mike Han Date: Mon, 3 Apr 2023 10:27:17 -0600 Subject: [PATCH 3/6] add prerun for segment tracking --- internal/cmd/hto/logs.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/internal/cmd/hto/logs.go b/internal/cmd/hto/logs.go index 954c9c054..540b85695 100644 --- a/internal/cmd/hto/logs.go +++ b/internal/cmd/hto/logs.go @@ -2,15 +2,33 @@ package hto import ( "context" + "errors" "fmt" + cmds "github.com/saucelabs/saucectl/internal/cmd" + "github.com/saucelabs/saucectl/internal/imagerunner" + "github.com/saucelabs/saucectl/internal/segment" + "github.com/saucelabs/saucectl/internal/usage" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func LogsCommand() *cobra.Command { cmd := &cobra.Command{ Use: "logs ", Short: "Fetch the logs for an HTO run", + PreRun: func(cmd *cobra.Command, args []string) { + tracker := segment.DefaultTracker + + go func() { + tracker.Collect( + cases.Title(language.English).String(cmds.FullName(cmd)), + usage.Properties{}.SetFlags(cmd.Flags()), + ) + _ = tracker.Close() + }() + }, RunE: func(cmd *cobra.Command, args []string) error { return exec(args[0]) }, From 65ab6d811d82cfa8e64d3bc42b1d0b8f1fae52c7 Mon Sep 17 00:00:00 2001 From: Mike Han Date: Mon, 3 Apr 2023 10:33:36 -0600 Subject: [PATCH 4/6] appease the linting gods --- internal/cmd/hto/logs.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/cmd/hto/logs.go b/internal/cmd/hto/logs.go index 540b85695..370ba9efc 100644 --- a/internal/cmd/hto/logs.go +++ b/internal/cmd/hto/logs.go @@ -2,11 +2,9 @@ package hto import ( "context" - "errors" "fmt" cmds "github.com/saucelabs/saucectl/internal/cmd" - "github.com/saucelabs/saucectl/internal/imagerunner" "github.com/saucelabs/saucectl/internal/segment" "github.com/saucelabs/saucectl/internal/usage" "github.com/spf13/cobra" From 39d466ee252777ad9a115c5259f8e845b6dbcbbe Mon Sep 17 00:00:00 2001 From: Mike Han Date: Mon, 3 Apr 2023 14:03:25 -0600 Subject: [PATCH 5/6] Change namespace from hto to imagerunner --- cmd/saucectl/saucectl.go | 4 ++-- internal/cmd/{hto => imagerunner}/cmd.go | 6 +++--- internal/cmd/{hto => imagerunner}/logs.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename internal/cmd/{hto => imagerunner}/cmd.go (89%) rename internal/cmd/{hto => imagerunner}/logs.go (92%) diff --git a/cmd/saucectl/saucectl.go b/cmd/saucectl/saucectl.go index ea3fedc51..5b13f0196 100644 --- a/cmd/saucectl/saucectl.go +++ b/cmd/saucectl/saucectl.go @@ -13,7 +13,7 @@ import ( "github.com/saucelabs/saucectl/internal/cmd/completion" "github.com/saucelabs/saucectl/internal/cmd/configure" "github.com/saucelabs/saucectl/internal/cmd/doctor" - "github.com/saucelabs/saucectl/internal/cmd/hto" + "github.com/saucelabs/saucectl/internal/cmd/imagerunner" "github.com/saucelabs/saucectl/internal/cmd/ini" "github.com/saucelabs/saucectl/internal/cmd/jobs" "github.com/saucelabs/saucectl/internal/cmd/new" @@ -72,7 +72,7 @@ func main() { storage.Command(cmd.PersistentPreRun), artifacts.Command(cmd.PersistentPreRun), jobs.Command(cmd.PersistentPreRun), - hto.Command(cmd.PersistentPreRun), + imagerunner.Command(cmd.PersistentPreRun), ) if err := cmd.Execute(); err != nil { diff --git a/internal/cmd/hto/cmd.go b/internal/cmd/imagerunner/cmd.go similarity index 89% rename from internal/cmd/hto/cmd.go rename to internal/cmd/imagerunner/cmd.go index 197d0961c..1376cab71 100644 --- a/internal/cmd/hto/cmd.go +++ b/internal/cmd/imagerunner/cmd.go @@ -1,4 +1,4 @@ -package hto +package imagerunner import ( "errors" @@ -18,8 +18,8 @@ func Command(preRun func(cmd *cobra.Command, args []string)) *cobra.Command { var regio string cmd := &cobra.Command{ - Use: "hto", - Short: "Commands for interacting with Hosted Test Orchestration (HTO) runs", + Use: "imagerunner", + Short: "Commands for interacting with imagerunner runs", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if preRun != nil { preRun(cmd, args) diff --git a/internal/cmd/hto/logs.go b/internal/cmd/imagerunner/logs.go similarity index 92% rename from internal/cmd/hto/logs.go rename to internal/cmd/imagerunner/logs.go index 370ba9efc..00950deb3 100644 --- a/internal/cmd/hto/logs.go +++ b/internal/cmd/imagerunner/logs.go @@ -1,4 +1,4 @@ -package hto +package imagerunner import ( "context" @@ -15,7 +15,7 @@ import ( func LogsCommand() *cobra.Command { cmd := &cobra.Command{ Use: "logs ", - Short: "Fetch the logs for an HTO run", + Short: "Fetch the logs for an imagerunner run", PreRun: func(cmd *cobra.Command, args []string) { tracker := segment.DefaultTracker From 1ec13526149cbddc4b50b1eb42bc3deea14e2eda Mon Sep 17 00:00:00 2001 From: Mike Han Date: Mon, 3 Apr 2023 14:15:01 -0600 Subject: [PATCH 6/6] Use existing ErrResourceNotFound error --- internal/cmd/imagerunner/logs.go | 4 ++++ internal/http/imagerunner.go | 9 +-------- internal/imagerunner/imagerunner.go | 13 +------------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/internal/cmd/imagerunner/logs.go b/internal/cmd/imagerunner/logs.go index 00950deb3..7613dcbd4 100644 --- a/internal/cmd/imagerunner/logs.go +++ b/internal/cmd/imagerunner/logs.go @@ -5,6 +5,7 @@ import ( "fmt" cmds "github.com/saucelabs/saucectl/internal/cmd" + imgrunner "github.com/saucelabs/saucectl/internal/imagerunner" "github.com/saucelabs/saucectl/internal/segment" "github.com/saucelabs/saucectl/internal/usage" "github.com/spf13/cobra" @@ -38,6 +39,9 @@ func LogsCommand() *cobra.Command { func exec(runID string) error { log, err := imagerunnerClient.GetLogs(context.Background(), runID) if err != nil { + if err == imgrunner.ErrResourceNotFound { + return fmt.Errorf("could not find log URL for run with ID (%s): %w", runID, err) + } return err } fmt.Println(log) diff --git a/internal/http/imagerunner.go b/internal/http/imagerunner.go index dc892e89c..10ab4f27b 100644 --- a/internal/http/imagerunner.go +++ b/internal/http/imagerunner.go @@ -127,11 +127,6 @@ func (c *ImageRunner) DownloadArtifacts(ctx context.Context, id string) (io.Read } defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - return nil, imagerunner.LogURLNotFoundError{ - RunID: id, - } - } if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected server response (%d): %s", resp.StatusCode, b) @@ -175,9 +170,7 @@ func (c *ImageRunner) GetLogs(ctx context.Context, id string) (string, error) { defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { - return "", imagerunner.LogURLNotFoundError{ - RunID: id, - } + return "", imagerunner.ErrResourceNotFound } if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) diff --git a/internal/imagerunner/imagerunner.go b/internal/imagerunner/imagerunner.go index e6725facc..82f801c30 100644 --- a/internal/imagerunner/imagerunner.go +++ b/internal/imagerunner/imagerunner.go @@ -1,9 +1,6 @@ package imagerunner -import ( - "errors" - "fmt" -) +import "errors" // The different states that a runner can be in. const ( @@ -34,14 +31,6 @@ func Done(status string) bool { var ErrResourceNotFound = errors.New("resource not found") -type LogURLNotFoundError struct { - RunID string -} - -func (e LogURLNotFoundError) Error() string { - return fmt.Sprintf("Could not find log URL for run with ID %s", e.RunID) -} - type RunnerSpec struct { Container Container `json:"container,omitempty"` EntryPoint string `json:"entrypoint,omitempty"`