From 6a3b5e09b3d3ef308f514d6729e36cbfe019a4e3 Mon Sep 17 00:00:00 2001 From: varunv22 Date: Wed, 19 Jun 2024 14:02:02 -0400 Subject: [PATCH 1/9] del setupworkspace --- pkg/cmd/setupworkspace/setupworkspace.go | 69 ------------------------ 1 file changed, 69 deletions(-) delete mode 100644 pkg/cmd/setupworkspace/setupworkspace.go diff --git a/pkg/cmd/setupworkspace/setupworkspace.go b/pkg/cmd/setupworkspace/setupworkspace.go deleted file mode 100644 index cc7dc9de..00000000 --- a/pkg/cmd/setupworkspace/setupworkspace.go +++ /dev/null @@ -1,69 +0,0 @@ -package setupworkspace - -import ( - "fmt" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/setupworkspace" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/spf13/cobra" -) - -type SetupWorkspaceStore interface { - GetSetupParams() (*store.SetupParamsV0, error) - WriteSetupScript(script string) error - GetSetupScriptPath() string - GetCurrentUser() (*entity.User, error) - GetCurrentWorkspaceID() (string, error) -} - -const Name = "setupworkspace" - -// Internal command for setting up workspace // v1 similar to k8s post-start script -func NewCmdSetupWorkspace(store SetupWorkspaceStore) *cobra.Command { - var forceEnableSetup bool - cmd := &cobra.Command{ - Annotations: map[string]string{"hidden": ""}, - Use: Name, - RunE: func(cmd *cobra.Command, args []string) error { - breverrors.GetDefaultErrorReporter().AddTag("command", Name) - _, err := store.GetCurrentWorkspaceID() // do this to error reporting - if err != nil { - return breverrors.WrapAndTrace(err) - } - fmt.Println("setting up instance") - - params, err := store.GetSetupParams() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if !featureflag.IsDev() { - _, err = store.GetCurrentUser() // do this to set error user reporting - if err != nil { - fmt.Println(err) - if !params.DisableSetup { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err, "setup continued")) - } - } - } - - if !forceEnableSetup && params.DisableSetup { - fmt.Printf("WARNING: setup script not running [params.DisableSetup=%v, forceEnableSetup=%v]", params.DisableSetup, forceEnableSetup) - return nil - } - - err = setupworkspace.SetupWorkspace(params) - if err != nil { - return breverrors.WrapAndTrace(err) - } - fmt.Println("done setting up instance") - return nil - }, - } - cmd.PersistentFlags().BoolVar(&forceEnableSetup, "force-enable", false, "force the setup script to run despite params") - - return cmd -} From 65e31d42fe6fd033db9e29c679541dbd83a88bbf Mon Sep 17 00:00:00 2001 From: varunv22 Date: Wed, 19 Jun 2024 14:10:47 -0400 Subject: [PATCH 2/9] deleted in cmd.go --- pkg/cmd/cmd.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 99c65767..3f8a3a06 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -40,7 +40,6 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/scale" "github.com/brevdev/brev-cli/pkg/cmd/secret" "github.com/brevdev/brev-cli/pkg/cmd/set" - "github.com/brevdev/brev-cli/pkg/cmd/setupworkspace" "github.com/brevdev/brev-cli/pkg/cmd/shell" "github.com/brevdev/brev-cli/pkg/cmd/sshkeys" "github.com/brevdev/brev-cli/pkg/cmd/start" @@ -279,7 +278,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore)) cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore)) - cmd.AddCommand(setupworkspace.NewCmdSetupWorkspace(noLoginCmdStore)) cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore)) cmd.AddCommand(envsetup.NewCmdEnvSetup(loginCmdStore, loginAuth)) cmd.AddCommand(postinstall.NewCmdpostinstall(t, loginCmdStore)) From 8fa98594dede31a67c1440d72130ef4f6fa83e5d Mon Sep 17 00:00:00 2001 From: varunv22 Date: Thu, 20 Jun 2024 07:42:44 -0400 Subject: [PATCH 3/9] fixed make install --- Makefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Makefile b/Makefile index 2569de8c..1aff4585 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,21 @@ dev: clean install-tools generate vet fmt lint test mod-tidy ci: ## CI build ci: dev diff +.PHONY: install +install: dep-tools dep-python-tools ## go install tools + + +.PHONY: dep-tools +dep-tools: ## go install tools + $(call print-target) + cd tools && go install $(shell cd tools && go list -e -f '{{ join .Imports " " }}' -tags=tools) && cd - + +.PHONY: dep-python-tools +dep-python-tools: ## install python tools + pip install --upgrade Pygments + pip install jupyter==1.0.0 + + .PHONY: clean clean: ## remove files created during build pipeline $(call print-target) From 061aa0fb200ac2462b46e0805e08ebce5053083f Mon Sep 17 00:00:00 2001 From: varunv22 Date: Thu, 20 Jun 2024 11:37:27 -0400 Subject: [PATCH 4/9] deleted commands --- pkg/cmd/approve/approve.go | 47 - pkg/cmd/approve/approve_test.go | 1 - pkg/cmd/autostop/autostop.go | 86 - pkg/cmd/background/background.go | 216 -- pkg/cmd/background/background_test.go | 1 - pkg/cmd/bmon/bmon.go | 51 - pkg/cmd/clipboard/clipboard.go | 105 - pkg/cmd/clipboard/clipboard_listener.go | 71 - pkg/cmd/cmd.go | 82 +- pkg/cmd/configureenvvars/configureenvvars.go | 164 -- .../configureenvvars/configureenvvars_test.go | 433 ---- pkg/cmd/configureenvvars/lex.go | 266 -- pkg/cmd/configureenvvars/lex_test.go | 710 ------ pkg/cmd/connect/connect.go | 57 - pkg/cmd/create/create.go | 206 -- pkg/cmd/create/create_test.go | 1 - pkg/cmd/create/doc.md | 268 -- pkg/cmd/envsetup/envsetup.go | 743 ------ pkg/cmd/envsetup/envsetup_test.go | 87 - pkg/cmd/envsetup/motd.sh | 60 - pkg/cmd/envsetup/speedtest.py | 2184 ----------------- pkg/cmd/envvars/envvars.go | 33 - pkg/cmd/envvars/envvars_test.go | 1 - pkg/cmd/fu/fu.go | 107 - pkg/cmd/fu/fu_test.go | 1 - pkg/cmd/healthcheck/healthcheck.go | 51 - pkg/cmd/initfile/initfile.go | 31 - pkg/cmd/invite/invite.go | 104 - pkg/cmd/invite/invite_test.go | 1 - pkg/cmd/notebook/notebook.go | 82 - pkg/cmd/notebook/notebook_test.go | 1 - pkg/cmd/ollama/ollama.go | 344 --- pkg/cmd/ollama/ollama_test.go | 1 - pkg/cmd/ollama/ollamauiverb.yaml | 0 pkg/cmd/ollama/ollamaverb.yaml | 18 - pkg/cmd/open/open.go | 343 --- pkg/cmd/open/open_test.go | 1 - pkg/cmd/org/ls.go | 38 - pkg/cmd/org/org.go | 130 - pkg/cmd/org/org_test.go | 1 - pkg/cmd/org/set.go | 90 - pkg/cmd/postinstall/postinstall.go | 113 - pkg/cmd/profile/profile.go | 114 - pkg/cmd/profile/profile_test.go | 1 - pkg/cmd/proxy/proxy.go | 161 -- pkg/cmd/proxy/proxy_test.go | 13 - pkg/cmd/recreate/doc.md | 46 - pkg/cmd/recreate/recreate.go | 226 -- pkg/cmd/reset/doc.md | 27 - pkg/cmd/reset/reset.go | 252 -- pkg/cmd/reset/reset_test.go | 1 - pkg/cmd/runtasks/doc.md | 42 - pkg/cmd/runtasks/runtasks.go | 88 - pkg/cmd/runtasks/runtasks_test.go | 1 - pkg/cmd/scale/scale.go | 101 - pkg/cmd/secret/secret.go | 204 -- pkg/cmd/secret/secret_test.go | 1 - pkg/cmd/shell/shell.go | 227 -- pkg/cmd/shell/shell_test.go | 1 - pkg/cmd/sshall/sshall.go | 269 -- pkg/cmd/sshall/sshall_test.go | 25 - pkg/cmd/status/doc.md | 268 -- pkg/cmd/status/status.go | 61 - pkg/cmd/status/status_test.go | 1 - pkg/cmd/tasks/tasks.go | 132 - pkg/cmd/test/test.go | 76 - pkg/cmd/test/test_test.go | 1 - pkg/cmd/updatemodel/updatemodel.go | 422 ---- pkg/cmd/updatemodel/updatemodel_test.go | 292 --- pkg/cmd/workspacegroups/workspacegroups.go | 69 - .../writeconnectionevent.go | 44 - .../writeconnectionevent_test.go | 41 - 72 files changed, 1 insertion(+), 10536 deletions(-) delete mode 100644 pkg/cmd/approve/approve.go delete mode 100644 pkg/cmd/approve/approve_test.go delete mode 100644 pkg/cmd/autostop/autostop.go delete mode 100644 pkg/cmd/background/background.go delete mode 100644 pkg/cmd/background/background_test.go delete mode 100644 pkg/cmd/bmon/bmon.go delete mode 100644 pkg/cmd/clipboard/clipboard.go delete mode 100644 pkg/cmd/clipboard/clipboard_listener.go delete mode 100644 pkg/cmd/configureenvvars/configureenvvars.go delete mode 100644 pkg/cmd/configureenvvars/configureenvvars_test.go delete mode 100644 pkg/cmd/configureenvvars/lex.go delete mode 100644 pkg/cmd/configureenvvars/lex_test.go delete mode 100644 pkg/cmd/connect/connect.go delete mode 100644 pkg/cmd/create/create.go delete mode 100644 pkg/cmd/create/create_test.go delete mode 100644 pkg/cmd/create/doc.md delete mode 100644 pkg/cmd/envsetup/envsetup.go delete mode 100644 pkg/cmd/envsetup/envsetup_test.go delete mode 100644 pkg/cmd/envsetup/motd.sh delete mode 100644 pkg/cmd/envsetup/speedtest.py delete mode 100644 pkg/cmd/envvars/envvars.go delete mode 100644 pkg/cmd/envvars/envvars_test.go delete mode 100644 pkg/cmd/fu/fu.go delete mode 100644 pkg/cmd/fu/fu_test.go delete mode 100644 pkg/cmd/healthcheck/healthcheck.go delete mode 100644 pkg/cmd/initfile/initfile.go delete mode 100644 pkg/cmd/invite/invite.go delete mode 100644 pkg/cmd/invite/invite_test.go delete mode 100644 pkg/cmd/notebook/notebook.go delete mode 100644 pkg/cmd/notebook/notebook_test.go delete mode 100644 pkg/cmd/ollama/ollama.go delete mode 100644 pkg/cmd/ollama/ollama_test.go delete mode 100644 pkg/cmd/ollama/ollamauiverb.yaml delete mode 100644 pkg/cmd/ollama/ollamaverb.yaml delete mode 100644 pkg/cmd/open/open.go delete mode 100644 pkg/cmd/open/open_test.go delete mode 100644 pkg/cmd/org/ls.go delete mode 100644 pkg/cmd/org/org.go delete mode 100644 pkg/cmd/org/org_test.go delete mode 100644 pkg/cmd/org/set.go delete mode 100644 pkg/cmd/postinstall/postinstall.go delete mode 100644 pkg/cmd/profile/profile.go delete mode 100644 pkg/cmd/profile/profile_test.go delete mode 100644 pkg/cmd/proxy/proxy.go delete mode 100644 pkg/cmd/proxy/proxy_test.go delete mode 100644 pkg/cmd/recreate/doc.md delete mode 100644 pkg/cmd/recreate/recreate.go delete mode 100644 pkg/cmd/reset/doc.md delete mode 100644 pkg/cmd/reset/reset.go delete mode 100644 pkg/cmd/reset/reset_test.go delete mode 100644 pkg/cmd/runtasks/doc.md delete mode 100644 pkg/cmd/runtasks/runtasks.go delete mode 100644 pkg/cmd/runtasks/runtasks_test.go delete mode 100644 pkg/cmd/scale/scale.go delete mode 100644 pkg/cmd/secret/secret.go delete mode 100644 pkg/cmd/secret/secret_test.go delete mode 100644 pkg/cmd/shell/shell.go delete mode 100644 pkg/cmd/shell/shell_test.go delete mode 100644 pkg/cmd/sshall/sshall.go delete mode 100644 pkg/cmd/sshall/sshall_test.go delete mode 100644 pkg/cmd/status/doc.md delete mode 100644 pkg/cmd/status/status.go delete mode 100644 pkg/cmd/status/status_test.go delete mode 100644 pkg/cmd/tasks/tasks.go delete mode 100644 pkg/cmd/test/test.go delete mode 100644 pkg/cmd/test/test_test.go delete mode 100644 pkg/cmd/updatemodel/updatemodel.go delete mode 100644 pkg/cmd/updatemodel/updatemodel_test.go delete mode 100644 pkg/cmd/workspacegroups/workspacegroups.go delete mode 100644 pkg/cmd/writeconnectionevent/writeconnectionevent.go delete mode 100644 pkg/cmd/writeconnectionevent/writeconnectionevent_test.go diff --git a/pkg/cmd/approve/approve.go b/pkg/cmd/approve/approve.go deleted file mode 100644 index 04b49059..00000000 --- a/pkg/cmd/approve/approve.go +++ /dev/null @@ -1,47 +0,0 @@ -package approve - -import ( - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -var ( - approveDescriptionShort = "Brev admin approve user" - approveDescriptionLong = "Brev admin approve user" - approveExample = "brev approve " -) - -type ApproveStore interface { - ApproveUserByID(userID string) (*entity.User, error) -} - -func NewCmdApprove(t *terminal.Terminal, approveStore ApproveStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "approve", - DisableFlagsInUseLine: true, - Short: approveDescriptionShort, - Long: approveDescriptionLong, - Example: approveExample, - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(1)), - RunE: func(cmd *cobra.Command, args []string) error { - err := approveUser(args[0], t, approveStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func approveUser(userID string, _ *terminal.Terminal, approveStore ApproveStore) error { - _, err := approveStore.ApproveUserByID(userID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/approve/approve_test.go b/pkg/cmd/approve/approve_test.go deleted file mode 100644 index 551a61b4..00000000 --- a/pkg/cmd/approve/approve_test.go +++ /dev/null @@ -1 +0,0 @@ -package approve diff --git a/pkg/cmd/autostop/autostop.go b/pkg/cmd/autostop/autostop.go deleted file mode 100644 index 5911fb33..00000000 --- a/pkg/cmd/autostop/autostop.go +++ /dev/null @@ -1,86 +0,0 @@ -package autostop - -import ( - "github.com/hashicorp/go-multierror" - "github.com/samber/lo" - "github.com/samber/mo" - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -func NewCmdautostop(t *terminal.Terminal, store autostopStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "autostop", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - err := Runautostop( - runAutostopArgs{ - t: t, - args: args, - store: store, - }, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -type autostopStore interface { - AutoStopWorkspace(workspaceID string) (*entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetWorkspaceByNameOrID(orgID string, nameOrID string) ([]entity.Workspace, error) -} - -type runAutostopArgs struct { - t *terminal.Terminal - args []string - store autostopStore -} - -func Runautostop(args runAutostopArgs) error { - org, err := args.store.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - envs, err := args.store.GetWorkspaceByNameOrID(org.ID, args.args[0]) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - asResults := lo.Map( - envs, - func(env entity.Workspace, _ int) mo.Result[*entity.Workspace] { - return mo.TupleToResult(args.store.AutoStopWorkspace(env.ID)) - }, - ) - err = lo.Reduce( - asResults, - func(acc error, res mo.Result[*entity.Workspace], _ int) error { - if res.IsError() { - return multierror.Append(acc, res.Error()) - } - return acc - }, - nil, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/background/background.go b/pkg/cmd/background/background.go deleted file mode 100644 index 5dd38501..00000000 --- a/pkg/cmd/background/background.go +++ /dev/null @@ -1,216 +0,0 @@ -package background - -import ( - "bufio" - "fmt" - "log" - "os" - "os/exec" - "strings" - "time" - - "github.com/brevdev/brev-cli/pkg/analytics" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type BackgroundStore interface { - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - ModifyWorkspace(workspaceID string, options *store.ModifyWorkspaceRequest) (*entity.Workspace, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetCurrentWorkspaceID() (string, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) -} - -func DisableAutoStop(s BackgroundStore, workspaceID string) error { - isStoppable := false - _, err := s.ModifyWorkspace(workspaceID, &store.ModifyWorkspaceRequest{ - IsStoppable: &isStoppable, - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func EnableAutoStop(s BackgroundStore, workspaceID string) error { - isStoppable := true - _, err := s.ModifyWorkspace(workspaceID, &store.ModifyWorkspaceRequest{ - IsStoppable: &isStoppable, - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func NewCmdBackground(t *terminal.Terminal, s BackgroundStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "background [flags] [command]", - Aliases: []string{"bg"}, - DisableFlagsInUseLine: true, - Short: "Run a command in the background with optional 'brev stop self' at the end", - Long: "This command will run a specified command in the background using nohup and write logs to $HOME/brev-background-logs.", - Example: "brev background ./myscript.sh --stop", - RunE: func(cmd *cobra.Command, args []string) error { - // Parse the flags - stopFlag, err := cmd.Flags().GetBool("stop") - if err != nil { - log.Fatal(err) - } - progressFlag, err := cmd.Flags().GetBool("progress") - if err != nil { - log.Fatal(err) - } - if progressFlag { - checkProgress() - return nil - } - // Join the args into a command string - command := "" - if len(args) > 0 { - command = args[0] - } - for i := 1; i < len(args); i++ { - command += " " + args[i] - } - // Create logs directory if it doesn't exist - logsDir := os.Getenv("HOME") + "/brev-background-logs" - err = os.MkdirAll(logsDir, os.ModePerm) - if err != nil { - log.Fatal(err) - } - - checkAutoStop(s) - // Run the command in the background using nohup - c := exec.Command("nohup", "bash", "-c", command+">"+logsDir+"/log.txt 2>&1 &") // #nosec G204 - err = c.Start() - if err != nil { - log.Fatal(err) - } - // Write logs - logFile, err := os.OpenFile(logsDir+"/log.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) //nolint: gosec // TODO - if err != nil { - log.Fatal(err) - } - defer logFile.Close() //nolint: errcheck // TODO - logFile.WriteString(time.Now().Format("2006-01-02 15:04:05") + ": Command \"" + command + "\" was run in the background.\n") //nolint: errcheck,gosec // TODO - - // Write process details to data file - processesFile, err := os.OpenFile(logsDir+"/processes.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) //nolint: gosec // TODO - if err != nil { - log.Fatal(err) - } - defer processesFile.Close() //nolint: errcheck // TODO - _, _ = processesFile.WriteString(fmt.Sprintf("%d,%s,%s\n", c.Process.Pid, time.Now().Format("2006-01-02 15:04:05"), command)) - - if stopFlag { - // If --stop flag is set, run "brev stop self" at the end - defer func() { - stopCmd := exec.Command("brev", "stop", "self") - err = stopCmd.Run() - if err != nil { - log.Fatal(err) - } - }() - } - // Call analytics for open - _ = pushBackgroundAnalytics(s) - - t.Vprintf("Command \"%s\" has been run in the background. Check %s for logs.\n", command, logsDir) - return nil - }, - } - cmd.Flags().Bool("stop", false, "Stop the workspace after the command is finished") - cmd.Flags().Bool("progress", false, "Show progress of the background commands") - return cmd -} - -func checkAutoStop(s BackgroundStore) { - wsID, err := s.GetCurrentWorkspaceID() - if err == nil && wsID != "" { - // Disable auto stop - err = DisableAutoStop(s, wsID) - if err != nil { - log.Fatal(err) - } - } -} - -func pushBackgroundAnalytics(s BackgroundStore) error { - // Call analytics for open - userID := "" - user, err := s.GetCurrentUser() - if err != nil { - userID = "" - } else { - userID = user.ID - } - data := analytics.EventData{ - EventName: "Brev background", - UserID: userID, - } - err = analytics.TrackEvent(data) - return breverrors.WrapAndTrace(err) -} - -type Process struct { - ID int `json:"id"` - Command string `json:"command"` - LogsDir string `json:"logsDir"` - StartTime string `json:"startTime"` -} - -type Processes struct { - Processes []Process `json:"processes"` -} - -func checkProgress() { - filePath := fmt.Sprintf("%s/brev-background-logs/processes.txt", os.Getenv("HOME")) - file, err := os.Open(filePath) //nolint: gosec // TODO - if err != nil { - panic(err) - } - defer file.Close() //nolint: errcheck // TODO - - scanner := bufio.NewScanner(file) - - fmt.Println("Running processes:") - for scanner.Scan() { - processLine := scanner.Text() - processData := strings.Split(processLine, ",") - - startTime, err := time.Parse("2006-01-02 15:04:05", processData[1]) - if err != nil { - panic(err) - } - - // Check if the process is still running by its PID - processID := processData[0] - cmd := fmt.Sprintf("ps -p %s -o comm=", processID) - output, err := exec.Command("bash", "-c", cmd).Output() //nolint: gosec // TODO - - if err != nil || strings.TrimSpace(string(output)) == "" { - // Process is not running, show a checkmark - fmt.Printf("ID: %s \u2713\n", processID) - } else { - // Process is still running, display its info - fmt.Printf("ID: %s\n", processID) - fmt.Printf("Command: %s\n", processData[2]) - fmt.Printf("Start time: %s\n", startTime.Format("2006-01-02 15:04:05")) - } - - fmt.Println() - } - - if err := scanner.Err(); err != nil { - panic(err) - } -} diff --git a/pkg/cmd/background/background_test.go b/pkg/cmd/background/background_test.go deleted file mode 100644 index 313dbd6c..00000000 --- a/pkg/cmd/background/background_test.go +++ /dev/null @@ -1 +0,0 @@ -package background diff --git a/pkg/cmd/bmon/bmon.go b/pkg/cmd/bmon/bmon.go deleted file mode 100644 index de1eafd2..00000000 --- a/pkg/cmd/bmon/bmon.go +++ /dev/null @@ -1,51 +0,0 @@ -package bmon - -import ( - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -type bmonStore interface { - autostartconf.AutoStartStore -} - -func NewCmdbmon(t *terminal.Terminal, store bmonStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "bmon", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - err := Runbmon(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func Runbmon(_ *terminal.Terminal, _ []string, store bmonStore) error { - bmonConfig := autostartconf.NewBrevMonConfigure( - store, - false, - "10m", - "22", - ) - err := bmonConfig.Install() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/clipboard/clipboard.go b/pkg/cmd/clipboard/clipboard.go deleted file mode 100644 index 38857c81..00000000 --- a/pkg/cmd/clipboard/clipboard.go +++ /dev/null @@ -1,105 +0,0 @@ -package clipboard - -import ( - "fmt" - "os" - "os/exec" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - - "github.com/brevdev/brev-cli/pkg/cmd/portforward" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type ClipboardStore interface { - completions.CompletionStore -} - -// Step 1 -func EstablishConnection(t *terminal.Terminal, clipboardStore ClipboardStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"ssh": ""}, - Use: "connect", - DisableFlagsInUseLine: true, - Short: "Connects to remote", - Long: "Connects to remote", - Example: "connect", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(clipboardStore, t), - Run: func(cmd *cobra.Command, args []string) { - // Listen to Port - listener := CreateListener(&Config{ - Host: "127.0.0.1", - Port: "6969", - }) - listener.Run() - }, - } - return cmd -} - -func SaveToClipboard(output string) { - // copy to clipboard - command := fmt.Sprintf("echo %s | pbcopy", output) - cmd := exec.Command("bash", "-c", command) //nolint:gosec // testing - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Start() - if err != nil { - fmt.Println(err) - } - err1 := cmd.Wait() - if err1 != nil { - fmt.Println(err1) - } -} - -// Step 2 -func ForwardPort(t *terminal.Terminal, clipboardStore ClipboardStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"ssh": ""}, - Use: "remote-forward ", - DisableFlagsInUseLine: true, - Short: "remote forward port", - Long: "remote forward port", - Example: "remote-forward", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(1)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(clipboardStore, t), - Run: func(cmd *cobra.Command, args []string) { - // Portforward - _, sshError := portforward.RunSSHPortForward("-R", "6969", "6969", args[0]) - if sshError != nil { - t.Errprint(sshError, "Failed to connect to local") - return - } - }, - } - return cmd -} - -// Step 3 -func SendToClipboard(t *terminal.Terminal, clipboardStore ClipboardStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"clipboard": ""}, - Use: "clipboard", - DisableFlagsInUseLine: true, - Short: "Copies clipboard from remote instance to local clipboard", - Long: "Copies clipboard from remote instance to local clipboard", - Example: "clipboard", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(clipboardStore, t), - Run: func(cmd *cobra.Command, args []string) { - // Send output - err := SendRequest("localhost:6969", "hello world") - if err != nil { - t.Errprint(err, "Failed to copy to clipboard") - return - } - }, - } - - return cmd -} diff --git a/pkg/cmd/clipboard/clipboard_listener.go b/pkg/cmd/clipboard/clipboard_listener.go deleted file mode 100644 index 38faadb2..00000000 --- a/pkg/cmd/clipboard/clipboard_listener.go +++ /dev/null @@ -1,71 +0,0 @@ -package clipboard - -import ( - "context" - "fmt" - "io/ioutil" - "net/http" - "strings" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/gin-gonic/gin" -) - -// Server ... -type Server struct { - host string - port string -} - -// Config ... -type Config struct { - Host string - Port string -} - -// New ... -func CreateListener(config *Config) *Server { - return &Server{ - host: config.Host, - port: config.Port, - } -} - -// tcp req -func SendRequest(address string, message string) error { - reader := strings.NewReader(message) - request, err := http.NewRequestWithContext(context.TODO(), "GET", "http://"+address+"/", reader) - if err != nil { - fmt.Println(err) - return breverrors.WrapAndTrace(err) - } - client := &http.Client{} - resp, err := client.Do(request) - fmt.Println(resp) - if err != nil { - fmt.Println(err) - return breverrors.WrapAndTrace(err) - } - defer resp.Body.Close() //nolint:errcheck //deving and defer - return nil -} - -// Run ... -func (server *Server) Run() { - // Starts a new Gin instance with no middle-ware - r := gin.New() - - r.GET("/", func(c *gin.Context) { - jsonData, err := ioutil.ReadAll(c.Request.Body) - if err != nil { - // Handle error - c.String(http.StatusBadRequest, "Can't parse body") - } - SaveToClipboard(string(jsonData)) - c.String(http.StatusOK, "success") - }) - err := r.Run(server.host + ":" + server.port) - if err != nil { - fmt.Println(err) - } -} diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 3f8a3a06..82682587 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -5,52 +5,18 @@ import ( "fmt" "github.com/brevdev/brev-cli/pkg/auth" - "github.com/brevdev/brev-cli/pkg/cmd/approve" - "github.com/brevdev/brev-cli/pkg/cmd/autostop" - "github.com/brevdev/brev-cli/pkg/cmd/background" - "github.com/brevdev/brev-cli/pkg/cmd/bmon" - "github.com/brevdev/brev-cli/pkg/cmd/clipboard" - "github.com/brevdev/brev-cli/pkg/cmd/configureenvvars" - "github.com/brevdev/brev-cli/pkg/cmd/connect" - "github.com/brevdev/brev-cli/pkg/cmd/create" "github.com/brevdev/brev-cli/pkg/cmd/delete" - "github.com/brevdev/brev-cli/pkg/cmd/envsetup" - "github.com/brevdev/brev-cli/pkg/cmd/envvars" - "github.com/brevdev/brev-cli/pkg/cmd/fu" - "github.com/brevdev/brev-cli/pkg/cmd/healthcheck" "github.com/brevdev/brev-cli/pkg/cmd/hello" - "github.com/brevdev/brev-cli/pkg/cmd/importideconfig" - "github.com/brevdev/brev-cli/pkg/cmd/initfile" - "github.com/brevdev/brev-cli/pkg/cmd/invite" "github.com/brevdev/brev-cli/pkg/cmd/login" "github.com/brevdev/brev-cli/pkg/cmd/logout" "github.com/brevdev/brev-cli/pkg/cmd/ls" - "github.com/brevdev/brev-cli/pkg/cmd/notebook" - "github.com/brevdev/brev-cli/pkg/cmd/ollama" - "github.com/brevdev/brev-cli/pkg/cmd/open" - "github.com/brevdev/brev-cli/pkg/cmd/org" "github.com/brevdev/brev-cli/pkg/cmd/portforward" - "github.com/brevdev/brev-cli/pkg/cmd/postinstall" - "github.com/brevdev/brev-cli/pkg/cmd/profile" - "github.com/brevdev/brev-cli/pkg/cmd/proxy" - "github.com/brevdev/brev-cli/pkg/cmd/recreate" "github.com/brevdev/brev-cli/pkg/cmd/refresh" - "github.com/brevdev/brev-cli/pkg/cmd/reset" - "github.com/brevdev/brev-cli/pkg/cmd/runtasks" - "github.com/brevdev/brev-cli/pkg/cmd/scale" - "github.com/brevdev/brev-cli/pkg/cmd/secret" "github.com/brevdev/brev-cli/pkg/cmd/set" - "github.com/brevdev/brev-cli/pkg/cmd/shell" "github.com/brevdev/brev-cli/pkg/cmd/sshkeys" "github.com/brevdev/brev-cli/pkg/cmd/start" - "github.com/brevdev/brev-cli/pkg/cmd/status" "github.com/brevdev/brev-cli/pkg/cmd/stop" - "github.com/brevdev/brev-cli/pkg/cmd/tasks" - "github.com/brevdev/brev-cli/pkg/cmd/test" - "github.com/brevdev/brev-cli/pkg/cmd/updatemodel" "github.com/brevdev/brev-cli/pkg/cmd/upgrade" - "github.com/brevdev/brev-cli/pkg/cmd/workspacegroups" - "github.com/brevdev/brev-cli/pkg/cmd/writeconnectionevent" "github.com/brevdev/brev-cli/pkg/config" "github.com/brevdev/brev-cli/pkg/featureflag" "github.com/brevdev/brev-cli/pkg/files" @@ -231,62 +197,16 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore, loginAuth *auth.LoginAuth) { //nolint:funlen // define brev command cmd.AddCommand(set.NewCmdSet(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(ls.NewCmdLs(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(org.NewCmdOrg(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(invite.NewCmdInvite(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(portforward.NewCmdPortForwardSSH(loginCmdStore, t)) cmd.AddCommand(login.NewCmdLogin(t, noLoginCmdStore, loginAuth)) cmd.AddCommand(logout.NewCmdLogout(loginAuth, noLoginCmdStore)) - cmd.AddCommand(tasks.NewCmdTasks(t, noLoginCmdStore)) - cmd.AddCommand(tasks.NewCmdConfigure(t, noLoginCmdStore)) - cmd.AddCommand(initfile.NewCmdInitFile(t, noLoginCmdStore)) - cmd.AddCommand(hello.NewCmdHello(t, noLoginCmdStore)) - cmd.AddCommand(notebook.NewCmdNotebook(noLoginCmdStore, t)) - // dev feature toggle - if featureflag.IsDev() { - _ = 0 // noop - cmd.AddCommand(test.NewCmdTest(t, noLoginCmdStore)) - cmd.AddCommand(approve.NewCmdApprove(t, loginCmdStore)) - cmd.AddCommand(clipboard.EstablishConnection(t, loginCmdStore)) - cmd.AddCommand(clipboard.SendToClipboard(t, loginCmdStore)) - cmd.AddCommand(clipboard.ForwardPort(t, loginCmdStore)) - cmd.AddCommand(envvars.NewCmdEnvVars(t, loginCmdStore)) - cmd.AddCommand(connect.NewCmdConnect(t, noLoginCmdStore)) - cmd.AddCommand(fu.NewCmdFu(t, loginCmdStore, noLoginCmdStore)) - } else { - _ = 0 // noop - } - cmd.AddCommand(workspacegroups.NewCmdWorkspaceGroups(t, loginCmdStore)) - cmd.AddCommand(scale.NewCmdScale(t, noLoginCmdStore)) - cmd.AddCommand(configureenvvars.NewCmdConfigureEnvVars(t, loginCmdStore)) - cmd.AddCommand(importideconfig.NewCmdImportIDEConfig(t, noLoginCmdStore)) - cmd.AddCommand(shell.NewCmdShell(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(open.NewCmdOpen(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(ollama.NewCmdOllama(t, loginCmdStore)) - cmd.AddCommand(background.NewCmdBackground(t, loginCmdStore)) - cmd.AddCommand(status.NewCmdStatus(t, loginCmdStore)) - cmd.AddCommand(secret.NewCmdSecret(loginCmdStore, t)) cmd.AddCommand(sshkeys.NewCmdSSHKeys(t, loginCmdStore)) cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(create.NewCmdCreate(t, loginCmdStore)) cmd.AddCommand(stop.NewCmdStop(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(delete.NewCmdDelete(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(reset.NewCmdReset(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(profile.NewCmdProfile(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(refresh.NewCmdRefresh(t, loginCmdStore)) - cmd.AddCommand(runtasks.NewCmdRunTasks(t, noLoginCmdStore)) - cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore)) - cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore)) - - cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore)) - cmd.AddCommand(envsetup.NewCmdEnvSetup(loginCmdStore, loginAuth)) - cmd.AddCommand(postinstall.NewCmdpostinstall(t, loginCmdStore)) - cmd.AddCommand(postinstall.NewCMDOptimizeThis(t, loginCmdStore)) - cmd.AddCommand(bmon.NewCmdbmon(t, loginCmdStore)) + cmd.AddCommand(hello.NewCmdHello(t, noLoginCmdStore)) cmd.AddCommand(upgrade.NewCmdUpgrade(t, loginCmdStore)) - cmd.AddCommand(writeconnectionevent.NewCmdwriteConnectionEvent(t, loginCmdStore)) - cmd.AddCommand(autostop.NewCmdautostop(t, loginCmdStore)) - cmd.AddCommand(updatemodel.NewCmdupdatemodel(t, loginCmdStore)) } func hasQuickstartCommands(cmd *cobra.Command) bool { diff --git a/pkg/cmd/configureenvvars/configureenvvars.go b/pkg/cmd/configureenvvars/configureenvvars.go deleted file mode 100644 index da0a5af0..00000000 --- a/pkg/cmd/configureenvvars/configureenvvars.go +++ /dev/null @@ -1,164 +0,0 @@ -package configureenvvars - -import ( - "fmt" - "os" - "sort" - "strings" - - "github.com/alessio/shellescape" - "github.com/brevdev/brev-cli/pkg/collections" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -const ( - BrevWorkspaceEnvPath = "/home/brev/workspace/.env" - BrevDevPlaneEnvPath = "/home/ubuntu/.brev/.env" - BrevManagedEnvVarsKey = "BREV_MANAGED_ENV_VARS" -) - -type envVars map[string]string - -type ConfigureEnvVarsStore interface { - GetFileAsString(path string) (string, error) -} - -func NewCmdConfigureEnvVars(_ *terminal.Terminal, cevStore ConfigureEnvVarsStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "configure-env-vars", - DisableFlagsInUseLine: true, - Short: "configure env vars in supported shells", - Long: "configure env vars in supported shells", - Example: "", - RunE: func(cmd *cobra.Command, args []string) error { - output, err := RunConfigureEnvVars(cevStore) - if err != nil { - // todo bubble up error, but in the meantime make sure there - // is no output - return nil - } - fmt.Print(output) - return nil - }, - } - - return cmd -} - -func RunConfigureEnvVars(cevStore ConfigureEnvVarsStore) (string, error) { - brevEnvsString := os.Getenv(BrevManagedEnvVarsKey) - // intentionally ignoring err - envFileContents, _ := cevStore.GetFileAsString(BrevWorkspaceEnvPath) - devplaneContents, _ := cevStore.GetFileAsString(BrevDevPlaneEnvPath) - envFileContents = envFileContents + "\n" + devplaneContents - return generateExportString(brevEnvsString, envFileContents), nil -} - -func generateExportString(brevEnvsString, envFileContents string) string { - if brevEnvsString == "" && envFileContents == "" { - return "" - } - brevEnvKeys := strings.Split(brevEnvsString, ",") - - envfileEntries := parse(envFileContents) - for key, val := range envfileEntries { - if !strings.HasPrefix(val, "'") { // already quoted - envfileEntries[key] = shellescape.Quote(val) - } - } - envFileKeys := keys(envfileEntries) - // sort to make tests consistent - sort.Slice(envFileKeys, func(i, j int) bool { - return envFileKeys[i] < envFileKeys[j] - }) - - // todo parameterize by shell - envCmdOutput := makeEnvCmdOutputLines(brevEnvKeys, envFileKeys, envfileEntries) - - return strings.Join(envCmdOutput, "\n") -} - -func makeEnvCmdOutputLines(brevEnvKeys, envFileKeys []string, envfileEntries envVars) []string { - envCmdOutput := []string{} - envCmdOutput = addUnsetEntriesToOutput(brevEnvKeys, envFileKeys, envCmdOutput) - envCmdOutput = append(envCmdOutput, addExportPrefix(envfileEntries)...) - newBrevEnvKeys := strings.Join(envFileKeys, ",") - newBrevEnvKeysEntry := "" - if newBrevEnvKeys != "" { - newBrevEnvKeysEntry = BrevManagedEnvVarsKey + "=" + newBrevEnvKeys - } - if newBrevEnvKeysEntry != "" { - envCmdOutput = append(envCmdOutput, "export "+newBrevEnvKeysEntry) - } - return collections.FilterEmpty(envCmdOutput) -} - -func addExportPrefix(envFile envVars) []string { - if len(envFile) == 0 { - return []string{} - } - out := []string{} - - // sorted order to make tests consistent - envFileKeys := keys(envFile) - for _, k := range envFileKeys { - out = append(out, fmt.Sprintf("%s %s=%s", "export", k, envFile[k])) - } - return out -} - -// return map's keys in sorted order -func keys(m map[string]string) []string { - out := []string{} - for k := range m { - out = append(out, k) - } - sort.Slice(out, func(i, j int) bool { - return out[i] < out[j] - }) - return out -} - -// this may be a good place to parameterize bby shell -func addUnsetEntriesToOutput(currentEnvs, newEnvs, output []string) []string { - for _, envKey := range currentEnvs { - if !collections.Contains(newEnvs, envKey) && envKey != "" { - output = append(output, "unset "+envKey) - } - } - return output -} - -// https://stackoverflow.com/a/38579502 -func zip(elements []string, elementMap map[string]string) map[string]string { - for i := 0; i < len(elements); i += 2 { - elementMap[elements[i]] = elements[i+1] - } - return elementMap -} - -func parse(content string) envVars { - keyValPairs := []string{} - lexer := lex("keys from env", content) - scanning := true - for scanning { - token := lexer.nextItem() - switch token.typ { - case itemKey, itemValue: - keyValPairs = append(keyValPairs, token.val) - case itemError: - return nil - case itemEOF: - scanning = false - - } - - } - if len(keyValPairs)%2 != 0 { - return nil - } - - return zip(keyValPairs, make(envVars)) -} diff --git a/pkg/cmd/configureenvvars/configureenvvars_test.go b/pkg/cmd/configureenvvars/configureenvvars_test.go deleted file mode 100644 index 01272f04..00000000 --- a/pkg/cmd/configureenvvars/configureenvvars_test.go +++ /dev/null @@ -1,433 +0,0 @@ -package configureenvvars - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" -) - -func Test_generateExportString(t *testing.T) { //nolint:funlen // this is a test - type args struct { - brevEnvsString string - envFileContents string - } - tests := []struct { - name string - args args - want string - }{ - // TODO: Add test cases. - { - name: "base case", - args: args{ - brevEnvsString: "", - envFileContents: "", - }, - want: "", - }, - // TODO: Add test cases. - { - name: "deletes env vars not in envfile", - args: args{ - brevEnvsString: "foo,bar,baz", - envFileContents: "", - }, - want: `unset foo -unset bar -unset baz`, - }, - { - name: "sets env var", - args: args{ - brevEnvsString: "", - envFileContents: "foo=bar", - }, - want: `export foo=bar -export ` + BrevManagedEnvVarsKey + `=foo`, - }, - { - name: "sets env var with export prefix", - args: args{ - brevEnvsString: "", - envFileContents: "export foo=bar", - }, - want: `export foo=bar -export ` + BrevManagedEnvVarsKey + `=foo`, - }, - { - name: "is idempotent", - args: args{ - brevEnvsString: "foo", - envFileContents: "foo=bar", - }, - want: `export foo=bar -export ` + BrevManagedEnvVarsKey + "=foo", - }, - { - name: "multiple operations(journal) case", - args: args{ - brevEnvsString: "key1,key2,key3", - envFileContents: "export key4=val", - }, - want: `unset key1 -unset key2 -unset key3 -export key4=val -export ` + BrevManagedEnvVarsKey + "=key4", - }, - { - name: "using env format found on workspace", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='bar';export alice='bob'`, - }, - want: `export alice='bob' -export foo='bar' -export ` + BrevManagedEnvVarsKey + "=alice,foo", - }, - { - name: "multi line file", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='bar'; -export alice='bob'`, - }, - want: `export alice='bob' -export foo='bar' -export ` + BrevManagedEnvVarsKey + "=alice,foo", - }, - { - name: "semicolon -> newline file ", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='bar'; - -export alice='bob'`, - }, - want: `export alice='bob' -export foo='bar' -export ` + BrevManagedEnvVarsKey + "=alice,foo", - }, - { - name: "hyphen in env var shouldn't be included since that's not allowed in most shells", - args: args{ - brevEnvsString: "", - envFileContents: `export NADER-TEST='nader-testing' `, - }, - want: ``, - }, - { - name: "if we have an invalid env var, we should not include it", - args: args{ - brevEnvsString: "", - envFileContents: `export f$*;_=nader-testing`, - }, - want: ``, - }, - { - name: "invalid keys should be ignored", - args: args{ - brevEnvsString: "", - envFileContents: `export f$*;=nader-testing`, - }, - want: ``, - }, - { - name: "values are escaped", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='90ie&$>'`, - }, - want: `export foo='90ie&$>' -export ` + BrevManagedEnvVarsKey + "=foo", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := generateExportString(tt.args.brevEnvsString, tt.args.envFileContents) - diff := cmp.Diff(tt.want, got) - if diff != "" { - t.Fatalf(diff) - } - }) - } -} - -func Test_addUnsetEntriesToOutput(t *testing.T) { - type args struct { - currentEnvs []string - newEnvs []string - output []string - } - tests := []struct { - name string - args args - want []string - }{ - { - name: "base case", - args: args{ - currentEnvs: []string{}, - newEnvs: []string{}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "base case with empty strings", - args: args{ - currentEnvs: []string{""}, - newEnvs: []string{""}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "preserves output", - args: args{ - currentEnvs: []string{""}, - newEnvs: []string{""}, - output: []string{""}, - }, - want: []string{""}, - }, - { - name: "when a current env is not in the list of new envs, unset it", - args: args{ - currentEnvs: []string{"foo"}, - newEnvs: []string{}, - output: []string{}, - }, - want: []string{"unset foo"}, - }, - { - name: "when a current env is new envs, don't unset it", - args: args{ - currentEnvs: []string{}, - newEnvs: []string{"foo"}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "when a current env is enpty entry, don't unset it", - args: args{ - currentEnvs: []string{""}, - newEnvs: []string{"foo"}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "when a current env is new envs and current envs, don't unset it", - args: args{ - currentEnvs: []string{"foo"}, - newEnvs: []string{"foo"}, - output: []string{}, - }, - want: []string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := addUnsetEntriesToOutput(tt.args.currentEnvs, tt.args.newEnvs, tt.args.output) - diff := cmp.Diff(tt.want, got) - if diff != "" { - t.Fatalf(diff) - } - }) - } -} - -func Test_parse(t *testing.T) { //nolint:funlen // this is a test - type args struct { - content string - } - tests := []struct { - name string - args args - want envVars - }{ - // TODO: Add test cases. - { - name: "base case", - args: args{ - content: "", - }, - want: envVars{}, - }, - { - name: "parses envs", - args: args{ - content: "foo=bar", - }, - want: envVars{"foo": "bar"}, - }, - { - name: "parses envs other format", - args: args{ - content: "export foo='bar';export alice='bob'", - }, - want: envVars{"foo": "'bar'", "alice": "'bob'"}, - }, - { - name: "export prefixed file works ", - args: args{ - content: `export foo=bar`, - }, - want: envVars{"foo": "bar"}, - }, - { - name: "multi line file works", - args: args{ - content: `export foo=bar -export alice=bob`, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "multi newline file works", - args: args{ - content: `export foo=bar - -export alice=bob`, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "env var with space in val", - args: args{ - content: `export fo o=bar`, - }, - want: nil, - }, - { - name: "env var with space in key", - args: args{ - content: `export foo=ba r`, - }, - want: nil, - }, - { - name: "leading spaces works", - args: args{ - content: ` export foo=bar - export alice=bob`, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "trailing spaces works", - args: args{ - content: `export foo=bar -export alice=bob `, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "export as key works", - args: args{ - content: `export foo=bar -export export=bob`, - }, - want: envVars{"foo": "bar", "export": "bob"}, - }, - { - name: "export as key works", - args: args{ - content: `export=bar`, - }, - want: envVars{"export": "bar"}, - }, - { - name: "invalid chars not parsed", - args: args{ - content: `foo&bar>baz=foo\&bar\>baz`, - }, - want: nil, - }, - { - name: "escaped values are parsed", - args: args{ - content: `foo='foo&bar>baz'`, - }, - want: envVars{"foo": "'foo&bar>baz'"}, - }, - { - name: "unescaped values are parsed", - args: args{ - content: `foo=foo&bar>baz`, - }, - want: envVars{"foo": "foo&bar>baz"}, - }, - // todo add these cases in the correct formaat - // {"Empty", "", "", ""}, - // {"Emptyish", " ", "", ""}, - // {"OnlyComment", "# ...", "", ""}, - // {"OnlyCommentish", " # ...", "", ""}, - // {"EmptyValue", "FoO=", "FoO", ""}, - // {"EmptyValueComment", "F=# ...", "F", ""}, - // {"EmptyValueSpace", "F_O= ", "F_O", ""}, - // {"EmptyValueSpaceComment", "F= # ...", "F", ""}, - // {"Simple", "FOO=bar", "FOO", "bar"}, - // {"Export", "export FOO=bar", "FOO", "bar"}, - // {"Spaces", " FOO = bar baz ", "FOO", "bar baz"}, - // {"Tabs", " FOO = bar ", "FOO", "bar"}, - // {"ExportSpaces", "export FOO = bar", "FOO", "bar"}, - // {"ExportAsKey", "export = bar", "export", "bar"}, - // {"Nums", "A1B2C3=a1b2c3", "A1B2C3", "a1b2c3"}, - // {"Comments", "FOO=bar # ok", "FOO", "bar"}, - // {"EmptyComments1", "FOO=#bar#", "FOO", ""}, - // {"EmptyComments2", "FOO= # bar ", "FOO", ""}, - // {"DoubleQuotes", `FOO="bar#"`, "FOO", "bar#"}, - // {"DoubleQuoteNewline", `FOO="bar\n"`, "FOO", "bar\n"}, - // {"DoubleQuoteNewlineComment", `FOO="bar\n" # comment`, "FOO", "bar\n"}, - // {"DoubleQuoteSpaces", `FOO = " bar\t" `, "FOO", " bar\t"}, - // {"SingleQuotes", "FOO='bar#'", "FOO", "bar#"}, - // {"SingleQuotesNewline", `FOO='\n' # empty`, "FOO", "\\n"}, - // {"SingleQuotesEmpty", "FOO='' # empty", "FOO", ""}, - // {"NormalSingleMix", "FOO=normal'single ' ", "FOO", "normalsingle "}, - // {"NormalDoubleMix", `FOO= "double\\" normal # "EOL"`, "FOO", "double\\ normal"}, - // {"AllModes", `export FOO = 'single\n' \\normal\t "double\"\n " # comment`, "FOO", "single\\n \\\\normal\\t double\"\n "}, - // {"UnicodeLiteral", "U1=\U0001F525", "U1", "\U0001F525"}, - // {"UnicodeLiteralQuoted", "U2= ' \U0001F525 ' ", "U2", " \U0001F525 "}, - // {"EscapedUnicode1byte", `U3="\u2318"`, "U3", "\U00002318"}, - // {"EscapedUnicode2byte", `U3="\uD83D\uDE01"`, "U3", "\U0001F601"}, - // {"EscapedUnicodeCombined", `U4="\u2318\uD83D\uDE01"`, "U4", "\U00002318\U0001F601"}, - // {"README.mdEscapedUnicode", `FOO="The template value\nmay have included\nsome newlines!\n\ud83d\udd25"`, "FOO", "The template value\nmay have included\nsome newlines!\nšŸ”„"}, - // {"UnderscoreKey", "_=x' ' ", "_", "x "}, - // {"DottedKey", "FOO.BAR=x", "FOO.BAR", "x"}, - // {"FwdSlashedKey", "FOO/BAR=x", "FOO/BAR", "x"}, - // {"README.md", `SOME_KEY = normal unquoted \text 'plus single quoted\' "\"double quoted " # EOL`, "SOME_KEY", `normal unquoted \text plus single quoted\ "double quoted `}, - // {"WindowsNewline", `w="\r\n"`, "w", "\r\n"}, - // {"MissingEqual", "foo bar", ErrMissingSeparator, ""}, - // {"EmptyKey", "=bar", ErrEmptyKey, ""}, - // {"EqualOnly", "=", ErrEmptyKey, ""}, - // {"InvalidKey", "1abc=x", nil, "key"}, - // {"InvalidKey2", "@abc=x", nil, "key"}, - // {"InvalidKey3", "a b c=x", nil, "key"}, - // {"InvalidKey4", "a\nb=x", nil, "key"}, - // {"InvalidValue", "FOO=\x00", nil, "value"}, - // {"OpenDoubleQuote", `FOO=" bar`, ErrUnmatchedDouble, ""}, - // {"OpenSingleQuote", `FOO=' bar`, ErrUnmatchedSingle, ""}, - // {"UnmatchedMix", `FOO=ok '"ok"' \"not ok ''`, ErrUnmatchedDouble, ""}, - // {"UnmatchedMix2", `FOO=ok '"ok"' \"not ok '"'`, ErrUnmatchedSingle, ""}, - // {"InvalidEscape", `FOO="\a"`, nil, `"a"`}, - // {"IncompleteEscape", `FOO="\`, ErrIncompleteEscape, ""}, - // {"IncompleteHex", `FOO="\u12"`, ErrIncompleteHex, ""}, - // {"InvalidHex", `FOO="\uabcZ"`, nil, `"Z"`}, - // {"IncompleteSurrogatePair1", `FOO="abc \uD83D"`, ErrIncompleteSur, ""}, - // {"IncompleteSurrogatePair2", `FOO="abc \uD83D \uDE01"`, ErrIncompleteSur, ""}, - // {"IncompleteSurrogatePair3", `FOO="abc \uD83DDE01"`, ErrIncompleteSur, ""}, - // {"IncompleteSurrogatePair4", `FOO="abc \uD83D\uDE0"`, nil, `"\""`}, - - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := parse(tt.args.content); !reflect.DeepEqual(got, tt.want) { - t.Errorf("parse() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/cmd/configureenvvars/lex.go b/pkg/cmd/configureenvvars/lex.go deleted file mode 100644 index 89926ca7..00000000 --- a/pkg/cmd/configureenvvars/lex.go +++ /dev/null @@ -1,266 +0,0 @@ -package configureenvvars - -import ( - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -type itemType int - -const ( - itemError itemType = iota - itemKey - itemValue - itemEquals - itemSemiColon - itemNewline - itemSpace - itemTab - itemEOF -) - -type item struct { - typ itemType - val string -} - -func (i item) String() string { - switch { - case i.typ == itemEOF: - return "EOF" - case i.typ == itemError: - return i.val - } - return fmt.Sprintf("<%s>", i.val) -} - -// a function that returns a statefn -type stateFn func(*lexer) stateFn - -type lexer struct { - name string // used in error reports - input string // string being scanned - start int // start position of this item - pos int // current position of this item - width int // width of the last rune read - items chan item // last scanned item - state stateFn -} - -func lex(name, input string) *lexer { - l := &lexer{ - name: name, - input: input, - state: lexText, - items: make(chan item, 2), - } - go l.run() // concurrently begin lexing - return l -} - -// synchronously receive an item from lexer -func (l *lexer) nextItem() item { - return <-l.items -} - -func (l *lexer) run() { - for state := lexText; state != nil; { - state = state(l) - } - close(l.items) // no more tokens will be delivered -} - -func (l *lexer) emit(t itemType) { - l.items <- item{t, l.input[l.start:l.pos]} - l.start = l.pos -} - -const eof = -1 - -// next returns the next rune in the input. -func (l *lexer) next() rune { - if l.pos >= len(l.input) { - l.width = 0 - return eof - } - r, w := utf8.DecodeRuneInString(l.input[l.pos:]) - l.width = w - l.pos += l.width - return r -} - -// peek returns but does not consume the next rune in the input. -func (l *lexer) peek() rune { - r := l.next() - l.backup() - return r -} - -// backup steps back one rune. Can only be called once per call of next. -func (l *lexer) backup() { - l.pos -= l.width -} - -func (l *lexer) errorf(format string, args ...interface{}) stateFn { - l.items <- item{ - itemError, - fmt.Sprintf(format, args...), - } - return nil -} - -const ( - equalPrefix = "=" - space = " " - tab = "\t" -) - -func lexText(l *lexer) stateFn { - for { - if strings.HasPrefix(l.input[l.pos:], tab) { - return lexTab - } - if strings.HasPrefix(l.input[l.pos:], space) { - return lexSpace - } - if strings.HasPrefix(l.input[l.pos:], newline) { - return lexNewline - } - if strings.HasPrefix(l.input[l.pos:], semicolon) { - return lexSemiColon - } - if strings.HasPrefix(l.input[l.pos:], equalPrefix) { - return lexKey // next state - } - if l.next() == eof { - break - } - } - if len(l.input[l.start:l.pos]) != 0 { - return l.errorf("unexpected eof") - } - l.emit(itemEOF) - return nil -} - -func lexKey(l *lexer) stateFn { - s := l.input[l.start:l.pos] - // determine if s alphanumeric or an underscore - // https://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names - for _, r := range s { - if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') { - return l.errorf("unexpected key character: %q", r) - } - } - - l.emit(itemKey) - return lexEquals -} - -func lexEquals(l *lexer) stateFn { - l.next() - l.emit(itemEquals) - r := l.peek() - switch r { - case '\'', '"': - return lexQuotedValue - default: - return lexValue - } -} - -func lexSemiColon(l *lexer) stateFn { - l.next() - if l.input[l.start:l.pos] != semicolon { - return l.errorf("unexpected semicolon") - } - l.emit(itemSemiColon) - return lexText -} - -func lexNewline(l *lexer) stateFn { - l.next() - if l.input[l.start:l.pos] != "\n" { - return l.errorf("unexpected newline") - } - l.emit(itemNewline) - return lexText -} - -func lexSpace(l *lexer) stateFn { - if strings.HasPrefix(l.input[l.start:l.pos], "export") { - l.start += len("export") - } - l.next() - if l.input[l.start:l.pos] != space { - return l.errorf("unexpected space") - } - l.emit(itemSpace) - return lexText -} - -func lexTab(l *lexer) stateFn { - l.next() - if l.input[l.start:l.pos] != tab { - return l.errorf("unexpected tab") - } - l.emit(itemTab) - return lexText -} - -const ( - semicolon = ";" - newline = "\n" -) - -func lexValue(l *lexer) stateFn { - for { - if strings.HasPrefix(l.input[l.pos:], semicolon) { - l.emit(itemValue) - return lexSemiColon - } - if strings.HasPrefix(l.input[l.pos:], newline) { - l.emit(itemValue) - return lexNewline - - } - if strings.HasPrefix(l.input[l.pos:], space) { - l.emit(itemValue) - return lexText - - } - if strings.HasPrefix(l.input[l.pos:], tab) { - l.emit(itemValue) - return lexText - - } - if l.next() == eof { - l.emit(itemValue) - l.emit(itemEOF) - return nil - } - } -} - -func lexQuotedValue(l *lexer) stateFn { - endQuote := map[rune]string{ - '\'': "'", - '"': "\"", - }[l.next()] - for { - if strings.HasPrefix(l.input[l.pos:], newline) { - return l.errorf("unexpected newline") - } - if strings.HasPrefix(l.input[l.pos:], endQuote) { - l.next() - l.emit(itemValue) - return lexText - } - - if l.next() == eof { - l.errorf("unexpected eof") - } - } -} diff --git a/pkg/cmd/configureenvvars/lex_test.go b/pkg/cmd/configureenvvars/lex_test.go deleted file mode 100644 index 56cc3a7a..00000000 --- a/pkg/cmd/configureenvvars/lex_test.go +++ /dev/null @@ -1,710 +0,0 @@ -package configureenvvars - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func Test_lex(t *testing.T) { //nolint:funlen // this is a test - type args struct { - input string - } - tests := []struct { - name string - args args - want []item - }{ - { - name: "base case", - args: args{ - input: "", - }, - want: []item{{ - typ: itemEOF, - val: "", - }}, - }, - { - name: "key=val works", - args: args{ - input: "key=val", - }, - want: []item{ - { - typ: itemKey, - val: "key", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "val", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "parses envs other format", - args: args{ - input: "export foo='bar';export alice='bob'", - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "'bar'", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "'bob'", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "export prefixed file works ", - args: args{ - input: `export foo=bar`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "multi line file works", - args: args{ - input: `export foo=bar -export alice=bob`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "multi newline file works", - args: args{ - input: `export foo=bar - -export alice=bob`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemNewline, - val: "\n", - }, - - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "semi colon -> newline file works", - args: args{ - input: `export foo=bar; - -export alice=bob`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemNewline, - val: "\n", - }, - - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "leading newline file works", - args: args{ - input: ` -export foo=bar; - -export alice=bob`, - }, - want: []item{ - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemNewline, - val: "\n", - }, - - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "trailing space with semi colon at end", - args: args{ - input: `foo=bar ;`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "trailing space", - args: args{ - input: `foo=bar `, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSpace, - val: " ", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "leading space", - args: args{ - input: ` foo=bar`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "spaces in vals with quotes", - args: args{ - input: `foo='b ar'`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "'b ar'", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "spaces in vals without quotes", - args: args{ - input: `foo=b ar`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "b", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemError, - val: "unexpected eof", - }, - }, - }, - { - name: "spaces in vals without quotes, multiline", - args: args{ - input: `foo=b ar -alice=bob`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "b", - }, - { - typ: itemSpace, - val: " ", - }, - - { - typ: itemError, - val: "unexpected newline", - }, - }, - }, - { - name: "spaces in keys", - args: args{ - input: `fo o=bar`, - }, - want: []item{ - { - typ: itemError, - val: "unexpected space", - }, - }, - }, - { - name: "lower case export in env var name with space after doesn't screw things up", - args: args{ - input: `foexport o=bar`, - }, - want: []item{ - { - typ: itemError, - val: "unexpected space", - }, - }, - }, - { - name: "tabs instead of spaces", - args: args{ - input: ` foo=bar`, - }, - want: []item{ - { - typ: itemTab, - val: "\t", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "tabs value", - args: args{ - input: ` foo=ba r`, - }, - want: []item{ - { - typ: itemTab, - val: "\t", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "ba", - }, - { - typ: itemTab, - val: "\t", - }, - { - typ: itemError, - val: "unexpected eof", - }, - }, - }, - { - name: "quoted value newline", - args: args{ - input: `foo="bar -"`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemError, - val: "unexpected newline", - }, - }, - }, - { - name: "quoted value eof", - args: args{ - input: `foo="bar`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemError, - val: "unexpected eof", - }, - }, - }, - { - name: "don't include invalid env var names", - args: args{ - input: `export f$*;=nader-testing`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemError, - val: "unexpected semicolon", - }, - }, - }, - { - name: "don't include invalid env var names w/o semicolon", - args: args{ - input: `export f$*=nader-testing`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemError, - val: "unexpected key character: '$'", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := lex(tt.name, tt.args.input) - out := []item{} - - for { - token := got.nextItem() - out = append(out, token) - if token.typ == itemEOF || token.typ == itemError { - break - } - - } - diff := cmp.Diff(out, tt.want, cmp.AllowUnexported(item{})) - if diff != "" { - t.Fatalf(diff) - } - }) - } -} diff --git a/pkg/cmd/connect/connect.go b/pkg/cmd/connect/connect.go deleted file mode 100644 index dbb26346..00000000 --- a/pkg/cmd/connect/connect.go +++ /dev/null @@ -1,57 +0,0 @@ -package connect - -import ( - "github.com/spf13/cobra" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - short = "Connect Brev to your AWS account" - long = "Connect Brev to your AWS account" - example = "brev connect" -) - -type connectStore interface{} - -func NewCmdConnect(t *terminal.Terminal, store connectStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "connect-aws", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - err := RunConnect(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func RunConnect(t *terminal.Terminal, _ []string, _ connectStore) error { - t.Vprintf("Connect the AWS IAM user to create instances in your AWS account.\n") - t.Vprintf(t.Yellow("\tFollow the guide here: %s", "https://onboarding.brev.dev/connect-aws\n\n")) - // t.Vprintf(t.Yellow("Connect the AWS IAM user to create dev environments in your AWS account.\n\n")) - - AccessKeyID := terminal.PromptGetInput(terminal.PromptContent{ - Label: "Access Key ID: ", - ErrorMsg: "error", - }) - - SecretAccessKey := terminal.PromptGetInput(terminal.PromptContent{ - Label: "Secret Access Key: ", - ErrorMsg: "error", - Mask: '*', - }) - - t.Vprintf("\n") - t.Vprintf(AccessKeyID) - t.Vprintf(SecretAccessKey) - - return nil -} diff --git a/pkg/cmd/create/create.go b/pkg/cmd/create/create.go deleted file mode 100644 index e9fbfd32..00000000 --- a/pkg/cmd/create/create.go +++ /dev/null @@ -1,206 +0,0 @@ -package create - -import ( - "fmt" - "strings" - "time" - - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - -var ( - createLong = "Create a new Brev machine" - createExample = ` - brev create - ` - // instanceTypes = []string{"p4d.24xlarge", "p3.2xlarge", "p3.8xlarge", "p3.16xlarge", "p3dn.24xlarge", "p2.xlarge", "p2.8xlarge", "p2.16xlarge", "g5.xlarge", "g5.2xlarge", "g5.4xlarge", "g5.8xlarge", "g5.16xlarge", "g5.12xlarge", "g5.24xlarge", "g5.48xlarge", "g5g.xlarge", "g5g.2xlarge", "g5g.4xlarge", "g5g.8xlarge", "g5g.16xlarge", "g5g.metal", "g4dn.xlarge", "g4dn.2xlarge", "g4dn.4xlarge", "g4dn.8xlarge", "g4dn.16xlarge", "g4dn.12xlarge", "g4dn.metal", "g4ad.xlarge", "g4ad.2xlarge", "g4ad.4xlarge", "g4ad.8xlarge", "g4ad.16xlarge", "g3s.xlarge", "g3.4xlarge", "g3.8xlarge", "g3.16xlarge"} -) - -type CreateStore interface { - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) -} - -func NewCmdCreate(t *terminal.Terminal, createStore CreateStore) *cobra.Command { - var detached bool - var gpu string - var cpu string - - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "create", - DisableFlagsInUseLine: true, - Short: "Create a new instance ", - Long: createLong, - Example: createExample, - RunE: func(cmd *cobra.Command, args []string) error { - name := "" - if len(args) > 0 { - name = args[0] - } - - err := runCreateWorkspace(t, CreateOptions{ - Name: name, - WorkspaceClass: cpu, - Detached: detached, - InstanceType: gpu, - }, createStore) - if err != nil { - if strings.Contains(err.Error(), "duplicate instance with name") { - t.Vprint(t.Yellow("try running:")) - t.Vprint(t.Yellow("\tbrev start --name [different name] [repo] # or")) - t.Vprint(t.Yellow("\tbrev delete [name]")) - } - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - cmd.Flags().BoolVarP(&detached, "detached", "d", false, "run the command in the background instead of blocking the shell") - cmd.Flags().StringVarP(&cpu, "cpu", "c", "", "CPU instance type. Defaults to 2x8 [2x8, 4x16, 8x32, 16x32]. See docs.brev.dev/cpu for details") - cmd.Flags().StringVarP(&gpu, "gpu", "g", "n1-highmem-4:nvidia-tesla-t4:1", "GPU instance type. See https://brev.dev/docs/reference/gpu for details") - return cmd -} - -type CreateOptions struct { - Name string - WorkspaceClass string - Detached bool - InstanceType string -} - -func runCreateWorkspace(t *terminal.Terminal, options CreateOptions, createStore CreateStore) error { - user, err := createStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = createEmptyWorkspace(user, t, options, createStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func createEmptyWorkspace(user *entity.User, t *terminal.Terminal, options CreateOptions, createStore CreateStore) error { - // ensure name - if len(options.Name) == 0 { - return breverrors.NewValidationError("A name field is required to create a workspace!") - } - - // ensure org - var orgID string - activeorg, err := createStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - - clusterID := config.GlobalConfig.GetDefaultClusterID() - cwOptions := store.NewCreateWorkspacesOptions(clusterID, options.Name) - - if options.WorkspaceClass != "" { - cwOptions.WithClassID(options.WorkspaceClass) - } - - cwOptions = resolveWorkspaceUserOptions(cwOptions, user) - - if options.InstanceType != "" { - cwOptions.WithInstanceType(options.InstanceType) - } - - t.Vprintf("Creating instane %s in org %s\n", t.Green(cwOptions.Name), t.Green(orgID)) - t.Vprintf("\tname %s\n", t.Green(cwOptions.Name)) - if options.InstanceType != "" { - t.Vprintf("\tGPU instance %s\n", t.Green(options.InstanceType)) - } else { - t.Vprintf("\tCPU instance %s\n", t.Green(cwOptions.WorkspaceClassID)) - } - t.Vprintf("\tCloud %s\n\n", t.Green(cwOptions.WorkspaceGroupID)) - - s := t.NewSpinner() - s.Suffix = " Creating your instance. Hang tight šŸ¤™" - s.Start() - w, err := createStore.CreateWorkspace(orgID, cwOptions) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Stop() - - if options.Detached { - return nil - } else { - err = pollUntil(t, w.ID, entity.Running, createStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - fmt.Print("\n") - t.Vprint(t.Green("Your instance is ready!\n")) - displayConnectBreadCrumb(t, w) - - return nil - } -} - -func resolveWorkspaceUserOptions(options *store.CreateWorkspacesOptions, user *entity.User) *store.CreateWorkspacesOptions { - if options.WorkspaceTemplateID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceTemplateID = store.DevWorkspaceTemplateID - } else { - options.WorkspaceTemplateID = store.UserWorkspaceTemplateID - } - } - if options.WorkspaceClassID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceClassID = store.DevWorkspaceClassID - } else { - options.WorkspaceClassID = store.UserWorkspaceClassID - } - } - return options -} - -func displayConnectBreadCrumb(t *terminal.Terminal, workspace *entity.Workspace) { - t.Vprintf(t.Green("Connect to the instance:\n")) - t.Vprintf(t.Yellow(fmt.Sprintf("\tbrev open %s\t# brev open -> open instance in VS Code\n", workspace.Name))) - t.Vprintf(t.Yellow(fmt.Sprintf("\tbrev shell %s\t# brev shell -> ssh into instance (shortcut)\n", workspace.Name))) - // t.Vprintf(t.Yellow(fmt.Sprintf("\tssh %s\t# ssh -> ssh directly to dev environment\n", workspace.GetLocalIdentifier()))) -} - -func pollUntil(t *terminal.Terminal, wsid string, state string, createStore CreateStore, canSafelyExit bool) error { - s := t.NewSpinner() - isReady := false - if canSafelyExit { - t.Vprintf("You can safely ctrl+c to exit\n") - } - s.Suffix = " hang tight šŸ¤™" - s.Start() - for !isReady { - time.Sleep(5 * time.Second) - ws, err := createStore.GetWorkspace(wsid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Suffix = " instance is " + strings.ToLower(ws.Status) - if ws.Status == state { - s.Suffix = "Instance is ready!" - s.Stop() - isReady = true - } - } - return nil -} diff --git a/pkg/cmd/create/create_test.go b/pkg/cmd/create/create_test.go deleted file mode 100644 index ef4f218a..00000000 --- a/pkg/cmd/create/create_test.go +++ /dev/null @@ -1 +0,0 @@ -package create diff --git a/pkg/cmd/create/doc.md b/pkg/cmd/create/doc.md deleted file mode 100644 index fb97a991..00000000 --- a/pkg/cmd/create/doc.md +++ /dev/null @@ -1,268 +0,0 @@ -create start and join a workspace - -## Synopsis - -``` - -brev start { ARG | -e} {-n | --name} {-c | --class} { -s | --setup-script} - {-r | --setup-repo} {-p | --setup-path } { -o | --org} -``` - -## Description - -brev start can do the following: - -- start a stopped workspace -- join a workspace in an organization -- create an empty workspace -- create a workspace from a directory on your computer -- create a workspace from a git url - -## Flags - -### -n --name - -specify the name for your workspace instead of brev-cli generating one for you. - -for example, to override the name of a workspace when creating a workspace from -a git repo you could do it with then `-n` flag. This example creates a repo with -the name `cli` from the git repo `https://github.com/brevdev/brev-cli`. - -``` -$ brev start https://github.com/brevdev/brev-cli -n cli -``` - -## Examples - -### Create an empty workspace - -``` -$ brev start -e -n foo -``` - -which has an output similar too: - -``` -name foo -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -workspace is starting. this can take up to 2 minutes the first time. - -you can safely ctrl+c to exit -⣽ workspace is deploying -your workspace is ready! - -connect to the workspace: - brev open foo # brev open -> open workspace in preferred editor - brev shell foo # brev shell -> ssh into workspace (shortcut) - ssh foo-8j4u # ssh -> ssh directly to workspace - -``` - -or - -``` -$ brev start --empty --name foo -``` - -which has an output similar too: - -``` -name foo -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -workspace is starting. this can take up to 2 minutes the first time. - -you can safely ctrl+c to exit -⣽ workspace is deploying -your workspace is ready! - -connect to the workspace: - brev open foo # brev open -> open workspace in preferred editor - brev shell foo # brev shell -> ssh into workspace (shortcut) - ssh foo-8j4u # ssh -> ssh directly to workspace - -``` - -view your workspace with `brev ls` - -### create a workspace, and do not block shell until workspace is created - -use the `-d` or `--detached` flag to create a workspace and immediately exit -rather than wait for workspace to be successfully created before exiting. - -``` -$ brev start -d -e -n bar -``` - -which has an output similar too: - -``` -name bar -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -Workspace is starting. This can take up to 2 minutes the first time. -``` - -### Create a workspace from a file path - -if in your current directory has a directory in it called `merge-json`, you can -create a workspace using the contents of that directory using -`brev start merge-json` - -``` -$ ls -merge-json -``` - -``` -$ brev start merge-json - -``` - -which has an output similar too: - -``` -Workspace is starting. This can take up to 2 minutes the first time. - -name merge-json -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -┿ workspace is deploying -Your workspace is ready! - -Connect to the workspace: - brev open merge-json # brev open -> open workspace in preferred editor - brev shell merge-json # brev shell -> ssh into workspace (shortcut) - ssh merge-json-wd6q # ssh -> ssh directly to workspace -``` - -### Create a workspace from a git repository - -``` -$ brev start https://github.com/brevdev/react-starter-app -``` - -which has an output similar too: - -``` -Workspace is starting. This can take up to 2 minutes the first time. - -name react-starter-app -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⣾ workspace is deploying -Your workspace is ready! - -Connect to the workspace: - brev open react-starter-app # brev open -> open workspace in preferred editor - brev shell react-starter-app # brev shell -> ssh into workspace (shortcut) - ssh react-starter-app-8v8p # ssh -> ssh directly to workspace - -``` - -### Join a workspace in your orginization - -view your orgs workspaces with `brev ls --all`. Workspaces in your org that you -have not joined appear at the bottom of the output. - -``` -$ brev ls --all -``` - -which has an output similar too: - -``` -You have 1 workspace in Org brev.dev - NAME STATUS URL ID - brev-cli RUNNING brev-cli-p09m-brevdev.wgt-us-west-2-test.brev.dev x1yxqp09m - -Connect to running workspace: - brev open brev-cli # brev open -> open workspace in preferred editor - brev shell brev-cli # brev shell -> ssh into workspace (shortcut) - ssh brev-cli-p09m # ssh -> ssh directly to workspace - -7 other projects in Org brev.dev - NAME MEMBERS - new-docs 1 - brev-landing-page 2 - todo-app 1 - vagrant-guide 1 - mern-template 1 - solidity-nextjs-starter 1 - akka-http-quickstart-scala 1 - -Join a project: - brev start new-docs - -``` - -join the project new-docs - -``` -$ brev start new-docs -``` - -which has an output similar too: - -``` -Name flag omitted, using auto generated name: new-docs -Workspace is starting. This can take up to 2 minutes the first time. - -name new-docs -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⣟ workspace is deploying Connect to the workspace: - brev open new-docs # brev open -> open workspace in preferred editor - brev shell new-docs # brev shell -> ssh into workspace (shortcut) - ssh new-docs-pek9 # ssh -> ssh directly to workspace -``` - -### Start a stopped workspace - -If you have already joined a workspace and have stopped it with `brev stop`, -you can start it again with `brev start` - -view your current workspaces with `brev ls` - -``` -$ brev ls -``` - -which has an output similar too: - -``` -You have 1 workspace in Org brev.dev - NAME STATUS URL ID - linear-client STOPPED linear-client-yw1a-brevdev.wgt-us-west-2-test.brev.dev gov5jyw1a - -Connect to running workspace: - brev open linear-client # brev open -> open workspace in preferred editor - brev shell linear-client # brev shell -> ssh into workspace (shortcut) - ssh linear-client-yw1a # ssh -> ssh directly to workspace - -``` - -join the workspace - -``` -$ brev start linear-client -``` - -which has an output similar too: - -``` -Workspace linear-client is starting. -Note: this can take about a minute. Run 'brev ls' to check status - -You can safely ctrl+c to exit -``` diff --git a/pkg/cmd/envsetup/envsetup.go b/pkg/cmd/envsetup/envsetup.go deleted file mode 100644 index 03ba625d..00000000 --- a/pkg/cmd/envsetup/envsetup.go +++ /dev/null @@ -1,743 +0,0 @@ -package envsetup - -import ( - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "os/user" - "path/filepath" - "strings" - "time" - - "github.com/hashicorp/go-multierror" - "github.com/spf13/afero" - "github.com/spf13/cobra" - - _ "embed" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - "github.com/brevdev/brev-cli/pkg/cmd/updatemodel" - "github.com/brevdev/brev-cli/pkg/cmd/version" - "github.com/brevdev/brev-cli/pkg/collections" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/setupworkspace" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/util" -) - -type envsetupStore interface { - GetEnvSetupParams(wsid string) (*store.SetupParamsV0, error) - WriteSetupScript(script string) error - GetSetupScriptPath() string - GetCurrentUser() (*entity.User, error) - GetCurrentWorkspaceID() (string, error) - GetOSUser() string - GetOrCreateSetupLogFile(path string) (afero.File, error) - GetBrevHomePath() (string, error) - BuildBrevHome() error - CopyBin(targetBin string) error - WriteString(path, data string) error - UserHomeDir() (string, error) - Remove(target string) error - FileExists(target string) (bool, error) - DownloadBinary(url string, target string) error - AppendString(path string, content string) error - Chmod(path string, mode os.FileMode) error - ChownFilePathToUser(path string) error - OverWriteString(path string, content string) error - GetFileAsString(path string) (string, error) -} - -type nologinEnvStore interface { - LoginWithToken(token string) error -} - -const name = "envsetup" - -func NewCmdEnvSetup(store envsetupStore, noLoginStore nologinEnvStore) *cobra.Command { - var forceEnableSetup bool - // add debugger flag to toggle features when running command through a debugger - // this is useful for debugging setup scripts - debugger := false - configureSystemSSHConfig := true - - // if a token flag is supplied, log in with it - var token string - - var datadogAPIKey string - - var disableAutostop bool - - var reportInterval string - - var autostopPort string - cmd := &cobra.Command{ - Use: name, - DisableFlagsInUseLine: true, - Short: "TODO", - Long: "TODO", - Example: "TODO", - RunE: func(cmd *cobra.Command, args []string) error { - var errors error - for _, arg := range args { - err := RunEnvSetup( - store, - name, - forceEnableSetup, - debugger, - configureSystemSSHConfig, - arg, - token, - noLoginStore, - datadogAPIKey, - disableAutostop, - reportInterval, - autostopPort, - ) - if err != nil { - errors = multierror.Append(err) - } - } - if errors != nil { - return breverrors.WrapAndTrace(errors) - } - return nil - }, - } - cmd.PersistentFlags().BoolVar(&forceEnableSetup, "force-enable", false, "force the setup script to run despite params") - cmd.PersistentFlags().BoolVar(&debugger, "debugger", debugger, "toggle features that don't play well with debuggers") - cmd.PersistentFlags().BoolVar(&configureSystemSSHConfig, "configure-system-ssh-config", configureSystemSSHConfig, "configure system ssh config") - cmd.PersistentFlags().StringVar(&token, "token", "", "token to use for login") - cmd.PersistentFlags().StringVar(&datadogAPIKey, "datadog-api-key", "", "datadog API key to use for logging") - cmd.PersistentFlags().BoolVar(&disableAutostop, "disable-autostop", false, "disable autostop") - cmd.PersistentFlags().StringVar(&reportInterval, "report-interval", "10m", "report interval") - cmd.PersistentFlags().StringVar(&autostopPort, "autostop-port", "22", "autostop port") - - return cmd -} - -func RunEnvSetup( - store envsetupStore, - name string, - forceEnableSetup, debugger, configureSystemSSHConfig bool, - workspaceid, token string, - noLoginStore nologinEnvStore, - datadogAPIKey string, - disableAutostop bool, - reportInterval string, - portToCheckAutostopTrafficOn string, -) error { - if token != "" { - err := noLoginStore.LoginWithToken(token) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - breverrors.GetDefaultErrorReporter().AddTag("command", name) - _, err := store.GetCurrentWorkspaceID() // do this to error reporting - if err != nil { - return breverrors.WrapAndTrace(err) - } - fmt.Println("setting up instance") - - params, err := store.GetEnvSetupParams(workspaceid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - res, err := json.MarshalIndent(params, "", "") - if err != nil { - return breverrors.WrapAndTrace(err) - } - fmt.Println(string(res)) - - if !featureflag.IsDev() && !debugger { - _, err = store.GetCurrentUser() // do this to set error user reporting - if err != nil { - fmt.Println(err) - if !params.DisableSetup { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err, "setup continued")) - } - } - } - - if !forceEnableSetup && params.DisableSetup { - fmt.Printf("WARNING: setup script not running [params.DisableSetup=%v, forceEnableSetup=%v]", params.DisableSetup, forceEnableSetup) - return nil - } - - err = setupEnv( - store, - params, - configureSystemSSHConfig, - datadogAPIKey, - disableAutostop, - reportInterval, - portToCheckAutostopTrafficOn, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - fmt.Println("done setting up instance") - return nil -} - -type envInitier struct { - setupworkspace.WorkspaceIniter - ConfigureSystemSSHConfig bool - brevMonConfigurer autostartconf.DaemonConfigurer - datadogAPIKey string - store envsetupStore -} - -func appendLogToFile(content string, file string) error { - cmd := setupworkspace.CmdStringBuilder(fmt.Sprintf(`echo "%s" >> %s`, content, file)) - err := cmd.Run() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -//go:embed motd.sh -var motd string - -func (e *envInitier) SetupMOTD() error { - err := e.store.OverWriteString("/etc/ssh/my_banner", motd) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - fstring, err := e.store.GetFileAsString("/etc/ssh/sshd_config") - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if !strings.Contains(fstring, "Banner /etc/ssh/my_banner") { - err = e.store.AppendString("/etc/ssh/sshd_config", "Banner /etc/ssh/my_banner") - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - err = setupworkspace.BuildAndRunCmd("systemctl", "reload", "ssh.service") - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -//go:embed speedtest.py -var speedtest string - -func (e *envInitier) SetupSpeedTest() error { - err := e.store.WriteString("/usr/local/bin/speedtest", speedtest) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = e.store.Chmod("/usr/local/bin/speedtest", 0o755) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (e *envInitier) SetupUpdateModel() error { - dc := updatemodel.DaemonConfigurer{ - Store: e.store, - } - err := dc.Configure() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (e envInitier) Setup() error { //nolint:funlen,gocyclo // TODO - var setupErr error - - err := appendLogToFile("setup started", "/var/log/brev-setup-steps.log") - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - - err = setupworkspace.BuildAndRunCmd("systemctl", "stop", "unattended-upgrades") - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - out, err := setupworkspace.RunCMDWithOutput("apt-get", "-y", "remove", "unattended-upgrades") - if err != nil { - setupErr = multierror.Append(setupErr, - breverrors.WrapAndTrace(err, "apt-get -y remove unattended-upgrades", out)) - } - - cmd := setupworkspace.CmdStringBuilder("echo user: $(whoami) && echo pwd: $(pwd)") - err = cmd.Run() - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - - postPrepare := util.RunEAsync( - e.SetupVsCodeExtensions, - e.SetupSpeedTest, - e.SetupMOTD, - e.SetupUpdateModel, - ) - - err = util.RunEAsync( - e.SetupSSH, - e.SetupGit, - ).Await() - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - - err = appendLogToFile("starting repo setup", "/var/log/brev-steps.log") - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - err = e.SetupRepos() - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - fmt.Println("------ Git repo cloned ------") - err = appendLogToFile("repo setup done", "/var/log/brev-steps.log") - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - - err = e.SetupEnvVars() // here - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - err = appendLogToFile("starting to run execs", "/var/log/brev-steps.log") - if err != nil { - setupErr = multierror.Append(setupErr, breverrors.WrapAndTrace(err)) - } - err = e.RunExecs() // here - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - - err = e.CreateVerbYamlFile() // create - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - - fmt.Println("------ Done running execs ------") - err = appendLogToFile("done running execs", "/var/log/brev-steps.log") - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - - err = e.brevMonConfigurer.Install() - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - - if e.datadogAPIKey != "" { - err = e.SetupDatadog() - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - } - - err = postPrepare.Await() - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - - err = appendLogToFile("setup done", "/var/log/brev-steps.log") - if err != nil { - setupErr = multierror.Append(breverrors.WrapAndTrace(err)) - } - - if setupErr != nil { - return breverrors.WrapAndTrace(setupErr) - } - - return nil -} - -func (e envInitier) SetupDatadog() error { - installScriptURL := "https://s3.amazonaws.com/dd-agent/scripts/install_script.sh" - var installScript string - - resp, err := collections.GetRequestWithContext(context.TODO(), installScriptURL) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - defer resp.Body.Close() //nolint: errcheck // we don't care about the error here b/c defer - - if resp.StatusCode != 200 { - return breverrors.WrapAndTrace(fmt.Errorf("failed to download datadog install script")) - } - - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - installScript = string(bodyBytes) - - cmd := setupworkspace.CmdStringBuilder(installScript) - - cmd.Env = append(cmd.Env, - append( - os.Environ(), - []string{ - "DD_API_KEY=" + e.datadogAPIKey, - "DD_AGENT_MAJOR_VERSION=7", - "DD_SITE=\"datadoghq.com\"", - }..., - )...) - - err = cmd.Run() - if err != nil { - out, err0 := cmd.CombinedOutput() - if err0 != nil { - return breverrors.WrapAndTrace(err0) - } - return breverrors.WrapAndTrace(fmt.Errorf("failed to install datadog agent: %s", string(out))) - } - - err = e.store.WriteString("/etc/datadog-agent/conf.d/systemd.d/conf.yaml", ` -init_config: -instances: - ## @param unit_names - list of strings - required - ## List of systemd units to monitor. - ## Full names must be used. Examples: ssh.service, docker.socket - # - - unit_names: - - ssh.service - - brevmon.service -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = e.store.WriteString("/etc/datadog-agent/conf.d/journald.d/conf.yaml", ` -logs: - - type: journald - path: /var/log/journal/ - include_units: - - brevmon.service - - sshd.service -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = setupworkspace.BuildAndRunCmd( - "/usr/sbin/usermod", - "-a", - "-G", - "systemd-journal", - "dd-agent", - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - hostname, _ := os.Hostname() - stringToAppend := fmt.Sprintf("\nlogs_enabled: true\nhostname: %s\n", hostname) - // add logs_enabled: true to /etc/datadog-agent/datadog.yaml - err = e.store.AppendString("/etc/datadog-agent/datadog.yaml", stringToAppend) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = setupworkspace.BuildAndRunCmd( - "systemctl", - "restart", - "datadog-agent", - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -type setupKeyI interface { - WriteString(path, content string) error - Chmod(path string, mode os.FileMode) error - ChownFilePathToUser(path string) error -} - -func setupKey(path, content string, perm os.FileMode, store setupKeyI) error { - err := store.WriteString(path, content) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = store.Chmod(path, perm) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = store.ChownFilePathToUser(path) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (e envInitier) setupPrivateKey(content string) error { - pkpath := e.BuildHomePath(".ssh", "id_rsa") - err := setupKey(pkpath, content, 0o600, e.store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (e envInitier) setupPublicKey(content string) error { - pubkeypath := e.BuildHomePath(".ssh", "id_rsa.pub") - err := setupKey(pubkeypath, content, 0o644, e.store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (e envInitier) SetupSSHKeys(keys *store.KeyPair) error { - err := e.setupPrivateKey(keys.PrivateKeyData) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = e.setupPublicKey(keys.PublicKeyData) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (e envInitier) SetupSSH() error { - keys := e.Params.WorkspaceKeyPair - err := e.SetupSSHKeys(keys) - if err != nil { - return breverrors.WrapAndTrace(err) - } - c := fmt.Sprintf(`eval "$(ssh-agent -s)" && ssh-add %s`, e.BuildHomePath(".ssh", "id_rsa")) - cmd := setupworkspace.CmdStringBuilder(c) - err = cmd.Run() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - authorizedKeyPath := e.BuildHomePath(".ssh", "authorized_keys") - - err = e.store.AppendString(authorizedKeyPath, "\n"+keys.PublicKeyData) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = e.store.ChownFilePathToUser(authorizedKeyPath) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if e.ConfigureSystemSSHConfig { - err = e.store.WriteString( - filepath.Join( - "/etc", - "ssh", - "sshd_config.d", - fmt.Sprintf("%s.conf", e.User.Username), - ), - fmt.Sprintf( - `PubkeyAuthentication yes -AuthorizedKeysFile %s -PasswordAuthentication no`, authorizedKeyPath), - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - return nil -} - -func (e envInitier) SetupEnvVars() error { - // set env vars - err := e.store.AppendString("/etc/bash.bashrc", ` -_brev_hook() { - local previous_exit_status=$?; - trap -- '' SIGINT; - eval "$(/usr/local/bin/brev configure-env-vars bash)"; - trap - SIGINT; - return $previous_exit_status; -}; -if ! [[ "${PROMPT_COMMAND:-}" =~ _brev_hook ]]; then - PROMPT_COMMAND="_brev_hook${PROMPT_COMMAND:+;$PROMPT_COMMAND}" -fi -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - fileExists, err := e.store.FileExists("/etc/zsh/zshrc") - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !fileExists { - err = e.store.WriteString("/etc/zsh/zshrc", "") - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - err = e.store.AppendString("/etc/zsh/zshrc", ` -_brev_hook() { - trap -- '' SIGINT; - eval "$(/usr/local/bin/brev configure-env-vars zsh)"; - trap - SIGINT; -} -typeset -ag precmd_functions; -if [[ -z "${precmd_functions[(r)_brev_hook]+1}" ]]; then - precmd_functions=( _brev_hook ${precmd_functions[@]} ) -fi -typeset -ag chpwd_functions; -if [[ -z "${chpwd_functions[(r)_brev_hook]+1}" ]]; then - chpwd_functions=( _brev_hook ${chpwd_functions[@]} ) -fi -export PATH="/opt/conda/bin:$PATH" -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func newEnvIniter( - user *user.User, - params *store.SetupParamsV0, - configureSystemSSHConfig bool, - store envsetupStore, - datadogAPIKey string, - disableAbleAUtosop bool, - reportInterval string, - portToCheckTrafficOn string, -) *envInitier { - workspaceIniter := setupworkspace.NewWorkspaceIniter(user.HomeDir, user, params) - return &envInitier{ - *workspaceIniter, - configureSystemSSHConfig, - autostartconf.NewBrevMonConfigure( - store, - disableAbleAUtosop, - reportInterval, - portToCheckTrafficOn, - ), - datadogAPIKey, - store, - } -} - -func setupEnv( - store envsetupStore, - params *store.SetupParamsV0, - configureSystemSSHConfig bool, - datadogAPIKey string, - disableAutoStop bool, - reportInterval string, - portToCheckAutostopTrafficOn string, -) error { - err := store.BuildBrevHome() - if err != nil { - return breverrors.WrapAndTrace(err) - } - user, err := setupworkspace.GetUserFromUserStr(store.GetOSUser()) - if err != nil { - return breverrors.WrapAndTrace(err) - } - wi := newEnvIniter( - user, - params, - configureSystemSSHConfig, - store, - datadogAPIKey, - disableAutoStop, - reportInterval, - portToCheckAutostopTrafficOn, - ) - // set logfile path to ~/.brev/envsetup.log - logFilePath := filepath.Join(user.HomeDir, ".brev", "envsetup.log") - done, err := mirrorPipesToFile(store, logFilePath) - if err != nil { - return breverrors.WrapAndTrace(err) - } - defer done() - fmt.Printf("brev %s\n", version.Version) - - fmt.Println("------ Setup Begin ------") - err = wi.Setup() - fmt.Println("------ Setup End ------") - if err != nil { - fmt.Println("------ Failure ------") - time.Sleep(time.Millisecond * 100) // wait for buffer to be written - //nolint:gosec // constant - logFile, errF := ioutil.ReadFile(logFilePath) - if errF != nil { - return multierror.Append(err, errF) - } - breverrors.GetDefaultErrorReporter().AddBreadCrumb(breverrors.ErrReportBreadCrumb{Type: "log-file", Message: string(logFile)}) - return breverrors.WrapAndTrace(err) - } else { - fmt.Println("------ Success ------") - } - return nil -} - -func mirrorPipesToFile(store envsetupStore, logFile string) (func(), error) { - f, err := store.GetOrCreateSetupLogFile(logFile) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // save existing stdout | MultiWriter writes to saved stdout and file - stdOut := os.Stdout - stdErr := os.Stderr - mw := io.MultiWriter(stdOut, f) - - // get pipe reader and writer | writes to pipe writer come out pipe reader - r, w, err := os.Pipe() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // replace stdout,stderr with pipe writer | all writes to stdout, stderr will go through pipe instead (fmt.print, log) - os.Stdout = w - os.Stderr = w - - // writes with log.Print should also write to mw - log.SetOutput(mw) - - // create channel to control exit | will block until all copies are finished - exit := make(chan bool) - - go func() { - // copy all reads from pipe to multiwriter, which writes to stdout and file - _, _ = io.Copy(mw, r) - // when r or w is closed copy will finish and true will be sent to channel - exit <- true - }() - - // function to be deferred in main until program exits - return func() { - // close writer then block on exit channel | this will let mw finish writing before the program exits - _ = w.Close() - <-exit - // close file after all writes have finished - _ = f.Close() - os.Stdout = stdOut - os.Stderr = stdErr - }, nil -} diff --git a/pkg/cmd/envsetup/envsetup_test.go b/pkg/cmd/envsetup/envsetup_test.go deleted file mode 100644 index 2819e074..00000000 --- a/pkg/cmd/envsetup/envsetup_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package envsetup - -import ( - _ "embed" - "os" - "os/user" - "testing" - - "github.com/brevdev/brev-cli/pkg/store" - "github.com/spf13/afero" - "github.com/tweekmonster/luser" -) - -func Test_appendLogToFile(t *testing.T) { - t.Skip() - err := appendLogToFile("test", "test") - if err != nil { - t.Errorf("error appending to file %s", err) - } -} - -func Test_MOTDExists(t *testing.T) { - if motd == "" { - t.Errorf("motd is empty") - } -} - -func Test_SpeedtestExists(t *testing.T) { - if speedtest == "" { - t.Errorf("speedtest is empty") - } -} - -func makeMockFS() setupKeyI { - bs := store.NewBasicStore().WithEnvGetter( - func(s string) string { - return "test" - }, - ) - fs := bs.WithFileSystem(afero.NewMemMapFs()) - - fs = fs.WithUserHomeDirGetter( - func() (string, error) { - return "/home/test", nil - }, - ) - fs.User = &luser.User{ - User: &user.User{ - Uid: "1000", - Gid: "1000", - }, - } - return fs -} - -func Test_setupKey(t *testing.T) { - type args struct { - path string - content string - perm os.FileMode - store setupKeyI - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - { - name: "test", - args: args{ - path: "test", - content: "test", - perm: 0o644, - store: makeMockFS(), - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := setupKey(tt.args.path, tt.args.content, tt.args.perm, tt.args.store); (err != nil) != tt.wantErr { - t.Errorf("setupKey() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/cmd/envsetup/motd.sh b/pkg/cmd/envsetup/motd.sh deleted file mode 100644 index c64db4c6..00000000 --- a/pkg/cmd/envsetup/motd.sh +++ /dev/null @@ -1,60 +0,0 @@ -Welcome to brev.dev - - ##@@@#. - #@@@@@@@@@@. - #@@@. .@@@@% - #@% :@@@@- - #@ =@@@= -#@* +@@@= -=%@%= -#@: =@@@@ *@@@@@@@@ -#@: =@@@@ -@@@= +@+@@@@@@+ -#@: @@@@% @@@- @@: @@@@# @@@@@ -#@@= @@@@+ %@@- +@@* *@@@@@@@@. - #@@- @@@@.%@@- #@@ .@@@@.. @@@@@= @@@. - #@@@= @@@@@@@@ @@@@@@@@@@% *@@@@- @@# - #@@@@ -@@@@@@ -@# #@@+ @@@@# @@@ - #@@@% @@@@@@* -@ .@@ =@@@@: @@@ - #@@@- @@@@@@ @@@: -# @ @@@@* @@@ - #@@@- @@@@@@ :@@@@% -. @ @@@@ @@# - #@@@ +@@@@@= :@@@@@ - @@@@@ %@@ - #@@@ ..@@@@ =@@@@@ - @@@@+ .@@- - #@@: :@@ @@@@@@ @@@@: -@@@ - #@@ @ @@@@@@ @@@@@ .@@@ - #@@. *% @@@@@: #+ @@ @@@. - #@@ @ @@@@# @ @@ @@@ - #@@- = @@@@ @ +@# @ @@@. - #@@ . . @@@ . @@@ @@ *@@# - #@@ @@* - @+ #. -@@@ @@ @@+ - #@@@ @@@: - .@ =@@@ @+ @@@ - #@@@ *@@@ - @@ @@@@ @ %@@ - #@@. +@@@ - @@* @@@@ @ -@@: - #@@+ .@@@ - @@@. :%@ @@% - #@@@ @@@: - .@@@ =. #@@ - #@@@ @@@ - #@@ @@ - #@@@ @@@ - @# .@@+ - #@@@ @@@ . @: @@@ - #@@+ @ + @: +@@: - #@@@ @ @ @@@ - #@@@ % @ % = %@@ - #@@@ *% @ # @ .@@@ - #@@@. @ .@ .: +@ @@@+ - #@@+ :@ .@ # @..=@@ +@@@- - #@@@ @= .@ # @@@@@@ @@@@ - #@@ *@ @@ @ #@@@* %@@@@ - #@@# @@ @- : +@* @@@@ - #@@@. -@@ @- :: %* *@@@@ - #@@@%%%@@@ @- : * @@@@= - #@@@@@@@@ *@- @ * @@@@@ - #@@@@@@ @@ # @ =@@@@ - #@@@@@= .@@ -# @ =@@@@@ - #@@@@= @@@ # @@@@@@@@ - #@@@@@@@@@@: .@ @@@@@@@. - #@ @@ @@@@@@@= - #@@@@# #@@@@@@= - #@@@@@. .@@@@@@@ - #%@@@@@@@@@@@@@@- - ###@@@@@## - -Internet Speed: -Avg Upload: 1574.31 Mbit/s -Avg Download: 1103.68 Mbit/s \ No newline at end of file diff --git a/pkg/cmd/envsetup/speedtest.py b/pkg/cmd/envsetup/speedtest.py deleted file mode 100644 index b7e73eb1..00000000 --- a/pkg/cmd/envsetup/speedtest.py +++ /dev/null @@ -1,2184 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Copyright 2012 Matt Martz -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import csv -import datetime -import errno -import math -import os -import platform -import re -import signal -import socket -import sys -import threading -import subprocess -import timeit -import xml.parsers.expat - -try: - import gzip - GZIP_BASE = gzip.GzipFile -except ImportError: - gzip = None - GZIP_BASE = object - -__version__ = '2.1.4b1' - -downloadspeed = "" -uploadspeed = "" - -class FakeShutdownEvent(object): - """Class to fake a threading.Event.isSet so that users of this module - are not required to register their own threading.Event() - """ - - @staticmethod - def isSet(): - "Dummy method to always return false""" - return False - - is_set = isSet - - -# Some global variables we use -DEBUG = False -_GLOBAL_DEFAULT_TIMEOUT = object() -PY25PLUS = sys.version_info[:2] >= (2, 5) -PY26PLUS = sys.version_info[:2] >= (2, 6) -PY32PLUS = sys.version_info[:2] >= (3, 2) -PY310PLUS = sys.version_info[:2] >= (3, 10) - -# Begin import game to handle Python 2 and Python 3 -try: - import json -except ImportError: - try: - import simplejson as json - except ImportError: - json = None - -try: - import xml.etree.ElementTree as ET - try: - from xml.etree.ElementTree import _Element as ET_Element - except ImportError: - pass -except ImportError: - from xml.dom import minidom as DOM - from xml.parsers.expat import ExpatError - ET = None - -try: - from urllib2 import (urlopen, Request, HTTPError, URLError, - AbstractHTTPHandler, ProxyHandler, - HTTPDefaultErrorHandler, HTTPRedirectHandler, - HTTPErrorProcessor, OpenerDirector) -except ImportError: - from urllib.request import (urlopen, Request, HTTPError, URLError, - AbstractHTTPHandler, ProxyHandler, - HTTPDefaultErrorHandler, HTTPRedirectHandler, - HTTPErrorProcessor, OpenerDirector) - -try: - from httplib import HTTPConnection, BadStatusLine -except ImportError: - from http.client import HTTPConnection, BadStatusLine - -try: - from httplib import HTTPSConnection -except ImportError: - try: - from http.client import HTTPSConnection - except ImportError: - HTTPSConnection = None - -try: - from httplib import FakeSocket -except ImportError: - FakeSocket = None - -try: - from Queue import Queue -except ImportError: - from queue import Queue - -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - -try: - from urlparse import parse_qs -except ImportError: - try: - from urllib.parse import parse_qs - except ImportError: - from cgi import parse_qs - -try: - from hashlib import md5 -except ImportError: - from md5 import md5 - -try: - from argparse import ArgumentParser as ArgParser - from argparse import SUPPRESS as ARG_SUPPRESS - PARSER_TYPE_INT = int - PARSER_TYPE_STR = str - PARSER_TYPE_FLOAT = float -except ImportError: - from optparse import OptionParser as ArgParser - from optparse import SUPPRESS_HELP as ARG_SUPPRESS - PARSER_TYPE_INT = 'int' - PARSER_TYPE_STR = 'string' - PARSER_TYPE_FLOAT = 'float' - -try: - from cStringIO import StringIO - BytesIO = None -except ImportError: - try: - from StringIO import StringIO - BytesIO = None - except ImportError: - from io import StringIO, BytesIO - -try: - import __builtin__ -except ImportError: - import builtins - from io import TextIOWrapper, FileIO - - class _Py3Utf8Output(TextIOWrapper): - """UTF-8 encoded wrapper around stdout for py3, to override - ASCII stdout - """ - def __init__(self, f, **kwargs): - buf = FileIO(f.fileno(), 'w') - super(_Py3Utf8Output, self).__init__( - buf, - encoding='utf8', - errors='strict' - ) - - def write(self, s): - super(_Py3Utf8Output, self).write(s) - self.flush() - - _py3_print = getattr(builtins, 'print') - try: - _py3_utf8_stdout = _Py3Utf8Output(sys.stdout) - _py3_utf8_stderr = _Py3Utf8Output(sys.stderr) - except OSError: - # sys.stdout/sys.stderr is not a compatible stdout/stderr object - # just use it and hope things go ok - _py3_utf8_stdout = sys.stdout - _py3_utf8_stderr = sys.stderr - - def to_utf8(v): - """No-op encode to utf-8 for py3""" - return v - - def print_(*args, **kwargs): - """Wrapper function for py3 to print, with a utf-8 encoded stdout""" - if kwargs.get('file') == sys.stderr: - kwargs['file'] = _py3_utf8_stderr - else: - kwargs['file'] = kwargs.get('file', _py3_utf8_stdout) - _py3_print(*args, **kwargs) -else: - del __builtin__ - - def to_utf8(v): - """Encode value to utf-8 if possible for py2""" - try: - return v.encode('utf8', 'strict') - except AttributeError: - return v - - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5. - - Taken from https://pypi.python.org/pypi/six/ - - Modified to set encoding to UTF-8 always, and to flush after write - """ - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - encoding = 'utf8' # Always trust UTF-8 for output - if (isinstance(fp, file) and - isinstance(data, unicode) and - encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(encoding, errors) - fp.write(data) - fp.flush() - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) - -# Exception "constants" to support Python 2 through Python 3 -try: - import ssl - try: - CERT_ERROR = (ssl.CertificateError,) - except AttributeError: - CERT_ERROR = tuple() - - HTTP_ERRORS = ( - (HTTPError, URLError, socket.error, ssl.SSLError, BadStatusLine) + - CERT_ERROR - ) -except ImportError: - ssl = None - HTTP_ERRORS = (HTTPError, URLError, socket.error, BadStatusLine) - -if PY32PLUS: - etree_iter = ET.Element.iter -elif PY25PLUS: - etree_iter = ET_Element.getiterator - -if PY26PLUS: - thread_is_alive = threading.Thread.is_alive -else: - thread_is_alive = threading.Thread.isAlive - - -def event_is_set(event): - try: - return event.is_set() - except AttributeError: - return event.isSet() - - -class SpeedtestException(Exception): - """Base exception for this module""" - - -class SpeedtestCLIError(SpeedtestException): - """Generic exception for raising errors during CLI operation""" - - -class SpeedtestHTTPError(SpeedtestException): - """Base HTTP exception for this module""" - - -class SpeedtestConfigError(SpeedtestException): - """Configuration XML is invalid""" - - -class SpeedtestServersError(SpeedtestException): - """Servers XML is invalid""" - - -class ConfigRetrievalError(SpeedtestHTTPError): - """Could not retrieve config.php""" - - -class ServersRetrievalError(SpeedtestHTTPError): - """Could not retrieve speedtest-servers.php""" - - -class InvalidServerIDType(SpeedtestException): - """Server ID used for filtering was not an integer""" - - -class NoMatchedServers(SpeedtestException): - """No servers matched when filtering""" - - -class SpeedtestMiniConnectFailure(SpeedtestException): - """Could not connect to the provided speedtest mini server""" - - -class InvalidSpeedtestMiniServer(SpeedtestException): - """Server provided as a speedtest mini server does not actually appear - to be a speedtest mini server - """ - - -class ShareResultsConnectFailure(SpeedtestException): - """Could not connect to speedtest.net API to POST results""" - - -class ShareResultsSubmitFailure(SpeedtestException): - """Unable to successfully POST results to speedtest.net API after - connection - """ - - -class SpeedtestUploadTimeout(SpeedtestException): - """testlength configuration reached during upload - Used to ensure the upload halts when no additional data should be sent - """ - - -class SpeedtestBestServerFailure(SpeedtestException): - """Unable to determine best server""" - - -class SpeedtestMissingBestServer(SpeedtestException): - """get_best_server not called or not able to determine best server""" - - -def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, - source_address=None): - """Connect to *address* and return the socket object. - - Convenience function. Connect to *address* (a 2-tuple ``(host, - port)``) and return the socket object. Passing the optional - *timeout* parameter will set the timeout on the socket instance - before attempting to connect. If no *timeout* is supplied, the - global default timeout setting returned by :func:`getdefaulttimeout` - is used. If *source_address* is set it must be a tuple of (host, port) - for the socket to bind as a source address before making the connection. - An host of '' or port 0 tells the OS to use the default. - - Largely vendored from Python 2.7, modified to work with Python 2.4 - """ - - host, port = address - err = None - for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - sock = None - try: - sock = socket.socket(af, socktype, proto) - if timeout is not _GLOBAL_DEFAULT_TIMEOUT: - sock.settimeout(float(timeout)) - if source_address: - sock.bind(source_address) - sock.connect(sa) - return sock - - except socket.error: - err = get_exception() - if sock is not None: - sock.close() - - if err is not None: - raise err - else: - raise socket.error("getaddrinfo returns an empty list") - - -class SpeedtestHTTPConnection(HTTPConnection): - """Custom HTTPConnection to support source_address across - Python 2.4 - Python 3 - """ - def __init__(self, *args, **kwargs): - source_address = kwargs.pop('source_address', None) - timeout = kwargs.pop('timeout', 10) - - self._tunnel_host = None - - HTTPConnection.__init__(self, *args, **kwargs) - - self.source_address = source_address - self.timeout = timeout - - def connect(self): - """Connect to the host and port specified in __init__.""" - try: - self.sock = socket.create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - except (AttributeError, TypeError): - self.sock = create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - - if self._tunnel_host: - self._tunnel() - - -if HTTPSConnection: - class SpeedtestHTTPSConnection(HTTPSConnection): - """Custom HTTPSConnection to support source_address across - Python 2.4 - Python 3 - """ - default_port = 443 - - def __init__(self, *args, **kwargs): - source_address = kwargs.pop('source_address', None) - timeout = kwargs.pop('timeout', 10) - - self._tunnel_host = None - - HTTPSConnection.__init__(self, *args, **kwargs) - - self.timeout = timeout - self.source_address = source_address - - def connect(self): - "Connect to a host on a given (SSL) port." - try: - self.sock = socket.create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - except (AttributeError, TypeError): - self.sock = create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - - if self._tunnel_host: - self._tunnel() - - if ssl: - try: - kwargs = {} - if hasattr(ssl, 'SSLContext'): - if self._tunnel_host: - kwargs['server_hostname'] = self._tunnel_host - else: - kwargs['server_hostname'] = self.host - self.sock = self._context.wrap_socket(self.sock, **kwargs) - except AttributeError: - self.sock = ssl.wrap_socket(self.sock) - try: - self.sock.server_hostname = self.host - except AttributeError: - pass - elif FakeSocket: - # Python 2.4/2.5 support - try: - self.sock = FakeSocket(self.sock, socket.ssl(self.sock)) - except AttributeError: - raise SpeedtestException( - 'This version of Python does not support HTTPS/SSL ' - 'functionality' - ) - else: - raise SpeedtestException( - 'This version of Python does not support HTTPS/SSL ' - 'functionality' - ) - - -def _build_connection(connection, source_address, timeout, context=None): - """Cross Python 2.4 - Python 3 callable to build an ``HTTPConnection`` or - ``HTTPSConnection`` with the args we need - - Called from ``http(s)_open`` methods of ``SpeedtestHTTPHandler`` or - ``SpeedtestHTTPSHandler`` - """ - def inner(host, **kwargs): - kwargs.update({ - 'source_address': source_address, - 'timeout': timeout - }) - if context: - kwargs['context'] = context - return connection(host, **kwargs) - return inner - - -class SpeedtestHTTPHandler(AbstractHTTPHandler): - """Custom ``HTTPHandler`` that can build a ``HTTPConnection`` with the - args we need for ``source_address`` and ``timeout`` - """ - def __init__(self, debuglevel=0, source_address=None, timeout=10): - AbstractHTTPHandler.__init__(self, debuglevel) - self.source_address = source_address - self.timeout = timeout - - def http_open(self, req): - return self.do_open( - _build_connection( - SpeedtestHTTPConnection, - self.source_address, - self.timeout - ), - req - ) - - http_request = AbstractHTTPHandler.do_request_ - - -class SpeedtestHTTPSHandler(AbstractHTTPHandler): - """Custom ``HTTPSHandler`` that can build a ``HTTPSConnection`` with the - args we need for ``source_address`` and ``timeout`` - """ - def __init__(self, debuglevel=0, context=None, source_address=None, - timeout=10): - AbstractHTTPHandler.__init__(self, debuglevel) - self._context = context - self.source_address = source_address - self.timeout = timeout - - def https_open(self, req): - return self.do_open( - _build_connection( - SpeedtestHTTPSConnection, - self.source_address, - self.timeout, - context=self._context, - ), - req - ) - - https_request = AbstractHTTPHandler.do_request_ - - -def build_opener(source_address=None, timeout=10): - """Function similar to ``urllib2.build_opener`` that will build - an ``OpenerDirector`` with the explicit handlers we want, - ``source_address`` for binding, ``timeout`` and our custom - `User-Agent` - """ - - printer('Timeout set to %d' % timeout, debug=True) - - if source_address: - source_address_tuple = (source_address, 0) - printer('Binding to source address: %r' % (source_address_tuple,), - debug=True) - else: - source_address_tuple = None - - handlers = [ - ProxyHandler(), - SpeedtestHTTPHandler(source_address=source_address_tuple, - timeout=timeout), - SpeedtestHTTPSHandler(source_address=source_address_tuple, - timeout=timeout), - HTTPDefaultErrorHandler(), - HTTPRedirectHandler(), - HTTPErrorProcessor() - ] - - opener = OpenerDirector() - opener.addheaders = [('User-agent', build_user_agent())] - - for handler in handlers: - opener.add_handler(handler) - - return opener - - -class GzipDecodedResponse(GZIP_BASE): - """A file-like object to decode a response encoded with the gzip - method, as described in RFC 1952. - - Largely copied from ``xmlrpclib``/``xmlrpc.client`` and modified - to work for py2.4-py3 - """ - def __init__(self, response): - # response doesn't support tell() and read(), required by - # GzipFile - if not gzip: - raise SpeedtestHTTPError('HTTP response body is gzip encoded, ' - 'but gzip support is not available') - IO = BytesIO or StringIO - self.io = IO() - while 1: - chunk = response.read(1024) - if len(chunk) == 0: - break - self.io.write(chunk) - self.io.seek(0) - gzip.GzipFile.__init__(self, mode='rb', fileobj=self.io) - - def close(self): - try: - gzip.GzipFile.close(self) - finally: - self.io.close() - - -def get_exception(): - """Helper function to work with py2.4-py3 for getting the current - exception in a try/except block - """ - return sys.exc_info()[1] - - -def distance(origin, destination): - """Determine distance between 2 sets of [lat,lon] in km""" - - lat1, lon1 = origin - lat2, lon2 = destination - radius = 6371 # km - - dlat = math.radians(lat2 - lat1) - dlon = math.radians(lon2 - lon1) - a = (math.sin(dlat / 2) * math.sin(dlat / 2) + - math.cos(math.radians(lat1)) * - math.cos(math.radians(lat2)) * math.sin(dlon / 2) * - math.sin(dlon / 2)) - c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) - d = radius * c - - return d - - -def build_user_agent(): - """Build a Mozilla/5.0 compatible User-Agent string""" - - ua_tuple = ( - 'Mozilla/5.0', - '(%s; U; %s; en-us)' % (platform.platform(), - platform.architecture()[0]), - 'Python/%s' % platform.python_version(), - '(KHTML, like Gecko)', - 'speedtest-cli/%s' % __version__ - ) - user_agent = ' '.join(ua_tuple) - printer('User-Agent: %s' % user_agent, debug=True) - return user_agent - - -def build_request(url, data=None, headers=None, bump='0', secure=False): - """Build a urllib2 request object - - This function automatically adds a User-Agent header to all requests - - """ - - if not headers: - headers = {} - - if url[0] == ':': - scheme = ('http', 'https')[bool(secure)] - schemed_url = '%s%s' % (scheme, url) - else: - schemed_url = url - - if '?' in url: - delim = '&' - else: - delim = '?' - - # WHO YOU GONNA CALL? CACHE BUSTERS! - final_url = '%s%sx=%s.%s' % (schemed_url, delim, - int(timeit.time.time() * 1000), - bump) - - headers.update({ - 'Cache-Control': 'no-cache', - }) - - printer('%s %s' % (('GET', 'POST')[bool(data)], final_url), - debug=True) - - return Request(final_url, data=data, headers=headers) - - -def catch_request(request, opener=None): - """Helper function to catch common exceptions encountered when - establishing a connection with a HTTP/HTTPS request - - """ - - if opener: - _open = opener.open - else: - _open = urlopen - - try: - uh = _open(request) - if request.get_full_url() != uh.geturl(): - printer('Redirected to %s' % uh.geturl(), debug=True) - return uh, False - except HTTP_ERRORS: - e = get_exception() - return None, e - - -def get_response_stream(response): - """Helper function to return either a Gzip reader if - ``Content-Encoding`` is ``gzip`` otherwise the response itself - - """ - - try: - getheader = response.headers.getheader - except AttributeError: - getheader = response.getheader - - if getheader('content-encoding') == 'gzip': - return GzipDecodedResponse(response) - - return response - - -def get_attributes_by_tag_name(dom, tag_name): - """Retrieve an attribute from an XML document and return it in a - consistent format - - Only used with xml.dom.minidom, which is likely only to be used - with python versions older than 2.5 - """ - elem = dom.getElementsByTagName(tag_name)[0] - return dict(list(elem.attributes.items())) - - -def print_dots(shutdown_event): - """Built in callback function used by Thread classes for printing - status - """ - def inner(current, total, start=False, end=False): - if event_is_set(shutdown_event): - return - - sys.stdout.write('.') - if current + 1 == total and end is True: - sys.stdout.write('\n') - sys.stdout.flush() - return inner - - -def do_nothing(*args, **kwargs): - pass - - -class HTTPDownloader(threading.Thread): - """Thread class for retrieving a URL""" - - def __init__(self, i, request, start, timeout, opener=None, - shutdown_event=None): - threading.Thread.__init__(self) - self.request = request - self.result = [0] - self.starttime = start - self.timeout = timeout - self.i = i - if opener: - self._opener = opener.open - else: - self._opener = urlopen - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - def run(self): - try: - if (timeit.default_timer() - self.starttime) <= self.timeout: - f = self._opener(self.request) - while (not event_is_set(self._shutdown_event) and - (timeit.default_timer() - self.starttime) <= - self.timeout): - self.result.append(len(f.read(10240))) - if self.result[-1] == 0: - break - f.close() - except IOError: - pass - except HTTP_ERRORS: - pass - - -class HTTPUploaderData(object): - """File like object to improve cutting off the upload once the timeout - has been reached - """ - - def __init__(self, length, start, timeout, shutdown_event=None): - self.length = length - self.start = start - self.timeout = timeout - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - self._data = None - - self.total = [0] - - def pre_allocate(self): - chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - multiplier = int(round(int(self.length) / 36.0)) - IO = BytesIO or StringIO - try: - self._data = IO( - ('content1=%s' % - (chars * multiplier)[0:int(self.length) - 9] - ).encode() - ) - except MemoryError: - raise SpeedtestCLIError( - 'Insufficient memory to pre-allocate upload data. Please ' - 'use --no-pre-allocate' - ) - - @property - def data(self): - if not self._data: - self.pre_allocate() - return self._data - - def read(self, n=10240): - if ((timeit.default_timer() - self.start) <= self.timeout and - not event_is_set(self._shutdown_event)): - chunk = self.data.read(n) - self.total.append(len(chunk)) - return chunk - else: - raise SpeedtestUploadTimeout() - - def __len__(self): - return self.length - - -class HTTPUploader(threading.Thread): - """Thread class for putting a URL""" - - def __init__(self, i, request, start, size, timeout, opener=None, - shutdown_event=None): - threading.Thread.__init__(self) - self.request = request - self.request.data.start = self.starttime = start - self.size = size - self.result = 0 - self.timeout = timeout - self.i = i - - if opener: - self._opener = opener.open - else: - self._opener = urlopen - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - def run(self): - request = self.request - try: - if ((timeit.default_timer() - self.starttime) <= self.timeout and - not event_is_set(self._shutdown_event)): - try: - f = self._opener(request) - except TypeError: - # PY24 expects a string or buffer - # This also causes issues with Ctrl-C, but we will concede - # for the moment that Ctrl-C on PY24 isn't immediate - request = build_request(self.request.get_full_url(), - data=request.data.read(self.size)) - f = self._opener(request) - f.read(11) - f.close() - self.result = sum(self.request.data.total) - else: - self.result = 0 - except (IOError, SpeedtestUploadTimeout): - self.result = sum(self.request.data.total) - except HTTP_ERRORS: - self.result = 0 - - -class SpeedtestResults(object): - """Class for holding the results of a speedtest, including: - - Download speed - Upload speed - Ping/Latency to test server - Data about server that the test was run against - - Additionally this class can return a result data as a dictionary or CSV, - as well as submit a POST of the result data to the speedtest.net API - to get a share results image link. - """ - - def __init__(self, download=0, upload=0, ping=0, server=None, client=None, - opener=None, secure=False): - self.download = download - self.upload = upload - self.ping = ping - if server is None: - self.server = {} - else: - self.server = server - self.client = client or {} - - self._share = None - self.timestamp = '%sZ' % datetime.datetime.utcnow().isoformat() - self.bytes_received = 0 - self.bytes_sent = 0 - - if opener: - self._opener = opener - else: - self._opener = build_opener() - - self._secure = secure - - def __repr__(self): - return repr(self.dict()) - - def share(self): - """POST data to the speedtest.net API to obtain a share results - link - """ - - if self._share: - return self._share - - download = int(round(self.download / 1000.0, 0)) - ping = int(round(self.ping, 0)) - upload = int(round(self.upload / 1000.0, 0)) - - # Build the request to send results back to speedtest.net - # We use a list instead of a dict because the API expects parameters - # in a certain order - api_data = [ - 'recommendedserverid=%s' % self.server['id'], - 'ping=%s' % ping, - 'screenresolution=', - 'promo=', - 'download=%s' % download, - 'screendpi=', - 'upload=%s' % upload, - 'testmethod=http', - 'hash=%s' % md5(('%s-%s-%s-%s' % - (ping, upload, download, '297aae72')) - .encode()).hexdigest(), - 'touchscreen=none', - 'startmode=pingselect', - 'accuracy=1', - 'bytesreceived=%s' % self.bytes_received, - 'bytessent=%s' % self.bytes_sent, - 'serverid=%s' % self.server['id'], - ] - - headers = {'Referer': 'http://c.speedtest.net/flash/speedtest.swf'} - request = build_request('://www.speedtest.net/api/api.php', - data='&'.join(api_data).encode(), - headers=headers, secure=self._secure) - f, e = catch_request(request, opener=self._opener) - if e: - raise ShareResultsConnectFailure(e) - - response = f.read() - code = f.code - f.close() - - if int(code) != 200: - raise ShareResultsSubmitFailure('Could not submit results to ' - 'speedtest.net') - - qsargs = parse_qs(response.decode()) - resultid = qsargs.get('resultid') - if not resultid or len(resultid) != 1: - raise ShareResultsSubmitFailure('Could not submit results to ' - 'speedtest.net') - - self._share = 'http://www.speedtest.net/result/%s.png' % resultid[0] - - return self._share - - def dict(self): - """Return dictionary of result data""" - - return { - 'download': self.download, - 'upload': self.upload, - 'ping': self.ping, - 'server': self.server, - 'timestamp': self.timestamp, - 'bytes_sent': self.bytes_sent, - 'bytes_received': self.bytes_received, - 'share': self._share, - 'client': self.client, - } - - @staticmethod - def csv_header(delimiter=','): - """Return CSV Headers""" - - row = ['Server ID', 'Sponsor', 'Server Name', 'Timestamp', 'Distance', - 'Ping', 'Download', 'Upload', 'Share', 'IP Address'] - out = StringIO() - writer = csv.writer(out, delimiter=delimiter, lineterminator='') - writer.writerow([to_utf8(v) for v in row]) - return out.getvalue() - - def csv(self, delimiter=','): - """Return data in CSV format""" - - data = self.dict() - out = StringIO() - writer = csv.writer(out, delimiter=delimiter, lineterminator='') - row = [data['server']['id'], data['server']['sponsor'], - data['server']['name'], data['timestamp'], - data['server']['d'], data['ping'], data['download'], - data['upload'], self._share or '', self.client['ip']] - writer.writerow([to_utf8(v) for v in row]) - return out.getvalue() - - def json(self, pretty=False): - """Return data in JSON format""" - - kwargs = {} - if pretty: - kwargs.update({ - 'indent': 4, - 'sort_keys': True - }) - return json.dumps(self.dict(), **kwargs) - - -class Speedtest(object): - """Class for performing standard speedtest.net testing operations""" - - def __init__(self, config=None, source_address=None, timeout=10, - secure=False, shutdown_event=None): - self.config = {} - - self._source_address = source_address - self._timeout = timeout - self._opener = build_opener(source_address, timeout) - - self._secure = secure - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - self.get_config() - if config is not None: - self.config.update(config) - - self.servers = {} - self.closest = [] - self._best = {} - - self.results = SpeedtestResults( - client=self.config['client'], - opener=self._opener, - secure=secure, - ) - - @property - def best(self): - if not self._best: - self.get_best_server() - return self._best - - def get_config(self): - """Download the speedtest.net configuration and return only the data - we are interested in - """ - - headers = {} - if gzip: - headers['Accept-Encoding'] = 'gzip' - request = build_request('://www.speedtest.net/speedtest-config.php', - headers=headers, secure=self._secure) - uh, e = catch_request(request, opener=self._opener) - if e: - raise ConfigRetrievalError(e) - configxml_list = [] - - stream = get_response_stream(uh) - - while 1: - try: - configxml_list.append(stream.read(1024)) - except (OSError, EOFError): - raise ConfigRetrievalError(get_exception()) - if len(configxml_list[-1]) == 0: - break - stream.close() - uh.close() - - if int(uh.code) != 200: - return None - - configxml = ''.encode().join(configxml_list) - - printer('Config XML:\n%s' % configxml, debug=True) - - try: - try: - root = ET.fromstring(configxml) - except ET.ParseError: - e = get_exception() - raise SpeedtestConfigError( - 'Malformed speedtest.net configuration: %s' % e - ) - server_config = root.find('server-config').attrib - download = root.find('download').attrib - upload = root.find('upload').attrib - # times = root.find('times').attrib - client = root.find('client').attrib - - except AttributeError: - try: - root = DOM.parseString(configxml) - except ExpatError: - e = get_exception() - raise SpeedtestConfigError( - 'Malformed speedtest.net configuration: %s' % e - ) - server_config = get_attributes_by_tag_name(root, 'server-config') - download = get_attributes_by_tag_name(root, 'download') - upload = get_attributes_by_tag_name(root, 'upload') - # times = get_attributes_by_tag_name(root, 'times') - client = get_attributes_by_tag_name(root, 'client') - - ignore_servers = [ - int(i) for i in server_config['ignoreids'].split(',') if i - ] - - ratio = int(upload['ratio']) - upload_max = int(upload['maxchunkcount']) - up_sizes = [32768, 65536, 131072, 262144, 524288, 1048576, 7340032] - sizes = { - 'upload': up_sizes[ratio - 1:], - 'download': [350, 500, 750, 1000, 1500, 2000, 2500, - 3000, 3500, 4000] - } - - size_count = len(sizes['upload']) - - upload_count = int(math.ceil(upload_max / size_count)) - - counts = { - 'upload': upload_count, - 'download': int(download['threadsperurl']) - } - - threads = { - 'upload': int(upload['threads']), - 'download': int(server_config['threadcount']) * 2 - } - - length = { - 'upload': int(upload['testlength']), - 'download': int(download['testlength']) - } - - self.config.update({ - 'client': client, - 'ignore_servers': ignore_servers, - 'sizes': sizes, - 'counts': counts, - 'threads': threads, - 'length': length, - 'upload_max': upload_count * size_count - }) - - try: - self.lat_lon = (float(client['lat']), float(client['lon'])) - except ValueError: - raise SpeedtestConfigError( - 'Unknown location: lat=%r lon=%r' % - (client.get('lat'), client.get('lon')) - ) - - printer('Config:\n%r' % self.config, debug=True) - - return self.config - - def get_servers(self, servers=None, exclude=None): - """Retrieve a the list of speedtest.net servers, optionally filtered - to servers matching those specified in the ``servers`` argument - """ - if servers is None: - servers = [] - - if exclude is None: - exclude = [] - - self.servers.clear() - - for server_list in (servers, exclude): - for i, s in enumerate(server_list): - try: - server_list[i] = int(s) - except ValueError: - raise InvalidServerIDType( - '%s is an invalid server type, must be int' % s - ) - - urls = [ - '://www.speedtest.net/speedtest-servers-static.php', - 'http://c.speedtest.net/speedtest-servers-static.php', - '://www.speedtest.net/speedtest-servers.php', - 'http://c.speedtest.net/speedtest-servers.php', - ] - - headers = {} - if gzip: - headers['Accept-Encoding'] = 'gzip' - - errors = [] - for url in urls: - try: - request = build_request( - '%s?threads=%s' % (url, - self.config['threads']['download']), - headers=headers, - secure=self._secure - ) - uh, e = catch_request(request, opener=self._opener) - if e: - errors.append('%s' % e) - raise ServersRetrievalError() - - stream = get_response_stream(uh) - - serversxml_list = [] - while 1: - try: - serversxml_list.append(stream.read(1024)) - except (OSError, EOFError): - raise ServersRetrievalError(get_exception()) - if len(serversxml_list[-1]) == 0: - break - - stream.close() - uh.close() - - if int(uh.code) != 200: - raise ServersRetrievalError() - - serversxml = ''.encode().join(serversxml_list) - - printer('Servers XML:\n%s' % serversxml, debug=True) - - try: - try: - try: - root = ET.fromstring(serversxml) - except ET.ParseError: - e = get_exception() - raise SpeedtestServersError( - 'Malformed speedtest.net server list: %s' % e - ) - elements = etree_iter(root, 'server') - except AttributeError: - try: - root = DOM.parseString(serversxml) - except ExpatError: - e = get_exception() - raise SpeedtestServersError( - 'Malformed speedtest.net server list: %s' % e - ) - elements = root.getElementsByTagName('server') - except (SyntaxError, xml.parsers.expat.ExpatError): - raise ServersRetrievalError() - - for server in elements: - try: - attrib = server.attrib - except AttributeError: - attrib = dict(list(server.attributes.items())) - - if servers and int(attrib.get('id')) not in servers: - continue - - if (int(attrib.get('id')) in self.config['ignore_servers'] - or int(attrib.get('id')) in exclude): - continue - - try: - d = distance(self.lat_lon, - (float(attrib.get('lat')), - float(attrib.get('lon')))) - except Exception: - continue - - attrib['d'] = d - - try: - self.servers[d].append(attrib) - except KeyError: - self.servers[d] = [attrib] - - break - - except ServersRetrievalError: - continue - - if (servers or exclude) and not self.servers: - raise NoMatchedServers() - - return self.servers - - def set_mini_server(self, server): - """Instead of querying for a list of servers, set a link to a - speedtest mini server - """ - - urlparts = urlparse(server) - - name, ext = os.path.splitext(urlparts[2]) - if ext: - url = os.path.dirname(server) - else: - url = server - - request = build_request(url) - uh, e = catch_request(request, opener=self._opener) - if e: - raise SpeedtestMiniConnectFailure('Failed to connect to %s' % - server) - else: - text = uh.read() - uh.close() - - extension = re.findall('upload_?[Ee]xtension: "([^"]+)"', - text.decode()) - if not extension: - for ext in ['php', 'asp', 'aspx', 'jsp']: - try: - f = self._opener.open( - '%s/speedtest/upload.%s' % (url, ext) - ) - except Exception: - pass - else: - data = f.read().strip().decode() - if (f.code == 200 and - len(data.splitlines()) == 1 and - re.match('size=[0-9]', data)): - extension = [ext] - break - if not urlparts or not extension: - raise InvalidSpeedtestMiniServer('Invalid Speedtest Mini Server: ' - '%s' % server) - - self.servers = [{ - 'sponsor': 'Speedtest Mini', - 'name': urlparts[1], - 'd': 0, - 'url': '%s/speedtest/upload.%s' % (url.rstrip('/'), extension[0]), - 'latency': 0, - 'id': 0 - }] - - return self.servers - - def get_closest_servers(self, limit=5): - """Limit servers to the closest speedtest.net servers based on - geographic distance - """ - - if not self.servers: - self.get_servers() - - for d in sorted(self.servers.keys()): - for s in self.servers[d]: - self.closest.append(s) - if len(self.closest) == limit: - break - else: - continue - break - - printer('Closest Servers:\n%r' % self.closest, debug=True) - return self.closest - - def get_best_server(self, servers=None): - """Perform a speedtest.net "ping" to determine which speedtest.net - server has the lowest latency - """ - - if not servers: - if not self.closest: - servers = self.get_closest_servers() - servers = self.closest - - if self._source_address: - source_address_tuple = (self._source_address, 0) - else: - source_address_tuple = None - - user_agent = build_user_agent() - - results = {} - for server in servers: - cum = [] - url = os.path.dirname(server['url']) - stamp = int(timeit.time.time() * 1000) - latency_url = '%s/latency.txt?x=%s' % (url, stamp) - for i in range(0, 3): - this_latency_url = '%s.%s' % (latency_url, i) - printer('%s %s' % ('GET', this_latency_url), - debug=True) - urlparts = urlparse(latency_url) - try: - if urlparts[0] == 'https': - h = SpeedtestHTTPSConnection( - urlparts[1], - source_address=source_address_tuple - ) - else: - h = SpeedtestHTTPConnection( - urlparts[1], - source_address=source_address_tuple - ) - headers = {'User-Agent': user_agent} - path = '%s?%s' % (urlparts[2], urlparts[4]) - start = timeit.default_timer() - h.request("GET", path, headers=headers) - r = h.getresponse() - total = (timeit.default_timer() - start) - except HTTP_ERRORS: - e = get_exception() - printer('ERROR: %r' % e, debug=True) - cum.append(3600) - continue - - text = r.read(9) - if int(r.status) == 200 and text == 'test=test'.encode(): - cum.append(total) - else: - cum.append(3600) - h.close() - - avg = round((sum(cum) / 6) * 1000.0, 3) - results[avg] = server - - try: - fastest = sorted(results.keys())[0] - except IndexError: - raise SpeedtestBestServerFailure('Unable to connect to servers to ' - 'test latency.') - best = results[fastest] - best['latency'] = fastest - - self.results.ping = fastest - self.results.server = best - - self._best.update(best) - printer('Best Server:\n%r' % best, debug=True) - return best - - def download(self, callback=do_nothing, threads=None): - """Test download speed against speedtest.net - - A ``threads`` value of ``None`` will fall back to those dictated - by the speedtest.net configuration - """ - - urls = [] - for size in self.config['sizes']['download']: - for _ in range(0, self.config['counts']['download']): - urls.append('%s/random%sx%s.jpg' % - (os.path.dirname(self.best['url']), size, size)) - - request_count = len(urls) - requests = [] - for i, url in enumerate(urls): - requests.append( - build_request(url, bump=i, secure=self._secure) - ) - - max_threads = threads or self.config['threads']['download'] - in_flight = {'threads': 0} - - def producer(q, requests, request_count): - for i, request in enumerate(requests): - thread = HTTPDownloader( - i, - request, - start, - self.config['length']['download'], - opener=self._opener, - shutdown_event=self._shutdown_event - ) - while in_flight['threads'] >= max_threads: - timeit.time.sleep(0.001) - thread.start() - q.put(thread, True) - in_flight['threads'] += 1 - callback(i, request_count, start=True) - - finished = [] - - def consumer(q, request_count): - _is_alive = thread_is_alive - while len(finished) < request_count: - thread = q.get(True) - while _is_alive(thread): - thread.join(timeout=0.001) - in_flight['threads'] -= 1 - finished.append(sum(thread.result)) - callback(thread.i, request_count, end=True) - - q = Queue(max_threads) - prod_thread = threading.Thread(target=producer, - args=(q, requests, request_count)) - cons_thread = threading.Thread(target=consumer, - args=(q, request_count)) - start = timeit.default_timer() - prod_thread.start() - cons_thread.start() - _is_alive = thread_is_alive - while _is_alive(prod_thread): - prod_thread.join(timeout=0.001) - while _is_alive(cons_thread): - cons_thread.join(timeout=0.001) - - stop = timeit.default_timer() - self.results.bytes_received = sum(finished) - self.results.download = ( - (self.results.bytes_received / (stop - start)) * 8.0 - ) - if self.results.download > 100000: - self.config['threads']['upload'] = 8 - return self.results.download - - def upload(self, callback=do_nothing, pre_allocate=True, threads=None): - """Test upload speed against speedtest.net - - A ``threads`` value of ``None`` will fall back to those dictated - by the speedtest.net configuration - """ - - sizes = [] - - for size in self.config['sizes']['upload']: - for _ in range(0, self.config['counts']['upload']): - sizes.append(size) - - # request_count = len(sizes) - request_count = self.config['upload_max'] - - requests = [] - for i, size in enumerate(sizes): - # We set ``0`` for ``start`` and handle setting the actual - # ``start`` in ``HTTPUploader`` to get better measurements - data = HTTPUploaderData( - size, - 0, - self.config['length']['upload'], - shutdown_event=self._shutdown_event - ) - if pre_allocate: - data.pre_allocate() - - headers = {'Content-length': size} - requests.append( - ( - build_request(self.best['url'], data, secure=self._secure, - headers=headers), - size - ) - ) - - max_threads = threads or self.config['threads']['upload'] - in_flight = {'threads': 0} - - def producer(q, requests, request_count): - for i, request in enumerate(requests[:request_count]): - thread = HTTPUploader( - i, - request[0], - start, - request[1], - self.config['length']['upload'], - opener=self._opener, - shutdown_event=self._shutdown_event - ) - while in_flight['threads'] >= max_threads: - timeit.time.sleep(0.001) - thread.start() - q.put(thread, True) - in_flight['threads'] += 1 - callback(i, request_count, start=True) - - finished = [] - - def consumer(q, request_count): - _is_alive = thread_is_alive - while len(finished) < request_count: - thread = q.get(True) - while _is_alive(thread): - thread.join(timeout=0.001) - in_flight['threads'] -= 1 - finished.append(thread.result) - callback(thread.i, request_count, end=True) - - q = Queue(threads or self.config['threads']['upload']) - prod_thread = threading.Thread(target=producer, - args=(q, requests, request_count)) - cons_thread = threading.Thread(target=consumer, - args=(q, request_count)) - start = timeit.default_timer() - prod_thread.start() - cons_thread.start() - _is_alive = thread_is_alive - while _is_alive(prod_thread): - prod_thread.join(timeout=0.1) - while _is_alive(cons_thread): - cons_thread.join(timeout=0.1) - - stop = timeit.default_timer() - self.results.bytes_sent = sum(finished) - self.results.upload = ( - (self.results.bytes_sent / (stop - start)) * 8.0 - ) - return self.results.upload - - -def ctrl_c(shutdown_event): - """Catch Ctrl-C key sequence and set a SHUTDOWN_EVENT for our threaded - operations - """ - def inner(signum, frame): - shutdown_event.set() - printer('\nCancelling...', error=True) - sys.exit(0) - return inner - - -def version(): - """Print the version""" - - printer('speedtest-cli %s' % __version__) - printer('Python %s' % sys.version.replace('\n', '')) - sys.exit(0) - - -def csv_header(delimiter=','): - """Print the CSV Headers""" - - printer(SpeedtestResults.csv_header(delimiter=delimiter)) - sys.exit(0) - - -def parse_args(): - """Function to handle building and parsing of command line arguments""" - description = ( - 'Command line interface for testing internet bandwidth using ' - 'speedtest.net.\n' - '------------------------------------------------------------' - '--------------\n' - 'https://github.com/sivel/speedtest-cli') - - parser = ArgParser(description=description) - # Give optparse.OptionParser an `add_argument` method for - # compatibility with argparse.ArgumentParser - try: - parser.add_argument = parser.add_option - except AttributeError: - pass - parser.add_argument('--no-download', dest='download', default=True, - action='store_const', const=False, - help='Do not perform download test') - parser.add_argument('--no-upload', dest='upload', default=True, - action='store_const', const=False, - help='Do not perform upload test') - parser.add_argument('--single', default=False, action='store_true', - help='Only use a single connection instead of ' - 'multiple. This simulates a typical file ' - 'transfer.') - parser.add_argument('--bytes', dest='units', action='store_const', - const=('byte', 8), default=('bit', 1), - help='Display values in bytes instead of bits. Does ' - 'not affect the image generated by --share, nor ' - 'output from --json or --csv') - parser.add_argument('--share', action='store_true', - help='Generate and provide a URL to the speedtest.net ' - 'share results image, not displayed with --csv') - parser.add_argument('--simple', action='store_true', default=False, - help='Suppress verbose output, only show basic ' - 'information') - parser.add_argument('--csv', action='store_true', default=False, - help='Suppress verbose output, only show basic ' - 'information in CSV format. Speeds listed in ' - 'bit/s and not affected by --bytes') - parser.add_argument('--csv-delimiter', default=',', type=PARSER_TYPE_STR, - help='Single character delimiter to use in CSV ' - 'output. Default ","') - parser.add_argument('--csv-header', action='store_true', default=False, - help='Print CSV headers') - parser.add_argument('--json', action='store_true', default=False, - help='Suppress verbose output, only show basic ' - 'information in JSON format. Speeds listed in ' - 'bit/s and not affected by --bytes') - parser.add_argument('--list', action='store_true', - help='Display a list of speedtest.net servers ' - 'sorted by distance') - parser.add_argument('--server', type=PARSER_TYPE_INT, action='append', - help='Specify a server ID to test against. Can be ' - 'supplied multiple times') - parser.add_argument('--exclude', type=PARSER_TYPE_INT, action='append', - help='Exclude a server from selection. Can be ' - 'supplied multiple times') - parser.add_argument('--mini', help='URL of the Speedtest Mini server') - parser.add_argument('--source', help='Source IP address to bind to') - parser.add_argument('--timeout', default=10, type=PARSER_TYPE_FLOAT, - help='HTTP timeout in seconds. Default 10') - parser.add_argument('--secure', action='store_true', - help='Use HTTPS instead of HTTP when communicating ' - 'with speedtest.net operated servers') - parser.add_argument('--no-pre-allocate', dest='pre_allocate', - action='store_const', default=True, const=False, - help='Do not pre allocate upload data. Pre allocation ' - 'is enabled by default to improve upload ' - 'performance. To support systems with ' - 'insufficient memory, use this option to avoid a ' - 'MemoryError') - parser.add_argument('--version', action='store_true', - help='Show the version number and exit') - parser.add_argument('--debug', action='store_true', - help=ARG_SUPPRESS, default=ARG_SUPPRESS) - - options = parser.parse_args() - if isinstance(options, tuple): - args = options[0] - else: - args = options - return args - - -def validate_optional_args(args): - """Check if an argument was provided that depends on a module that may - not be part of the Python standard library. - - If such an argument is supplied, and the module does not exist, exit - with an error stating which module is missing. - """ - optional_args = { - 'json': ('json/simplejson python module', json), - 'secure': ('SSL support', HTTPSConnection), - } - - for arg, info in optional_args.items(): - if getattr(args, arg, False) and info[1] is None: - raise SystemExit('%s is not installed. --%s is ' - 'unavailable' % (info[0], arg)) - - -def printer(string, quiet=False, debug=False, error=False, **kwargs): - """Helper function print a string with various features""" - - if debug and not DEBUG: - return - - if debug: - if sys.stdout.isatty(): - out = '\033[1;30mDEBUG: %s\033[0m' % string - else: - out = 'DEBUG: %s' % string - else: - out = string - - if error: - kwargs['file'] = sys.stderr - - if not quiet: - print_(out, **kwargs) - - -def shell(): - """Run the full speedtest.net test""" - - global DEBUG - shutdown_event = threading.Event() - - signal.signal(signal.SIGINT, ctrl_c(shutdown_event)) - - args = parse_args() - - # Print the version and exit - if args.version: - version() - - if not args.download and not args.upload: - raise SpeedtestCLIError('Cannot supply both --no-download and ' - '--no-upload') - - if len(args.csv_delimiter) != 1: - raise SpeedtestCLIError('--csv-delimiter must be a single character') - - if args.csv_header: - csv_header(args.csv_delimiter) - - validate_optional_args(args) - - debug = getattr(args, 'debug', False) - if debug == 'SUPPRESSHELP': - debug = False - if debug: - DEBUG = True - - if args.simple or args.csv or args.json: - quiet = True - else: - quiet = False - - if args.csv or args.json: - machine_format = True - else: - machine_format = False - - # Don't set a callback if we are running quietly - if quiet or debug: - callback = do_nothing - else: - callback = print_dots(shutdown_event) - - printer('Retrieving speedtest.net configuration...', quiet) - try: - speedtest = Speedtest( - source_address=args.source, - timeout=args.timeout, - secure=args.secure - ) - except (ConfigRetrievalError,) + HTTP_ERRORS: - printer('Cannot retrieve speedtest configuration', error=True) - raise SpeedtestCLIError(get_exception()) - - if args.list: - try: - speedtest.get_servers() - except (ServersRetrievalError,) + HTTP_ERRORS: - printer('Cannot retrieve speedtest server list', error=True) - raise SpeedtestCLIError(get_exception()) - - for _, servers in sorted(speedtest.servers.items()): - for server in servers: - line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s) ' - '[%(d)0.2f km]' % server) - try: - printer(line) - except IOError: - e = get_exception() - if e.errno != errno.EPIPE: - raise - sys.exit(0) - - printer('Testing from %(isp)s (%(ip)s)...' % speedtest.config['client'], - quiet) - - if not args.mini: - printer('Retrieving speedtest.net server list...', quiet) - try: - speedtest.get_servers(servers=args.server, exclude=args.exclude) - except NoMatchedServers: - raise SpeedtestCLIError( - 'No matched servers: %s' % - ', '.join('%s' % s for s in args.server) - ) - except (ServersRetrievalError,) + HTTP_ERRORS: - printer('Cannot retrieve speedtest server list', error=True) - raise SpeedtestCLIError(get_exception()) - except InvalidServerIDType: - raise SpeedtestCLIError( - '%s is an invalid server type, must ' - 'be an int' % ', '.join('%s' % s for s in args.server) - ) - - if args.server and len(args.server) == 1: - printer('Retrieving information for the selected server...', quiet) - else: - printer('Selecting best server based on ping...', quiet) - speedtest.get_best_server() - elif args.mini: - speedtest.get_best_server(speedtest.set_mini_server(args.mini)) - - results = speedtest.results - - printer('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: ' - '%(latency)s ms' % results.server, quiet) - - if args.download: - printer('Testing download speed', quiet, - end=('', '\n')[bool(debug)]) - speedtest.download( - callback=callback, - threads=(None, 1)[args.single] - ) - printer('Download: %0.2f M%s/s' % - ((results.download / 1000.0 / 1000.0) / args.units[1], - args.units[0]), - quiet) - - - - - - else: - printer('Skipping download test', quiet) - - if args.upload: - printer('Testing upload speed', quiet, - end=('', '\n')[bool(debug)]) - speedtest.upload( - callback=callback, - pre_allocate=args.pre_allocate, - threads=(None, 1)[args.single] - ) - - printer('Upload: %0.2f M%s/s' % - ((results.upload / 1000.0 / 1000.0) / args.units[1], - args.units[0]), - quiet) - - - else: - printer('Skipping upload test', quiet) - - if args.upload and args.download: - uploadspeed = 'Upload: %0.2f M%s/s' %\ - ((results.upload / 1000.0 / 1000.0) / args.units[1], - args.units[0]) - downloadspeed = 'Download: %0.2f M%s/s' % \ - ((results.download / 1000.0 / 1000.0) / args.units[1], - args.units[0]) - - f = open("speedtest-output.sh", "w") - f.write("""#!/bin/sh -[ -r /etc/lsb-release ] && . /etc/lsb-release - -if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then - # Fall back to using the very slow lsb_release utility - DISTRIB_DESCRIPTION=$(lsb_release -s -d) -fi - -printf "Welcome to brev.dev\n" -printf "\n" -printf " ##@@@#.\n" -printf " #@@@@@@@@@@.\n" -printf " #@@@. .@@@@%\n" -printf " #@% :@@@@-\n" -printf " #@ =@@@=\n" -printf "#@* +@@@= -=%@%=\n" -printf "#@: =@@@@ *@@@@@@@@\n" -printf "#@: =@@@@ -@@@= +@+@@@@@@+\n" -printf "#@: @@@@% @@@- @@: @@@@# @@@@@\n" -printf "#@@= @@@@+ %@@- +@@* *@@@@@@@@.\n" -printf " #@@- @@@@.%@@- #@@ .@@@@.. @@@@@= @@@.\n" -printf " #@@@= @@@@@@@@ @@@@@@@@@@% *@@@@- @@#\n" -printf " #@@@@ -@@@@@@ -@# #@@+ @@@@# @@@\n" -printf " #@@@% @@@@@@* -@ .@@ =@@@@: @@@\n" -printf " #@@@- @@@@@@ @@@: -# @ @@@@* @@@\n" -printf " #@@@- @@@@@@ :@@@@% -. @ @@@@ @@#\n" -printf " #@@@ +@@@@@= :@@@@@ - @@@@@ %@@\n" -printf " #@@@ ..@@@@ =@@@@@ - @@@@+ .@@-\n" -printf " #@@: :@@ @@@@@@ @@@@: -@@@\n" -printf " #@@ @ @@@@@@ @@@@@ .@@@\n" -printf " #@@. *% @@@@@: #+ @@ @@@.\n" -printf " #@@ @ @@@@# @ @@ @@@\n" -printf " #@@- = @@@@ @ +@# @ @@@.\n" -printf " #@@ . . @@@ . @@@ @@ *@@#\n" -printf " #@@ @@* - @+ #. -@@@ @@ @@+\n" -printf " #@@@ @@@: - .@ =@@@ @+ @@@\n" -printf " #@@@ *@@@ - @@ @@@@ @ %@@\n" -printf " #@@. +@@@ - @@* @@@@ @ -@@:\n" -printf " #@@+ .@@@ - @@@. :%@ @@%\n" -printf " #@@@ @@@: - .@@@ =. #@@\n" -printf " #@@@ @@@ - #@@ @@\n" -printf " #@@@ @@@ - @# .@@+\n" -printf " #@@@ @@@ . @: @@@\n" -printf " #@@+ @ + @: +@@:\n" -printf " #@@@ @ @ @@@\n" -printf " #@@@ % @ % = %@@\n" -printf " #@@@ *% @ # @ .@@@\n" -printf " #@@@. @ .@ .: +@ @@@+\n" -printf " #@@+ :@ .@ # @..=@@ +@@@-\n" -printf " #@@@ @= .@ # @@@@@@ @@@@\n" -printf " #@@ *@ @@ @ #@@@* %@@@@\n" -printf " #@@# @@ @- : +@* @@@@\n" -printf " #@@@. -@@ @- :: %* *@@@@\n" -printf " #@@@%%%@@@ @- : * @@@@=\n" -printf " #@@@@@@@@ *@- @ * @@@@@\n" -printf " #@@@@@@ @@ # @ =@@@@\n" -printf " #@@@@@= .@@ -# @ =@@@@@\n" -printf " #@@@@= @@@ # @@@@@@@@\n" -printf " #@@@@@@@@@@: .@ @@@@@@@.\n" -printf " #@ @@ @@@@@@@=\n" -printf " #@@@@# #@@@@@@=\n" -printf " #@@@@@. .@@@@@@@\n" -printf " #%@@@@@@@@@@@@@@-\n" -printf " ###@@@@@##\n" -printf "\n\nInternet Speed:\n" - - """) - f.write('printf "' + uploadspeed + '\n"\nprintf "' + downloadspeed + '\n"') - f.write(""" -printf "\nRunning %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)" - """) - - f.close() - - superstring = f""" -[ -r /etc/lsb-release ] && . /etc/lsb-release - -if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then - # Fall back to using the very slow lsb_release utility - DISTRIB_DESCRIPTION=$(lsb_release -s -d) -fi - -printf "Welcome to brev.dev\n" -printf "\n" -printf " ##@@@#.\n" -printf " #@@@@@@@@@@.\n" -printf " #@@@. .@@@@%\n" -printf " #@% :@@@@-\n" -printf " #@ =@@@=\n" -printf "#@* +@@@= -=%@%=\n" -printf "#@: =@@@@ *@@@@@@@@\n" -printf "#@: =@@@@ -@@@= +@+@@@@@@+\n" -printf "#@: @@@@% @@@- @@: @@@@# @@@@@\n" -printf "#@@= @@@@+ %@@- +@@* *@@@@@@@@.\n" -printf " #@@- @@@@.%@@- #@@ .@@@@.. @@@@@= @@@.\n" -printf " #@@@= @@@@@@@@ @@@@@@@@@@% *@@@@- @@#\n" -printf " #@@@@ -@@@@@@ -@# #@@+ @@@@# @@@\n" -printf " #@@@% @@@@@@* -@ .@@ =@@@@: @@@\n" -printf " #@@@- @@@@@@ @@@: -# @ @@@@* @@@\n" -printf " #@@@- @@@@@@ :@@@@% -. @ @@@@ @@#\n" -printf " #@@@ +@@@@@= :@@@@@ - @@@@@ %@@\n" -printf " #@@@ ..@@@@ =@@@@@ - @@@@+ .@@-\n" -printf " #@@: :@@ @@@@@@ @@@@: -@@@\n" -printf " #@@ @ @@@@@@ @@@@@ .@@@\n" -printf " #@@. *% @@@@@: #+ @@ @@@.\n" -printf " #@@ @ @@@@# @ @@ @@@\n" -printf " #@@- = @@@@ @ +@# @ @@@.\n" -printf " #@@ . . @@@ . @@@ @@ *@@#\n" -printf " #@@ @@* - @+ #. -@@@ @@ @@+\n" -printf " #@@@ @@@: - .@ =@@@ @+ @@@\n" -printf " #@@@ *@@@ - @@ @@@@ @ %@@\n" -printf " #@@. +@@@ - @@* @@@@ @ -@@:\n" -printf " #@@+ .@@@ - @@@. :%@ @@%\n" -printf " #@@@ @@@: - .@@@ =. #@@\n" -printf " #@@@ @@@ - #@@ @@\n" -printf " #@@@ @@@ - @# .@@+\n" -printf " #@@@ @@@ . @: @@@\n" -printf " #@@+ @ + @: +@@:\n" -printf " #@@@ @ @ @@@\n" -printf " #@@@ % @ % = %@@\n" -printf " #@@@ *% @ # @ .@@@\n" -printf " #@@@. @ .@ .: +@ @@@+\n" -printf " #@@+ :@ .@ # @..=@@ +@@@-\n" -printf " #@@@ @= .@ # @@@@@@ @@@@\n" -printf " #@@ *@ @@ @ #@@@* %@@@@\n" -printf " #@@# @@ @- : +@* @@@@\n" -printf " #@@@. -@@ @- :: %* *@@@@\n" -printf " #@@@%%%@@@ @- : * @@@@=\n" -printf " #@@@@@@@@ *@- @ * @@@@@\n" -printf " #@@@@@@ @@ # @ =@@@@\n" -printf " #@@@@@= .@@ -# @ =@@@@@\n" -printf " #@@@@= @@@ # @@@@@@@@\n" -printf " #@@@@@@@@@@: .@ @@@@@@@.\n" -printf " #@ @@ @@@@@@@=\n" -printf " #@@@@# #@@@@@@=\n" -printf " #@@@@@. .@@@@@@@\n" -printf " #%@@@@@@@@@@@@@@-\n" -printf " ###@@@@@##\n" -printf "\n\nInternet Speed:\n" -printf "{uploadspeed}\n{downloadspeed}\n" -printf "\nRunning %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)" -""" - - printer('Results:\n%r' % results.dict(), debug=True) - - if not args.simple and args.share: - results.share() - - if args.simple: - printer('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' % - (results.ping, - (results.download / 1000.0 / 1000.0) / args.units[1], - args.units[0], - (results.upload / 1000.0 / 1000.0) / args.units[1], - args.units[0])) - elif args.csv: - printer(results.csv(delimiter=args.csv_delimiter)) - elif args.json: - printer(results.json()) - - if args.share and not machine_format: - printer('Share results: %s' % results.share()) - - -def main(): - try: - shell() - except KeyboardInterrupt: - printer('\nCancelling...', error=True) - except (SpeedtestException, SystemExit): - e = get_exception() - # Ignore a successful exit, or argparse exit - if getattr(e, 'code', 1) not in (0, 2): - msg = '%s' % e - if not msg: - msg = '%r' % e - raise SystemExit('ERROR: %s' % msg) - - -if __name__ == '__main__': - main() - -def getRam(): - output=subprocess.run( - "free", - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - return float(str(output.stdout).split()[7])/1000000 \ No newline at end of file diff --git a/pkg/cmd/envvars/envvars.go b/pkg/cmd/envvars/envvars.go deleted file mode 100644 index aaa8d096..00000000 --- a/pkg/cmd/envvars/envvars.go +++ /dev/null @@ -1,33 +0,0 @@ -package envvars - -import ( - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type EnvVarsStore interface{} - -func NewCmdEnvVars(_ *terminal.Terminal, evStore EnvVarsStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "env-vars", - DisableFlagsInUseLine: true, - Short: "configure env vars in supported shells", - Long: "Import your IDE config", - Example: "", - RunE: func(cmd *cobra.Command, args []string) error { - err := RunEnvVars(evStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func RunEnvVars(_ EnvVarsStore) error { - return nil -} diff --git a/pkg/cmd/envvars/envvars_test.go b/pkg/cmd/envvars/envvars_test.go deleted file mode 100644 index 7cbaf8e6..00000000 --- a/pkg/cmd/envvars/envvars_test.go +++ /dev/null @@ -1 +0,0 @@ -package envvars diff --git a/pkg/cmd/fu/fu.go b/pkg/cmd/fu/fu.go deleted file mode 100644 index d61f5be7..00000000 --- a/pkg/cmd/fu/fu.go +++ /dev/null @@ -1,107 +0,0 @@ -package fu - -import ( - "fmt" - "time" - - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/hashicorp/go-multierror" - "github.com/spf13/cobra" - stripmd "github.com/writeas/go-strip-markdown" -) - -var ( - fuLong string - fuExample = "brev fu " -) - -type FuStore interface { - completions.CompletionStore - DeleteWorkspace(workspaceID string) (*entity.Workspace, error) - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - BanUser(userID string) error - GetWorkspaceByNameOrID(orgID string, nameOrID string) ([]entity.Workspace, error) - GetAllOrgsAsAdmin(userID string) ([]entity.Organization, error) -} - -func NewCmdFu(t *terminal.Terminal, loginFuStore FuStore, noLoginFuStore FuStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "fu", - DisableFlagsInUseLine: true, - Short: "Fetch all workspaces for a user and delete them", - Long: stripmd.Strip(fuLong), - Example: fuExample, - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginFuStore, t), - RunE: func(cmd *cobra.Command, args []string) error { - var allError error - for _, userID := range args { - err := fuUser(userID, t, loginFuStore) - if err != nil { - allError = multierror.Append(allError, err) - } - } - if allError != nil { - return breverrors.WrapAndTrace(allError) - } - return nil - }, - } - - return cmd -} - -func fuUser(userID string, t *terminal.Terminal, fuStore FuStore) error { - orgs, err := fuStore.GetAllOrgsAsAdmin(userID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var allWorkspaces []entity.Workspace - for _, org := range orgs { - workspaces, errr := fuStore.GetWorkspaces(org.ID, nil) - if errr != nil { - return breverrors.WrapAndTrace(errr) - } - allWorkspaces = append(allWorkspaces, workspaces...) - } - - s := t.NewSpinner() - s.Suffix = " Fetching workspaces for user " + userID - s.Start() - time.Sleep(5 * time.Second) - s.Stop() - - confirm := terminal.PromptGetInput(terminal.PromptContent{ - Label: fmt.Sprintf("Are you sure you want to delete all %d workspaces for user %s? (y/n)", len(allWorkspaces), userID), - ErrorMsg: "You must confirm to proceed.", - AllowEmpty: false, - }) - if confirm != "y" { - return nil - } - - for _, workspace := range allWorkspaces { - _, err2 := fuStore.DeleteWorkspace(workspace.ID) - if err2 != nil { - t.Vprintf(t.Red("Failed to delete workspace with ID: %s\n", workspace.ID)) - t.Vprintf(t.Red("Error: %s\n", err.Error())) - continue - } - t.Vprintf("āœ… Deleted workspace %s\n", workspace.Name) - } - - err = fuStore.BanUser(userID) - if err != nil { - t.Vprintf(t.Red("Failed to ban user with ID: %s\n", userID)) - t.Vprintf(t.Red("Error: %s\n", err.Error())) - } - t.Vprint("\n") - t.Vprintf("šŸ–• Banned user %s\n", userID) - - return nil -} diff --git a/pkg/cmd/fu/fu_test.go b/pkg/cmd/fu/fu_test.go deleted file mode 100644 index 5818a26b..00000000 --- a/pkg/cmd/fu/fu_test.go +++ /dev/null @@ -1 +0,0 @@ -package fu diff --git a/pkg/cmd/healthcheck/healthcheck.go b/pkg/cmd/healthcheck/healthcheck.go deleted file mode 100644 index 2b6cb025..00000000 --- a/pkg/cmd/healthcheck/healthcheck.go +++ /dev/null @@ -1,51 +0,0 @@ -// Package healthcheck checks the health of the backend -package healthcheck - -import ( - "github.com/brevdev/brev-cli/pkg/cmdcontext" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -type HealthcheckStore interface { - Healthcheck() error -} - -func NewCmdHealthcheck(t *terminal.Terminal, store HealthcheckStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "healthcheck", - Short: "Check backend to see if it's healthy", - Long: "Check backend to see if it's healthy", - Example: `brev healthcheck`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - err := healthcheck(t, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func healthcheck(t *terminal.Terminal, store HealthcheckStore) error { - err := store.Healthcheck() - if err != nil { - t.Print("Not Healthy!") - return breverrors.WrapAndTrace(err) - } - t.Print("Healthy!") - return nil -} diff --git a/pkg/cmd/initfile/initfile.go b/pkg/cmd/initfile/initfile.go deleted file mode 100644 index 1b5044cf..00000000 --- a/pkg/cmd/initfile/initfile.go +++ /dev/null @@ -1,31 +0,0 @@ -package initfile - -import ( - "github.com/brevdev/brev-cli/pkg/mergeshells" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type InitFileStore interface { - GetFileAsString(path string) (string, error) -} - -func NewCmdInitFile(t *terminal.Terminal, store InitFileStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "init", - DisableFlagsInUseLine: true, - Short: "initialize a .brev/setup.sh file if it does not exist", - Long: "initialize a .brev/setup.sh file if it does not exist", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - // then assume it is . - mergeshells.ImportPath(t, args[0], store) - } else { - mergeshells.ImportPath(t, ".", store) - } - return nil - }, - } - - return cmd -} diff --git a/pkg/cmd/invite/invite.go b/pkg/cmd/invite/invite.go deleted file mode 100644 index 4dc773b8..00000000 --- a/pkg/cmd/invite/invite.go +++ /dev/null @@ -1,104 +0,0 @@ -// Package invite is for inviting -package invite - -import ( - "fmt" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmdcontext" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -type InviteStore interface { - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetUsers(queryParams map[string]string) ([]entity.User, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) - CreateInviteLink(organizationID string) (string, error) -} - -func NewCmdInvite(t *terminal.Terminal, loginInviteStore InviteStore, noLoginInviteStore InviteStore) *cobra.Command { - var org string - - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "invite", - Short: "Generate an invite link", - Long: "Get an invite link to your active org. Use the optional org flag to invite to a different org", - Example: ` - brev org invite - brev org --org - `, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - Args: cmderrors.TransformToValidationError(cobra.NoArgs), - RunE: func(cmd *cobra.Command, args []string) error { - err := RunInvite(t, loginInviteStore, org) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().StringVarP(&org, "org", "o", "", "organization (will override active org)") - err := cmd.RegisterFlagCompletionFunc("org", completions.GetOrgsNameCompletionHandler(noLoginInviteStore, t)) - if err != nil { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) - fmt.Print(breverrors.WrapAndTrace(err)) - } - - return cmd -} - -func RunInvite(t *terminal.Terminal, inviteStore InviteStore, orgflag string) error { - var org *entity.Organization - if orgflag != "" { - orgs, err := inviteStore.GetOrganizations(&store.GetOrganizationsOptions{Name: orgflag}) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if len(orgs) == 0 { - return fmt.Errorf("no org found with name %s", orgflag) - } else if len(orgs) > 1 { - return fmt.Errorf("more than one org found with name %s", orgflag) - } - - org = &orgs[0] - } else { - currOrg, err := inviteStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if currOrg == nil { - return fmt.Errorf("no orgs exist") - } - org = currOrg - } - - token, err := inviteStore.CreateInviteLink(org.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprintf("Share this link to add someone to %s. It will expire in 7 days.", t.Green(org.Name)) - // t.Vprintf("\n\n\t%s", t.White("https://console.brev.dev/invite?token=%s\n\n", token)) - t.Vprintf("\n\n %s", t.Green("ā–ø")) - t.Vprintf(" %s", t.White("https://console.brev.dev/invite?token=%s\n\n", token)) - - return nil -} diff --git a/pkg/cmd/invite/invite_test.go b/pkg/cmd/invite/invite_test.go deleted file mode 100644 index 70300e4c..00000000 --- a/pkg/cmd/invite/invite_test.go +++ /dev/null @@ -1 +0,0 @@ -package invite diff --git a/pkg/cmd/notebook/notebook.go b/pkg/cmd/notebook/notebook.go deleted file mode 100644 index 7feb44d1..00000000 --- a/pkg/cmd/notebook/notebook.go +++ /dev/null @@ -1,82 +0,0 @@ -package notebook - -import ( - "github.com/brevdev/brev-cli/pkg/cmd/hello" - "github.com/brevdev/brev-cli/pkg/cmd/portforward" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/fatih/color" - "github.com/spf13/cobra" -) - -var ( - notebookLong = "Open a notebook on your Brev machine" - notebookExample = "brev notebook " -) - -type NotebookStore interface { - portforward.PortforwardStore -} - -type WorkspaceResult struct { - Workspace *entity.Workspace // Replace with the actual type of workspace returned by GetUserWorkspaceByNameOrIDErr - Err error -} - -func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { - cmd := &cobra.Command{ - Use: "notebook", - Short: "Open a notebook on your Brev machine", - Long: notebookLong, - Example: notebookExample, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - // Channel to get the result of the network call - resultCh := make(chan *WorkspaceResult) - - // Start the network call in a goroutine - go func() { - workspace, err := util.GetUserWorkspaceByNameOrIDErr(store, args[0]) - resultCh <- &WorkspaceResult{Workspace: workspace, Err: err} - }() - - // Type out the checking message - hello.TypeItToMeUnskippable27("Checking to make sure the workspace is running...") - - // Wait for the network call to finish - result := <-resultCh - - if result.Err != nil { - return breverrors.WrapAndTrace(result.Err) - } - - // Check if the workspace is running - if result.Workspace.Status != "RUNNING" { - hello.TypeItToMeUnskippable27("The workspace is not running. Please ensure it's in the running state before proceeding.") - return breverrors.WorkspaceNotRunning{Status: result.Workspace.Status} - } - - urlType := color.New(color.FgCyan, color.Bold).SprintFunc() - warningType := color.New(color.FgBlack, color.Bold, color.BgCyan).SprintFunc() - - hello.TypeItToMeUnskippable("\n" + warningType(" Please keep this terminal open šŸ¤™ ")) - - hello.TypeItToMeUnskippable27("\nClick here to go to your Jupyter notebook:\n\t šŸ‘‰" + urlType("http://localhost:8888") + "šŸ‘ˆ\n\n\n") - - // Port forward on 8888 - err2 := portforward.RunPortforward(store, args[0], "8888:8888") - if err2 != nil { - return breverrors.WrapAndTrace(err2) - } - - // Print out a link for the user - hello.TypeItToMeUnskippable27("Your notebook is accessible at: http://localhost:8888") - - return nil - }, - } - - return cmd -} diff --git a/pkg/cmd/notebook/notebook_test.go b/pkg/cmd/notebook/notebook_test.go deleted file mode 100644 index 00af4897..00000000 --- a/pkg/cmd/notebook/notebook_test.go +++ /dev/null @@ -1 +0,0 @@ -package notebook diff --git a/pkg/cmd/ollama/ollama.go b/pkg/cmd/ollama/ollama.go deleted file mode 100644 index 0ebfc99a..00000000 --- a/pkg/cmd/ollama/ollama.go +++ /dev/null @@ -1,344 +0,0 @@ -// Package ollama is for starting AI/ML model workspaces -package ollama - -import ( - _ "embed" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/google/uuid" - - "github.com/brevdev/brev-cli/pkg/cmd/refresh" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/collections" - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/instancetypes" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/cmd/hello" - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - -var ( - ollamaLong = "Start an Ollama server with specified model types" - ollamaExample = ` - brev ollama --model llama3 - ` -) - -//go:embed ollamaverb.yaml -var verbYaml string - -type OllamaStore interface { - refresh.RefreshStore - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - BuildVerbContainer(workspaceID string, verbYaml string) (*store.BuildVerbRes, error) - ModifyPublicity(workspace *entity.Workspace, applicationName string, publicity bool) (*entity.Tunnel, error) -} - -func validateModelType(input string) (bool, error) { - var model string - var tag string - - split := strings.Split(input, ":") - switch len(split) { - case 2: - model = split[0] - tag = split[1] - case 1: - model = input - tag = "latest" - default: - return false, fmt.Errorf("invalid model type: %s", input) - } - valid, err := store.ValidateOllamaModel(model, tag) - if err != nil { - return false, fmt.Errorf("error validating model: %s", err) - } - return valid, nil -} - -func NewCmdOllama(t *terminal.Terminal, ollamaStore OllamaStore) *cobra.Command { - var model string - var gpu string - - cmd := &cobra.Command{ - Use: "ollama", - DisableFlagsInUseLine: true, - Short: "Start an Ollama model server", - Long: ollamaLong, - Example: ollamaExample, - Annotations: map[string]string{ - "quickstart": "", - }, - RunE: func(cmd *cobra.Command, args []string) error { - if model == "" { - return fmt.Errorf("model type must be specified") - } - - isValid, valErr := validateModelType(model) - if valErr != nil { - return valErr - } - if !isValid { - return fmt.Errorf("invalid model type: %s", model) - } - if gpu != "" { - isValid := instancetypes.ValidateInstanceType(gpu) - if !isValid { - err := fmt.Errorf("invalid GPU instance type: %s, see https://brev.dev/docs/reference/gpu for a list of valid GPU instance types", gpu) - return breverrors.WrapAndTrace(err) - } - } - - // Start the network call in a goroutine - res := collections.Async(func() (any, error) { - err := runOllamaWorkspace(t, RunOptions{ - Model: model, - GPUType: gpu, - }, ollamaStore) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - return nil, nil - }) - - // Type out the creating workspace message - hello.TypeItToMeUnskippable27("šŸ¤™šŸ¦™šŸ¤™šŸ¦™šŸ¤™šŸ¦™šŸ¤™") - t.Vprint(t.Green("\n\n\n")) - - _, err := res.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - } - cmd.Flags().StringVarP(&model, "model", "m", "", "AI/ML model type (e.g., llama2, llama3, mistral7b)") - cmd.Flags().StringVarP(&gpu, "gpu", "g", "g5.xlarge", "GPU instance type. See https://brev.dev/docs/reference/gpu for details") - return cmd -} - -type RunOptions struct { - Model string - GPUType string -} - -func runOllamaWorkspace(t *terminal.Terminal, opts RunOptions, ollamaStore OllamaStore) error { //nolint:funlen, gocyclo // todo - _, err := ollamaStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - org, err := ollamaStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - instanceType := opts.GPUType - clusterID := config.GlobalConfig.GetDefaultClusterID() - uuid := uuid.New().String() - instanceName := fmt.Sprintf("ollama-%s", uuid) - cwOptions := store.NewCreateWorkspacesOptions(clusterID, instanceName).WithInstanceType(instanceType) - - hello.TypeItToMeUnskippable27(fmt.Sprintf("Creating Ollama server %s with model %s in org %s\n", t.Green(cwOptions.Name), t.Green(opts.Model), t.Green(org.ID))) - - s := t.NewSpinner() - - createWorkspaceRes := collections.Async(func() (*entity.Workspace, error) { - w, errr := ollamaStore.CreateWorkspace(org.ID, cwOptions) - if errr != nil { - return nil, breverrors.WrapAndTrace(errr) - } - return w, nil - }) - - s.Suffix = " Creating your workspace. Hang tight šŸ¤™" - s.Start() - - w, err := createWorkspaceRes.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var vmStatus bool - vmStatus, err = pollInstanceUntilVMReady(w, time.Second, time.Minute*5, ollamaStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !vmStatus { - return breverrors.New("instance did not start") - } - s.Stop() - hello.TypeItToMeUnskippable27("VM is ready!\n") - s.Start() - - // TODO: look into timing of verb call - time.Sleep(time.Second * 5) - - verbBuildRes := collections.Async(func() (*store.BuildVerbRes, error) { - lf, errr := ollamaStore.BuildVerbContainer(w.ID, verbYaml) - if errr != nil { - return nil, breverrors.WrapAndTrace(errr) - } - return lf, nil - }) - - s.Suffix = " Building the Ollama container. Hang tight šŸ¤™" - - _, err = verbBuildRes.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var vstatus bool - // TODO: 15 min for now because the image is not cached and takes a while to build. Remove this when the image is cached - vstatus, err = pollInstanceUntilVerbContainerReady(w, time.Second, time.Minute*20, ollamaStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if !vstatus { - return breverrors.New("verb container did not build correctly") - } - s.Stop() - - s = t.NewSpinner() - s.Suffix = "(connectivity) Pulling the %s model, just a bit more! šŸ„" - - // shell in and run ollama pull: - if refreshErr := refresh.RunRefresh(ollamaStore); refreshErr != nil { - return breverrors.WrapAndTrace(refreshErr) - } - // Reload workspace to get the latest status - w, err = ollamaStore.GetWorkspace(w.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - link, name, err := getOllamaTunnelLink(w) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - _, err = makeTunnelPublic(w, name, ollamaStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - s.Suffix = "Pulling the %s model, just a bit more! šŸ„" - - // shell in and run ollama pull: - if err := runSSHExec(instanceName, []string{"ollama", "pull", opts.Model}, false); err != nil { - return breverrors.WrapAndTrace(err) - } - if err := runSSHExec(instanceName, []string{"ollama", "run", opts.Model, "hello world"}, true); err != nil { - return breverrors.WrapAndTrace(err) - } - s.Stop() - - fmt.Print("\n") - t.Vprint(t.Green("Ollama is ready to go!\n")) - displayOllamaConnectBreadCrumb(t, link, opts.Model) - return nil -} - -func displayOllamaConnectBreadCrumb(t *terminal.Terminal, link string, model string) { - t.Vprintf(t.Green("Query the Ollama API with the following command:\n")) - t.Vprintf(t.Yellow(fmt.Sprintf("curl %s/api/chat -d '{\n \"model\": \"%s\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"why is the sky blue?\"\n }\n ]\n}'\n", link, model))) -} - -func pollInstanceUntilVMReady(workspace *entity.Workspace, interval time.Duration, timeout time.Duration, ollamaStore OllamaStore) (bool, error) { - elapsedTime := time.Duration(0) - - for elapsedTime < timeout { - w, err := ollamaStore.GetWorkspace(workspace.ID) - if err != nil { - return false, breverrors.WrapAndTrace(err) - } else if w.Status == "RUNNING" { - // adding a slight delay to make sure the instance is ready - time.Sleep(time.Minute * 1) - return true, nil - } - time.Sleep(interval) - elapsedTime += interval - } - return false, breverrors.New("Timeout waiting for machine to start") -} - -func pollInstanceUntilVerbContainerReady(workspace *entity.Workspace, interval time.Duration, timeout time.Duration, ollamaStore OllamaStore) (bool, error) { - elapsedTime := time.Duration(0) - - for elapsedTime < timeout { - w, err := ollamaStore.GetWorkspace(workspace.ID) - if err != nil { - return false, breverrors.WrapAndTrace(err) - } else if w.VerbBuildStatus == entity.Completed { - return true, nil - } - time.Sleep(interval) - elapsedTime += interval - } - return false, breverrors.New("Timeout waiting for container to build") -} - -func getOllamaTunnelLink(workspace *entity.Workspace) (string, string, error) { - for _, v := range workspace.Tunnel.Applications { - if v.Port == 11434 { - return v.Hostname, v.Name, nil - } - } - return "", "", breverrors.New("Could not find Ollama tunnel") -} - -// TODO: stub for granular permissioning -// func generateCloudflareAPIKeys(workspace *entity.Workspace, ollamaStore OllamaStore) (bool, error) { -// return false, nil -// } - -func makeTunnelPublic(workspace *entity.Workspace, applicationName string, ollamaStore OllamaStore) (bool, error) { - t, err := ollamaStore.ModifyPublicity(workspace, applicationName, true) - if err != nil { - return false, breverrors.WrapAndTrace(err) - } - for _, v := range t.Applications { - if v.Port == 11434 { - if v.Policy.AllowEveryone { - return true, nil - } - } - } - return false, breverrors.New("Could not find Ollama tunnel") -} - -func runSSHExec(sshAlias string, args []string, fireAndForget bool) error { - sshAgentEval := "eval $(ssh-agent -s)" - cmd := fmt.Sprintf("ssh %s -- %s", sshAlias, strings.Join(args, " ")) - cmd = fmt.Sprintf("%s && %s", sshAgentEval, cmd) - sshCmd := exec.Command("bash", "-c", cmd) //nolint:gosec //cmd is user input - - if fireAndForget { - if err := sshCmd.Start(); err != nil { - return fmt.Errorf("error starting SSH command: %w", err) - } - return nil - } - sshCmd.Stderr = os.Stderr - sshCmd.Stdout = os.Stdout - sshCmd.Stdin = os.Stdin - if err := sshCmd.Run(); err != nil { - return fmt.Errorf("error running SSH command: %w", err) - } - return nil -} diff --git a/pkg/cmd/ollama/ollama_test.go b/pkg/cmd/ollama/ollama_test.go deleted file mode 100644 index f74dfdc0..00000000 --- a/pkg/cmd/ollama/ollama_test.go +++ /dev/null @@ -1 +0,0 @@ -package ollama diff --git a/pkg/cmd/ollama/ollamauiverb.yaml b/pkg/cmd/ollama/ollamauiverb.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/pkg/cmd/ollama/ollamaverb.yaml b/pkg/cmd/ollama/ollamaverb.yaml deleted file mode 100644 index e8b9e896..00000000 --- a/pkg/cmd/ollama/ollamaverb.yaml +++ /dev/null @@ -1,18 +0,0 @@ -build: - python_version: "3.10" - cuda: 12.0.1 - python_packages: - - jupyterlab - run: - - curl -fsSL https://ollama.com/install.sh | sh -user: - shell: zsh - authorized_keys_path: /home/ubuntu/.ssh/authorized_keys -ports: - - "2222:22" - - "8000:8000" -services: - - name: ollama-server - entrypoint: OLLAMA_HOST=0.0.0.0 ollama serve - ports: - - 127.0.0.1:11434:11434 diff --git a/pkg/cmd/open/open.go b/pkg/cmd/open/open.go deleted file mode 100644 index 1509bd3d..00000000 --- a/pkg/cmd/open/open.go +++ /dev/null @@ -1,343 +0,0 @@ -package open - -import ( - "errors" - "fmt" - "os/exec" - "strings" - "time" - - "github.com/alessio/shellescape" - "github.com/brevdev/brev-cli/pkg/analytics" - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/hello" - "github.com/brevdev/brev-cli/pkg/cmd/refresh" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - uutil "github.com/brevdev/brev-cli/pkg/util" - "github.com/brevdev/brev-cli/pkg/writeconnectionevent" - "github.com/briandowns/spinner" - "github.com/hashicorp/go-multierror" - "github.com/samber/mo" - - "github.com/spf13/cobra" -) - -var ( - openLong = "[command in beta] This will open VS Code SSH-ed in to your workspace. You must have 'code' installed in your path." - openExample = "brev open workspace_id_or_name\nbrev open my-app\nbrev open h9fp5vxwe" -) - -type OpenStore interface { - util.GetWorkspaceByNameOrIDErrStore - refresh.RefreshStore - UpdateUser(string, *entity.UpdateUser) (*entity.User, error) - GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - StartWorkspace(workspaceID string) (*entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetWindowsDir() (string, error) - IsWorkspace() (bool, error) -} - -func NewCmdOpen(t *terminal.Terminal, store OpenStore, noLoginStartStore OpenStore) *cobra.Command { - var openWithCursor bool - var waitForSetupToFinish bool - var directory string - var host bool - - cmd := &cobra.Command{ - Annotations: map[string]string{"ssh": ""}, - Use: "open", - DisableFlagsInUseLine: true, - Short: "[beta] open VSCode or Cursor to your workspace", - Long: openLong, - Example: openExample, - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(1)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginStartStore, t), - RunE: func(cmd *cobra.Command, args []string) error { - setupDoneString := "------ Git repo cloned ------" - if waitForSetupToFinish { - setupDoneString = "------ Done running execs ------" - } - err := runOpenCommand(t, store, args[0], setupDoneString, directory, host) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - cmd.Flags().BoolVarP(&openWithCursor, "cursor", "c", false, "open cursor instead of VS Code") - cmd.Flags().BoolVarP(&host, "host", "", false, "ssh into the host machine instead of the container") - cmd.Flags().BoolVarP(&waitForSetupToFinish, "wait", "w", false, "wait for setup to finish") - cmd.Flags().StringVarP(&directory, "dir", "d", "", "directory to open") - - return cmd -} - -// Fetch workspace info, then open code editor -func runOpenCommand(t *terminal.Terminal, tstore OpenStore, wsIDOrName string, setupDoneString string, directory string, host bool) error { //nolint:funlen // define brev command - // todo check if workspace is stopped and start if it if it is stopped - fmt.Println("finding your instance...") - res := refresh.RunRefreshAsync(tstore) - workspace, err := util.GetUserWorkspaceByNameOrIDErr(tstore, wsIDOrName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if workspace.Status == "STOPPED" { // we start the env for the user - err = startWorkspaceIfStopped(t, tstore, wsIDOrName, workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - err = pollUntil(t, workspace.ID, "RUNNING", tstore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - workspace, err = util.GetUserWorkspaceByNameOrIDErr(tstore, wsIDOrName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if workspace.Status != "RUNNING" { - return breverrors.WorkspaceNotRunning{Status: workspace.Status} - } - - projPath := workspace.GetProjectFolderPath() - if directory != "" { - projPath = directory - } - - localIdentifier := workspace.GetLocalIdentifier() - if host { - localIdentifier = workspace.GetHostIdentifier() - } - - err = res.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = hello.SetHasRunOpen(true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - // we don't care about the error here but should log with sentry - // legacy environments wont support this and cause errrors, - // but we don't want to block the user from using vscode - _ = writeconnectionevent.WriteWCEOnEnv(tstore, string(localIdentifier)) - err = openVsCodeWithSSH(t, string(localIdentifier), projPath, tstore, setupDoneString) - if err != nil { - if strings.Contains(err.Error(), `"code": executable file not found in $PATH`) { - errMsg := "code\": executable file not found in $PATH\n\nadd 'code' to your $PATH to open VS Code from the terminal\n\texport PATH=\"/Applications/Visual Studio Code.app/Contents/Resources/app/bin:$PATH\"" - _, errStore := tstore.UpdateUser( - workspace.CreatedByUserID, - &entity.UpdateUser{ - OnboardingData: map[string]interface{}{ - "pathErrorTS": time.Now().UTC().Unix(), - }, - }) - if errStore != nil { - return errors.New(errMsg + "\n" + errStore.Error()) - } - return errors.New(errMsg) - } - return breverrors.WrapAndTrace(err) - } - // Call analytics for open - _ = pushOpenAnalytics(tstore, workspace) - return nil -} - -func pushOpenAnalytics(tstore OpenStore, workspace *entity.Workspace) error { - userID := "" - user, err := tstore.GetCurrentUser() - if err != nil { - userID = workspace.CreatedByUserID - } else { - userID = user.ID - } - data := analytics.EventData{ - EventName: "Brev Open", - UserID: userID, - Properties: map[string]string{ - "instanceId": workspace.ID, - }, - } - err = analytics.TrackEvent(data) - return breverrors.WrapAndTrace(err) -} - -func pollUntil(t *terminal.Terminal, wsid string, state string, openStore OpenStore) error { - s := t.NewSpinner() - isReady := false - s.Suffix = " hang tight šŸ¤™" - s.Start() - for !isReady { - time.Sleep(5 * time.Second) - ws, err := openStore.GetWorkspace(wsid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Suffix = " workspace is currently " + strings.ToLower(ws.Status) - if ws.Status == state { - s.Suffix = "Workspace is ready!" - s.Stop() - isReady = true - } - } - return nil -} - -func startWorkspaceIfStopped(t *terminal.Terminal, tstore OpenStore, wsIDOrName string, workspace *entity.Workspace) error { - startedWorkspace, err := tstore.StartWorkspace(workspace.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - t.Vprintf(t.Yellow("Instance %s is starting. \n\n", startedWorkspace.Name)) - workspace, err = util.GetUserWorkspaceByNameOrIDErr(tstore, wsIDOrName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func tryToInstallExtensions( - t *terminal.Terminal, - extIDs []string, -) { - for _, extID := range extIDs { - extInstalled, err0 := uutil.IsVSCodeExtensionInstalled(extID) - if !extInstalled { - err1 := uutil.InstallVscodeExtension(extID) - isRemoteInstalled, err2 := uutil.IsVSCodeExtensionInstalled(extID) - if !isRemoteInstalled { - err := multierror.Append(err0, err1, err2) - t.Print(t.Red("Couldn't install the necessary VSCode extension automatically.\nError: " + err.Error())) - t.Print("\tPlease install VSCode and the following VSCode extension: " + t.Yellow(extID) + ".\n") - _ = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Hit enter when finished:", - ErrorMsg: "error", - AllowEmpty: true, - }) - } - } - } -} - -// Opens code editor. Attempts to install code in path if not installed already -func openVsCodeWithSSH( - t *terminal.Terminal, - sshAlias string, - path string, - tstore OpenStore, - _ string, -) error { - // infinite for loop: - res := refresh.RunRefreshAsync(tstore) - err := res.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - s := t.NewSpinner() - s.Start() - s.Suffix = " checking if your instance is ready..." - err = waitForSSHToBeAvailable(t, s, sshAlias) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // todo: add it here - s.Suffix = " Instance is ready. Opening VS Code šŸ¤™" - time.Sleep(250 * time.Millisecond) - s.Stop() - t.Vprintf("\n") - - tryToInstallExtensions(t, []string{"ms-vscode-remote.remote-ssh", "ms-toolsai.jupyter-keymap", "ms-python.python"}) - - err = openVsCode(sshAlias, path, tstore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // check if we are in a brev environment, if so transform the error message - // to indicate that the user should run brev open locally instead of in - // the cloud and that we intend on supporting this in the future - // if there is an error getting the workspace, append that error with - // multierror, - // otherwise, just return the error - err = mo.TupleToResult(tstore.IsWorkspace()).Match( - func(value bool) (bool, error) { - if value { - // todo log original error to sentry - return true, errors.New("you are in a remote brev instance; brev open is not supported. Please run brev open locally instead") - } - return false, breverrors.WrapAndTrace(err) - }, - func(err2 error) (bool, error) { - return false, multierror.Append(err, err2) - }, - ).Error() - if err != nil { - if strings.Contains(err.Error(), "you are in a remote brev instance;") { - return breverrors.WrapAndTrace(err) - } - return breverrors.WrapAndTrace(fmt.Errorf(t.Red("couldn't open VSCode, try adding it to PATH (you can do this in VSCode by running CMD-SHIFT-P and typing 'install code in path')\n"))) - } else { - return nil - } -} - -func waitForSSHToBeAvailable(t *terminal.Terminal, s *spinner.Spinner, sshAlias string) error { - counter := 0 - for { - cmd := exec.Command("ssh", "-o", "ConnectTimeout=1", sshAlias, "echo", " ") - out, err := cmd.CombinedOutput() - if err == nil { - return nil - } - - outputStr := string(out) - stdErr := strings.Split(outputStr, "\n")[1] - - if counter == 160 || !store.SatisfactorySSHErrMessage(stdErr) { - return breverrors.WrapAndTrace(errors.New("\n" + stdErr)) - } - - if counter == 10 { - s.Suffix = t.Green(" waiting for SSH connection to be available šŸ¤™") - } - - counter++ - time.Sleep(1 * time.Second) - } -} - -type vscodePathStore interface { - GetWindowsDir() (string, error) -} - -func getWindowsVsCodePaths(store vscodePathStore) []string { - wd, _ := store.GetWindowsDir() - paths := append([]string{}, fmt.Sprintf("%s/AppData/Local/Programs/Microsoft VS Code/Code.exe", wd), fmt.Sprintf("%s/AppData/Local/Programs/Microsoft VS Code/bin/code", wd)) - return paths -} - -func openVsCode(sshAlias string, path string, store OpenStore) error { - vscodeString := fmt.Sprintf("vscode-remote://ssh-remote+%s%s", sshAlias, path) - vscodeString = shellescape.QuoteCommand([]string{vscodeString}) - - windowsPaths := getWindowsVsCodePaths(store) - _, err := uutil.TryRunVsCodeCommand([]string{"--folder-uri", vscodeString}, windowsPaths...) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/open/open_test.go b/pkg/cmd/open/open_test.go deleted file mode 100644 index a1ed6d51..00000000 --- a/pkg/cmd/open/open_test.go +++ /dev/null @@ -1 +0,0 @@ -package open diff --git a/pkg/cmd/org/ls.go b/pkg/cmd/org/ls.go deleted file mode 100644 index 772a48f7..00000000 --- a/pkg/cmd/org/ls.go +++ /dev/null @@ -1,38 +0,0 @@ -package org - -import ( - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmdcontext" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -func NewCmdOrgLs(t *terminal.Terminal, orgcmdStore OrgCmdStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"context": ""}, - Use: "ls", - Short: "List your organizations", - Long: `List your organizations, your current org will be prefixed -with * and highlighted with green`, - Example: `brev org ls`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - Args: cmderrors.TransformToValidationError(cobra.NoArgs), - // ValidArgs: []string{"new", "ls"}, - RunE: func(cmd *cobra.Command, args []string) error { - err := RunOrgs(t, orgcmdStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} diff --git a/pkg/cmd/org/org.go b/pkg/cmd/org/org.go deleted file mode 100644 index 82a47b4f..00000000 --- a/pkg/cmd/org/org.go +++ /dev/null @@ -1,130 +0,0 @@ -package org - -import ( - "fmt" - "os" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/invite" - "github.com/brevdev/brev-cli/pkg/cmdcontext" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/jedib0t/go-pretty/v6/table" - - "github.com/spf13/cobra" -) - -type OrgCmdStore interface { - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetUsers(queryParams map[string]string) ([]entity.User, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) - completions.CompletionStore - SetDefaultOrganization(org *entity.Organization) error - GetServerSockFile() string - CreateInviteLink(organizationID string) (string, error) - GetCurrentWorkspaceID() (string, error) -} - -func NewCmdOrg(t *terminal.Terminal, orgcmdStore OrgCmdStore, noorgcmdStore OrgCmdStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"context": ""}, - Use: "org", - Short: "Organization commands", - Long: "Organization commands", - Example: ` - brev org - brev org ls - brev org set - `, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - Args: cmderrors.TransformToValidationError(cobra.NoArgs), - // ValidArgs: []string{"new", "ls"}, - RunE: func(cmd *cobra.Command, args []string) error { - err := RunOrgs(t, orgcmdStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.AddCommand(NewCmdOrgSet(t, orgcmdStore, noorgcmdStore)) - cmd.AddCommand(NewCmdOrgLs(t, orgcmdStore)) - cmd.AddCommand(invite.NewCmdInvite(t, orgcmdStore, noorgcmdStore)) - - return cmd -} - -func RunOrgs(t *terminal.Terminal, store OrgCmdStore) error { - orgs, err := store.GetOrganizations(nil) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if len(orgs) == 0 { - t.Vprint(t.Yellow("You don't have any orgs. Create one! https://console.brev.dev")) - return nil - } - - defaultOrg, err := store.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - t.Vprint(t.Yellow("Your organizations:")) - displayOrgTable(t, orgs, defaultOrg) - if len(orgs) > 1 { - fmt.Print("\n") - t.Vprintf(t.Green("Switch orgs:\n")) - notDefaultOrg := getOtherOrg(orgs, *defaultOrg) - // TODO suggest org with max workspaces - t.Vprintf(t.Yellow("\tbrev org set ex: brev org set %s\n", notDefaultOrg.Name)) - } - - return nil -} - -func getBrevTableOptions() table.Options { - options := table.OptionsDefault - options.DrawBorder = false - options.SeparateColumns = false - options.SeparateRows = false - options.SeparateHeader = false - return options -} - -func getOtherOrg(orgs []entity.Organization, org entity.Organization) *entity.Organization { - for _, o := range orgs { - if org.ID != o.ID { - return &o - } - } - return nil -} - -func displayOrgTable(t *terminal.Terminal, orgs []entity.Organization, currentOrg *entity.Organization) { - ta := table.NewWriter() - ta.SetOutputMirror(os.Stdout) - ta.Style().Options = getBrevTableOptions() - header := table.Row{"NAME", "ID"} - ta.AppendHeader(header) - for _, o := range orgs { - workspaceRow := []table.Row{{o.Name, o.ID}} - if o.ID == currentOrg.ID { - workspaceRow = []table.Row{{t.Green("* " + o.Name), t.Green(o.ID)}} - } - ta.AppendRows(workspaceRow) - } - ta.Render() -} diff --git a/pkg/cmd/org/org_test.go b/pkg/cmd/org/org_test.go deleted file mode 100644 index 56df39da..00000000 --- a/pkg/cmd/org/org_test.go +++ /dev/null @@ -1 +0,0 @@ -package org diff --git a/pkg/cmd/org/set.go b/pkg/cmd/org/set.go deleted file mode 100644 index 2a6ea9e1..00000000 --- a/pkg/cmd/org/set.go +++ /dev/null @@ -1,90 +0,0 @@ -package org - -import ( - "fmt" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmdcontext" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -func NewCmdOrgSet(t *terminal.Terminal, orgcmdStore OrgCmdStore, noorgcmdStore OrgCmdStore) *cobra.Command { - var showAll bool - var org string - - cmd := &cobra.Command{ - Annotations: map[string]string{"context": ""}, - Use: "set", - Short: "Set active org", - Long: "Set your organization to view, open, create workspaces etc", - Example: ` - brev org set - `, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), - // ValidArgs: []string{"new", "ls"}, - RunE: func(cmd *cobra.Command, args []string) error { - err := set(args[0], orgcmdStore, t) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().StringVarP(&org, "org", "o", "", "organization (will override active org)") - err := cmd.RegisterFlagCompletionFunc("org", completions.GetOrgsNameCompletionHandler(noorgcmdStore, t)) - if err != nil { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) - fmt.Print(breverrors.WrapAndTrace(err)) - } - - cmd.Flags().BoolVar(&showAll, "all", false, "show all workspaces in org") - - return cmd -} - -func set(orgName string, setStore OrgCmdStore, t *terminal.Terminal) error { - workspaceID, err := setStore.GetCurrentWorkspaceID() - if err != nil { - return breverrors.WrapAndTrace(err) - } - fmt.Println(workspaceID) - - if workspaceID != "" { - return breverrors.NewValidationError("can not set orgs in a workspace") - } - orgs, err := setStore.GetOrganizations(&store.GetOrganizationsOptions{Name: orgName}) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if len(orgs) == 0 { - return breverrors.NewValidationError(fmt.Sprintf("no orgs exist with name %s", orgName)) - } else if len(orgs) > 1 { - return breverrors.NewValidationError(fmt.Sprintf("more than one org exist with name %s", orgName)) - } - - org := orgs[0] - - err = setStore.SetDefaultOrganization(&org) - if err != nil { - return breverrors.WrapAndTrace(err) - } - t.Vprintf("Org %s is now active šŸ¤™\n", t.Green(org.Name)) - - // Print workspaces within org - - return nil -} diff --git a/pkg/cmd/postinstall/postinstall.go b/pkg/cmd/postinstall/postinstall.go deleted file mode 100644 index 50ecbeb1..00000000 --- a/pkg/cmd/postinstall/postinstall.go +++ /dev/null @@ -1,113 +0,0 @@ -package postinstall - -import ( - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -type postinstallStore interface { - autostartconf.AutoStartStore - RegisterNotificationEmail(string) error - WriteEmail(email string) error -} - -func NewCmdpostinstall(_ *terminal.Terminal, store postinstallStore) *cobra.Command { - // var email string - - cmd := &cobra.Command{ - Use: "postinstall", - // DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - email := "" - if len(args) > 0 { - email = args[0] - } - err := Runpostinstall( - store, - email, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func NewCMDOptimizeThis(_ *terminal.Terminal, store postinstallStore) *cobra.Command { - // var email string - - cmd := &cobra.Command{ - Use: "optimize-this", - // DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - email := "" - if len(args) > 0 { - email = args[0] - } - err := Runpostinstall( - store, - email, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func Runpostinstall( - store postinstallStore, - email string, -) error { - if email == "" { - email = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Email: ", - ErrorMsg: "error", - }) - } - - err := store.WriteEmail(email) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = store.RegisterNotificationEmail(email) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - brevmonConfigurer := autostartconf.NewBrevMonConfigure( - store, - false, - "10m", // todo pass brevmon args instead of individual args - "22", - ) - - err = brevmonConfigurer.Install() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} diff --git a/pkg/cmd/profile/profile.go b/pkg/cmd/profile/profile.go deleted file mode 100644 index 030dc76d..00000000 --- a/pkg/cmd/profile/profile.go +++ /dev/null @@ -1,114 +0,0 @@ -package profile - -import ( - "errors" - "fmt" - "strings" - - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/start" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/fatih/color" - "github.com/pkg/browser" - - "github.com/spf13/cobra" -) - -var ( - startLong = "Make changes to your profile" - startExample = "brev profile --set-personal-config " -) - -type ProfileStore interface { - completions.CompletionStore - GetCurrentUser() (*entity.User, error) - UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) -} - -func NewCmdProfile(t *terminal.Terminal, loginProfileStore ProfileStore, noLoginProfileStore ProfileStore) *cobra.Command { - var personalSettingsRepo string - - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "profile", - DisableFlagsInUseLine: true, - Short: "Personal profile commands", - Long: startLong, - Example: startExample, - // Args: cmderrors.TransformToValidationError(cobra.NoArgs), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginProfileStore, t), - RunE: func(cmd *cobra.Command, args []string) error { - err := profile(personalSettingsRepo, t, loginProfileStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().StringVarP(&personalSettingsRepo, "set-personal-config", "p", "", "set your personal config repo") - - return cmd -} - -func goToProfileInConsole() { - url := "https://console.brev.dev/profile" - caretType := color.New(color.FgGreen, color.Bold).SprintFunc() - enterType := color.New(color.FgGreen, color.Bold).SprintFunc() - urlType := color.New(color.FgWhite, color.Bold).SprintFunc() - // fmt.Println("\n" + url + "\n") - _ = terminal.PromptGetInput(terminal.PromptContent{ - Label: " " + caretType("ā–ø") + " Press " + enterType("Enter") + " to edit your profile in browser", - ErrorMsg: "error", - AllowEmpty: true, - }) - - fmt.Print("\n") - - err := browser.OpenURL(url) - if err != nil { - fmt.Println("Error opening browser. Please copy", urlType(url), "and paste it in your browser.") - } -} - -func profile(personalSettingsRepo string, t *terminal.Terminal, profileStore ProfileStore) error { - user, err := profileStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if len(personalSettingsRepo) == 0 { - goToProfileInConsole() - return nil - } - - isURL := false - if strings.Contains(personalSettingsRepo, "https://") || strings.Contains(personalSettingsRepo, "git@") { - isURL = true - } - - if !isURL { - err = errors.New("please use a valid git url") - return breverrors.WrapAndTrace(err) - } - - temp := start.MakeNewWorkspaceFromURL(personalSettingsRepo) - t.Vprint(temp.GitRepo) - - // TODO: make sure the git repo format works!!!!!!! - - _, err = profileStore.UpdateUser(user.ID, &entity.UpdateUser{ - Username: user.Username, - Name: user.Name, - Email: user.Email, - BaseWorkspaceRepo: temp.GitRepo, - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprintf("Your personal config has been updated. All new instances will run this script.\n") - return nil -} diff --git a/pkg/cmd/profile/profile_test.go b/pkg/cmd/profile/profile_test.go deleted file mode 100644 index 73330f15..00000000 --- a/pkg/cmd/profile/profile_test.go +++ /dev/null @@ -1 +0,0 @@ -package profile diff --git a/pkg/cmd/proxy/proxy.go b/pkg/cmd/proxy/proxy.go deleted file mode 100644 index 784a5bde..00000000 --- a/pkg/cmd/proxy/proxy.go +++ /dev/null @@ -1,161 +0,0 @@ -package proxy - -import ( - "fmt" - "strings" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/huproxyclient" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/hashicorp/go-version" - "github.com/spf13/cobra" -) - -const ( - allowedWorkspaceImage = "brevdev/ubuntu-proxy" - allowedWorkspaceImageTag = ">= 0.3" - allowedWorkspaceInfraVersion = ">= 1.7" -) - -type ProxyStore interface { - huproxyclient.HubProxyStore - GetWorkspace(workspaceID string) (*entity.Workspace, error) - WritePrivateKey(pem string) error - GetCurrentUserKeys() (*entity.UserKeys, error) -} - -func NewCmdProxy(t *terminal.Terminal, store ProxyStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"hidden": ""}, - Use: "proxy", - DisableFlagsInUseLine: true, - Short: "http upgrade proxy", - Long: "http upgrade proxy for ssh ProxyCommand directive to use", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(1)), - RunE: func(cmd *cobra.Command, args []string) error { - err := Proxy(t, store, args[0]) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func Proxy(_ *terminal.Terminal, store ProxyStore, workspaceID string) error { - workspace, err := store.GetWorkspace(workspaceID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = CheckWorkspaceCanSSH(workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = WriteUserPrivateKey(store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - url := makeProxyURL(workspace) - err = huproxyclient.Run(url, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func makeProxyURL(w *entity.Workspace) string { - return fmt.Sprintf("wss://%s/proxy", w.GetSSHURL()) -} - -func CheckWorkspaceCanSSH(workspace *entity.Workspace) error { - if !featureflag.DisableSSHProxyVersionCheck() { - fmt.Println("checking instance version") - err := checkWorkspaceInfraVersionOrErr(workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - fmt.Println("checking instance image version") - err = checkWorkspaceImageVersionOrErr(workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - err := checkWorkspaceStatusOrErr(workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func checkWorkspaceStatusOrErr(workspace *entity.Workspace) error { - if workspace.Status != entity.Running { - return breverrors.NewValidationError(fmt.Sprintf("instance is not in RUNNING state, status: %s", workspace.Status)) - } - return nil -} - -func checkWorkspaceImageVersionOrErr(workspace *entity.Workspace) error { - imageSplit := strings.Split(workspace.WorkspaceTemplate.Image, ":") - if len(imageSplit) != 2 { - return breverrors.NewValidationError("problem parsing workspace image tag") - } - wiv, err := version.NewVersion(imageSplit[1]) - if err != nil { - if !strings.Contains(err.Error(), "Malformed") { - return breverrors.WrapAndTrace(err) - } else { - _ = 0 - // skip checking constraints since probably a test image - } - } else { - imageContraints, err := version.NewConstraint(allowedWorkspaceImageTag) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if !imageContraints.Check(wiv) && !strings.HasSuffix(imageSplit[0], allowedWorkspaceImage) { - return breverrors.NewValidationError(fmt.Sprintf("instance image version %s is not supported with this cli version\n upgrade your instance or downgrade your cli", workspace.WorkspaceTemplate.Image)) - } - } - return nil -} - -func checkWorkspaceInfraVersionOrErr(workspace *entity.Workspace) error { - fmt.Printf("instance version: %s\n", workspace.Version) - if workspace.Version != "" { - wv, err := version.NewVersion(workspace.Version) - if err != nil { - return breverrors.WrapAndTrace(err) - } - workspaceInfraConstraints, err := version.NewConstraint(allowedWorkspaceInfraVersion) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !workspaceInfraConstraints.Check(wv) { - return breverrors.NewValidationError(fmt.Sprintf("instance of version %s is not supported with this cli version\n upgrade your instance or downgrade your cli. Supported %s", workspace.Version, allowedWorkspaceInfraVersion)) - } - } else { - fmt.Println("instance version blank assuming dev, not checking constraint") - } - return nil -} - -func WriteUserPrivateKey(store ProxyStore) error { - keys, err := store.GetCurrentUserKeys() - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = store.WritePrivateKey(keys.PrivateKey) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/proxy/proxy_test.go b/pkg/cmd/proxy/proxy_test.go deleted file mode 100644 index d1ebab89..00000000 --- a/pkg/cmd/proxy/proxy_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package proxy - -import ( - "testing" - - "github.com/hashicorp/go-version" - "github.com/stretchr/testify/assert" -) - -func TestVersionParsing(t *testing.T) { - _, err := version.NewVersion("abadfjladsf") - assert.NotNil(t, err) -} diff --git a/pkg/cmd/recreate/doc.md b/pkg/cmd/recreate/doc.md deleted file mode 100644 index b8b4d8fc..00000000 --- a/pkg/cmd/recreate/doc.md +++ /dev/null @@ -1,46 +0,0 @@ -# Re Create Workspace by name or ID. - -## SYNOPSIS - -``` - brev recreate [ Workspace Name or ID... ] -``` - -## DESCRIPTION - -recreate a workspace is equivalent to running the following commands: - -``` -brev delete payments-fronted -brev start payments-frontend -``` - -This command has the effect of updating the base image of a workspace to the -latest. If your workspace has a git remote source, the workspace will start -with a fresh copy of the remote source and run the workspace setupscript. - -## EXAMPLE - -recreate a workspace with the name `naive-pubsub` - -``` -$ brev recreate payments-frontend -Starting hard reset šŸ¤™ This can take a couple of minutes. - -Deleting workspace - naive-pubsub. -Workspace is starting. This can take up to 2 minutes the first time. -name naive-pubsub -template v7nd45zsc Admin -resource class 4x16 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⢿ workspace is deploying -Your workspace is ready! - -SSH into your machine: - ssh naive-pubsub-uq0x -``` - -## SEE ALSO - - TODO diff --git a/pkg/cmd/recreate/recreate.go b/pkg/cmd/recreate/recreate.go deleted file mode 100644 index 37d15f8e..00000000 --- a/pkg/cmd/recreate/recreate.go +++ /dev/null @@ -1,226 +0,0 @@ -// Package recreate is for the recreate command -package recreate - -import ( - _ "embed" - "strings" - "time" - - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - stripmd "github.com/writeas/go-strip-markdown" -) - -//go:embed doc.md -var long string - -type recreateStore interface { - completions.CompletionStore - util.GetWorkspaceByNameOrIDErrStore - ResetWorkspace(workspaceID string) (*entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) - GetWorkspace(id string) (*entity.Workspace, error) - DeleteWorkspace(workspaceID string) (*entity.Workspace, error) -} - -func NewCmdRecreate(t *terminal.Terminal, store recreateStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "recreate", - DisableFlagsInUseLine: true, - Short: "TODO", - Long: stripmd.Strip(long), - Example: "TODO", - RunE: func(cmd *cobra.Command, args []string) error { - err := RunRecreate(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func RunRecreate(t *terminal.Terminal, args []string, recreateStore recreateStore) error { - for _, arg := range args { - err := hardResetProcess(arg, t, recreateStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -// hardResetProcess deletes an existing workspace and creates a new one -func hardResetProcess(workspaceName string, t *terminal.Terminal, recreateStore recreateStore) error { - t.Vprint(t.Green("recreating šŸ¤™ " + t.Yellow("This can take a couple of minutes.\n"))) - workspace, err := util.GetUserWorkspaceByNameOrIDErr(recreateStore, workspaceName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - deletedWorkspace, err := recreateStore.DeleteWorkspace(workspace.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Yellow("Deleting instance - %s.", deletedWorkspace.Name)) - time.Sleep(10 * time.Second) - - if len(deletedWorkspace.GitRepo) != 0 { - err := hardResetCreateWorkspaceFromRepo(t, recreateStore, deletedWorkspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err := hardResetCreateEmptyWorkspace(t, recreateStore, deletedWorkspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -// hardResetCreateWorkspaceFromRepo clone a GIT repository, triggeres from the --hardreset flag -func hardResetCreateWorkspaceFromRepo(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error { - t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.")) - var orgID string - activeorg, err := recreateStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - clusterID := config.GlobalConfig.GetDefaultClusterID() - options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name).WithGitRepo(workspace.GitRepo) - - user, err := recreateStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - options = resolveWorkspaceUserOptions(options, user) - - options.StartupScriptPath = workspace.StartupScriptPath - options.Execs = workspace.ExecsV0 - options.Repos = workspace.ReposV0 - options.IDEConfig = &workspace.IDEConfig - - w, err := recreateStore.CreateWorkspace(orgID, options) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = pollUntil(t, w.ID, entity.Running, recreateStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Green("\nYour instance is ready!")) - t.Vprintf(t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier())) - return nil -} - -// hardResetCreateEmptyWorkspace creates a new empty worksapce, triggered from the --hardreset flag -func hardResetCreateEmptyWorkspace(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error { - t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.\n")) - - // ensure name - if len(workspace.Name) == 0 { - return breverrors.NewValidationError("name field is required for empty workspaces") - } - - // ensure org - var orgID string - activeorg, err := recreateStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - clusterID := config.GlobalConfig.GetDefaultClusterID() - options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name) - - user, err := recreateStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - options = resolveWorkspaceUserOptions(options, user) - - options.StartupScriptPath = workspace.StartupScriptPath - options.Execs = workspace.ExecsV0 - options.Repos = workspace.ReposV0 - options.IDEConfig = &workspace.IDEConfig - - w, err := recreateStore.CreateWorkspace(orgID, options) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = pollUntil(t, w.ID, entity.Running, recreateStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Green("\nYour instance is ready!")) - t.Vprintf(t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier())) - - return nil -} - -func pollUntil(t *terminal.Terminal, wsid string, state string, recreateStore recreateStore, canSafelyExit bool) error { - s := t.NewSpinner() - isReady := false - if canSafelyExit { - t.Vprintf("You can safely ctrl+c to exit\n") - } - s.Suffix = " hang tight šŸ¤™" - s.Start() - for !isReady { - time.Sleep(5 * time.Second) - ws, err := recreateStore.GetWorkspace(wsid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Suffix = " workspace is " + strings.ToLower(ws.Status) - if ws.Status == state { - s.Suffix = "Workspace is ready!" - s.Stop() - isReady = true - } - } - return nil -} - -func resolveWorkspaceUserOptions(options *store.CreateWorkspacesOptions, user *entity.User) *store.CreateWorkspacesOptions { - if options.WorkspaceTemplateID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceTemplateID = store.DevWorkspaceTemplateID - } else { - options.WorkspaceTemplateID = store.UserWorkspaceTemplateID - } - } - if options.WorkspaceClassID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceClassID = store.DevWorkspaceClassID - } else { - options.WorkspaceClassID = store.UserWorkspaceClassID - } - } - return options -} diff --git a/pkg/cmd/reset/doc.md b/pkg/cmd/reset/doc.md deleted file mode 100644 index b53040cb..00000000 --- a/pkg/cmd/reset/doc.md +++ /dev/null @@ -1,27 +0,0 @@ -## SYNOPSIS - -``` - brev reset [ Workspace Name or ID... ] -``` - -## DESCRIPTION - -reset a workspace will stop a workspace, then start a workspace, perserving -files in `/home/brev/workspace/`. This will have the effect of rerunning your -setupscript in a newley created workspace with no changes made to it, and -replacing your workspace with that. - -## EXAMPLE - -reset a workspace with the name `payments-frontend` - -``` -$ brev reset payments-frontend -Workspace payments-frontend is resetting. -Note: this can take a few seconds. Run 'brev ls' to check status - -``` - -## SEE ALSO - - TODO diff --git a/pkg/cmd/reset/reset.go b/pkg/cmd/reset/reset.go deleted file mode 100644 index 5484f416..00000000 --- a/pkg/cmd/reset/reset.go +++ /dev/null @@ -1,252 +0,0 @@ -package reset - -import ( - _ "embed" - "strings" - "time" - - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/spf13/cobra" - stripmd "github.com/writeas/go-strip-markdown" -) - -var ( - //go:embed doc.md - long string - startExample = `brev reset ...` -) - -type ResetStore interface { - completions.CompletionStore - util.GetWorkspaceByNameOrIDErrStore - ResetWorkspace(workspaceID string) (*entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) - GetWorkspace(id string) (*entity.Workspace, error) - DeleteWorkspace(workspaceID string) (*entity.Workspace, error) -} - -func NewCmdReset(t *terminal.Terminal, loginResetStore ResetStore, noLoginResetStore ResetStore) *cobra.Command { - var hardreset bool - - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "reset", - DisableFlagsInUseLine: true, - Short: "Reset an instance if it's in a weird state.", - Long: stripmd.Strip(long), - Example: startExample, - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginResetStore, t), - RunE: func(cmd *cobra.Command, args []string) error { - for _, arg := range args { - if hardreset { - err := hardResetProcess(arg, t, loginResetStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err := resetWorkspace(arg, t, loginResetStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - } - return nil - }, - } - - cmd.Flags().BoolVarP(&hardreset, "hard", "", false, "DEPRECATED: use brev recreate") - return cmd -} - -// hardResetProcess deletes an existing workspace and creates a new one -func hardResetProcess(workspaceName string, t *terminal.Terminal, resetStore ResetStore) error { - t.Vprint(t.Green("Starting hard reset šŸ¤™ " + t.Yellow("This can take a couple of minutes.\n"))) - workspace, err := util.GetUserWorkspaceByNameOrIDErr(resetStore, workspaceName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - deletedWorkspace, err := resetStore.DeleteWorkspace(workspace.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Yellow("Deleting instance - %s.", deletedWorkspace.Name)) - time.Sleep(10 * time.Second) - - if len(deletedWorkspace.GitRepo) != 0 { - err := hardResetCreateWorkspaceFromRepo(t, resetStore, deletedWorkspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err := hardResetCreateEmptyWorkspace(t, resetStore, deletedWorkspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - t.Vprint(t.Red("NOTE: THIS COMMAND IS DEPRECATED")) - t.Vprint(t.Red("It still worked, but use brev recreate " + workspaceName + " next time")) - return nil -} - -// hardResetCreateWorkspaceFromRepo clone a GIT repository, triggeres from the --hardreset flag -func hardResetCreateWorkspaceFromRepo(t *terminal.Terminal, resetStore ResetStore, workspace *entity.Workspace) error { - t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.")) - var orgID string - activeorg, err := resetStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - clusterID := config.GlobalConfig.GetDefaultClusterID() - options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name).WithGitRepo(workspace.GitRepo) - - user, err := resetStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - options = resolveWorkspaceUserOptions(options, user) - - options.StartupScriptPath = workspace.StartupScriptPath - options.Execs = workspace.ExecsV0 - options.Repos = workspace.ReposV0 - options.IDEConfig = &workspace.IDEConfig - - w, err := resetStore.CreateWorkspace(orgID, options) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = pollUntil(t, w.ID, entity.Running, resetStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Green("\nYour instance is ready!")) - t.Vprintf(t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier())) - return nil -} - -// hardResetCreateEmptyWorkspace creates a new empty worksapce, triggered from the --hardreset flag -func hardResetCreateEmptyWorkspace(t *terminal.Terminal, resetStore ResetStore, workspace *entity.Workspace) error { - t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.\n")) - - // ensure name - if len(workspace.Name) == 0 { - return breverrors.NewValidationError("name field is required for empty instances") - } - - // ensure org - var orgID string - activeorg, err := resetStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - clusterID := config.GlobalConfig.GetDefaultClusterID() - options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name) - - user, err := resetStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - options = resolveWorkspaceUserOptions(options, user) - - options.StartupScriptPath = workspace.StartupScriptPath - options.Execs = workspace.ExecsV0 - options.Repos = workspace.ReposV0 - options.IDEConfig = &workspace.IDEConfig - - w, err := resetStore.CreateWorkspace(orgID, options) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = pollUntil(t, w.ID, entity.Running, resetStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Green("\nYour instance is ready!")) - t.Vprintf(t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier())) - - return nil -} - -func pollUntil(t *terminal.Terminal, wsid string, state string, resetStore ResetStore, canSafelyExit bool) error { - s := t.NewSpinner() - isReady := false - if canSafelyExit { - t.Vprintf("You can safely ctrl+c to exit\n") - } - s.Suffix = " hang tight šŸ¤™" - s.Start() - for !isReady { - time.Sleep(5 * time.Second) - ws, err := resetStore.GetWorkspace(wsid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Suffix = " workspace is " + strings.ToLower(ws.Status) - if ws.Status == state { - s.Suffix = "Workspace is ready!" - s.Stop() - isReady = true - } - } - return nil -} - -func resolveWorkspaceUserOptions(options *store.CreateWorkspacesOptions, user *entity.User) *store.CreateWorkspacesOptions { - if options.WorkspaceTemplateID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceTemplateID = store.DevWorkspaceTemplateID - } else { - options.WorkspaceTemplateID = store.UserWorkspaceTemplateID - } - } - if options.WorkspaceClassID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceClassID = store.DevWorkspaceClassID - } else { - options.WorkspaceClassID = store.UserWorkspaceClassID - } - } - return options -} - -func resetWorkspace(workspaceName string, t *terminal.Terminal, resetStore ResetStore) error { - workspace, err := util.GetUserWorkspaceByNameOrIDErr(resetStore, workspaceName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - startedWorkspace, err := resetStore.ResetWorkspace(workspace.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprintf(t.Yellow("Instance %s is resetting.\n", startedWorkspace.Name)) - t.Vprintf("Note: this can take a few seconds. Run 'brev ls' to check status\n") - - return nil -} diff --git a/pkg/cmd/reset/reset_test.go b/pkg/cmd/reset/reset_test.go deleted file mode 100644 index 8b777f24..00000000 --- a/pkg/cmd/reset/reset_test.go +++ /dev/null @@ -1 +0,0 @@ -package reset diff --git a/pkg/cmd/runtasks/doc.md b/pkg/cmd/runtasks/doc.md deleted file mode 100644 index a0200ae4..00000000 --- a/pkg/cmd/runtasks/doc.md +++ /dev/null @@ -1,42 +0,0 @@ -##### Synopsis - -``` - brev run-tasks -d -``` - -##### Description - -In order for brev to connect to workspaces, there needs to be background daemons -running to manage some things on your local machines environment. Currently, the -one that is being launched by run-tasks is an ssh config file configuration -daemon that periodically udpates a ssh config file with connection information -in order to access you workspaces. - -This command has to be run at every boot, see [Configuring SSH Proxy Daemon at Boot](https://docs.brev.dev/howto/configure-ssh-proxy-daemon-at-boot/) to -configure this command to be run at boot. - -This command is set to be deprecated in favor of `brev configure`. - -##### Examples - -to run tasks in the background - -``` -$ brev run-tasks -d -PID File: /home/f/.brev/task_daemon.pid -Log File: /home/f/.brev/task_daemon.log -``` - -to run tasks in the foreground - -``` -$ brev run-tasks -2022/07/11 15:28:44 creating new ssh config -2022/07/11 15:28:48 creating new ssh config - -``` - -##### See Also - -- [Configuring SSH Proxy Daemon at Boot](https://docs.brev.dev/howto/configure-ssh-proxy-daemon-at-boot/) - -TODO brev configure docs diff --git a/pkg/cmd/runtasks/runtasks.go b/pkg/cmd/runtasks/runtasks.go deleted file mode 100644 index 90e79cad..00000000 --- a/pkg/cmd/runtasks/runtasks.go +++ /dev/null @@ -1,88 +0,0 @@ -package runtasks - -import ( - _ "embed" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/ssh" - "github.com/brevdev/brev-cli/pkg/tasks" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" - stripmd "github.com/writeas/go-strip-markdown" -) - -//go:embed doc.md -var long string - -func NewCmdRunTasks(t *terminal.Terminal, store RunTasksStore) *cobra.Command { - var detached bool - // would be nice to have a way to pass in a list of tasks to run instead of the default - var runRemoteCMD bool - - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "run-tasks", - DisableFlagsInUseLine: true, - Short: "Run background tasks for brev", - Long: stripmd.Strip(long), - Example: "brev run-tasks -d", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - RunE: func(cmd *cobra.Command, args []string) error { - err := RunTasks(t, store, detached) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().BoolVarP(&detached, "detached", "d", false, "run the command in the background instead of blocking the shell") - cmd.Flags().BoolVarP(&runRemoteCMD, "run-remote-cmd", "r", true, "run the command on the instance to cd into ws default dir") - return cmd -} - -type RunTasksStore interface { - ssh.ConfigUpdaterStore - ssh.SSHConfigurerV2Store - tasks.RunTaskAsDaemonStore - GetCurrentUser() (*entity.User, error) - GetCurrentUserKeys() (*entity.UserKeys, error) -} - -func RunTasks(_ *terminal.Terminal, store RunTasksStore, detached bool) error { - ts, err := getDefaultTasks(store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if detached { - err := tasks.RunTaskAsDaemon(ts, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err := tasks.RunTasks(ts) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -func getDefaultTasks(store RunTasksStore) ([]tasks.Task, error) { - configs, err := ssh.GetSSHConfigs(store) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // get private key and set here - keys, err := store.GetCurrentUserKeys() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - cu := ssh.NewConfigUpdater(store, configs, keys.PrivateKey) - - return []tasks.Task{cu}, nil -} diff --git a/pkg/cmd/runtasks/runtasks_test.go b/pkg/cmd/runtasks/runtasks_test.go deleted file mode 100644 index 494a3765..00000000 --- a/pkg/cmd/runtasks/runtasks_test.go +++ /dev/null @@ -1 +0,0 @@ -package runtasks diff --git a/pkg/cmd/scale/scale.go b/pkg/cmd/scale/scale.go deleted file mode 100644 index 6e14c4fe..00000000 --- a/pkg/cmd/scale/scale.go +++ /dev/null @@ -1,101 +0,0 @@ -package scale - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - long = "Scale your Brev instance to get a more powerful machine or save costs" - example = ` - brev scale MyInstance --gpu p3.2xlarge - brev scale MyInstance --cpu 2x8 - ` - // TODO: we might wanna use the below for validation ĀÆ\_(惄)_/ĀÆ - // instanceTypes = []string{"p4d.24xlarge", "p3.2xlarge", "p3.8xlarge", "p3.16xlarge", "p3dn.24xlarge", "p2.xlarge", "p2.8xlarge", "p2.16xlarge", "g5.xlarge", "g5.2xlarge", "g5.4xlarge", "g5.8xlarge", "g5.16xlarge", "g5.12xlarge", "g5.24xlarge", "g5.48xlarge", "g5g.xlarge", "g5g.2xlarge", "g5g.4xlarge", "g5g.8xlarge", "g5g.16xlarge", "g5g.metal", "g4dn.xlarge", "g4dn.2xlarge", "g4dn.4xlarge", "g4dn.8xlarge", "g4dn.16xlarge", "g4dn.12xlarge", "g4dn.metal", "g4ad.xlarge", "g4ad.2xlarge", "g4ad.4xlarge", "g4ad.8xlarge", "g4ad.16xlarge", "g3s.xlarge", "g3.4xlarge", "g3.8xlarge", "g3.16xlarge"} -) - -type ScaleStore interface { - util.GetWorkspaceByNameOrIDErrStore - ModifyWorkspace(organizationID string, options *store.ModifyWorkspaceRequest) (*entity.Workspace, error) -} - -func NewCmdScale(t *terminal.Terminal, sstore ScaleStore) *cobra.Command { - // var instanceType string - var gpu string - var cpu string - - cmd := &cobra.Command{ - Use: "scale", - DisableFlagsInUseLine: true, - Short: "Scale your Brev instance", - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return breverrors.NewValidationError("You must provide an instance with flage -i") - } - - err := Runscale(t, args, gpu, cpu, sstore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().StringVarP(&gpu, "gpu", "g", "", "GPU instance type. See https://docs.brev.dev/docs/reference/gpu/#gpu-instance-types for details") - cmd.Flags().StringVarP(&cpu, "cpu", "c", "", "CPU instance type. See https://docs.brev.dev/docs/reference/gpu/#cpu-instance-types for details") - // cmd.Flags().StringVarP(&instanceType, "instance", "i", "", "GPU or CPU instance type. See docs.brev.dev/gpu for details") - return cmd -} - -func Runscale(t *terminal.Terminal, args []string, gpu string, cpu string, sstore ScaleStore) error { - workspace, err := util.GetUserWorkspaceByNameOrIDErr(sstore, args[0]) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var modifyBody store.ModifyWorkspaceRequest - - s := t.NewSpinner() - - if gpu != "" { - modifyBody.InstanceType = gpu - s.Suffix = fmt.Sprintf(" Scaling %s to %s GPU", workspace.Name, gpu) - - } else if cpu != "" { - modifyBody.WorkspaceClassID = cpu - s.Suffix = fmt.Sprintf(" Scaling %s to %s CPU", workspace.Name, cpu) - - } - - s.Start() - ws, err := sstore.ModifyWorkspace(workspace.ID, &modifyBody) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Stop() - - // fmt.Printf("name %s\n", result.Name) - // fmt.Printf("template %s %s\n", result.WorkspaceTemplate.ID, result.WorkspaceTemplate.Name) - // fmt.Printf("resource class %s\n", result.WorkspaceClassID) - // fmt.Printf("instance %s\n", result.InstanceType) - // fmt.Printf("workspace group %s\n", result.WorkspaceGroupID) - - if gpu != "" { - t.Vprintf("\n\nInstance %s scaled to %s šŸ¤™\n", t.Green(ws.Name), t.Green(ws.InstanceType)) - } else if cpu != "" { - t.Vprintf("\n\nInstance %s scaled to %s šŸ¤™\n", t.Green(ws.Name), t.Green(ws.WorkspaceClassID)) - } - t.Vprintf("\n%s", t.Yellow("Your instance might still be rebooting. Check the status with `brev ls`\n")) - - return nil -} diff --git a/pkg/cmd/secret/secret.go b/pkg/cmd/secret/secret.go deleted file mode 100644 index 11357242..00000000 --- a/pkg/cmd/secret/secret.go +++ /dev/null @@ -1,204 +0,0 @@ -// Package secret lets you add secrests. Language Go makes you write extra. -package secret - -import ( - "encoding/json" - "fmt" - - "github.com/brevdev/brev-cli/pkg/cmdcontext" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -type SecretStore interface { - CreateSecret(req store.CreateSecretRequest) (*store.CreateSecretRequest, error) - GetCurrentUser() (*entity.User, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) -} - -func NewCmdSecret(secretStore SecretStore, t *terminal.Terminal) *cobra.Command { - var envtype string - var name string - var value string - var path string - var scope string - - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "secret", - Short: "Add a secret/environment variable", - Long: "Add a secret/environment variable to your instance, all instances in an org, or all of your instances", - Example: ` - brev secret --name my_value --value my_value --type [file, variable] --file-path --scope [org, user] - brev secret --name SERVER_URL --value https://brev.sh --type variable --scope [org, user] - brev secret --name AWS_KEY --value ... --type file --file-path --scope [org, user] - `, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - // Args: cobra.MinimumNArgs(0), - // ValidArgs: []string{"orgs", "workspaces"}, - RunE: func(cmd *cobra.Command, args []string) error { - err := addSecret(secretStore, t, envtype, name, value, path, scope) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().StringVarP(&envtype, "type", "t", "", "type of secret (env var or file)") - cmd.Flags().StringVarP(&name, "name", "n", "", "name of environment variable or secret file") - cmd.Flags().StringVarP(&value, "value", "v", "", "value of environment variable or secret file") - cmd.Flags().StringVarP(&path, "file-path", "p", "", "file path (if secret file)") - cmd.Flags().StringVarP(&scope, "scope", "s", "", "scope for env var (org or private)") - - err := cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{"file", "variable"}, cobra.ShellCompDirectiveNoSpace - }) - if err != nil { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) - fmt.Print(breverrors.WrapAndTrace(err)) - } - - err = cmd.RegisterFlagCompletionFunc("scope", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{"org", "private"}, cobra.ShellCompDirectiveNoSpace - }) - if err != nil { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) - fmt.Print(breverrors.WrapAndTrace(err)) - } - - return cmd -} - -func addSecret(secretStore SecretStore, t *terminal.Terminal, envtype string, name string, value string, path string, scope string) error { //nolint:funlen, gocyclo // todo simplify me - if name == "" || envtype == "" || value == "" || path == "" { - t.Vprintf(t.Yellow("\nSome flags omitted, running interactive mode!\n")) - } - - if name == "" { - name = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Environment variable/secret name: ", - ErrorMsg: "error", - }) - } - - if envtype == "" { - envtype = terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Type of variable: ", - ErrorMsg: "error", - Items: []string{"file", "variable"}, - }) - } - - if value == "" { - value = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Environment variable/secret value: ", - ErrorMsg: "error", - }) - } - - if path == "" && envtype == "file" { - path = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Path for the file: ", - ErrorMsg: "error", - Default: "/home/brev/workspace/secret.txt", - }) - } - - if scope == "" { - scope = terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Scope: ", - ErrorMsg: "error", - Items: []string{"org", "user"}, - }) - } - - if envtype == "file" { - t.Vprintf("brev secret --name %s --value %s --type %s --file-path %s --scope %s\n", name, value, envtype, path, scope) - } else { - t.Vprintf("brev secret --name %s --value %s --type %s --scope %s\n", name, value, envtype, scope) - } - - s := t.NewSpinner() - s.Suffix = " encrypting and saving secret var" - s.Start() - - iScope := store.Org - var hierarchyID string - if scope == "user" { - iScope = store.User - // get user id - me, err := secretStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - hierarchyID = me.ID - } else { - // get org id - defaultOrg, err := secretStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if defaultOrg == nil { - return fmt.Errorf("no orgs exist") - } - hierarchyID = defaultOrg.ID - } - - var configDest store.DestConfig - iType := store.File - if envtype == "variable" { - iType = store.EnvVariable - configDest = store.DestConfig{ - Name: name, - } - } else { - configDest = store.DestConfig{ - Path: path, - } - } - - // NOTE: hieararchyID needs to be the org ID user ID - - b := store.CreateSecretRequest{ - Name: name, - HierarchyType: iScope, - HierarchyID: hierarchyID, - Src: store.SecretReqSrc{ - Type: store.KeyValue, - Config: store.SrcConfig{ - Value: value, - }, - }, - Dest: store.SecretReqDest{ - Type: iType, - Config: configDest, - }, - } - asstring, _ := json.MarshalIndent(b, "", "\t") - fmt.Print(string(asstring)) - secret, err := secretStore.CreateSecret(b) - if err != nil { - s.Stop() - t.Vprintf(t.Red(err.Error())) - return breverrors.WrapAndTrace(err) - } - t.Vprintf(secret.Name) - s.Suffix = " environment secret added" - s.Stop() - - t.Vprintf(t.Green("\nEnvironment %s added\n", iType) + t.Yellow("\tNote: It might take up to 2 minutes to load into your environment.")) - - return nil -} diff --git a/pkg/cmd/secret/secret_test.go b/pkg/cmd/secret/secret_test.go deleted file mode 100644 index a48db44e..00000000 --- a/pkg/cmd/secret/secret_test.go +++ /dev/null @@ -1 +0,0 @@ -package secret diff --git a/pkg/cmd/shell/shell.go b/pkg/cmd/shell/shell.go deleted file mode 100644 index 173ff8c5..00000000 --- a/pkg/cmd/shell/shell.go +++ /dev/null @@ -1,227 +0,0 @@ -package shell - -import ( - "errors" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/brevdev/brev-cli/pkg/analytics" - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/hello" - "github.com/brevdev/brev-cli/pkg/cmd/refresh" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/brev-cli/pkg/writeconnectionevent" - "github.com/briandowns/spinner" - - "github.com/spf13/cobra" -) - -var ( - openLong = "[command in beta] This will shell in to your workspace" - openExample = "brev shell workspace_id_or_name\nbrev shell my-app\nbrev open h9fp5vxwe" -) - -type ShellStore interface { - util.GetWorkspaceByNameOrIDErrStore - refresh.RefreshStore - GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - StartWorkspace(workspaceID string) (*entity.Workspace, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetCurrentUserKeys() (*entity.UserKeys, error) -} - -func NewCmdShell(t *terminal.Terminal, store ShellStore, noLoginStartStore ShellStore) *cobra.Command { - var runRemoteCMD bool - var directory string - var host bool - cmd := &cobra.Command{ - Annotations: map[string]string{"ssh": ""}, - Use: "shell", - Aliases: []string{"ssh"}, - DisableFlagsInUseLine: true, - Short: "[beta] open a shell in your instance", - Long: openLong, - Example: openExample, - Args: cmderrors.TransformToValidationError(cmderrors.TransformToValidationError(cobra.ExactArgs(1))), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginStartStore, t), - RunE: func(cmd *cobra.Command, args []string) error { - err := runShellCommand(t, store, args[0], directory, host) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - cmd.Flags().BoolVarP(&host, "host", "", false, "ssh into the host machine instead of the container") - cmd.Flags().BoolVarP(&runRemoteCMD, "remote", "r", true, "run remote commands") - cmd.Flags().StringVarP(&directory, "dir", "d", "", "override directory to launch shell") - - return cmd -} - -func runShellCommand(t *terminal.Terminal, sstore ShellStore, workspaceNameOrID, directory string, host bool) error { - s := t.NewSpinner() - workspace, err := util.GetUserWorkspaceByNameOrIDErr(sstore, workspaceNameOrID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if workspace.Status == "STOPPED" { // we start the env for the user - err = startWorkspaceIfStopped(t, s, sstore, workspaceNameOrID, workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - err = pollUntil(s, workspace.ID, "RUNNING", sstore, " waiting for instance to be ready...") - if err != nil { - return breverrors.WrapAndTrace(err) - } - refreshRes := refresh.RunRefreshAsync(sstore) - - workspace, err = util.GetUserWorkspaceByNameOrIDErr(sstore, workspaceNameOrID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if workspace.Status != "RUNNING" { - return breverrors.New("Workspace is not running") - } - - localIdentifier := workspace.GetLocalIdentifier() - if host { - localIdentifier = workspace.GetHostIdentifier() - } - - sshName := string(localIdentifier) - - err = refreshRes.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = waitForSSHToBeAvailable(sshName, s) - if err != nil { - return breverrors.WrapAndTrace(err) - } - // we don't care about the error here but should log with sentry - // legacy environments wont support this and cause errrors, - // but we don't want to block the user from using the shell - _ = writeconnectionevent.WriteWCEOnEnv(sstore, workspace.DNS) - err = runSSH(workspace, sshName, directory) - if err != nil { - return breverrors.WrapAndTrace(err) - } - // Call analytics for shell - userID := "" - user, err := sstore.GetCurrentUser() - if err != nil { - userID = workspace.CreatedByUserID - } else { - userID = user.ID - } - data := analytics.EventData{ - EventName: "Brev Open", - UserID: userID, - Properties: map[string]string{ - "instanceId": workspace.ID, - }, - } - _ = analytics.TrackEvent(data) - - return nil -} - -func waitForSSHToBeAvailable(sshAlias string, s *spinner.Spinner) error { - counter := 0 - s.Suffix = " waiting for SSH connection to be available" - s.Start() - for { - cmd := exec.Command("ssh", "-o", "ConnectTimeout=3", sshAlias, "echo", " ") - out, err := cmd.CombinedOutput() - if err == nil { - s.Stop() - return nil - } - - outputStr := string(out) - stdErr := strings.Split(outputStr, "\n")[1] - - if counter == 120 || !store.SatisfactorySSHErrMessage(stdErr) { - return breverrors.WrapAndTrace(errors.New("\n" + stdErr)) - } - - counter++ - time.Sleep(1 * time.Second) - } -} - -func runSSH(_ *entity.Workspace, sshAlias, _ string) error { - sshAgentEval := "eval $(ssh-agent -s)" - cmd := fmt.Sprintf("ssh %s", sshAlias) - cmd = fmt.Sprintf("%s && %s", sshAgentEval, cmd) - sshCmd := exec.Command("bash", "-c", cmd) //nolint:gosec //cmd is user input - sshCmd.Stderr = os.Stderr - sshCmd.Stdout = os.Stdout - sshCmd.Stdin = os.Stdin - - err := hello.SetHasRunShell(true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = sshCmd.Run() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func startWorkspaceIfStopped(t *terminal.Terminal, s *spinner.Spinner, tstore ShellStore, wsIDOrName string, workspace *entity.Workspace) error { - activeOrg, err := tstore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - workspaces, err := tstore.GetWorkspaceByNameOrID(activeOrg.ID, wsIDOrName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - startedWorkspace, err := tstore.StartWorkspace(workspaces[0].ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - t.Vprintf(t.Yellow("Instance %s is starting. \n\n", startedWorkspace.Name)) - err = pollUntil(s, workspace.ID, entity.Running, tstore, " hang tight šŸ¤™") - if err != nil { - return breverrors.WrapAndTrace(err) - } - workspace, err = util.GetUserWorkspaceByNameOrIDErr(tstore, wsIDOrName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func pollUntil(s *spinner.Spinner, wsid string, state string, shellStore ShellStore, waitMsg string) error { - isReady := false - s.Suffix = waitMsg - s.Start() - for !isReady { - time.Sleep(5 * time.Second) - ws, err := shellStore.GetWorkspace(wsid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Suffix = waitMsg - if ws.Status == state { - isReady = true - } - } - return nil -} diff --git a/pkg/cmd/shell/shell_test.go b/pkg/cmd/shell/shell_test.go deleted file mode 100644 index b1f847e4..00000000 --- a/pkg/cmd/shell/shell_test.go +++ /dev/null @@ -1 +0,0 @@ -package shell diff --git a/pkg/cmd/sshall/sshall.go b/pkg/cmd/sshall/sshall.go deleted file mode 100644 index 9f61d5a2..00000000 --- a/pkg/cmd/sshall/sshall.go +++ /dev/null @@ -1,269 +0,0 @@ -package sshall - -import ( - "fmt" - "io/ioutil" - "log" - "math/rand" - "os" - "os/signal" - "strconv" - "sync" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/k8s" - "github.com/brevdev/brev-cli/pkg/portforward" - "github.com/pkg/errors" - "golang.org/x/crypto/ssh" - "k8s.io/apimachinery/pkg/util/runtime" -) - -type ( - connectionMap map[string]chan struct{} - retrymap map[string]int -) - -type SSHAll struct { - workspaces []entity.WorkspaceWithMeta - workspaceGroupClientMapper k8s.WorkspaceGroupClientMapper - sshResolver SSHResolver - workspaceConnections connectionMap - workspaceConnectionsMutex *sync.RWMutex - retries retrymap -} - -type SSHResolver interface { - GetConfiguredWorkspacePort(entity.WorkspaceLocalID) (string, error) - GetPrivateKeyPath() (string, error) -} - -func NewSSHAll( - workspaces []entity.WorkspaceWithMeta, - workspaceGroupClientMapper k8s.WorkspaceGroupClientMapper, - sshResolver SSHResolver, -) *SSHAll { - return &SSHAll{ - workspaces: workspaces, - workspaceGroupClientMapper: workspaceGroupClientMapper, - sshResolver: sshResolver, - workspaceConnections: make(connectionMap), - workspaceConnectionsMutex: &sync.RWMutex{}, - retries: make(retrymap), - } -} - -func (s SSHAll) workspaceSSHConnectionHealthCheck(w entity.WorkspaceWithMeta) (bool, error) { - var hostKey ssh.PublicKey - // A public key may be used to authenticate against the remote - // var hostKey ssh.PublicKey - // A public key may be used to authenticate against the remote - // server by using an unencrypted PEM-encoded private key file. - // - // If you have an encrypted private key, the crypto/x509 package - // can be used to decrypt it. - privateKeyPath, err := s.sshResolver.GetPrivateKeyPath() - if err != nil { - return false, breverrors.WrapAndTrace(err) - } - key, err := ioutil.ReadFile(privateKeyPath) //nolint:gosec // not including file via variable - if err != nil { - return false, breverrors.WrapAndTrace(err, "unable to read private key: %v") - } - - // Create the Signer for this private key. - signer, err := ssh.ParsePrivateKey(key) - if err != nil { - return false, breverrors.WrapAndTrace(err, "unable to parse private key: %v") - } - - config := &ssh.ClientConfig{ - User: "brev", - Auth: []ssh.AuthMethod{ - // Use the PublicKeys method for remote authentication. - ssh.PublicKeys(signer), - }, - HostKeyCallback: ssh.FixedHostKey(hostKey), - } - - // Connect to the remote server and perform the SSH handshake. - client, err := ssh.Dial("tcp", w.DNS, config) - if err != nil { - return false, breverrors.WrapAndTrace(err, "unable to connect: %v") - } - err = client.Close() - if err != nil { - return true, breverrors.WrapAndTrace(err) - } - return true, nil -} - -// NOTE: not sure if passing in all the workspaces to check for name is useful here, like in entity.go: GetLocalIdentifier -func (s SSHAll) runPortForwardWorkspace(workspace entity.WorkspaceWithMeta, _ []entity.WorkspaceWithMeta) { - err := s.portforwardWorkspace(workspace) - if err != nil { - // todo have verbose version with trace - fmt.Printf("%v [workspace=%s]\n", errors.Cause(err), workspace.DNS) - } -} - -func (s SSHAll) Run() error { - errorQueue := make(chan error, 1) - - // set up error handling for the ssh connections, very much brute force - // since kubectl was not intended to be used a library like this - runtime.ErrorHandlers = append(runtime.ErrorHandlers, func(err error) { - errorQueue <- err - }) - - go func() { - for { - <-errorQueue - fmt.Println("resetting unhealthy connections") - for _, w := range s.workspaces { - isHealthy, _ := s.workspaceSSHConnectionHealthCheck(w) - if !isHealthy { - fmt.Printf("resetting [w=%s]\n", w.DNS) - TryClose(s.workspaceConnections[w.ID]) - if s.retries[w.ID] > 0 { - s.retries[w.ID]-- - s.runPortForwardWorkspace(w, s.workspaces) - } - } - } - } - }() - - if len(s.workspaces) == 0 { - fmt.Println("No instances in org") - return nil - } - - fmt.Println(`Please add the following to your ssh config to -avoid false positives for user host key checking. - -Host * - NoHostAuthenticationForLocalhost yes - - `) - - fmt.Println() - for _, w := range s.workspaces { - fmt.Printf("ssh %s\n", w.GetLocalIdentifier()) - s.retries[w.ID] = 3 // TODO magic number - s.runPortForwardWorkspace(w, s.workspaces) - } - fmt.Println() - - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) - defer signal.Stop(signals) - <-signals - - return nil -} - -func TryClose(toClose chan struct{}) { - defer func() { - if r := recover(); r != nil { - fmt.Println("recovered panic:", r) - } - }() - close(toClose) -} - -func (s SSHAll) portforwardWorkspace(workspace entity.WorkspaceWithMeta) error { - port, err := s.sshResolver.GetConfiguredWorkspacePort(workspace.Workspace.GetLocalIdentifier()) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if port == "" { - return fmt.Errorf("port not found") - } - portMapping := makeSSHPortMapping(port) - err = s.portforwardWorkspaceAtPort(workspace, portMapping) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (s *SSHAll) portforwardWorkspaceAtPort(workspace entity.WorkspaceWithMeta, portMapping string) error { - dpf := portforward.NewDefaultPortForwarder() - pf := portforward.NewPortForwardOptions( - s.workspaceGroupClientMapper, - dpf, - ) - - s.workspaceConnectionsMutex.Lock() - s.workspaceConnections[workspace.ID] = pf.StopChannel - s.workspaceConnectionsMutex.Unlock() - - _, err := pf.WithWorkspace(workspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - pf.WithPort(portMapping) - - go func() { - err = pf.RunPortforward() - if err != nil { - log.Println(err) - } - }() - - return nil -} - -type ( - RandomSSHResolver struct { - WorkspaceResolver WorkspaceResolver - } - WorkspaceResolver interface { - GetMyWorkspaces(orgID string) ([]entity.Workspace, error) - GetWorkspaceMetaData(wsID string) (*entity.WorkspaceMetaData, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - } -) - -func NewRandomPortSSHResolver(workspaceResolver WorkspaceResolver) *RandomSSHResolver { - return &RandomSSHResolver{ - WorkspaceResolver: workspaceResolver, - } -} - -func (r RandomSSHResolver) GetWorkspaces() ([]entity.WorkspaceWithMeta, error) { - activeOrg, err := r.WorkspaceResolver.GetActiveOrganizationOrDefault() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - wss, err := r.WorkspaceResolver.GetMyWorkspaces(activeOrg.ID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - var workspacesWithMeta []entity.WorkspaceWithMeta - for _, w := range wss { - wmeta, err := r.WorkspaceResolver.GetWorkspaceMetaData(w.ID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - workspaceWithMeta := entity.WorkspaceWithMeta{WorkspaceMetaData: *wmeta, Workspace: w} - workspacesWithMeta = append(workspacesWithMeta, workspaceWithMeta) - } - - return workspacesWithMeta, nil -} - -func (r RandomSSHResolver) GetConfiguredWorkspacePort(_ entity.Workspace) (string, error) { - minPort := 1024 - maxPort := 65535 - port := rand.Intn(maxPort-minPort) + minPort // #nosec no need to by cryptographically secure - return strconv.Itoa(port), nil -} - -func makeSSHPortMapping(localPort string) string { - return fmt.Sprintf("%s:22", localPort) -} diff --git a/pkg/cmd/sshall/sshall_test.go b/pkg/cmd/sshall/sshall_test.go deleted file mode 100644 index c8482cc6..00000000 --- a/pkg/cmd/sshall/sshall_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package sshall - -// func Test_getActiveWorkspaces(t *testing.T) { -// _, err := getUserActiveWorkspaces() -// ae := breverrors.ActiveOrgFileNotFound{} -// assert.ErrorIs(t, err, &ae) -// } - -// func Test_getLocalPortForWorkspace(t *testing.T) { -// port := getRandomLocalPortForWorkspace(brevapi.Workspace{}) -// assert.NotEmpty(t, port) -// } - -// func Test_portforwardWorkspace(t *testing.T) { -// err := portforwardWorkspace("jtevxj5g5", "4444:4444") -// assert.Nil(t, err) -// } - -// func Test_runSSHAll(t *testing.T) { -// workspaces, err := getUserActiveWorkspaces() -// assert.Nil(t, err) - -// err = Run(workspaces, getRandomLocalPortForWorkspace) -// assert.Nil(t, err) -// } diff --git a/pkg/cmd/status/doc.md b/pkg/cmd/status/doc.md deleted file mode 100644 index fb97a991..00000000 --- a/pkg/cmd/status/doc.md +++ /dev/null @@ -1,268 +0,0 @@ -create start and join a workspace - -## Synopsis - -``` - -brev start { ARG | -e} {-n | --name} {-c | --class} { -s | --setup-script} - {-r | --setup-repo} {-p | --setup-path } { -o | --org} -``` - -## Description - -brev start can do the following: - -- start a stopped workspace -- join a workspace in an organization -- create an empty workspace -- create a workspace from a directory on your computer -- create a workspace from a git url - -## Flags - -### -n --name - -specify the name for your workspace instead of brev-cli generating one for you. - -for example, to override the name of a workspace when creating a workspace from -a git repo you could do it with then `-n` flag. This example creates a repo with -the name `cli` from the git repo `https://github.com/brevdev/brev-cli`. - -``` -$ brev start https://github.com/brevdev/brev-cli -n cli -``` - -## Examples - -### Create an empty workspace - -``` -$ brev start -e -n foo -``` - -which has an output similar too: - -``` -name foo -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -workspace is starting. this can take up to 2 minutes the first time. - -you can safely ctrl+c to exit -⣽ workspace is deploying -your workspace is ready! - -connect to the workspace: - brev open foo # brev open -> open workspace in preferred editor - brev shell foo # brev shell -> ssh into workspace (shortcut) - ssh foo-8j4u # ssh -> ssh directly to workspace - -``` - -or - -``` -$ brev start --empty --name foo -``` - -which has an output similar too: - -``` -name foo -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -workspace is starting. this can take up to 2 minutes the first time. - -you can safely ctrl+c to exit -⣽ workspace is deploying -your workspace is ready! - -connect to the workspace: - brev open foo # brev open -> open workspace in preferred editor - brev shell foo # brev shell -> ssh into workspace (shortcut) - ssh foo-8j4u # ssh -> ssh directly to workspace - -``` - -view your workspace with `brev ls` - -### create a workspace, and do not block shell until workspace is created - -use the `-d` or `--detached` flag to create a workspace and immediately exit -rather than wait for workspace to be successfully created before exiting. - -``` -$ brev start -d -e -n bar -``` - -which has an output similar too: - -``` -name bar -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -Workspace is starting. This can take up to 2 minutes the first time. -``` - -### Create a workspace from a file path - -if in your current directory has a directory in it called `merge-json`, you can -create a workspace using the contents of that directory using -`brev start merge-json` - -``` -$ ls -merge-json -``` - -``` -$ brev start merge-json - -``` - -which has an output similar too: - -``` -Workspace is starting. This can take up to 2 minutes the first time. - -name merge-json -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -┿ workspace is deploying -Your workspace is ready! - -Connect to the workspace: - brev open merge-json # brev open -> open workspace in preferred editor - brev shell merge-json # brev shell -> ssh into workspace (shortcut) - ssh merge-json-wd6q # ssh -> ssh directly to workspace -``` - -### Create a workspace from a git repository - -``` -$ brev start https://github.com/brevdev/react-starter-app -``` - -which has an output similar too: - -``` -Workspace is starting. This can take up to 2 minutes the first time. - -name react-starter-app -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⣾ workspace is deploying -Your workspace is ready! - -Connect to the workspace: - brev open react-starter-app # brev open -> open workspace in preferred editor - brev shell react-starter-app # brev shell -> ssh into workspace (shortcut) - ssh react-starter-app-8v8p # ssh -> ssh directly to workspace - -``` - -### Join a workspace in your orginization - -view your orgs workspaces with `brev ls --all`. Workspaces in your org that you -have not joined appear at the bottom of the output. - -``` -$ brev ls --all -``` - -which has an output similar too: - -``` -You have 1 workspace in Org brev.dev - NAME STATUS URL ID - brev-cli RUNNING brev-cli-p09m-brevdev.wgt-us-west-2-test.brev.dev x1yxqp09m - -Connect to running workspace: - brev open brev-cli # brev open -> open workspace in preferred editor - brev shell brev-cli # brev shell -> ssh into workspace (shortcut) - ssh brev-cli-p09m # ssh -> ssh directly to workspace - -7 other projects in Org brev.dev - NAME MEMBERS - new-docs 1 - brev-landing-page 2 - todo-app 1 - vagrant-guide 1 - mern-template 1 - solidity-nextjs-starter 1 - akka-http-quickstart-scala 1 - -Join a project: - brev start new-docs - -``` - -join the project new-docs - -``` -$ brev start new-docs -``` - -which has an output similar too: - -``` -Name flag omitted, using auto generated name: new-docs -Workspace is starting. This can take up to 2 minutes the first time. - -name new-docs -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⣟ workspace is deploying Connect to the workspace: - brev open new-docs # brev open -> open workspace in preferred editor - brev shell new-docs # brev shell -> ssh into workspace (shortcut) - ssh new-docs-pek9 # ssh -> ssh directly to workspace -``` - -### Start a stopped workspace - -If you have already joined a workspace and have stopped it with `brev stop`, -you can start it again with `brev start` - -view your current workspaces with `brev ls` - -``` -$ brev ls -``` - -which has an output similar too: - -``` -You have 1 workspace in Org brev.dev - NAME STATUS URL ID - linear-client STOPPED linear-client-yw1a-brevdev.wgt-us-west-2-test.brev.dev gov5jyw1a - -Connect to running workspace: - brev open linear-client # brev open -> open workspace in preferred editor - brev shell linear-client # brev shell -> ssh into workspace (shortcut) - ssh linear-client-yw1a # ssh -> ssh directly to workspace - -``` - -join the workspace - -``` -$ brev start linear-client -``` - -which has an output similar too: - -``` -Workspace linear-client is starting. -Note: this can take about a minute. Run 'brev ls' to check status - -You can safely ctrl+c to exit -``` diff --git a/pkg/cmd/status/status.go b/pkg/cmd/status/status.go deleted file mode 100644 index a05a5986..00000000 --- a/pkg/cmd/status/status.go +++ /dev/null @@ -1,61 +0,0 @@ -package status - -import ( - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -var ( - createLong = "Create a new Brev machine" - createExample = ` - brev create - ` - // instanceTypes = []string{"p4d.24xlarge", "p3.2xlarge", "p3.8xlarge", "p3.16xlarge", "p3dn.24xlarge", "p2.xlarge", "p2.8xlarge", "p2.16xlarge", "g5.xlarge", "g5.2xlarge", "g5.4xlarge", "g5.8xlarge", "g5.16xlarge", "g5.12xlarge", "g5.24xlarge", "g5.48xlarge", "g5g.xlarge", "g5g.2xlarge", "g5g.4xlarge", "g5g.8xlarge", "g5g.16xlarge", "g5g.metal", "g4dn.xlarge", "g4dn.2xlarge", "g4dn.4xlarge", "g4dn.8xlarge", "g4dn.16xlarge", "g4dn.12xlarge", "g4dn.metal", "g4ad.xlarge", "g4ad.2xlarge", "g4ad.4xlarge", "g4ad.8xlarge", "g4ad.16xlarge", "g3s.xlarge", "g3.4xlarge", "g3.8xlarge", "g3.16xlarge"} -) - -type StatusStore interface { - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetCurrentWorkspaceID() (string, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) -} - -func NewCmdStatus(t *terminal.Terminal, statusStore StatusStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "status", - DisableFlagsInUseLine: true, - Short: "About this instance", - Long: createLong, - Example: createExample, - RunE: func(cmd *cobra.Command, args []string) error { - runShowStatus(t, statusStore) - return nil - }, - } - return cmd -} - -func runShowStatus(t *terminal.Terminal, statusStore StatusStore) { - terminal.DisplayBrevLogo(t) - t.Vprintf("\n") - wsID, err := statusStore.GetCurrentWorkspaceID() - if err != nil { - t.Vprintf("\n Error: %s", t.Red(err.Error())) - return - } - ws, err := statusStore.GetWorkspace(wsID) - if err != nil { - t.Vprintf("\n Error: %s", t.Red(err.Error())) - return - } - - t.Vprintf("\nYou're on instance %s", t.Yellow(ws.Name)) - t.Vprintf("\n\tID: %s", t.Yellow(ws.ID)) - t.Vprintf("\n\tMachine: %s", t.Yellow(util.GetInstanceString(*ws))) -} diff --git a/pkg/cmd/status/status_test.go b/pkg/cmd/status/status_test.go deleted file mode 100644 index 6c12ae38..00000000 --- a/pkg/cmd/status/status_test.go +++ /dev/null @@ -1 +0,0 @@ -package status diff --git a/pkg/cmd/tasks/tasks.go b/pkg/cmd/tasks/tasks.go deleted file mode 100644 index bb9cad48..00000000 --- a/pkg/cmd/tasks/tasks.go +++ /dev/null @@ -1,132 +0,0 @@ -package tasks - -import ( - "fmt" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/k8s" - "github.com/brevdev/brev-cli/pkg/ssh" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/tasks" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/hashicorp/go-multierror" - "github.com/spf13/afero" - "github.com/spf13/cobra" -) - -type TaskMap map[string]tasks.Task - -var all bool // used for run command - -type TaskStore interface { - k8s.K8sStore - CopyBin(targetBin string) error - WriteString(path, data string) error - RegisterNode(publicKey string) error - GetOrCreateFile(path string) (afero.File, error) - GetNetworkAuthKey() (*store.GetAuthKeyResponse, error) - GetCurrentWorkspaceID() (string, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetCurrentUser() (*entity.User, error) - ssh.ConfigUpaterFactoryStore -} - -func NewCmdTasks(t *terminal.Terminal, store TaskStore) *cobra.Command { - taskMap := getTaskMap(store) - cmd := &cobra.Command{ - Use: "tasks", - DisableFlagsInUseLine: true, - Short: "run background daemons for brev", - Long: "run background daemons for brev", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - RunE: func(cmd *cobra.Command, args []string) error { - return fmt.Errorf("invalid command") - }, - } - - configure := NewCmdConfigure(t, store) - cmd.AddCommand(configure) - run := NewCmdRun(t, store, taskMap) - run.PersistentFlags().BoolVarP(&all, "all", "a", false, "specifies all tasks") - cmd.AddCommand(run) - - return cmd -} - -func NewCmdConfigure(_ *terminal.Terminal, store TaskStore) *cobra.Command { - taskMap := getTaskMap(store) - cmd := &cobra.Command{ - Use: "configure [task to configure]", - Short: "configure system startup daemon for task", - Long: "configure system startup daemon for task", - RunE: func(cmd *cobra.Command, args []string) error { - // todo if --user flag is not provided and if not run as root, raise - // an error - fmt.Println("configuring...") - var allError error - for k, value := range taskMap { - fmt.Printf("configuring %s\n", k) - err := value.Configure() - if err != nil { - fmt.Println(k) - allError = multierror.Append(allError, err) - } - } - fmt.Println("done configuring") - if allError != nil { - return breverrors.WrapAndTrace(allError) - } - return nil - }, - } - return cmd -} - -func NewCmdRun(_ *terminal.Terminal, _ TaskStore, taskMap TaskMap) *cobra.Command { - cmd := &cobra.Command{ - Use: "run [task to configure]", - Short: "run a task", - Long: "run a task", - RunE: func(cmd *cobra.Command, args []string) error { - if all { - var allError error - for _, value := range taskMap { - err := value.Run() - if err != nil { - allError = multierror.Append(allError, err) - } - } - if allError != nil { - return breverrors.WrapAndTrace(allError) - } - } else { - if len(args) == 0 { - return fmt.Errorf("provide a task name or --all") - } - if task, ok := taskMap[args[0]]; ok { - err := task.Run() - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - fmt.Println("could not find task") - } - } - return nil - }, - } - return cmd -} - -func Tasks(_ *terminal.Terminal, _ TaskStore, _ TaskMap) error { - return nil -} - -func getTaskMap(store TaskStore) TaskMap { - taskmap := make(TaskMap) - sshcd := ssh.NewSSHConfigurerTask(store) - taskmap["sshcd"] = sshcd - return taskmap -} diff --git a/pkg/cmd/test/test.go b/pkg/cmd/test/test.go deleted file mode 100644 index b0626886..00000000 --- a/pkg/cmd/test/test.go +++ /dev/null @@ -1,76 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/brev-cli/pkg/util" - - "github.com/spf13/cobra" -) - -var ( - startLong = "[internal] test" - startExample = "[internal] test" -) - -type TestStore interface { - completions.CompletionStore - ResetWorkspace(workspaceID string) (*entity.Workspace, error) - GetAllWorkspaces(options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetWorkspace(id string) (*entity.Workspace, error) - GetWorkspaceMetaData(workspaceID string) (*entity.WorkspaceMetaData, error) - CopyBin(targetBin string) error - GetSetupScriptContentsByURL(url string) (string, error) - UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) -} - -type ServiceMeshStore interface { - autostartconf.AutoStartStore - GetWorkspace(workspaceID string) (*entity.Workspace, error) -} - -func NewCmdTest(_ *terminal.Terminal, _ TestStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"devonly": ""}, - Use: "test", - DisableFlagsInUseLine: true, - Short: "[internal] Test random stuff.", - Long: startLong, - Example: startExample, - // Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), - RunE: func(cmd *cobra.Command, args []string) error { - // fmt.Printf("NAME ID URL SOMETHING ELSE") - // hello.TypeItToMe("\n\n\n") - // hello.TypeItToMe("šŸ‘† this is the name of your environment (which you can use to open the environment)") - // time.Sleep(1 * time.Second) - // fmt.Printf("\332K\r") - // fmt.Println(" ") - // hello.TypeItToMe(" šŸ‘† you can expose your localhost to this public URL") - // time.Sleep(1 * time.Second) - // fmt.Printf("\332K\r") - // fmt.Printf("bye world") - // fmt.Printf("bye world") - - // s := t.Yellow("\n\nCould you please install the following VSCode extension? %s", t.Green("ms-vscode-remote.remote-ssh")) - // s += "\nDo that then run " + t.Yellow("brev hello") + " to resume this walk-through\n" - // // s += "Here's a video of me installing the VS Code extension šŸ‘‰ " + "" - // hello.TypeItToMe(s) - - res := util.DoesPathExist("/Users/naderkhalil/brev-cli") - // res := util.DoesPathExist("/home/brev/workspace") - fmt.Println(res) - - return nil - }, - } - - return cmd -} diff --git a/pkg/cmd/test/test_test.go b/pkg/cmd/test/test_test.go deleted file mode 100644 index 56e54040..00000000 --- a/pkg/cmd/test/test_test.go +++ /dev/null @@ -1 +0,0 @@ -package test diff --git a/pkg/cmd/updatemodel/updatemodel.go b/pkg/cmd/updatemodel/updatemodel.go deleted file mode 100644 index 032563f2..00000000 --- a/pkg/cmd/updatemodel/updatemodel.go +++ /dev/null @@ -1,422 +0,0 @@ -package updatemodel - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "github.com/hashicorp/go-multierror" - "github.com/samber/lo" - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/parse/pkg/parse" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/transport/ssh" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -type updatemodelStore interface { - ModifyWorkspace(workspaceID string, options *store.ModifyWorkspaceRequest) (*entity.Workspace, error) - GetCurrentWorkspaceID() (string, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - WriteString(path, data string) error - UserHomeDir() (string, error) - ListDirs(path string) ([]string, error) - FileExists(filepath string) (bool, error) - GetEnvSetupParams(wsid string) (*store.SetupParamsV0, error) - GetCurrentUser() (*entity.User, error) -} - -func NewCmdupdatemodel(t *terminal.Terminal, store updatemodelStore) *cobra.Command { - var configure bool - cmd := &cobra.Command{ - Use: "updatemodel", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: updateModel{ - t: t, - Store: store, - clone: func(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) { - workspaceID, err := store.GetCurrentWorkspaceID() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - params, err := store.GetEnvSetupParams(workspaceID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - if params != nil && params.WorkspaceKeyPair != nil { - keys := params.WorkspaceKeyPair - pubkeys, err := ssh.NewPublicKeys("ubuntu", []byte(keys.PrivateKeyData), "") //nolint:govet //abc - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - o.Auth = pubkeys - } - - r, err := git.PlainClone(path, isBare, o) - return r, breverrors.WrapAndTrace(err) - }, - open: func(path string) (repo, error) { - r, err := git.PlainOpen(path) - return r, breverrors.WrapAndTrace(err) - }, - configure: configure, - }.RunE, - } - - cmd.Flags().BoolVarP(&configure, "configure", "c", false, "configure daemon") - return cmd -} - -type repo interface { - Remotes() ([]*git.Remote, error) -} - -type updateModel struct { - t *terminal.Terminal - Store updatemodelStore - clone func(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) - open func(path string) (repo, error) - configure bool -} - -func (u updateModel) RunE(_ *cobra.Command, _ []string) error { //nolint:funlen //abc - if u.configure { - return breverrors.WrapAndTrace( - DaemonConfigurer{ - Store: u.Store, - }.Configure(), - ) - } - - remotes, err := u.remotes() - if err != nil { - return breverrors.WrapAndTrace(err) - } - workspaceID, err := u.Store.GetCurrentWorkspaceID() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - workspace, err := u.Store.GetWorkspace(workspaceID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // there should only be one but sometimes there are more and thats not - // handled right now - urls := lo.FlatMap( - remotes, - func(remote *git.Remote, _ int) []string { - return remote.Config().URLs - }, - ) - - // filter user dotbrev remote url - user, err := u.Store.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - dotbrev := user.BaseWorkspaceRepo - urls = lo.Filter( - urls, - func(url string, _ int) bool { - return !parse.CMP(url, dotbrev) - }, - ) - - reposv1FromENV := makeReposFromRemotes(urls) - - // merge reposv0 and gitrepo field into reposv1fromBE - - reposv1FromBE := make(entity.ReposV1) - reposv1FromBE[entity.RepoName(parse.GetRepoNameFromOrigin(workspace.GitRepo))] = entity.RepoV1{ - GitRepo: entity.GitRepo{ - Repository: workspace.GitRepo, - }, - Type: entity.GitRepoType, - } - - for key, val := range workspace.ReposV0 { - reposv1FromBE[key] = entity.RepoV1{ - GitRepo: entity.GitRepo{ - Repository: val.Repository, - }, - Type: entity.GitRepoType, - } - } - if workspace.ReposV1 != nil { - for key, val := range *workspace.ReposV1 { - key := key - val := val - reposv1FromBE[key] = val - } - } - - rm := &repoMerger{ - acc: &reposv1FromBE, - repos: []*entity.ReposV1{reposv1FromENV}, - } - _, err = u.Store.ModifyWorkspace( - workspaceID, - &store.ModifyWorkspaceRequest{ - ReposV1: rm.MergeBE(), - }, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - dir, err := u.Store.UserHomeDir() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - cloneErrors := lo.Map( - rm.ReposToClone(), - func(repo *entity.RepoV1, _ int) error { - _, err := u.clone(dir, false, &git.CloneOptions{ - URL: repo.GitRepo.Repository, - }) - return breverrors.WrapAndTrace(err) - }, - ) - return breverrors.WrapAndTrace( - lo.Reduce( - cloneErrors, - func(acc error, err error, _ int) error { - if err != nil { - txt := fmt.Sprintf("%s", err) - if strings.Contains(txt, "already exists") { - return acc - } - if acc == nil { - return breverrors.WrapAndTrace(err) - } - return multierror.Append(acc, err) - } - if acc == nil { - return breverrors.WrapAndTrace(err) - } - return acc - }, - nil, - ), - ) -} - -type stringWriter interface { - WriteString(path, data string) error -} - -type DaemonConfigurer struct { - Store stringWriter -} - -func (dc DaemonConfigurer) Configure() error { - // create systemd service file to run - // brev updatemodel -d /home/ubuntu - configFile := filepath.Join("/etc/systemd/system", "brev-updatemodel.service") - err := dc.Store.WriteString( - configFile, - `[Unit] -Description=Brev Update Model -After=network.target - -[Service] -Type=simple -User=ubuntu -ExecStart=/usr/bin/brev updatemodel -d /home/ubuntu -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if autostartconf.ShouldSymlink() { - symlinkTarget := path.Join("/etc/systemd/system/default.target.wants/", "brev-updatemodel.service") - err2 := os.Symlink(configFile, symlinkTarget) - if err2 != nil { - return breverrors.WrapAndTrace(err2) - } - } - // create systemd timer to run every 5 seconds - err = dc.Store.WriteString( - "/etc/systemd/system/brev-updatemodel.timer", - `[Unit] -Description=Brev Update Model Timer - -[Timer] -OnBootSec=5 -OnUnitActiveSec=5 - -[Install] -WantedBy=timers.target -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // enable timer - err = autostartconf.ExecCommands( - [][]string{ - {"systemctl", "enable", "brev-updatemodel.timer"}, - {"systemctl", "start", "brev-updatemodel.timer"}, - {"systemctl", "daemon-reload"}, - }, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func makeReposFromRemotes(remotes []string) *entity.ReposV1 { - return lo.Reduce( - remotes, - func(acc *entity.ReposV1, remote string, _ int) *entity.ReposV1 { - name := parse.GetRepoNameFromOrigin(remote) - url := parse.GetSSHURLFromOrigin(remote) - a := *acc - a[entity.RepoName(name)] = entity.RepoV1{ - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: url, - }, - } - return &a - }, - &entity.ReposV1{}, - ) -} - -type repoMerger struct { - acc *entity.ReposV1 - repos []*entity.ReposV1 -} - -func (r *repoMerger) MergeBE() *entity.ReposV1 { - for _, repo := range r.repos { - for k, v := range *repo { - if _, ok := (*r.acc)[k]; ok { - continue - } - _, valueInAcc := lo.Find( - r.accValues(), - func(repo *entity.RepoV1) bool { - return repo.GitRepo.Repository == v.GitRepo.Repository - }, - ) - if valueInAcc { - continue - } - (*r.acc)[k] = v - } - } - return r.acc -} - -func (r *repoMerger) ReposToClone() []*entity.RepoV1 { - // repos present in the BE but not in the ENV - return lo.Filter( - r.accValues(), - func(accrepo *entity.RepoV1, _ int) bool { - _, valueInENV := lo.Find( - r.reposValues(), - func(repo *entity.RepoV1) bool { - return accrepo.GitRepo.Repository == repo.GitRepo.Repository - }, - ) - return !valueInENV - }, - ) -} - -func (r repoMerger) reposValues() []*entity.RepoV1 { - values := []*entity.RepoV1{} - for _, repo := range r.repos { - for _, v := range *repo { - // explicit memory aliasing in for loop. - v := v - values = append(values, &v) - } - } - return values -} - -func (r repoMerger) accValues() []*entity.RepoV1 { - if r.acc == nil { - return []*entity.RepoV1{} - } - values := []*entity.RepoV1{} - for _, v := range *r.acc { - newV := v - values = append(values, &newV) - } - return values -} - -func (u updateModel) remotes() ([]*git.Remote, error) { - dir, err := u.Store.UserHomeDir() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - dirs, err := u.Store.ListDirs(dir) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // filter dirs that start with . - dirs = lo.Filter( - dirs, - func(dir string, _ int) bool { - return !strings.HasPrefix(dir, ".") - }, - ) - - dirsWithGit := []string{} - for _, dir := range dirs { - gitDir := path.Join(dir, ".git") - exists, err := u.Store.FileExists(gitDir) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - if exists { - dirsWithGit = append(dirsWithGit, dir) - } - } - - repos := []*git.Repository{} - for _, dir := range dirsWithGit { - repo, err := git.PlainOpen(dir) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - repos = append(repos, repo) - } - - remotes := []*git.Remote{} - for _, repo := range repos { - repoRemotes, err := repo.Remotes() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - remotes = append(remotes, repoRemotes...) - } - return remotes, nil -} diff --git a/pkg/cmd/updatemodel/updatemodel_test.go b/pkg/cmd/updatemodel/updatemodel_test.go deleted file mode 100644 index 7a13c50f..00000000 --- a/pkg/cmd/updatemodel/updatemodel_test.go +++ /dev/null @@ -1,292 +0,0 @@ -package updatemodel - -import ( - "testing" - - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/go-git/go-git/v5" - "github.com/google/go-cmp/cmp" - "github.com/spf13/cobra" -) - -type updatemodelStoreMock struct{} - -func (u updatemodelStoreMock) ModifyWorkspace(_ string, _ *store.ModifyWorkspaceRequest) (*entity.Workspace, error) { - return nil, nil -} - -func (u updatemodelStoreMock) GetCurrentWorkspaceID() (string, error) { - return "test", nil -} - -func (u updatemodelStoreMock) GetWorkspace(_ string) (*entity.Workspace, error) { - reposv1 := entity.ReposV1{ - entity.RepoName("test"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "foo", - }, - }, - } - return &entity.Workspace{ - ReposV1: &reposv1, - }, nil -} - -func (u updatemodelStoreMock) WriteString(_, _ string) error { - return nil -} - -func (u updatemodelStoreMock) UserHomeDir() (string, error) { - return "", nil -} - -func (u updatemodelStoreMock) ListDirs(_ string) ([]string, error) { - return nil, nil -} - -func (u updatemodelStoreMock) FileExists(_ string) (bool, error) { - return false, nil -} - -func (u updatemodelStoreMock) GetEnvSetupParams(_ string) (*store.SetupParamsV0, error) { - return nil, nil -} - -func (u updatemodelStoreMock) GetCurrentUser() (*entity.User, error) { - return nil, nil -} - -func mockPlainClone(_ string, _ bool, _ *git.CloneOptions) (*git.Repository, error) { - return nil, nil -} - -type remotes struct{} - -func (r remotes) Remotes() ([]*git.Remote, error) { - return nil, nil -} - -func mockPlainOpen(_ string) (repo, error) { - return remotes{}, nil -} - -func TestUpdateModel_RunE(t *testing.T) { - type fields struct { - t *terminal.Terminal - Store updatemodelStore - // directory string - clone func(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) - open func(path string) (repo, error) - configure bool - } - type args struct { - in0 *cobra.Command - in1 []string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - // TODO: Add test cases. - // TODO: Add test cases. - { - name: "test", - fields: fields{ - t: nil, - Store: updatemodelStoreMock{}, - clone: mockPlainClone, - open: mockPlainOpen, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u := updateModel{ - t: tt.fields.t, - Store: tt.fields.Store, - clone: tt.fields.clone, - open: tt.fields.open, - configure: tt.fields.configure, - } - t.Skip("TODO: fix this test") - if err := u.RunE(tt.args.in0, tt.args.in1); (err != nil) != tt.wantErr { - t.Errorf("UpdateModel.RunE() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_makeReposFromRemotes(t *testing.T) { - type args struct { - remotes []string - } - tests := []struct { - name string - args args - want *entity.ReposV1 - }{ - // TODO: Add test cases. - { - name: "test", - args: args{ - remotes: []string{"git@github.com:brevdev/brev-cli.git", "git@github.com:brevdev/brev-deploy.git"}, - }, - want: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com:brevdev/brev-cli.git", - }, - }, - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com:brevdev/brev-deploy.git", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := makeReposFromRemotes(tt.args.remotes) - // cmp.Diff is used to compare the two structs - if diff := cmp.Diff(got, tt.want); diff != "" { - t.Errorf("makeReposFromRemotes() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func Test_repoMerger_MergeBE(t *testing.T) { - type fields struct { - acc *entity.ReposV1 - repos []*entity.ReposV1 - } - tests := []struct { - name string - fields fields - want *entity.ReposV1 - }{ - // TODO: Add test cases. - { - name: "test", - fields: fields{ - acc: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - }, - repos: []*entity.ReposV1{ - { - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-deploy.git", - }, - }, - }, - { - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-cli.git", - }, - }, - }, - }, - }, - want: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-deploy.git", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &repoMerger{ - acc: tt.fields.acc, - repos: tt.fields.repos, - } - // using cmp - if diff := cmp.Diff(r.MergeBE(), tt.want); diff != "" { - t.Errorf("repoMerger.MergeBE() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func Test_repoMerger_ReposToClone(t *testing.T) { - type fields struct { - acc *entity.ReposV1 - repos []*entity.ReposV1 - } - tests := []struct { - name string - fields fields - want []*entity.RepoV1 - }{ - // TODO: Add test cases. - { - name: "test", - fields: fields{ - acc: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - }, - repos: []*entity.ReposV1{ - { - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-deploy.git", - }, - }, - }, - }, - }, - want: []*entity.RepoV1{ - { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &repoMerger{ - acc: tt.fields.acc, - repos: tt.fields.repos, - } - if diff := cmp.Diff(r.ReposToClone(), tt.want); diff != "" { - t.Errorf("repoMerger.ReposToClone() mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/cmd/workspacegroups/workspacegroups.go b/pkg/cmd/workspacegroups/workspacegroups.go deleted file mode 100644 index 13fa5423..00000000 --- a/pkg/cmd/workspacegroups/workspacegroups.go +++ /dev/null @@ -1,69 +0,0 @@ -package workspacegroups - -import ( - "os" - - "github.com/jedib0t/go-pretty/v6/table" - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -type WorkspaceGroupsStore interface { - GetWorkspaceGroups(organizationID string) ([]entity.WorkspaceGroup, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) -} - -func NewCmdWorkspaceGroups(t *terminal.Terminal, store WorkspaceGroupsStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "workspacegroups", - DisableFlagsInUseLine: true, - Short: "TODO", - Long: "TODO", - Example: "TODO", - RunE: func(cmd *cobra.Command, args []string) error { - err := RunWorkspaceGroups(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func RunWorkspaceGroups(_ *terminal.Terminal, _ []string, store WorkspaceGroupsStore) error { - org, err := store.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - wsgs, err := store.GetWorkspaceGroups(org.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - ta := table.NewWriter() - ta.SetOutputMirror(os.Stdout) - ta.Style().Options = getBrevTableOptions() - header := table.Row{"NAME", "PLATFORM ID", "PLATFORM TYPE"} - ta.AppendHeader(header) - for _, w := range wsgs { - workspaceRow := []table.Row{{ - w.Name, w.PlatformID, w.Platform, - }} - ta.AppendRows(workspaceRow) - } - ta.Render() - return nil -} - -func getBrevTableOptions() table.Options { - options := table.OptionsDefault - options.DrawBorder = false - options.SeparateColumns = false - options.SeparateRows = false - options.SeparateHeader = false - return options -} diff --git a/pkg/cmd/writeconnectionevent/writeconnectionevent.go b/pkg/cmd/writeconnectionevent/writeconnectionevent.go deleted file mode 100644 index e7440ccf..00000000 --- a/pkg/cmd/writeconnectionevent/writeconnectionevent.go +++ /dev/null @@ -1,44 +0,0 @@ -package writeconnectionevent - -import ( - "github.com/spf13/cobra" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -type writeConnectionEventStore interface { - WriteConnectionEvent() error -} - -func NewCmdwriteConnectionEvent(t *terminal.Terminal, store writeConnectionEventStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "write-connection-event", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - err := RunWriteConnectionEvent(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func RunWriteConnectionEvent(_ *terminal.Terminal, _ []string, store writeConnectionEventStore) error { - err := store.WriteConnectionEvent() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/writeconnectionevent/writeconnectionevent_test.go b/pkg/cmd/writeconnectionevent/writeconnectionevent_test.go deleted file mode 100644 index 4d2784e3..00000000 --- a/pkg/cmd/writeconnectionevent/writeconnectionevent_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package writeconnectionevent - -import ( - "testing" - - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/afero" -) - -func TestRunWriteConnectionEvent(t *testing.T) { - fs := afero.NewMemMapFs() - type args struct { - in0 *terminal.Terminal - in1 []string - store writeConnectionEventStore - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - { - name: "write connection event", - args: args{ - nil, - []string{}, - store.NewBasicStore().WithFileSystem(fs), - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := RunWriteConnectionEvent(tt.args.in0, tt.args.in1, tt.args.store); (err != nil) != tt.wantErr { - t.Errorf("RunWriteConnectionEvent() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} From 6c6203731113d4200a387651426a621258fbe99a Mon Sep 17 00:00:00 2001 From: varunv22 Date: Fri, 21 Jun 2024 12:43:26 -0400 Subject: [PATCH 5/9] linter test fixed --- Makefile | 4 +++- pkg/cmd/cmd.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1aff4585..52ad8111 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,9 @@ fmtcheck: ## go fmt --check .PHONY: lint lint: ## golangci-lint $(call print-target) - golangci-lint run --timeout 5m + golangci-lint run --timeout 10m0s + workflowcheck -show-pos ./... + buf lint .PHONY: test test: ## go test with race detector and code covarage diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 82682587..c2ca1123 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -194,7 +194,7 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin return cmds } -func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore, loginAuth *auth.LoginAuth) { //nolint:funlen // define brev command +func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore, loginAuth *auth.LoginAuth) { //nolint:funlen,nolintlint // define brev command cmd.AddCommand(set.NewCmdSet(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(ls.NewCmdLs(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(portforward.NewCmdPortForwardSSH(loginCmdStore, t)) From f589aa4167a503cda0d9205df9de9593bb305a77 Mon Sep 17 00:00:00 2001 From: varunv22 Date: Fri, 21 Jun 2024 13:02:55 -0400 Subject: [PATCH 6/9] revert lint test in makefile --- Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 52ad8111..1aff4585 100644 --- a/Makefile +++ b/Makefile @@ -72,9 +72,7 @@ fmtcheck: ## go fmt --check .PHONY: lint lint: ## golangci-lint $(call print-target) - golangci-lint run --timeout 10m0s - workflowcheck -show-pos ./... - buf lint + golangci-lint run --timeout 5m .PHONY: test test: ## go test with race detector and code covarage From ced667617b80d51d022e21ca1620d2e528d8bb56 Mon Sep 17 00:00:00 2001 From: varunv22 Date: Fri, 21 Jun 2024 13:32:01 -0400 Subject: [PATCH 7/9] test fix in workspace.go --- pkg/store/workspace.go | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/pkg/store/workspace.go b/pkg/store/workspace.go index 6ddae33e..e88f4a68 100644 --- a/pkg/store/workspace.go +++ b/pkg/store/workspace.go @@ -707,17 +707,26 @@ func ValidateOllamaModel(model string, tag string) (bool, error) { if err != nil { return false, breverrors.WrapAndTrace(err) } - if res.StatusCode() == 200 { //nolint:gocritic // 200 is a valid status code - if err := json.Unmarshal(res.Body(), &OllamaRegistrySuccessResponse{}); err != nil { - return false, breverrors.WrapAndTrace(err) - } - return true, nil - } else if res.StatusCode() == 404 { - if err := json.Unmarshal(res.Body(), &OllamaRegistryFailureResponse{}); err != nil { - return false, breverrors.WrapAndTrace(err) + + if len(res.Body()) > 0 && res.Body()[0] == '{' { + if res.StatusCode() == 200 { + var successResponse OllamaRegistrySuccessResponse + if err := json.Unmarshal(res.Body(), &successResponse); err != nil { + return false, fmt.Errorf("error parsing success response: %w", err) + } + return true, nil + } else { + var failureResponse OllamaRegistryFailureResponse + if err := json.Unmarshal(res.Body(), &failureResponse); err != nil { + return false, fmt.Errorf("error parsing failure response: %w", err) + } + return false, nil } + } + + // check if json or plain text + if res.StatusCode() == 404 { return false, nil - } else { - return false, breverrors.New("invalid response from ollama registry") } + return false, breverrors.New("invalid response from ollama registry") } From 8923e2d3f649994b7412e17ab1efee871e8b6f99 Mon Sep 17 00:00:00 2001 From: varunv22 Date: Fri, 21 Jun 2024 13:49:25 -0400 Subject: [PATCH 8/9] Update dependencies --- go.sum | 83 ---------------------------------------------------------- 1 file changed, 83 deletions(-) diff --git a/go.sum b/go.sum index b7962c66..2687d4e0 100644 --- a/go.sum +++ b/go.sum @@ -54,20 +54,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -119,7 +111,6 @@ github.com/brevdev/parse v0.0.11 h1:OamoC1hKFW75ngzSQx9HHRh5bf/G6154Y9M2y4HNmIw= github.com/brevdev/parse v0.0.11/go.mod h1:ML13fBCP6yZsZearRnglD+6UlqkpiVN7Hjf8R9pd0TY= github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -128,8 +119,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -155,8 +144,6 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -182,24 +169,9 @@ github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSy github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70= github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQBDE= -github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw= -github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -220,18 +192,8 @@ github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -343,8 +305,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -364,11 +324,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.3.1 h1:aOXiD9oqiuLH8btPQW6SfgtQN5zwhyfzZls8a6sPJ/I= github.com/jedib0t/go-pretty/v6 v6.3.1/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -401,8 +358,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -416,8 +371,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -489,8 +442,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= -github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -510,15 +461,11 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samber/lo v1.33.0 h1:2aKucr+rQV6gHpY3bpeZu69uYoQOzVhGT3J22Op6Cjk= -github.com/samber/lo v1.33.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/samber/mo v1.5.1 h1:5dRSevAB33Q/OrYwTmtksHHxquuf2urnRSUTsdTFysY= github.com/samber/mo v1.5.1/go.mod h1:pDuQgWscOVGGoEz+NAeth/Xq+MPAcXxCeph1XIAm/DU= github.com/schollz/progressbar/v3 v3.9.0 h1:k9SRNQ8KZyibz1UZOaKxnkUE3iGtmGSDt1YY9KlCYQk= @@ -536,8 +483,6 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -575,8 +520,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -585,15 +528,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/wk8/go-ordered-map/v2 v2.0.0 h1:jWOAU/F5AkYb8jr/rkVPe418g7nf2CZBzyfOR4Y7Q1w= github.com/wk8/go-ordered-map/v2 v2.0.0/go.mod h1:fGIuB3GmY3JZP6L3t5riKtaSH9u13IYVYvar5Ee+9lM= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -601,7 +539,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -631,14 +568,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -678,7 +610,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -722,8 +653,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -752,7 +681,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -812,24 +740,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -903,7 +823,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1034,8 +953,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From a88c31499ba2465e79e28d28b02dee4df881f1f1 Mon Sep 17 00:00:00 2001 From: varunv22 Date: Fri, 21 Jun 2024 13:50:01 -0400 Subject: [PATCH 9/9] update dependencies --- go.mod | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 87b46090..fcb9f8ae 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.21 require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 - github.com/alessio/shellescape v1.4.1 github.com/aws/aws-sdk-go-v2 v1.16.16 github.com/aws/aws-sdk-go-v2/config v1.17.8 github.com/aws/aws-sdk-go-v2/service/costexplorer v1.21.0 @@ -18,8 +17,6 @@ require ( github.com/docker/docker v20.10.23+incompatible github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.14.0 - github.com/gin-gonic/gin v1.8.1 - github.com/go-git/go-git/v5 v5.5.2 github.com/go-resty/resty/v2 v2.7.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.5.9 @@ -27,7 +24,6 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-version v1.4.0 github.com/jarcoal/httpmock v1.0.8 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 @@ -36,7 +32,6 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pkg/errors v0.9.1 github.com/robfig/cron/v3 v3.0.1 - github.com/samber/lo v1.33.0 github.com/samber/mo v1.5.1 github.com/schollz/progressbar/v3 v3.9.0 github.com/segmentio/ksuid v1.0.4 @@ -54,15 +49,12 @@ require ( golang.org/x/crypto v0.3.0 golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 golang.org/x/text v0.4.0 - k8s.io/apimachinery v0.24.3 k8s.io/cli-runtime v0.24.3 k8s.io/client-go v0.24.3 ) require ( github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect - github.com/acomagu/bufpipe v1.0.3 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.12.21 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect @@ -74,26 +66,15 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect github.com/aws/smithy-go v1.13.3 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/cloudflare/circl v1.1.0 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful v2.9.5+incompatible // indirect - github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.4.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.1 // indirect - github.com/goccy/go-json v0.9.11 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -102,21 +83,18 @@ require ( github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect - github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/ugorji/go/codec v1.2.7 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.4.0 // indirect + k8s.io/apimachinery v0.24.3 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect )