diff --git a/e2etest/setup/setup.go b/e2etest/setup/setup.go index a2840daf..c47c1eb4 100644 --- a/e2etest/setup/setup.go +++ b/e2etest/setup/setup.go @@ -13,7 +13,6 @@ import ( breverrors "github.com/brevdev/brev-cli/pkg/errors" "github.com/brevdev/brev-cli/pkg/files" "github.com/brevdev/brev-cli/pkg/store" - "github.com/hashicorp/go-multierror" "github.com/stretchr/testify/assert" ) @@ -106,20 +105,6 @@ func NewWorkspaceTestClient(setupParams *store.SetupParamsV0, containerParams [] } } -func (w WorkspaceTestClient) Done() error { - var allError error - for _, w := range w.TestWorkspaces { - err := w.Done() - if err != nil { - allError = multierror.Append(allError, err) - } - } - if allError != nil { - return breverrors.WrapAndTrace(allError) - } - return nil -} - type workspaceTest func(workspace Workspace, err error) func (w WorkspaceTestClient) Test(test workspaceTest) error { @@ -482,12 +467,6 @@ func AssertPathExists(t *testing.T, workspace Workspace, path string) bool { return assert.Nil(t, err) } -func AssertPathNotExist(t *testing.T, workspace Workspace, path string) bool { - t.Helper() - _, err := workspace.Exec("ls", path) - return assert.NotNil(t, err) -} - func AssertPathDoesNotExist(t *testing.T, workspace Workspace, path string) bool { t.Helper() _, err := workspace.Exec("ls", path) @@ -520,13 +499,6 @@ func AssertDockerRunning(t *testing.T, w Workspace) bool { return strings.Contains(string(out), "active") } -func AssertRepoHasNumFiles(t *testing.T, w Workspace, filePath string, num int) { - t.Helper() - out, err := w.Exec("ls", "-a", filePath) - assert.Nil(t, err) - assert.Len(t, strings.Fields(string(out)), num) -} - func UpdateFile(w Workspace, filePath string, content string) error { _, err := w.Exec("sh", "-c", fmt.Sprintf(`echo '%s' > %s`, content, filePath)) if err != nil { diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index b7d4c674..2363c896 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -320,7 +320,3 @@ func isAccessTokenValid(token string) (bool, error) { } return true, nil } - -func IsAuthError(err error) bool { - return strings.Contains(err.Error(), "403") -} diff --git a/pkg/auth/auth0.go b/pkg/auth/auth0.go index 600471c9..21b9a9b6 100644 --- a/pkg/auth/auth0.go +++ b/pkg/auth/auth0.go @@ -70,18 +70,8 @@ type State struct { } // RequiredScopes returns the scopes used for login. -func RequiredScopes() []string { return requiredScopes } // RequiredScopesMin returns minimum scopes used for login in integration tests. -func RequiredScopesMin() []string { - min := []string{} - for _, s := range requiredScopes { - if s != "offline_access" && s != "openid" { - min = append(min, s) - } - } - return min -} func (s *State) IntervalDuration() time.Duration { return time.Duration(s.Interval+waitThresholdInSeconds) * time.Second diff --git a/pkg/autostartconf/autostartconf.go b/pkg/autostartconf/autostartconf.go index db1f1a8f..eff094f2 100644 --- a/pkg/autostartconf/autostartconf.go +++ b/pkg/autostartconf/autostartconf.go @@ -44,127 +44,6 @@ func firstAndRest(commandstring []string) (string, []string) { return first, rest } -func NewVPNConfig(store AutoStartStore) DaemonConfigurer { - switch runtime.GOOS { - case osLinux: - return LinuxSystemdConfigurer{ - Store: store, - ValueConfigFile: ` -[Install] -WantedBy=multi-user.target - -[Unit] -Description=Brev vpn daemon -After=systemd-user-sessions.service - -[Service] -Type=simple -ExecStart=brev tasks run vpnd -Restart=always -`, - ServiceName: "brevvpnd.service", - ServiceType: "system", - TargetBin: targetBin, - } - case osDarwin: - return DarwinPlistConfigurer{ - Store: store, - ValueConfigFile: ` - - - - - - Label - com.brev.vpnd - - ProgramArguments - - /usr/local/bin/brev - tasks - run - vpnd - - - RunAtLoad - - - StandardOutPath - /var/log/brevvpnd.log - StandardErrorPath - /var/log/brevvpnd.log - - - - `, - ServiceName: "com.brev.vpnd", - ServiceType: System, - } - } - return nil -} - -func NewRPCConfig(store AutoStartStore) DaemonConfigurer { - switch runtime.GOOS { - case osLinux: - return LinuxSystemdConfigurer{ - Store: store, - ValueConfigFile: ` -[Install] -WantedBy=multi-user.target - -[Unit] -Description=Brev rpc daemon -After=systemd-user-sessions.service - -[Service] -Type=simple -ExecStart=brev tasks run rpcd --user ` + store.GetOSUser() + ` -Restart=always -`, - ServiceName: "brevrpcd.service", - ServiceType: "system", - } - case osDarwin: - return DarwinPlistConfigurer{ - Store: store, - ValueConfigFile: ` - - - - - - Label - com.brev.rpcd - - ProgramArguments - - /usr/local/bin/brev - tasks - run - rpcd - --user - ` + store.GetOSUser() + ` - - - RunAtLoad - - - StandardOutPath - /var/log/brevrpcd.log - StandardErrorPath - /var/log/brevrpcd.log - - - - `, - ServiceName: "com.brev.rpcd", - ServiceType: System, - } - } - return nil -} - // func NewDaemonConfiguration(user string, command string, serviceName string, serviceType string) DaemonConfigurer { // switch runtime.GOOS { // case osLinux: diff --git a/pkg/autostartconf/staticbinary.go b/pkg/autostartconf/staticbinary.go index f48a6778..19ecc7db 100644 --- a/pkg/autostartconf/staticbinary.go +++ b/pkg/autostartconf/staticbinary.go @@ -1,48 +1,11 @@ package autostartconf -import ( - "path/filepath" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - type StaticBinaryConfigurer struct { LinuxSystemdConfigurer URL string Name string } -func (sbc StaticBinaryConfigurer) Install() error { - _ = sbc.UnInstall() // best effort - - // download binary - err := sbc.Store.DownloadBinary( - sbc.URL, - filepath.Join("/usr/local/bin", sbc.Name), - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } +// best effort - err = sbc.Store.WriteString(sbc.getDestConfigFile(), sbc.ValueConfigFile) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if ShouldSymlink() { - errother := sbc.CreateForcedSymlink() - if errother != nil { - return breverrors.WrapAndTrace(errother) - } - } else { - errother := ExecCommands([][]string{ - {"systemctl", "enable", sbc.ServiceName}, - {"systemctl", "start", sbc.ServiceName}, - {"systemctl", "daemon-reload"}, - }) - if errother != nil { - return breverrors.WrapAndTrace(errother) - } - } - return nil -} +// download binary diff --git a/pkg/cmd/cmderrors/cmderrors.go b/pkg/cmd/cmderrors/cmderrors.go index c13f94e6..0d6aa3e2 100644 --- a/pkg/cmd/cmderrors/cmderrors.go +++ b/pkg/cmd/cmderrors/cmderrors.go @@ -15,24 +15,8 @@ import ( ) // determines if should print error stack trace and/or send to crash monitor -func DisplayAndHandleCmdError(name string, cmdFunc func() error) error { - er := breverrors.GetDefaultErrorReporter() - er.AddTag("command", name) - err := cmdFunc() - if err != nil { - er.AddBreadCrumb(breverrors.ErrReportBreadCrumb{ - Type: "err", - Message: err.Error(), - }) - er.ReportError(err) - if featureflag.Debug() || featureflag.IsDev() { - return err - } else { - return errors.Cause(err) //nolint:wrapcheck //no check - } - } - return nil -} + +//nolint:wrapcheck //no check func DisplayAndHandleError(err error) { er := breverrors.GetDefaultErrorReporter() diff --git a/pkg/cmd/hello/onboarding_utils.go b/pkg/cmd/hello/onboarding_utils.go index ab773f67..7ea80b2f 100644 --- a/pkg/cmd/hello/onboarding_utils.go +++ b/pkg/cmd/hello/onboarding_utils.go @@ -200,31 +200,13 @@ func SetOnboardingObject(oo OnboardingObject) error { return nil } -func SetOnboardingStep(step int) error { - // get path - path, err := GetOnboardingFilePath() - if err != nil { - return breverrors.WrapAndTrace(err) - } +// get path - // Ensure file exists - err = SetupDefaultOnboardingFile() - if err != nil { - return breverrors.WrapAndTrace(err) - } +// Ensure file exists - // write file - oo := OnboardingObject{ - Step: step, - } - err = files.OverwriteJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } +// write file - // return data - return nil -} +// return data func SetHasRunShell(hasRunShell bool) error { // get path @@ -287,8 +269,3 @@ func SetHasRunOpen(hasRunOpen bool) error { // return data return nil } - -func Poll() { - s := "Got it." - TypeItToMe(s) -} diff --git a/pkg/cmd/login/login.go b/pkg/cmd/login/login.go index 98a7dd0c..61b6c9f0 100644 --- a/pkg/cmd/login/login.go +++ b/pkg/cmd/login/login.go @@ -2,11 +2,9 @@ package login import ( - "bufio" "fmt" "os" "os/exec" - "path/filepath" "strings" "github.com/brevdev/brev-cli/pkg/auth" @@ -283,50 +281,11 @@ func CreateNewUser(loginStore LoginStore, idToken string) (bool, error) { return true, nil } -func OnboardUserWithSSHKeys(t *terminal.Terminal, user *entity.User, _ LoginStore, firstLoop bool) error { - if firstLoop { - t.Vprint(t.Yellow("\n\nYou must add your SSH key to pull and push from your repos.\n")) - } else { - t.Vprint(t.Red("\n\nYou must add your SSH key to pull and push from your repos.\n")) - } - // SSH Keys - _ = terminal.PromptGetInput(terminal.PromptContent{ - Label: "🔒 Click enter to get your secure SSH key:", - ErrorMsg: "error", - AllowEmpty: true, - }) - - t.Vprintf("\n" + user.PublicKey + "\n\n") - - _ = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Copy your public key 👆 then hit enter", - ErrorMsg: "error", - AllowEmpty: true, - }) +// SSH Keys - fmt.Print("\n") - - t.Vprint(t.Green("Add the key to your git repo provider")) - t.Vprint(t.Green("\tClick here if you use Github 👉 https://github.com/settings/ssh/new\n\n")) - // t.Eprintf(t.Yellow("\n\tClick here for Gitlab: https://gitlab.com/-/profile/keys\n")) - - isFin := terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Did you finish adding your SSH key?", - Items: []string{"Yes", "No"}, - ErrorMsg: "error", - }) - if isFin == "Yes" { - return nil - } else { - // t.Vprint(t.Red("\nYou must add your SSH key to pull and push from your repos. ")) - err := OnboardUserWithSSHKeys(t, user, nil, false) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } +// t.Eprintf(t.Yellow("\n\tClick here for Gitlab: https://gitlab.com/-/profile/keys\n")) - return nil -} +// t.Vprint(t.Red("\nYou must add your SSH key to pull and push from your repos. ")) func OnboardUserWithEditors(t *terminal.Terminal, _ LoginStore, ide string) (string, error) { if ide == "VSCode" { @@ -381,115 +340,17 @@ func (o LoginOptions) showBreadCrumbs(t *terminal.Terminal, org *entity.Organiza return nil } -func CheckAndInstallGateway(t *terminal.Terminal, store LoginStore) error { - localOS := terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Which operating system does your local computer have?", - ErrorMsg: "error", - Items: []string{"Mac OS (Intel)", "Mac OS (M1 Chip)", "Linux"}, - }) - - home, err := store.UserHomeDir() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - jetBrainsDirectory, installScript, err := CreateDownloadPathAndInstallScript(localOS, home) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Print("Searching for JetBrains Gateway...") - t.Print("") - - // Check if Gateway is already installed - gatewayInstalled := IsGatewayInstalled(jetBrainsDirectory) - if gatewayInstalled { - terminal.DisplayGatewayAlreadyInstalledInstructions(t) - return nil - } +// Check if Gateway is already installed - // Check if Toolbox is already installed - toolboxInstalled, gatewayInToolboxInstalled := IsToolboxInstalled(jetBrainsDirectory) - if gatewayInToolboxInstalled { - terminal.DisplayGatewayAlreadyInstalledInstructions(t) - return nil - } - - if toolboxInstalled { - terminal.DisplayToolboxInstalledInstructions(t) - return nil - } - - InstallGateway(installScript, t) - return nil -} +// Check if Toolbox is already installed -func IsToolboxInstalled(jetBrainsBaseDirectory string) (toolboxInstalled bool, gatewayInToolboxInstalled bool) { - _, err := os.Stat(jetBrainsBaseDirectory + `/Toolbox`) - if !os.IsNotExist(err) { - // Check if Gateway is already installed in Toolbox - _, err = os.Stat(jetBrainsBaseDirectory + `/Toolbox/apps/JetBrainsGateway`) - return true, !os.IsNotExist(err) - } - return false, false -} +// Check if Gateway is already installed in Toolbox -func IsGatewayInstalled(jetBrainsBaseDirectory string) bool { - matches, _ := filepath.Glob(jetBrainsBaseDirectory + `/JetBrainsGateway*`) - return len(matches) > 0 -} +// n -func InstallGateway(installScript string, t *terminal.Terminal) { - reader := bufio.NewReader(os.Stdin) - t.Print("You will need to install JetBrains Gateway to use JetBrains IDEs.") - t.Print(`Would you like to install JetBrains Gateway? [y/n]: `) - text, _ := reader.ReadString('\n') - if strings.Compare(text, "y") != 1 { - // n - t.Print("") - t.Print(t.Yellow("Click here to install manually: ")) - t.Print("https://www.jetbrains.com/remote-development/gateway") - t.Print("") - } else { - // y - spinner := t.NewSpinner() - spinner.Suffix = " Installing JetBrains Gateway..." - spinner.Start() - // #nosec - cmd := exec.Command("/bin/sh", "-c", installScript) - _, err := cmd.Output() - spinner.Stop() - if err != nil { - t.Errprint(err, "Error installing JetBrains Gateway") - t.Print("") - t.Print(t.Yellow("Click here to install manually: ")) - t.Print("https://www.jetbrains.com/remote-development/gateway") - t.Print("") - } else { - t.Print("") - t.Print("") - t.Print(t.Yellow("JetBrains Gateway successfully installed!")) - t.Print("") - t.Print("Run " + t.Green("brev jetbrains") + " and then open Gateway to begin.") - t.Print("") - } - } -} +// y -func CreateDownloadPathAndInstallScript(localOS string, homeDirectory string) (jetBrainsDirectory string, installScript string, err error) { - switch localOS { - case "Mac OS (Intel)": - jetBrainsDirectory = homeDirectory + `/Library/Application Support/JetBrains` - installScript = `set -e; echo "Start installation..."; curl "https://data.services.jetbrains.com/products/download?code=GW&platform=macM1&type=eap,rc,release,beta" --output ./gateway.dmg -L; hdiutil attach gateway.dmg; cp -R "/Volumes/JetBrains Gateway/JetBrains Gateway.app" /Applications; hdiutil unmount "/Volumes/JetBrains Gateway"; rm ./gateway.dmg; echo "JetBrains Gateway successfully installed"` - case "Mac OS (M1 Chip)": - jetBrainsDirectory = homeDirectory + `/Library/Application Support/JetBrains` - installScript = `set -e; echo "Start installation..."; curl "https://data.services.jetbrains.com/products/download?code=GW&platform=macM1&type=eap,rc,release,beta" --output ./gateway.dmg -L; hdiutil attach gateway.dmg; cp -R "/Volumes/JetBrains Gateway/JetBrains Gateway.app" /Applications; hdiutil unmount "/Volumes/JetBrains Gateway"; rm ./gateway.dmg; echo "JetBrains Gateway successfully installed"` - case "Linux": - jetBrainsDirectory = homeDirectory + `/.local/share/JetBrains` - installScript = `set -e; echo "Start installation..."; wget --show-progress -qO ./gateway.tar.gz "https://data.services.jetbrains.com/products/download?code=GW&platform=linux&type=eap,rc,release,beta"; GATEWAY_TEMP_DIR=$(mktemp -d); tar -C "$GATEWAY_TEMP_DIR" -xf gateway.tar.gz; rm ./gateway.tar.gz; "$GATEWAY_TEMP_DIR"/*/bin/gateway.sh; rm -r "$GATEWAY_TEMP_DIR"; echo "JetBrains Gateway was successfully installed";` - } - return jetBrainsDirectory, installScript, nil -} +// #nosec func makeFirstOrgName(username string) string { return fmt.Sprintf("%s-hq", username) diff --git a/pkg/cmd/ls/ls.go b/pkg/cmd/ls/ls.go index ae1e3528..e4324c10 100644 --- a/pkg/cmd/ls/ls.go +++ b/pkg/cmd/ls/ls.go @@ -336,27 +336,6 @@ func (ls Ls) displayWorkspacesAndHelp(org *entity.Organization, otherOrgs []enti } } -func DisplayLsConnectBreadCrumb(t *terminal.Terminal, workspaces []entity.Workspace) { - foundRunning := false - for _, w := range workspaces { - if w.Status == entity.Running { - foundRunning = true - t.Vprintf(t.Green("Connect to running instance:\n")) - t.Vprintf(t.Yellow(fmt.Sprintf("\tbrev open %s\t# brev open -> open instance in preferred editor\n", w.Name))) - t.Vprintf(t.Yellow(fmt.Sprintf("\tbrev shell %s\t# brev shell -> ssh into instance (shortcut)\n", w.Name))) - t.Vprintf(t.Yellow(fmt.Sprintf("\tssh %s\t# ssh -> ssh directly to instance\n", w.GetLocalIdentifier()))) - if enableSSHCol { - t.Vprintf(t.Yellow("\tssh ex: ssh %s\n", w.GetLocalIdentifier())) - } - break - } - } - if !foundRunning && len(workspaces) > 0 { - t.Vprintf(t.Green("Start a stopped instance:\n")) - t.Vprintf(t.Yellow("\tbrev start %s # brev start -> start stopped instance\n", workspaces[0].Name)) - } -} - func displayLsResetBreadCrumb(t *terminal.Terminal, workspaces []entity.Workspace) { foundAResettableWorkspace := false for _, w := range workspaces { diff --git a/pkg/cmd/paths/paths.go b/pkg/cmd/paths/paths.go index b68bdfb0..e1e69f4c 100644 --- a/pkg/cmd/paths/paths.go +++ b/pkg/cmd/paths/paths.go @@ -3,32 +3,9 @@ package paths import ( "fmt" "io/ioutil" - "strings" - - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" // breverrors "github.com/brevdev/brev-cli/pkg/errors" ) -func NewCmdApprove(_ *terminal.Terminal) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{}, - Use: "paths", - RunE: func(cmd *cobra.Command, args []string) error { - paths := GetPaths() - fmt.Println(paths) - return nil - }, - } - - return cmd -} - -func GetPaths() string { - vscodePaths := GetVsCodePaths() - return strings.Join(vscodePaths, ":") -} - func GetVsCodePaths() []string { fi, err := ioutil.ReadDir("/home/brev/.vscode-server/bin") if err != nil { diff --git a/pkg/cmd/stop/stop.go b/pkg/cmd/stop/stop.go index e08ce1a1..e746e849 100644 --- a/pkg/cmd/stop/stop.go +++ b/pkg/cmd/stop/stop.go @@ -146,18 +146,6 @@ func stopWorkspace(workspaceName string, t *terminal.Terminal, stopStore StopSto return nil } -func StopThisWorkspace(store StopStore, _ *terminal.Terminal) error { - isWorkspace, err := store.IsWorkspace() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if isWorkspace { - _ = 0 - // get current workspace - // stopWorkspace("") - // stop the workspace - } else { - return breverrors.NewValidationError("this is not a workspace -- please provide a workspace id") - } - return nil -} +// get current workspace +// stopWorkspace("") +// stop the workspace diff --git a/pkg/cmd/tasks/tasks.go b/pkg/cmd/tasks/tasks.go index 1103355f..1b0af8e7 100644 --- a/pkg/cmd/tasks/tasks.go +++ b/pkg/cmd/tasks/tasks.go @@ -117,10 +117,6 @@ func NewCmdRun(_ *terminal.Terminal, _ TaskStore, taskMap TaskMap) *cobra.Comman return cmd } -func Tasks(_ *terminal.Terminal, _ TaskStore, _ TaskMap) error { - return nil -} - func getTaskMap(store TaskStore) TaskMap { taskmap := make(TaskMap) sshcd := ssh.NewSSHConfigurerTask(store) diff --git a/pkg/cmd/textceo/textceo.go b/pkg/cmd/textceo/textceo.go index dfd66bfa..638fbe85 100644 --- a/pkg/cmd/textceo/textceo.go +++ b/pkg/cmd/textceo/textceo.go @@ -4,9 +4,6 @@ import ( "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/spf13/cobra" ) var ( @@ -25,21 +22,5 @@ type TextCEOStore interface { GetWorkspaceMetaData(workspaceID string) (*entity.WorkspaceMetaData, error) } -func NewCmdTextCEO(t *terminal.Terminal, _ TextCEOStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "test", - DisableFlagsInUseLine: true, - Short: "Send a text message to our CEO, Nader", - Long: startLong, - Example: startExample, - Run: func(cmd *cobra.Command, args []string) { - t.Vprint("\ntest cmd\n") - }, - } - - // cmd.Flags().StringVarP(&message, "message", "m", "", "message to send Brev's CEO, Nader") - // cmd.Flags().StringVarP(&message, "phone", "p", "", "(Optional) leave a number for Nader to follow up") - - return cmd -} +// cmd.Flags().StringVarP(&message, "message", "m", "", "message to send Brev's CEO, Nader") +// cmd.Flags().StringVarP(&message, "phone", "p", "", "(Optional) leave a number for Nader to follow up") diff --git a/pkg/cmd/util/util.go b/pkg/cmd/util/util.go index 223364d9..88855168 100644 --- a/pkg/cmd/util/util.go +++ b/pkg/cmd/util/util.go @@ -64,18 +64,6 @@ type MakeWorkspaceWithMetaStore interface { GetWorkspaceMetaData(workspaceID string) (*entity.WorkspaceMetaData, error) } -func MakeWorkspaceWithMeta(store MakeWorkspaceWithMetaStore, workspace *entity.Workspace) (entity.WorkspaceWithMeta, error) { - workspaceMetaData, err := store.GetWorkspaceMetaData(workspace.ID) - if err != nil { - return entity.WorkspaceWithMeta{}, breverrors.WrapAndTrace(err) - } - - return entity.WorkspaceWithMeta{ - WorkspaceMetaData: *workspaceMetaData, - Workspace: *workspace, - }, nil -} - func GetClassIDString(classID string) string { // switch statement on class ID switch classID { diff --git a/pkg/cmdcontext/cmdwriter.go b/pkg/cmdcontext/cmdwriter.go index 56009f9f..80ef4386 100644 --- a/pkg/cmdcontext/cmdwriter.go +++ b/pkg/cmdcontext/cmdwriter.go @@ -3,7 +3,3 @@ package cmdcontext // NoopWriter is an implementation of the standard Writer which takes no action // upon being asked to write. type NoopWriter struct{} - -func (w NoopWriter) Write(_ []byte) (n int, err error) { - return 0, nil -} diff --git a/pkg/collections/collections.go b/pkg/collections/collections.go index bf159ff5..9f094ae1 100644 --- a/pkg/collections/collections.go +++ b/pkg/collections/collections.go @@ -1,29 +1,14 @@ package collections import ( - "bytes" "context" - "encoding/base64" - "encoding/json" - "fmt" - "math" "net/http" - "reflect" - "runtime" - "runtime/debug" "sort" - "strings" "sync" - "testing" "time" "github.com/brevdev/brev-cli/pkg/errors" - "github.com/cenkalti/backoff/v4" "github.com/jinzhu/copier" - "github.com/mitchellh/mapstructure" - "golang.org/x/exp/constraints" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -45,47 +30,19 @@ func GetRequestWithContext(ctx context.Context, url string) (*http.Response, err return resp, nil } -func Foldr[T any, R any](fn func(next T, carry R) R, base R, list []T) R { - for idx := len(list) - 1; idx >= 0; idx-- { - base = fn(list[idx], base) - } - - return base -} - -func Fmap[T any, R any](fn func(some T) R, list []T) []R { +func Fmap[T, R any](fn func(some T) R, list []T) []R { return Foldl(func(acc []R, next T) []R { return append(acc, fn(next)) }, []R{}, list) } -// there is no function overloading [and the need to describe dependent relations between the types of the functions rules out variadic arguments] -// so we will define c2, c3, c4, and c5 which will allow simple composition of up to 5 functions -// anything more than that should be refactored so that subcomponents of the composition are renamed, anyway (or named itself) - -func Compose[T any, S any, R any](fn1 func(some S) R, fn2 func(some T) S) func(some T) R { +func Compose[T, S, R any](fn1 func(some S) R, fn2 func(some T) S) func(some T) R { return func(some T) R { return fn1(fn2(some)) } } -func C2[T any, S any, R any](fn1 func(some S) R, fn2 func(some T) S) func(some T) R { - return Compose(fn1, fn2) -} - -func C3[T any, S any, R any, U any](fn0 func(some R) U, fn1 func(some S) R, fn2 func(some T) S) func(some T) U { - return func(some T) U { - return fn0(fn1(fn2(some))) - } -} - -func C4[T any, S any, R any, U any, V any](fn01 func(some U) V, fn0 func(some R) U, fn1 func(some S) R, fn2 func(some T) S) func(some T) V { - return func(some T) V { - return fn01(fn0(fn1(fn2(some)))) - } -} - -func C5[T any, S any, R any, U any, V any, W any](fn02 func(some V) W, fn01 func(some U) V, fn0 func(some R) U, fn1 func(some S) R, fn2 func(some T) S) func(some T) W { +func C5[T, S, R, U, V, W any](fn02 func(some V) W, fn01 func(some U) V, fn0 func(some R) U, fn1 func(some S) R, fn2 func(some T) S) func(some T) W { return func(some T) W { return fn02(fn01(fn0(fn1(fn2(some))))) } @@ -95,21 +52,17 @@ func ID[T any](x T) T { return x } -func C[T any](fns ...func(some T) T) func(some T) T { - return Foldr(Compose[T, T, T], ID[T], fns) -} - func S[T any](fns ...func(some T) T) func(some T) T { return Foldl(Compose[T, T, T], ID[T], fns) } -func P2[X any, Y any, Z any](fn func(X, Y) Z, x X) func(Y) Z { +func P2[X, Y, Z any](fn func(X, Y) Z, x X) func(Y) Z { return func(y Y) Z { return fn(x, y) } } -func Flip[X any, Y any, Z any](fn func(X, Y) Z) func(Y, X) Z { +func Flip[X, Y, Z any](fn func(X, Y) Z) func(Y, X) Z { return func(y Y, x X) Z { return fn(x, y) } @@ -122,25 +75,7 @@ func First[X any](list []X) *X { return nil } -func Cons[X any](x X, list []X) []X { - return Concat([]X{x}, list) -} - -func Enconcat[X any](before []X, x X, after []X) []X { - return Concat(before, Cons(x, after)) -} - -func Any[T any](f func(T) bool, list []T) { - Foldl(func(acc bool, el T) bool { - if acc { - return acc - } else { - return f(el) - } - }, false, list) -} - -func Fanout[T any, R any](fs []func(T) R, item T) []R { +func Fanout[T, R any](fs []func(T) R, item T) []R { return Fmap(func(f func(T) R) R { return f(item) }, fs) @@ -169,7 +104,7 @@ func ToDict[T comparable](xs []T) map[T]bool { }, map[T]bool{}, xs) } -func Difference[T comparable](from []T, remove []T) []T { +func Difference[T comparable](from, remove []T) []T { returnval := Foldl(func(acc maplist[T], el T) maplist[T] { if _, ok := acc.Map[el]; !ok { acc.Map[el] = true @@ -180,7 +115,7 @@ func Difference[T comparable](from []T, remove []T) []T { return returnval.List } -func DictMerge[K comparable, V any](left map[K]V, right map[K]V) map[K]V { +func DictMerge[K comparable, V any](left, right map[K]V) map[K]V { newMap := map[K]V{} for key, val := range left { if _, ok := right[key]; ok { @@ -222,33 +157,11 @@ func Contains[T comparable](s []T, e T) bool { return false } -func MapContainsKey[K comparable, V any](m map[K]V, key K) bool { - _, ok := m[key] - return ok -} - -func ValueOrZero[T any](ptr *T) T { - if ptr == nil { - res, _ := reflect.Zero(reflect.TypeOf(ptr)).Interface().(T) - return res - } else { - return *ptr - } -} - -func MapFromList[T any, R comparable](list []T, keySelector func(l T) R) map[R]T { - result := map[R]T{} - for _, item := range list { - result[keySelector(item)] = item +func Foldl[T any, R any](fn func(acc R, next T) R, base R, list []T) R { + for _, value := range list { + base = fn(base, value) } - return result -} - -// Await blocks until the asynchronous operation completes, returning the result and error. -func Except[T comparable](items []T, except []T) []T { - return Filter(items, func(item T) bool { - return !ListContains(except, item) - }) + return base } // loops over list and returns when has returns true @@ -260,459 +173,23 @@ func ListHas[K any](list []K, has func(l K) bool) bool { return false } -func MapHasKey[K comparable, V any](m map[K]V, key K) bool { - _, ok := m[key] - return ok -} - func ListContains[K comparable](list []K, item K) bool { return ListHas(list, func(l K) bool { return l == item }) } -func ManyIntegersToInts[T constraints.Integer](i []T) []int { - return Map(i, func(i T) int { return int(i) }) -} - -func ManyStringLikeToStrings[T ~string](i []T) []string { - return Map(i, func(i T) string { return string(i) }) -} - -// map over a go map -func MapMap[K comparable, V any, R any](m map[K]V, f func(K, V) R) []R { - results := []R{} - for k, v := range m { - r := f(k, v) - results = append(results, r) - } - return results -} - -func MapMapE[K comparable, V any, R any](m map[K]V, f func(K, V) (R, error)) ([]R, error) { - results := []R{} - for k, v := range m { - r, e := f(k, v) - if e != nil { - return nil, e - } - results = append(results, r) - } - return results, nil -} - -// map over a go map and return a map, merge the maps -func MapMapMerge[K1 comparable, V1 any, K2 comparable, V2 any](m map[K1]V1, f func(K1, V1) map[K2]V2) map[K2]V2 { - return MergeMaps(MapMap(m, f)...) -} - -func MapE[T, R any](items []T, mapper func(T) (R, error)) ([]R, error) { - results := []R{} - for _, item := range items { - res, err := mapper(item) - if err != nil { - return results, errors.WrapAndTrace(err) - } - results = append(results, res) - } - return results, nil -} - -func ParallelMapE[T, R any](items []T, mapper func(T) (R, error)) ([]R, error) { - return ParallelWorkerMapE(items, mapper, 100) -} - -func AccumulateMap[A any, K comparable, V any](m map[K]V, accumulator func(A, K, V) A) A { - var result A - for k, v := range m { - result = accumulator(result, k, v) - } - return result -} - -func Accumulate[A any, T any](items []T, accumulator func(A, T) A) A { - var result A - for _, item := range items { - result = accumulator(result, item) - } - return result -} - -func AccumulateE[A any, T any](items []T, accumulator func(A, T) (A, error)) (A, error) { - var result A - for _, item := range items { - var err error - result, err = accumulator(result, item) - if err != nil { - var zero A - return zero, errors.WrapAndTrace(err) - } - } - return result, nil -} - -func Flatten[T any](listOfLists [][]T) []T { - result := []T{} - for _, list := range listOfLists { - result = append(result, list...) - } - return result -} - -func Foldl[T any, R any](fn func(acc R, next T) R, base R, list []T) R { - for _, value := range list { - base = fn(base, value) - } - return base -} - -func FoldlE[T any, R any](fn func(acc R, next T) (R, error), base R, list []T) (R, error) { - for _, value := range list { - var err error - base, err = fn(base, value) - if err != nil { - var zero R - return zero, errors.WrapAndTrace(err) - } - } - return base, nil -} - -// Take a list of things and a function that returns a list of things then combines list after mapping (return early from error) -// func T -> [R, R, R ...] -// [T, T, T ...] -> [R, R, R ...] -func FlatmapE[T any, R any](fn func(some T) ([]R, error), list []T) ([]R, error) { - return FoldlE(func(acc []R, el T) ([]R, error) { - res, err := fn(el) - if err != nil { - return nil, errors.WrapAndTrace(err) - } - return Concat(acc, res), nil - }, []R{}, list) -} - // Take a list of things and a function that returns a list of things then combines list after mapping // func T -> [R, R, R ...] // [T, T, T ...] -> [R, R, R ...] -func Flatmap[T any, R any](fn func(some T) []R, list []T) []R { +func Flatmap[T, R any](fn func(some T) []R, list []T) []R { return Foldl(func(acc []R, el T) []R { return Concat(acc, fn(el)) }, []R{}, list) } -func Concat[T any](left []T, right []T) []T { +func Concat[T any](left, right []T) []T { return append(left, right...) } -// func T -> R -// [T, T, T ...] -> [R, R, R ...] -func Map[T, R any](items []T, mapper func(T) R) []R { - results := []R{} - for _, item := range items { - results = append(results, mapper(item)) - } - return results -} - -func ListToMap[T any, R comparable](list []T, keySelector func(l T) R) map[R]T { - result := map[R]T{} - for _, item := range list { - result[keySelector(item)] = item - } - return result -} - -func ListToMapE[T any, R comparable](list []T, keySelector func(l T) (R, error)) (map[R]T, error) { - result := map[R]T{} - for _, item := range list { - res, err := keySelector(item) - if err != nil { - return nil, errors.WrapAndTrace(err) - } - result[res] = item - } - return result, nil -} - -func ListToMapKV[T any, R comparable, V any](list []T, keySelector func(l T) R, valueSelector func(l T) V) map[R]V { - result := map[R]V{} - for _, item := range list { - result[keySelector(item)] = valueSelector(item) - } - return result -} - -func ListToCollisionMap[T comparable](list []T) map[T]bool { - result := map[T]bool{} - for _, item := range list { - result[item] = true - } - return result -} - -func ListOfPointersToListOfValues[T any](list []*T) []T { - return Map(list, func(i *T) T { return *i }) -} - -func DefaultValue[T any](value T, defaultValue T) T { - if reflect.ValueOf(value).IsZero() { - return defaultValue - } else { - return value - } -} - -func DefaultPtr[T any](value *T, defaultValue T) T { - if reflect.ValueOf(value).IsZero() { - return defaultValue - } else { - return *value - } -} - -// return default if ptr is nil or de-referenced ptr value is empty -func DefaultPtrOrValue[T any](value *T, defaultValue T) T { - if value == nil || reflect.ValueOf(*value).IsZero() { - return defaultValue - } else { - return *value - } -} - -// right maps override left if they have the same key -func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { - result := map[K]V{} - for _, m := range maps { - for k, v := range m { - result[k] = v - } - } - return result -} - -func MapToGenericMap[v any](m map[string]v) map[string]interface{} { - result := map[string]interface{}{} - for k, v := range m { - result[k] = v - } - return result -} - -func GetAValueFromMap[K comparable, V any](m map[K]V) *V { - for _, v := range m { - return &v - } - return nil -} - -func ListOfSomethingToListOfAny[T any](l []T) []any { - newList := []any{} - for _, item := range l { - newList = append(newList, item) - } - return newList -} - -func Ptr[T any](x T) *T { - return &x -} - -func Deref[T any](x *T) T { - return *x -} - -// return value or nil if value is zero -func ZeroValueToNil[T any](x T) *T { - if reflect.ValueOf(x).IsZero() { - return nil - } else { - return &x - } -} - -func IsEmpty[T any](x T) bool { - return reflect.ValueOf(x).IsZero() -} - -func IsEmptyValP[T any](x *T) bool { - if x == nil { - return true - } else { - return reflect.ValueOf(*x).IsZero() - } -} - -func ReturnOnCondition[T any](ctx context.Context, fetcher func(ctx context.Context) (T, error), conditional func(i T) bool, updateDuration time.Duration) (T, error) { - for ctx.Err() == nil { - result, err := fetcher(ctx) - if err != nil { - return result, errors.WrapAndTrace(err) - } - if conditional(result) { - return result, nil - } - time.Sleep(updateDuration) - } - var t T - return t, ctx.Err() //nolint:wrapcheck // fine -} - -func InitialNotEqual[T any](i T) func(l T) bool { - return func(l T) bool { - return !reflect.DeepEqual(i, l) - } -} - -func DeepEqual[T any](i T, l T) bool { - return reflect.DeepEqual(i, l) -} - -func FromJSON[T any](j []byte) (T, error) { - var t T - err := json.Unmarshal(j, &t) - if err != nil { - return t, errors.WrapAndTrace(err) - } - return t, nil -} - -func CopyVal[T any](t T) (T, error) { - var r T - if err := copier.CopyWithOption(&r, t, CopyOptions{}.ToCopierOptions()); err != nil { - return r, errors.WrapAndTrace(err) - } - return r, nil -} - -func CopyPtrVal[T any](t *T) (*T, error) { - var r T - if err := copier.CopyWithOption(&r, t, CopyOptions{}.ToCopierOptions()); err != nil { - return nil, errors.WrapAndTrace(err) - } - return &r, nil -} - -func TryCopyToNew[T any, R any](t T, options ...CopyOption) (R, error) { - var r R - copyOptions := CopyOptions{} - for _, o := range options { - o.apply(t, r, ©Options) - } - if err := copier.CopyWithOption(&r, t, copyOptions.ToCopierOptions()); err != nil { - return r, errors.WrapAndTrace(err) - } - return r, nil -} - -type CopyOption interface { - apply(t any, r any, o *CopyOptions) -} - -type CopyMap map[string]string - -func (c CopyMap) apply(t any, r any, o *CopyOptions) { - o.Mappers = append(o.Mappers, DumbCopyMapper{Mapping: copier.FieldNameMapping{ - SrcType: t, - DstType: r, - Mapping: c, - }}) -} - -func TryCopyToNewOptions[T any, R any](t T, options CopyOptions) (R, error) { - var r R - if err := copier.CopyWithOption(&r, t, options.ToCopierOptions()); err != nil { - return r, errors.WrapAndTrace(err) - } - return r, nil -} - -// For testing different DX -func TryCopyToNewE[T any, R any](t T, mappers ...CopyMappingFunc[T, R]) (R, error) { - var r R - opts := CopyOptions{}.ToCopierOptions() - opts.FieldNameMapping = append(opts.FieldNameMapping, Map(mappers, func(m CopyMappingFunc[T, R]) copier.FieldNameMapping { return m.ToCopierMapping() })...) - if err := copier.CopyWithOption(&r, t, opts); err != nil { - return r, errors.WrapAndTrace(err) - } - return r, nil -} - -type CopyOptions struct { - ShallowCopy bool - OmitDefaultConverters bool - Converters CopierConverters - Mappers CopyMappers // create mappings for any arbitrary type -} - -type CopyMappers []CopyMapper - -func (c CopyMappers) ToCopierMappings() []copier.FieldNameMapping { - var result []copier.FieldNameMapping - for _, cc := range c { - result = append(result, cc.ToCopierMapping()) - } - return result -} - -type CopyMapper interface { - ToCopierMapping() copier.FieldNameMapping -} - -type DumbCopyMapper struct { - Mapping copier.FieldNameMapping -} - -func (d DumbCopyMapper) ToCopierMapping() copier.FieldNameMapping { - return d.Mapping -} - -type CopyMapping[T, R any] map[string]string - -func (c CopyMapping[T, R]) ToCopierMapping() copier.FieldNameMapping { - var t T - var r R - return copier.FieldNameMapping{ - SrcType: t, - DstType: r, - Mapping: c, - } -} - -type CopyMappingFunc[T, R any] func(T, R) map[any]any - -// Doesn't work but leaving for future reference -func (c CopyMappingFunc[T, R]) ToCopierMapping() copier.FieldNameMapping { - var t T - valueT := reflect.ValueOf(&t).Elem() - var r R - valueR := reflect.ValueOf(&r).Elem() - copierMapping := map[string]string{} - realMapping := c(t, r) - var allErr error - for tf, rf := range realMapping { - tfFound := findStructField(valueT, reflect.ValueOf(tf)) - rfFound := findStructField(valueR, reflect.ValueOf(rf)) - isErr := false - if tfFound == nil { - allErr = errors.Join(allErr, errors.Errorf("field %s not found in struct %T", tf, t)) - isErr = true - } - if rfFound == nil { - allErr = errors.Join(allErr, errors.Errorf("field %s not found in struct %T", rf, r)) - isErr = true - } - if !isErr { - copierMapping[tfFound.Name] = rfFound.Name - } - } - if allErr != nil { - panic(allErr) - } - return copier.FieldNameMapping{ - SrcType: t, - DstType: r, - Mapping: copierMapping, - } -} - var timeToPBTimeStamp CopyConverter[time.Time, *timestamppb.Timestamp] = func(src time.Time) (*timestamppb.Timestamp, error) { return timestamppb.New(src), nil } @@ -743,28 +220,8 @@ var DefaultConverters = CopierConverters{ pbTimeStampToTimePtr, } -func (c CopyOptions) ToCopierOptions() copier.Option { - convs := c.Converters.ToCopierTypeConverters() - if !c.OmitDefaultConverters { - convs = append(convs, DefaultConverters.ToCopierTypeConverters()...) - } - return copier.Option{ - DeepCopy: !c.ShallowCopy, - Converters: convs, - FieldNameMapping: c.Mappers.ToCopierMappings(), - } -} - type CopierConverters []CopierConverter -func (c CopierConverters) ToCopierTypeConverters() []copier.TypeConverter { - var result []copier.TypeConverter - for _, cc := range c { - result = append(result, cc.ToCopierTypeConverter()) - } - return result -} - type CopierConverter interface { ToCopierTypeConverter() copier.TypeConverter } @@ -788,58 +245,14 @@ func (c CopyConverter[T, R]) ToCopierTypeConverter() copier.TypeConverter { return ctc } -func TryCopyTo[T any, R any](t T, r R) (R, error) { - if err := copier.CopyWithOption(&r, t, CopyOptions{}.ToCopierOptions()); err != nil { - return r, errors.WrapAndTrace(err) - } - return r, nil -} - -func TryCopyToOptions[T any, R any](t T, r R, options CopyOptions) (R, error) { - if err := copier.CopyWithOption(&r, t, options.ToCopierOptions()); err != nil { - return r, errors.WrapAndTrace(err) - } - return r, nil +type Params[T any] struct { + Value T + Ctx context.Context } -func GetMapKeys[K comparable, V any](m map[K]V) []K { - keys := []K{} - for k := range m { - keys = append(keys, k) - } - return keys -} - -func GetMapValues[K comparable, V any](m map[K]V) []V { - values := []V{} - for _, v := range m { - values = append(values, v) - } - return values -} - -type Params[T any] struct { - Value T - Ctx context.Context -} - -type Result[T any] struct { - Value T - Err error -} - -func (r Result[T]) Unwrap() (T, error) { - return r.Value, r.Err -} - -// pass in m a map of string to any -func MapToStruct[T any](m any) (T, error) { - var t T - err := mapstructure.Decode(m, &t) - if err != nil { - return t, errors.WrapAndTrace(err) - } - return t, nil +type Result[T any] struct { + Value T + Err error } type MapKeyVal[K comparable, V any] struct { @@ -847,27 +260,6 @@ type MapKeyVal[K comparable, V any] struct { Value V } -func MapToList[K comparable, T any](m map[K]T) []MapKeyVal[K, T] { - var results []MapKeyVal[K, T] - for k, v := range m { - results = append(results, MapKeyVal[K, T]{Key: k, Value: v}) - } - return results -} - -// sortFn if i < j then ascending (1,2,3), if i > j then descending (3,2,1) -func SortByE[T any](sortFn func(T, T) (bool, error), list []T) ([]T, error) { - var retErr error - sort.SliceStable(list, func(i, j int) bool { - cmp, err := sortFn(list[i], list[j]) - if err != nil { - retErr = err - } - return cmp - }) - return list, retErr -} - // sortFn if i < j then ascending (1,2,3), if i > j then descending (3,2,1) func SortBy[T any](sortFn func(T, T) bool, list []T) []T { sort.SliceStable(list, func(i, j int) bool { @@ -876,43 +268,6 @@ func SortBy[T any](sortFn func(T, T) bool, list []T) []T { return list } -func RemoveDuplicates[T comparable](list []T) []T { - seen := map[T]bool{} - result := []T{} - for _, item := range list { - if !seen[item] { - result = append(result, item) - seen[item] = true - } - } - return result -} - -func ContainsDuplicatesErr[T comparable](list []T) error { - seen := map[T]bool{} - for _, item := range list { - if seen[item] { - return errors.Errorf("duplicate item: %v", item) - } - seen[item] = true - } - return nil -} - -// takes a list of items and checks if items are elements in another list -func ListItemsAreErr[T comparable](items []T, are []T) error { - check := map[T]bool{} - for _, r := range are { - check[r] = true - } - for _, i := range items { - if !check[i] { - return errors.Errorf("item %v is not in list %v", i, are) - } - } - return nil -} - func Find[T any](list []T, f func(T) bool) *T { for _, item := range list { if f(item) { @@ -922,30 +277,6 @@ func Find[T any](list []T, f func(T) bool) *T { return nil } -func FindPtr[T any](list []*T, f func(*T) bool) *T { - for _, item := range list { - if f(item) { - return item - } - } - return nil -} - -// returns those that are true -func FilterE[T any](list []T, f func(T) (bool, error)) ([]T, error) { - result := []T{} - for _, item := range list { - res, err := f(item) - if err != nil { - return nil, errors.WrapAndTrace(err) - } - if res { - result = append(result, item) - } - } - return result, nil -} - // returns those that are true func Filter[T any](list []T, f func(T) bool) []T { result := []T{} @@ -957,783 +288,20 @@ func Filter[T any](list []T, f func(T) bool) []T { return result } -func FilterOutEmpty[T any](list []T) []T { - return Filter(list, func(i T) bool { - return !IsEmpty(i) - }) -} - -func Max[T constraints.Ordered](x T, y T) T { - if x > y { - return x - } else { - return y - } -} - -func Min[T constraints.Ordered](x T, y T) T { - if x < y { - return x - } else { - return y - } -} - -func Deduplicate[T comparable](list []T) []T { - seen := map[T]bool{} - result := []T{} - for _, item := range list { - if !seen[item] { - result = append(result, item) - seen[item] = true - } - } - return result -} - -func MultiGroupBy[T comparable, A any](list []A, f func(A) []T) map[T][]A { - result := map[T][]A{} - for _, item := range list { - key := f(item) - for _, k := range key { - result[k] = append(result[k], item) - } - } - return result -} - -func GroupBy[K comparable, A any](list []A, keyGetter func(A) K) map[K][]A { - result := map[K][]A{} - for _, item := range list { - key := keyGetter(item) - result[key] = append(result[key], item) - } - return result -} - -func SortEachBucket[T comparable, A any](bucketedMap map[T][]A, f func(a A, b A) bool) map[T][]A { - for k, v := range bucketedMap { - sorted := SortBy(f, v) - bucketedMap[k] = sorted - } - return bucketedMap -} - -func GroupByE[T comparable, A any](list []A, f func(A) (T, error)) (map[T][]A, error) { - result := map[T][]A{} - for _, item := range list { - key, err := f(item) - if err != nil { - return nil, errors.WrapAndTrace(err) - } - result[key] = append(result[key], item) - } - return result, nil -} - -func Chunk[T any](list []T, chunkSize int) [][]T { - var result [][]T - for i := 0; i < len(list); i += chunkSize { - end := i + chunkSize - if end > len(list) { - end = len(list) - } - result = append(result, list[i:end]) - } - return result -} - -func DecodeBase64OrValue(s string) (string, error) { - if strings.HasPrefix(s, "base64:") { - s = strings.TrimPrefix(s, "base64:") - decoded, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", errors.WrapAndTrace(err) - } - return string(decoded), nil - } - return s, nil -} - -func LastXChars(s string, x int) string { - if len(s) < x { - return s - } - return s[len(s)-x:] -} - -// creates a map of the keys in a and not in b -func MapDiff[K comparable, V1 any, V2 any](a map[K]V1, b map[K]V2) map[K]V1 { - c := CloneMap(a) - for k := range b { - delete(c, k) - } - return c -} - -func ListAnyDiff[T any, C comparable](a []T, b []T, toComp func(t T) C) []T { - aM := ListToMap(a, toComp) - bM := ListToMap(b, toComp) - return GetMapValues(MapDiff(aM, bM)) -} - -func ListDiff[T comparable](a []T, b []T) []T { - aCM := ListToCollisionMap(a) - bCM := ListToCollisionMap(b) - aNotB := MapDiff(aCM, bCM) - return GetMapKeys(aNotB) -} - -func CloneMap[T any, K comparable](m map[K]T) map[K]T { - result := map[K]T{} - for k, v := range m { - result[k] = v - } - return result -} - -func CloneList[T any](l []T) []T { - result := []T{} - for _, v := range l { //nolint:gosimple //ok - result = append(result, v) - } - return result -} - -func ReverseList[T any](list []T) { - length := len(list) - for i := 0; i < length/2; i++ { - list[i], list[length-i-1] = list[length-i-1], list[i] - } -} - -func GetFirstKeyThatContainsNoCase[T any](m map[string]T, s string) string { - s = strings.ToLower(s) - for k := range m { - if strings.Contains(strings.ToLower(k), s) { - return k - } - } - return "" -} - -func Run[T any](l []T, f func(t T)) { - for _, item := range l { - f(item) - } -} - -func ParallelWorkerMapE[T, R any](items []T, mapper func(T) (R, error), maxWorkers int) ([]R, error) { - var wg sync.WaitGroup - var mu sync.Mutex - - length := len(items) - results := make([]R, length) - var allErr error - - // Create a buffered channel to act as a semaphore. - semaphore := make(chan struct{}, maxWorkers) - - for idx, item := range items { - // Acquire a token from the semaphore. - semaphore <- struct{}{} - - wg.Add(1) - go func(i int, itm T) { - defer wg.Done() - defer func() { - if p := recover(); p != nil { - // attach call stack to avoid missing in different goroutine - mu.Lock() - allErr = errors.Join(allErr, errors.Errorf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack())))) - mu.Unlock() - } - }() - res, err := mapper(itm) - mu.Lock() - if err != nil { - allErr = errors.Join(allErr, errors.Wrap(err, fmt.Sprint(i))) - } else { - results[i] = res - } - mu.Unlock() - - // Release a token back to the semaphore. - <-semaphore - }(idx, item) - } - - wg.Wait() - - return results, allErr //nolint:wrapcheck // fine for internal -} - -// Early returns if one error is found, will return partial work -func ParallelWorkerMapExitOnE[T, R any](ctx context.Context, items []T, mapper func(context.Context, T) (R, error), maxWorkers int) ([]R, error) { - var wg sync.WaitGroup - var mu sync.Mutex - - length := len(items) - results := make([]R, length) - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - var firstErr error - - // Create a buffered channel to act as a semaphore. - semaphore := make(chan struct{}, maxWorkers) - - for idx, item := range items { - select { - case <-ctx.Done(): - case semaphore <- struct{}{}: - wg.Add(1) - go func(i int, itm T) { - defer wg.Done() - defer func() { <-semaphore }() - defer func() { - if p := recover(); p != nil { - err := errors.Errorf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack()))) - mu.Lock() - if firstErr == nil { - firstErr = err - cancel() - } - mu.Unlock() - } - }() - - if res, err := mapper(ctx, itm); err != nil { - mu.Lock() - if firstErr == nil { - firstErr = err - cancel() - } - mu.Unlock() - } else { - mu.Lock() - results[i] = res - mu.Unlock() - } - }(idx, item) - } - } - - wg.Wait() - - // Priority is given to firstErr if it's set - if firstErr != nil { - return results, errors.WrapAndTrace(firstErr) - } - - // If firstErr is nil, then we check if the context was canceled - return results, errors.WrapAndTrace(ctx.Err()) -} - -func Iterate[T any](hasNext func() bool, next func() (T, error), do func(a T) (bool, error)) error { - for hasNext() { - a, err := next() - if err != nil { - return errors.WrapAndTrace(err) - } - cont, err := do(a) - if err != nil { - return errors.WrapAndTrace(err) - } - if !cont { - break - } - } - return nil -} - -// Assumes that if cont is false res is empty -func IterateToSlice[T any](f func() (bool, T, error)) ([]T, error) { - allRes := []T{} - for { - cont, res, err := f() - if err != nil { - return nil, errors.WrapAndTrace(err) - } - if !cont { - return allRes, nil - } - allRes = append(allRes, res) - } -} - -func RetryWithDataAndAttemptCount[T any](o backoff.OperationWithData[T], b backoff.BackOff) (T, error) { - attemptCount := 0 - t, err := backoff.RetryWithData(func() (T, error) { - attemptCount++ - return o() - }, b) - if err != nil { - return t, errors.WrapAndTrace(errors.Errorf("attemptCount %d: %w", attemptCount, err)) - } - return t, nil -} - -func RetryWithAttemptCount(o backoff.Operation, b backoff.BackOff) error { - attemptCount := 0 - err := backoff.Retry(func() error { - attemptCount++ - return o() - }, b) - if err != nil { - return errors.WrapAndTrace(errors.Errorf("attemptCount %d: %w", attemptCount, err)) - } - return nil -} - type Runnable interface { Run(ctx context.Context) error Shutdown(ctx context.Context) error } -// RunAllWithShutdown runs Runnabls in parallel and waits for shutdown signal (max n seconds) -// if one runner errors or panics -func RunAllWithShutdown(ctx context.Context, runners []Runnable, shutdownChan <-chan any) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - var wgRun sync.WaitGroup - errChan := make(chan error, len(runners)) - doneChan := make(chan struct{}) - for _, r := range runners { - wgRun.Add(1) - go func(runner Runnable) { - defer wgRun.Done() - defer func() { - if p := recover(); p != nil { - // attach call stack to avoid missing in different goroutine - errChan <- errors.Errorf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack()))) - } - }() - if err := runner.Run(ctx); err != nil { - errChan <- err - } - }(r) - } - - go func() { - wgRun.Wait() - close(doneChan) - }() - - var runErr error - select { - case <-shutdownChan: - // Received shutdown signal - case err := <-errChan: - // One of the Run methods returned an error - runErr = err - case <-doneChan: - return nil - } - - // Initiate shutdown of all runners - var shutdownErrors error - var mu sync.Mutex - var wgShutdown sync.WaitGroup - for _, r := range runners { - wgShutdown.Add(1) - go func(runner Runnable) { - defer wgShutdown.Done() - err := DoWithTimeout(func(ctx context.Context) error { - err := runner.Shutdown(ctx) - if err != nil { - return errors.WrapAndTrace(err) - } - return nil - }, 5*time.Second, TimeoutOptions{ - CatchPanic: true, - }) - if err != nil { - mu.Lock() - shutdownErrors = errors.Join(shutdownErrors, err) - mu.Unlock() - } - }(r) - } - - wgShutdown.Wait() - return errors.WrapAndTrace(errors.Join(runErr, shutdownErrors)) -} - type ContextKey string const IdempotencyKeyName ContextKey = "idempotencyKey" -func ContextWithIdempotencyKey(ctx context.Context, idempotencyKey string) context.Context { - if idempotencyKey == "" { - return ctx - } - return context.WithValue(ctx, IdempotencyKeyName, idempotencyKey) -} - -func GetIdempotencyKeyFromContext(ctx context.Context) string { - idempotencyKey, _ := ctx.Value(IdempotencyKeyName).(string) // do not check for error because if fail, then just leave as empty string - return idempotencyKey -} - -func MakePrefixIdempotencyKeyFromCtx(ctx context.Context, prefix string) string { - key := GetIdempotencyKeyFromContext(ctx) - if key == "" { - return "" - } - return fmt.Sprintf("%s-%s", prefix, key) -} - -// for testing, and printing to screen, ignores error -func ProtoToFormattedString(m proto.Message) string { - res, _ := protojson.Marshal(m) - var prettyJSON bytes.Buffer - _ = json.Indent(&prettyJSON, res, "", " ") - return prettyJSON.String() -} - -type SafeSlice[T any] struct { - slice []T - mu sync.RWMutex -} - -func NewSafeSlice[T any]() *SafeSlice[T] { - return &SafeSlice[T]{ - slice: []T{}, - mu: sync.RWMutex{}, - } -} - -// Append adds a new element to the slice. -func (s *SafeSlice[T]) Append(value ...T) { - s.mu.Lock() - defer s.mu.Unlock() - s.slice = append(s.slice, value...) -} - -// Get retrieves an element at a specific index. -func (s *SafeSlice[T]) Get(index int) (T, bool) { - s.mu.RLock() - defer s.mu.RUnlock() - if index < 0 || index >= len(s.slice) { - var zero T // Create a zero value of type T - return zero, false - } - return s.slice[index], true -} - -func (s *SafeSlice[T]) Set(slice []T) { - s.mu.Lock() - defer s.mu.Unlock() - s.slice = slice -} - -func (s *SafeSlice[T]) SetAt(index int, value T) bool { - s.mu.Lock() - defer s.mu.Unlock() - if index < 0 || index >= len(s.slice) { - return false - } - s.slice[index] = value - return true -} - -func (s *SafeSlice[T]) Delete(index int) bool { - s.mu.Lock() - defer s.mu.Unlock() - if index < 0 || index >= len(s.slice) { - return false - } - s.slice = append(s.slice[:index], s.slice[index+1:]...) - return true -} - -func (s *SafeSlice[T]) Slice() []T { - s.mu.RLock() - defer s.mu.RUnlock() - newSlice := make([]T, len(s.slice)) - for i, v := range s.slice { //nolint:gosimple //ok - newSlice[i] = v - } - return newSlice -} - -func SliceToSafeSet[T comparable](s []T) *SafeSet[T] { - set := SafeSet[T]{} - for _, item := range s { - set.Add(item) - } - return &set -} - -type SafeSet[K comparable] struct { - m SafeMap[K, any] -} - -func NewSafeSet[K comparable]() *SafeSet[K] { - return &SafeSet[K]{ - m: *NewSafeMap[K, any](), - } -} - -func (s *SafeSet[K]) Add(key K) { - if _, ok := s.m.Get(key); !ok { - s.m.Set(key, nil) - } -} - -func (s *SafeSet[K]) Remove(key K) { - s.m.Delete(key) -} - -func (s *SafeSet[K]) Clear() { - s.m.Clear() -} - -func (s *SafeSet[K]) Contains(key K) bool { - _, ok := s.m.Get(key) - return ok -} - -func (s *SafeSet[K]) Len() int { - return s.m.Len() -} - -func (s *SafeSet[K]) Values() []K { - return s.m.Keys() -} - -// SafeMap is a generic map guarded by a RW mutex. -type SafeMap[K comparable, V any] struct { - mu sync.RWMutex - items map[K]V -} - -// NewSafeMap creates a new SafeMap. -func NewSafeMap[K comparable, V any]() *SafeMap[K, V] { - return &SafeMap[K, V]{ - items: map[K]V{}, - } -} - -func (m *SafeMap[K, V]) Override(s map[K]V) { - m.mu.Lock() - defer m.mu.Unlock() - m.items = s -} - -// Set sets a value in the map. -func (m *SafeMap[K, V]) Set(key K, value V) { - m.mu.Lock() - defer m.mu.Unlock() - m.items[key] = value -} - -// Get retrieves a value from the map. -func (m *SafeMap[K, V]) Get(key K) (V, bool) { - m.mu.RLock() - defer m.mu.RUnlock() - val, ok := m.items[key] - return val, ok -} - -// Delete removes a key from the map. -func (m *SafeMap[K, V]) Delete(key K) { - m.mu.Lock() - defer m.mu.Unlock() - delete(m.items, key) -} - -// Clear removes all entries from the map. -func (m *SafeMap[K, V]) Clear() { - m.mu.Lock() - defer m.mu.Unlock() - m.items = make(map[K]V) -} - -func (m *SafeMap[K, V]) Values() []V { - m.mu.RLock() - defer m.mu.RUnlock() - return GetMapValues(m.items) -} - -func (m *SafeMap[K, V]) Keys() []K { - m.mu.RLock() - defer m.mu.RUnlock() - return GetMapKeys(m.items) -} - -func (m *SafeMap[K, V]) Len() int { - m.mu.RLock() - defer m.mu.RUnlock() - return len(m.items) -} - -// Copy creates a new SafeMap with the same key-value pairs. -func (m *SafeMap[K, V]) Copy() *SafeMap[K, V] { - m.mu.RLock() - defer m.mu.RUnlock() - - newMap := NewSafeMap[K, V]() - for key, value := range m.items { - newMap.items[key] = value - } - return newMap -} - -// Sets the pointer of the current map to a copy of the map passed in. -func (m *SafeMap[K, V]) CopyMap(c *SafeMap[K, V]) *SafeMap[K, V] { - m.mu.Lock() - defer m.mu.Unlock() - m.items = c.Copy().items - return m -} - type SafeCounter struct { mu sync.Mutex c int } -func NewSafeCounter() *SafeCounter { - return &SafeCounter{} -} - -func (c *SafeCounter) Inc() { - c.mu.Lock() - defer c.mu.Unlock() - c.c++ -} - -func (c *SafeCounter) Dec() { - c.mu.Lock() - defer c.mu.Unlock() - c.c-- -} - -func (c *SafeCounter) Get() int { - c.mu.Lock() - defer c.mu.Unlock() - return c.c -} - -// SafeValue holds an arbitrary value with read and write protection. -// T is the type of the value. -type SafeValue[T any] struct { - value T - mutex sync.RWMutex -} - -// NewSafeValue creates a new SafeValue. -func NewSafeValue[T any](initialValue T) *SafeValue[T] { - return &SafeValue[T]{ - value: initialValue, - } -} - -// Get returns the value safely. -func (sv *SafeValue[T]) Get() T { - sv.mutex.RLock() - defer sv.mutex.RUnlock() - return sv.value -} - -// Set updates the value safely. -func (sv *SafeValue[T]) Set(newValue T) { - sv.mutex.Lock() - defer sv.mutex.Unlock() - sv.value = newValue -} - -func RoundToNearestBase(num float64, base float64) float64 { - return math.Round(num/base) * base -} - -func RoundToNearestDecimal(num float64, decimalPlaces int) float64 { - shift := math.Pow(10, float64(decimalPlaces)) - return math.Round(num*shift) / shift -} - -func ListToChannel[T any](l []T) chan T { - c := make(chan T, len(l)) - for _, item := range l { - c <- item - } - close(c) - return c -} - -func DistinctChan[T any, K comparable]( - keyGetter func(T any) K, bufSize int, -) (input chan T, output chan T) { - input = make(chan T, bufSize) - output = make(chan T, bufSize) - - go func() { - set := make(map[K]T) - for i := range input { - k := keyGetter(i) - if _, ok := set[k]; !ok { - set[k] = i - output <- i - delete(set, k) - } - } - close(output) - }() - return -} - -func GetFunctionName(i any) string { - nameStr, ok := i.(string) - if ok { - return nameStr - } - return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() -} - -func GetFieldName(structPtr any, fieldPtr any) string { - res := findStructField(reflect.ValueOf(structPtr).Elem(), reflect.ValueOf(fieldPtr)) - return res.Name -} - -// findStructField looks for a field in the given struct. -// The field being looked for should be a pointer to the actual struct field. -// If found, the field info will be returned. Otherwise, nil will be returned. -func findStructField(structValue reflect.Value, fieldValue reflect.Value) *reflect.StructField { - ptr := fieldValue.Pointer() - for i := structValue.NumField() - 1; i >= 0; i-- { - sf := structValue.Type().Field(i) - if ptr == structValue.Field(i).UnsafeAddr() { - // do additional type comparison because it's possible that the address of - // an embedded struct is the same as the first field of the embedded struct - if sf.Type == fieldValue.Elem().Type() { - return &sf - } - } - if sf.Anonymous { - // delve into anonymous struct to look for the field - fi := structValue.Field(i) - if sf.Type.Kind() == reflect.Ptr { - fi = fi.Elem() - } - if fi.Kind() == reflect.Struct { - if f := findStructField(fi, fieldValue); f != nil { - return f - } - } - } - } - return nil -} - -// returns json string or empty if fails -func ToJSONString(i any) string { - b, _ := json.Marshal(i) - return string(b) -} - type AsyncResult[T any] struct { result chan result[T] } @@ -1762,287 +330,6 @@ type Rollback struct { undos []func() error } -func NewRollback(undo func() error) *Rollback { - return &Rollback{undos: []func() error{undo}} -} - -func (r *Rollback) Add(undo func() error) { - r.undos = append(r.undos, undo) -} - -func (r Rollback) Rollback() error { - var err error - for i := len(r.undos) - 1; i >= 0; i-- { - if e := r.undos[i](); e != nil { - err = errors.Join(err, e) - } - } - return err //nolint:wrapcheck // fine for internal -} - -// SleepWithHealthCheck sleeps for the specified duration `d` and periodically calls `heartbeatFn` -// at every `tickRate` until `d` has elapsed. -func SleepWithHealthCheck(d time.Duration, tickRate time.Duration, heartbeatFn func()) { - heartbeatFn() // Call the heartbeat function immediately - // Timer to manage the total sleep duration - sleepTimer := time.NewTimer(d) - // Ticker to manage the heartbeat function calls - tickTicker := time.NewTicker(tickRate) - defer tickTicker.Stop() // Ensures the ticker is stopped to free resources - - go func() { - for { - select { - case <-tickTicker.C: // On every tick, call the heartbeat function - heartbeatFn() - case <-sleepTimer.C: // Once the total duration has passed, return - heartbeatFn() - return - } - } - }() - - <-sleepTimer.C // Wait for the sleep duration to pass before returning -} - -func OnTick(ctx context.Context, d time.Duration, f func()) *time.Ticker { - ticker := time.NewTicker(d) - go func() { - for { - select { - case <-ctx.Done(): - ticker.Stop() - return - case <-ticker.C: - f() - } - } - }() - return ticker -} - -// cancel context to end -func DoForever(ctx context.Context, f func()) { - _ = DoForeverE(ctx, - func() error { - f() - return nil - }, func(_ context.Context) error { - return nil - }) -} - -func DoForeverE(ctx context.Context, - f func() error, - done func(context.Context) error, -) error { - for { - select { - case <-ctx.Done(): - return done(ctx) - default: - return f() - } - } -} - -func DoAfterE(ctx context.Context, d time.Duration, - f func() error, - done func(context.Context) error, -) error { - for { - select { - case <-ctx.Done(): - return done(ctx) - case <-time.After(d): - return f() - } - } -} - -// cancel context to end -func DoOnDuration(ctx context.Context, d time.Duration, f func()) { - ticker := time.NewTicker(d) - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - f() - } - } -} - -// end early if err -func DoOnDurationE(ctx context.Context, d time.Duration, - f func() error, - done func(context.Context) error, -) error { - ticker := time.NewTicker(d) - for { - select { - case <-ctx.Done(): - return done(ctx) - case <-ticker.C: - err := f() - if err != nil { - return err - } - } - } -} - -type UniqueBufferedObjects[T any] struct { - bufferedObjects *BufferedObjects[T] - getID func(T) string - objectsInBuffer *SafeSet[string] -} - -func NewUniqueBufferedObjects[T any]( - flushSize int, - flushInterval time.Duration, - getID func(T) string, - flushHandler func([]T), -) *UniqueBufferedObjects[T] { - ubo := &UniqueBufferedObjects[T]{ - getID: getID, - objectsInBuffer: NewSafeSet[string](), - } - bo := NewBufferedObjects(flushSize, flushInterval, func(t []T) { - flushHandler(t) - for _, item := range t { - ubo.objectsInBuffer.Remove(getID(item)) - } - }) - ubo.bufferedObjects = bo - return ubo -} - -func (bi *UniqueBufferedObjects[T]) Add(object T) { - objID := bi.getID(object) - if bi.objectsInBuffer.Contains(objID) { - return - } - bi.objectsInBuffer.Add(objID) - bi.bufferedObjects.Add(object) -} - -func (bi *UniqueBufferedObjects[T]) Flush() { - bi.bufferedObjects.Flush() -} - -func (bi *UniqueBufferedObjects[T]) Stop() { - bi.bufferedObjects.Stop() -} - -func (bi *UniqueBufferedObjects[T]) WaitTillEmpty() { - bi.bufferedObjects.WaitTillEmpty() -} - -type BufferedObjects[T any] struct { - objects []T - lock sync.Mutex - flushSize int - flushInterval time.Duration - flushChan chan []T - stopChan chan struct{} - handlingFlush SafeValue[bool] -} - -func NewBufferedObjects[T any](flushSize int, flushInterval time.Duration, flushHandler func([]T)) *BufferedObjects[T] { - bi := &BufferedObjects[T]{ - flushSize: flushSize, - flushInterval: flushInterval, - flushChan: make(chan []T, 100), // [][]T buffer - stopChan: make(chan struct{}), - } - go bi.run(flushHandler) - return bi -} - -func (bi *BufferedObjects[T]) SetBufferSize(flushSize int) { - bi.flushChan = make(chan []T, flushSize) -} - -func (bi *BufferedObjects[T]) Add(object T) { - bi.lock.Lock() - defer bi.lock.Unlock() - - bi.objects = append(bi.objects, object) - if len(bi.objects) >= bi.flushSize { - bi.Flush() - } -} - -func (bi *BufferedObjects[T]) Flush() { - // Copy and reset buffer under lock to minimize lock time - toFlush := make([]T, len(bi.objects)) - copy(toFlush, bi.objects) - bi.objects = nil - - // Send to flush channel - bi.flushChan <- toFlush -} - -func (bi *BufferedObjects[T]) WaitTillEmpty() { - for { - bi.lock.Lock() - if len(bi.objects) == 0 && len(bi.flushChan) == 0 && !bi.handlingFlush.Get() { - bi.lock.Unlock() - return - } - bi.lock.Unlock() - time.Sleep(50 * time.Millisecond) - } -} - -func (bi *BufferedObjects[T]) run(flushHandler func([]T)) { - ticker := time.NewTicker(bi.flushInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - bi.lock.Lock() - if len(bi.objects) > 0 { - bi.Flush() - } - bi.lock.Unlock() - case objects := <-bi.flushChan: - bi.handlingFlush.Set(true) - flushHandler(objects) - bi.handlingFlush.Set(false) - case <-bi.stopChan: - return - } - } -} - -func (bi *BufferedObjects[T]) Stop() { - close(bi.stopChan) -} - -func ContainsAny(s string, subs ...string) bool { - for _, sub := range subs { - if strings.Contains(s, sub) { - return true - } - } - return false -} - -func RetryTest(t *testing.T, testFunc func(t *testing.T), numRetries int) { - t.Helper() // Mark this function as a helper - for i := 0; i < numRetries; i++ { - tt := &testing.T{} - testFunc(tt) - if !tt.Failed() { - return - } - } - t.Fail() // If we reach here, all retries failed -} - var ( // ErrCanceled is the error returned when the context is canceled. ErrCanceled = context.Canceled @@ -2057,68 +344,3 @@ type TimeoutOptions struct { ParentContext context.Context CatchPanic bool } - -// if you loop forever, make sure you have a way to break the loop -// see Test_DoWithTimeoutTimeoutLoop -func DoWithTimeout(fn func(ctx context.Context) error, timeout time.Duration, opts ...TimeoutOptions) error { - _, err := DoWithTimeoutData(func(ctx context.Context) (interface{}, error) { - return nil, fn(ctx) - }, timeout, opts...) - return err -} - -// if you loop forever, make sure you have a way to break the loop -// see Test_DoWithTimeoutTimeoutLoop -func DoWithTimeoutData[T any](fn func(ctx context.Context) (T, error), timeout time.Duration, opts ...TimeoutOptions) (T, error) { - type result struct { - res T - err error - } - options := TimeoutOptions{ - ParentContext: context.Background(), - } - for _, opt := range opts { - options = opt - } - if options.ParentContext == nil { - options.ParentContext = context.Background() - } - ctx, cancel := context.WithTimeout(options.ParentContext, timeout) - defer cancel() - - // create channel with buffer size 1 to avoid goroutine leak - resChan := make(chan result, 1) - panicChan := make(chan interface{}, 1) - go func() { - defer func() { - if p := recover(); p != nil { - // attach call stack to avoid missing in different goroutine - panicChan <- fmt.Sprintf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack()))) - } - }() - res, err := fn(ctx) - resChan <- result{res, err} - }() - - var emptyT T - - select { - case p := <-panicChan: - if options.CatchPanic { - return emptyT, fmt.Errorf("panic: %v", p) - } else { - panic(p) - } - case result := <-resChan: - return result.res, result.err - case <-ctx.Done(): - return emptyT, ctx.Err() //nolint:wrapcheck // no need to wrap - } -} - -// WithContext customizes a DoWithTimeout call with given ctx. -func WithContext(ctx context.Context) DoOption { - return func() context.Context { - return ctx - } -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 720c02af..f5538733 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -32,14 +32,6 @@ func (c ConstantsConfig) GetOllamaAPIURL() string { return getEnvOrDefault(ollamaAPIURL, "https://registry.ollama.ai") } -func (c ConstantsConfig) GetServiceMeshCoordServerURL() string { - return getEnvOrDefault(coordURL, "") -} - -func (c ConstantsConfig) GetVersion() string { - return getEnvOrDefault(version, "unknown") -} - func (c ConstantsConfig) GetDefaultClusterID() string { return getEnvOrDefault(clusterID, "devplane-brev-1") } @@ -53,10 +45,6 @@ func (c ConstantsConfig) GetDefaultWorkspaceTemplate() string { return getEnvOrDefault(defaultWorkspaceTemplate, "") } -func (c ConstantsConfig) GetSentryURL() string { - return getEnvOrDefault(sentryURL, "https://4f3dca96f17e4c7995588dda4a31b37f@o410659.ingest.sentry.io/6383105") -} - func (c ConstantsConfig) GetDebugHTTP() bool { return getEnvOrDefault(debugHTTP, "") != "" } @@ -75,26 +63,14 @@ type EnvVarConfig struct { ConstantsConfig } -func (c *ConstantsConfig) WithEnvVars() *EnvVarConfig { - return &EnvVarConfig{*c} -} - type FileConfig struct { EnvVarConfig } -func (c *EnvVarConfig) WithFileConfig() *FileConfig { - return &FileConfig{*c} -} - type FlagsConfig struct { FileConfig } -func (c *FileConfig) WithFlags() *FlagsConfig { - return &FlagsConfig{*c} -} - type InitConfig interface{} type AllConfig interface { diff --git a/pkg/entity/entity.go b/pkg/entity/entity.go index d4a5f302..daa871d5 100644 --- a/pkg/entity/entity.go +++ b/pkg/entity/entity.go @@ -224,14 +224,6 @@ type WorkspaceWithMeta struct { Workspace } -func WorkspacesWithMetaToWorkspaces(wms []WorkspaceWithMeta) []Workspace { - ws := []Workspace{} - for _, wm := range wms { - ws = append(ws, wm.Workspace) - } - return ws -} - type Application struct { ID string `json:"id"` Name string `json:"name"` @@ -498,25 +490,12 @@ func (w Workspace) GetHostIdentifier() WorkspaceLocalID { return w.createSimpleName() + "-host" } -func MakeIDSuffix(id string) string { - return id[len(id)-4:] -} - var ( whitespaceCharPattern = regexp.MustCompile(`\s+`) invalidCharPattern = regexp.MustCompile(`[^a-z0-9-]`) ) // lowercase, replace whitespace with '-', remove all [^a-z0-9-], trim '-' front and back -func CleanSubdomain(in string) string { - lowered := strings.ToLower(in) - whitespaceReplacedWithDash := whitespaceCharPattern.ReplaceAllString(lowered, "-") - removedInvalidChars := invalidCharPattern.ReplaceAllString(whitespaceReplacedWithDash, "") - removedPrefixSuffixDashses := strings.Trim(removedInvalidChars, "-") - - out := removedPrefixSuffixDashses - return out -} func (w Workspace) GetID() string { return w.ID diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 5c021d37..be340a52 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -4,7 +4,6 @@ import ( "fmt" "runtime" "strconv" - "strings" "time" stderrors "errors" @@ -129,11 +128,6 @@ type DeclineToLoginError struct{} func (d *DeclineToLoginError) Error() string { return "declined to login" } func (d *DeclineToLoginError) Directive() string { return "log in to run this command" } -func MakeErrorMessage(message string) string { - _, fn, line, _ := runtime.Caller(2) - return fmt.Sprintf("[error] %s:%d %s\n\t", fn, line, message) -} - var NetworkErrorMessage = "possible internet connection problem" type CredentialsFileNotFound struct{} @@ -268,13 +262,6 @@ func WrapAndTraceInMsg(err error) error { return pkgerrors.Wrap(err, makeErrorMessage("", 0)) // this wrap also adds a stacktrace which can be nice } -func WrapAndTrace2[T any](t T, err error) (T, error) { - if err == nil { - return t, nil - } - return t, pkgerrors.Wrap(err, makeErrorMessage("", 0)) -} - func makeErrorMessage(message string, skip int) string { skip += 2 pc, file, line, _ := runtime.Caller(skip) @@ -289,37 +276,4 @@ func makeErrorMessage(message string, skip int) string { return fmt.Sprintf("[error] %s\n%s\n%s:%s\n", message, funcName, file, lineNum) } -func HandleErrDefer(f func() error) { - _ = f() - // logger.L().Error("", zap.Error(err)) -} - -func ErrorContainsAny(err error, substrs ...string) bool { - for _, substr := range substrs { - if ErrorContains(err, substr) { - return true - } - } - return false -} - -func ErrorContains(err error, substr string) bool { - return err != nil && strings.Contains(err.Error(), substr) -} - -func IsErrorExcept(err error, errs ...error) bool { - return err != nil && !IsAny(err, errs...) -} - -func IsErrorExceptSubstr(err error, substr ...string) bool { - return err != nil && !ErrorContainsAny(err, substr...) -} - -func IsAny(err error, errs ...error) bool { - for _, e := range errs { - if Is(err, e) { - return true - } - } - return false -} +// logger.L().Error("", zap.Error(err)) diff --git a/pkg/featureflag/featureflag.go b/pkg/featureflag/featureflag.go index acdf6646..80ea0af9 100644 --- a/pkg/featureflag/featureflag.go +++ b/pkg/featureflag/featureflag.go @@ -25,21 +25,11 @@ func IsAdmin(userType entity.GlobalUserType) bool { } // use feature flag if not provided default true for admin but not others -func ServiceMeshSSH(userType entity.GlobalUserType) bool { - if viper.IsSet("feature.service_mesh_ssh") { - return viper.GetBool("feature.service_mesh_ssh") - } - return IsAdmin(userType) -} func DisableSSHProxyVersionCheck() bool { return viper.GetBool("feature.disable_ssh_proxy_version_check") } -func DisableErrorReporting() bool { - return viper.GetBool("feature.disable_error_reporting") -} - func ShowVersionOnRun() bool { return viper.GetBool("feature.show_version_on_run") } diff --git a/pkg/files/files.go b/pkg/files/files.go index 81ac7cc1..c0554772 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -7,7 +7,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" breverrors "github.com/brevdev/brev-cli/pkg/errors" "golang.org/x/text/encoding/charmap" @@ -34,38 +33,10 @@ const ( var AppFs = afero.NewOsFs() -func GetBrevDirectory() string { - return brevDirectory -} - -func GetActiveOrgFile() string { - return activeOrgFile -} - -func GetPersonalSettingsCache() string { - return personalSettingsCache -} - -func GetOrgCacheFile() string { - return orgCacheFile -} - -func GetWorkspaceCacheFile() string { - return workspaceCacheFile -} - -func GetKubeCertFileName() string { - return kubeCertFileName -} - func GetSSHPrivateKeyFileName() string { return sshPrivateKeyFileName } -func GetTailScaleOutFileName() string { - return tailscaleOutFileName -} - func GetNewBackupSSHConfigFileName() string { return fmt.Sprintf("%s.%s", backupSSHConfigFileNamePrefix, uuid.New()) } @@ -93,11 +64,6 @@ func GetActiveOrgsPath(home string) string { return fpath } -func GetPersonalSettingsCachePath(home string) string { - fpath := makeBrevFilePath(personalSettingsCache, home) - return fpath -} - func GetSSHPrivateKeyPath(home string) string { fpath := makeBrevFilePath(GetSSHPrivateKeyFileName(), home) return fpath @@ -132,11 +98,6 @@ func GetNewBackupSSHConfigFilePath(home string) string { return fp } -func GetTailScaleOutFilePath(home string) string { - fp := makeBrevFilePath(GetTailScaleOutFileName(), home) - return fp -} - // ReadJSON reads data from a file into the given struct // // Usage: @@ -232,29 +193,10 @@ func OverwriteJSON(fs afero.Fs, filepath string, v interface{}) error { // Usage // // OverwriteString("tmp/a/b/c.txt", "hi there") -func OverwriteString(fs afero.Fs, filepath string, data string) error { - f, err := touchFile(fs, filepath) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // clear - err = f.Truncate(0) - if err != nil { - return breverrors.WrapAndTrace(err) - } - // write - err = ioutil.WriteFile(filepath, []byte(data), os.ModePerm) - if err != nil { - return breverrors.WrapAndTrace(err) - } +// clear - if err = f.Close(); err != nil { - return breverrors.WrapAndTrace(err) - } - return breverrors.WrapAndTrace(err) -} +// write func WriteSSHPrivateKey(fs afero.Fs, data string, home string) error { pkPath := GetSSHPrivateKeyPath(home) @@ -306,57 +248,6 @@ func CatFile(filePath string) (string, error) { } } -func GetAliasesFromFile(file string) []string { - var result []string - - dirname, err := os.UserHomeDir() - if err != nil { - // if this doesn't work, just exit - return nil - } - lines, err := CatFile(dirname + "/" + file) - if err != nil { - // if this doesn't work, just exit - return nil - } - for _, line := range strings.Split(lines, "\n") { - if strings.HasPrefix(line, "alias ") { - result = append(result, line) - } - } - return result -} - -func GetAllAliases() []string { - var lines []string - lines = append(lines, GetAliasesFromFile(".zshrc")...) - lines = append(lines, GetAliasesFromFile(".bashrc")...) - lines = append(lines, GetAliasesFromFile(".zprofile")...) - lines = append(lines, GetAliasesFromFile(".bash_profile")...) - lines = append(lines, GetAliasesFromFile(".config/fish/config.fish")...) - - var output []string - for _, line := range lines { - output = append(output, fmt.Sprintf("echo '%s' >> /home/brev/.zshrc", line)) - output = append(output, fmt.Sprintf("echo '%s' >> /home/brev/.bashrc", line)) - } - - return output -} - -func GenerateSetupScript(lines []string) string { - introString := ` -#!/bin/bash - -set -euo pipefail +// if this doesn't work, just exit -##### This is your brev.dev setup script -##### Commit this file to the repo to make the environment reproducible -##### https://docs.brev.dev/howto/automatically-set-up - - ` - output := []string{introString, `##### Adding Aliases From Your Local Machine #####\n`, `(echo ""; echo "##### Adding Aliases From Your Local Machine #####"; echo "";)\n`} - output = append(output, lines...) - - return strings.Join(output, "\n") -} +// if this doesn't work, just exit diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index bc001af6..1ed5754b 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -1,14 +1,9 @@ package k8s import ( - "fmt" - "github.com/brevdev/brev-cli/pkg/entity" - "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" ) type K8sClient interface { @@ -33,36 +28,6 @@ type K8sClientConfig interface { GetCA() []byte } -func NewDefaultClient(config K8sClientConfig) (K8sClient, error) { - restConfig := dynamic.ConfigFor(&rest.Config{ - Host: config.GetHost(), - APIPath: "/api", - TLSClientConfig: rest.TLSClientConfig{ - CertData: config.GetCert(), - KeyData: config.GetKey(), - CAData: config.GetCA(), - }, - }) - - k8sClient, err := kubernetes.NewForConfig(restConfig) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - return &DefaultClient{ - k8sClientset: k8sClient, - k8sRestConfig: restConfig, - }, nil -} - -func (c DefaultClient) GetK8sClient() *kubernetes.Clientset { - return c.k8sClientset -} - -func (c DefaultClient) GetK8sRestConfig() *rest.Config { - return c.k8sRestConfig -} - type DefaultWorkspaceGroupClientMapper struct { workspaceK8sClientMap map[string]K8sClient workspaceK8sAPIURLMap map[string]string @@ -73,70 +38,9 @@ type K8sStore interface { GetCurrentUserKeys() (*entity.UserKeys, error) } -func NewDefaultWorkspaceGroupClientMapper(k8sStore K8sStore) (*DefaultWorkspaceGroupClientMapper, error) { - keys, err := k8sStore.GetCurrentUserKeys() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - wkc := make(map[string]K8sClient) - wka := make(map[string]string) - for _, wk := range keys.WorkspaceGroups { - rcc := RemoteK8sClientConfig{ - host: wk.APIURL, - cert: []byte(wk.Cert), - key: []byte(keys.PrivateKey), - ca: []byte(wk.CA), - } - wkc[wk.GroupID], err = NewDefaultClient(rcc) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - wka[wk.GroupID] = wk.APIURL - } - - return &DefaultWorkspaceGroupClientMapper{workspaceK8sClientMap: wkc, workspaceK8sAPIURLMap: wka, privateKey: keys.PrivateKey}, nil -} - -func (d DefaultWorkspaceGroupClientMapper) GetK8sClient(workspaceGroupID string) (K8sClient, error) { - client, doesExists := d.workspaceK8sClientMap[workspaceGroupID] - if !doesExists { - return nil, fmt.Errorf("client for workspace group does not exist [workspace group id=%s]", workspaceGroupID) - } - return client, nil -} - -func (d DefaultWorkspaceGroupClientMapper) GetK8sAPIURL(workspaceGroupID string) (string, error) { - url, doesExists := d.workspaceK8sAPIURLMap[workspaceGroupID] - if !doesExists { - return "", fmt.Errorf("k8s api url for workspace group does not exist [workspace group id=%s]", workspaceGroupID) - } - return url, nil -} - -func (d DefaultWorkspaceGroupClientMapper) GetPrivateKey() string { - return d.privateKey -} - type RemoteK8sClientConfig struct { host string cert []byte key []byte ca []byte } - -func (k RemoteK8sClientConfig) GetHost() string { - return k.host -} - -func (k RemoteK8sClientConfig) GetCert() []byte { - return k.cert -} - -func (k RemoteK8sClientConfig) GetKey() []byte { - return k.key -} - -func (k RemoteK8sClientConfig) GetCA() []byte { - return k.ca -} diff --git a/pkg/mergeshells/mergeshells.go b/pkg/mergeshells/mergeshells.go index ddc7c4d2..6de97c7c 100644 --- a/pkg/mergeshells/mergeshells.go +++ b/pkg/mergeshells/mergeshells.go @@ -17,7 +17,6 @@ import ( "github.com/brevdev/brev-cli/pkg/files" "github.com/brevdev/brev-cli/pkg/terminal" "github.com/tidwall/gjson" - "golang.org/x/text/encoding/charmap" ) //go:embed templates/* @@ -546,18 +545,6 @@ func goVersion(path string) *string { return nil } -func IsRuby(path string) bool { - paths := recursivelyFindFile([]string{"Gemfile.lock", "Gemfile"}, path) - - return len(paths) > 0 -} - -func IsPython(path string) bool { - paths := recursivelyFindFile([]string{"Gemfile.lock", "Gemfile"}, path) - - return len(paths) > 0 -} - func appendPath(a string, b string) string { if a == "." { return b @@ -613,20 +600,7 @@ func recursivelyFindFile(filenames []string, path string) []string { // read from gomod // read from json -func CatFile(filePath string) (string, error) { - gocmd := exec.Command("cat", filePath) // #nosec G204 - in, err := gocmd.Output() - if err != nil { - return "", breverrors.Wrap(err, "error reading file "+filePath) - } else { - d := charmap.CodePage850.NewDecoder() - out, err := d.Bytes(in) - if err != nil { - return "", breverrors.Wrap(err, "error reading file "+filePath) - } - return string(out), nil - } -} +// #nosec G204 func readGoMod(filePath string) (string, error) { contents, err := files.CatFile(filePath) diff --git a/pkg/portforward/portforward.go b/pkg/portforward/portforward.go index 20aa9c62..89c65ec4 100644 --- a/pkg/portforward/portforward.go +++ b/pkg/portforward/portforward.go @@ -1,20 +1,10 @@ package portforward import ( - "fmt" - "net/http" "net/url" - "os" - "os/signal" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - - "github.com/brevdev/brev-cli/pkg/entity" "github.com/brevdev/brev-cli/pkg/k8s" "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/transport/spdy" - - toolsportforward "k8s.io/client-go/tools/portforward" ) type PortForwardOptions struct { @@ -37,101 +27,10 @@ type PortForwarder interface { ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error } -func NewPortForwardOptions(workspaceGroupClientMapper k8s.WorkspaceGroupClientMapper, portforwarder PortForwarder) *PortForwardOptions { - p := &PortForwardOptions{ - PortForwarder: portforwarder, - WorkspaceGroupClientMapper: workspaceGroupClientMapper, - } - - p.Address = []string{"localhost"} - p.StopChannel = make(chan struct{}, 1) - p.ReadyChannel = make(chan struct{}) - - return p -} - -func (o *PortForwardOptions) WithWorkspace(workspace entity.WorkspaceWithMeta) (*PortForwardOptions, error) { - o.Namespace = workspace.GetNamespaceName() - o.PodName = workspace.GetPodName() - - k8sAPIURL, err := o.WorkspaceGroupClientMapper.GetK8sAPIURL(workspace.WorkspaceGroupID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - o.K8sAPIURL = k8sAPIURL - - k8sClient, err := o.WorkspaceGroupClientMapper.GetK8sClient(workspace.WorkspaceGroupID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - o.K8sClient = k8sClient - - if o.PodName == "" { - return nil, fmt.Errorf("unable to forward port because pod is not found-- workspace may not be running") - } - - return o, nil -} - // TODO with stopchannel -func (o *PortForwardOptions) WithPort(port string) *PortForwardOptions { - o.Ports = []string{port} - return o -} - -func (o PortForwardOptions) RunPortforward() error { - // cmd := portforward.NewCmdPortForward(tf, streams) // This command is useful to have around to go to def of kubectl cmd - - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) - defer signal.Stop(signals) - - go func() { - <-signals - if o.StopChannel != nil { - close(o.StopChannel) - } - }() - - urlStr := fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/portforward", o.K8sAPIURL, o.Namespace, o.PodName) - - url, err := url.Parse(urlStr) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = o.PortForwarder.ForwardPorts("POST", url, o) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} +// cmd := portforward.NewCmdPortForward(tf, streams) // This command is useful to have around to go to def of kubectl cmd type DefaultPortForwarder struct { genericclioptions.IOStreams } - -func NewDefaultPortForwarder() *DefaultPortForwarder { - return &DefaultPortForwarder{ - IOStreams: genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}, - } -} - -func (f *DefaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error { - transport, upgrader, err := spdy.RoundTripperFor(opts.K8sClient.GetK8sRestConfig()) - if err != nil { - return breverrors.WrapAndTrace(err) - } - var fw *toolsportforward.PortForwarder - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url) - fw, err = toolsportforward.NewOnAddresses(dialer, opts.Address, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = fw.ForwardPorts() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/prefixid/prefixid.go b/pkg/prefixid/prefixid.go index 1801424b..c83b1b2c 100644 --- a/pkg/prefixid/prefixid.go +++ b/pkg/prefixid/prefixid.go @@ -1,16 +1,7 @@ package prefixid -import ( - "fmt" - - "github.com/segmentio/ksuid" -) - type PrefixID string const prefixSep = "-" // New generates a unique ID that can be used as an identifier for an entity. -func New(prefix string) PrefixID { - return PrefixID(fmt.Sprintf("%s%s%s", prefix, prefixSep, ksuid.New().String())) -} diff --git a/pkg/setupworkspace/setupworkspace.go b/pkg/setupworkspace/setupworkspace.go index 0403186a..a06340dc 100644 --- a/pkg/setupworkspace/setupworkspace.go +++ b/pkg/setupworkspace/setupworkspace.go @@ -311,14 +311,6 @@ func SendLogToFiles(cmd *exec.Cmd, filePaths ...string) (func(), error) { }, nil } -func (w WorkspaceIniter) ChownFileToUser(file *os.File) error { - err := ChownFileToUser(file, w.User) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - func (w WorkspaceIniter) BuildHomePath(suffix ...string) string { return filepath.Join(append([]string{w.User.HomeDir}, suffix...)...) } @@ -1050,14 +1042,6 @@ tar --no-same-owner -xzv --strip-components=1 -C ~/.vscode-server/bin/"${commit_ return nil } -func (w WorkspaceIniter) RunApplicationScripts(scripts []string) error { - for _, s := range scripts { - cmd := CmdStringBuilder(s) - _ = cmd.Run() - } - return nil -} - func allRepoFormats(repo string) []string { repos := []string{ repo, @@ -1332,35 +1316,7 @@ type CommandGroup struct { User *user.User } -func NewCommandGroup() *CommandGroup { - return &CommandGroup{} -} - -func (c *CommandGroup) WithUser(user *user.User) *CommandGroup { - c.User = user - return c -} - -func (c *CommandGroup) AddCmd(cmd *exec.Cmd) { - c.Cmds = append(c.Cmds, cmd) -} - -func (c *CommandGroup) Run() error { - // TODO batch - for _, cmd := range c.Cmds { - if c.User != nil && (cmd.SysProcAttr == nil || cmd.SysProcAttr.Credential == nil) { - err := CmdAsUser(cmd, c.User) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - err := cmd.Run() - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} +// TODO batch func CmdAsUser(cmd *exec.Cmd, user *user.User) error { uid, err := strconv.ParseInt(user.Uid, 10, 32) diff --git a/pkg/setupworkspace/validate.go b/pkg/setupworkspace/validate.go index aac5ad0b..9b0f388e 100644 --- a/pkg/setupworkspace/validate.go +++ b/pkg/setupworkspace/validate.go @@ -1,7 +1 @@ package setupworkspace - -import "github.com/brevdev/brev-cli/pkg/store" - -func ValidateSetup(_ store.SetupParamsV0) error { - return nil -} diff --git a/pkg/ssh/sshconfigurer.go b/pkg/ssh/sshconfigurer.go index 73374d0a..d93931bb 100644 --- a/pkg/ssh/sshconfigurer.go +++ b/pkg/ssh/sshconfigurer.go @@ -267,11 +267,6 @@ type SSHConfigEntryV2 struct { Port int } -func MapContainsKey[K comparable, V any](m map[K]V, key K) bool { - _, ok := m[key] - return ok -} - func tmplAndValToString(tmpl *template.Template, val interface{}) (string, error) { buf := &bytes.Buffer{} err := tmpl.Execute(buf, val) @@ -526,82 +521,9 @@ type SSHConfigurerServiceMesh struct { store SSHConfigurerV2Store } -var _ Config = SSHConfigurerServiceMesh{} +// Deprecated: var _ Config = SSHConfigurerServiceMesh{} -func NewSSHConfigurerServiceMesh(store SSHConfigurerV2Store) *SSHConfigurerServiceMesh { - return &SSHConfigurerServiceMesh{ - store: store, - } -} - -func (s SSHConfigurerServiceMesh) Update(workspaces []entity.Workspace) error { - newConfig, err := s.CreateNewSSHConfig(workspaces) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = s.store.WriteBrevSSHConfig(newConfig) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = s.EnsureConfigHasInclude() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func (s SSHConfigurerServiceMesh) EnsureConfigHasInclude() error { - // openssh-7.3 - - brevConfigPath, err := s.store.GetBrevSSHConfigPath() - if err != nil { - return breverrors.WrapAndTrace(err) - } - conf, err := s.store.GetUserSSHConfig() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !doesUserSSHConfigIncludeBrevConfig(conf, brevConfigPath) { - newConf, err := AddIncludeToUserConfig(conf, brevConfigPath) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = s.store.WriteUserSSHConfig(newConf) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - return nil -} - -func (s SSHConfigurerServiceMesh) CreateNewSSHConfig(workspaces []entity.Workspace) (string, error) { - log.Print("creating new service mesh ssh config") - - configPath, err := s.store.GetUserSSHConfigPath() - if err != nil { - return "", breverrors.WrapAndTrace(err) - } - - sshConfig := fmt.Sprintf("# included in %s\n", configPath) - for _, w := range workspaces { - pk, err := s.store.GetPrivateKeyPath() - if err != nil { - return "", breverrors.WrapAndTrace(err) - } - entry, err := makeSSHConfigServiceMeshEntry(string(w.GetLocalIdentifier()), w.GetNodeIdentifierForVPN(), pk) - if err != nil { - return "", breverrors.WrapAndTrace(err) - } - - sshConfig += entry - } - - return sshConfig, nil -} +// openssh-7.3 const SSHConfigEntryTemplateServiceMesh = `Host {{ .Alias }} HostName {{ .Host }} @@ -620,28 +542,6 @@ type SSHConfigEntryServiceMesh struct { Port string } -func makeSSHConfigServiceMeshEntry(alias string, host string, privateKeyPath string) (string, error) { - entry := SSHConfigEntryServiceMesh{ - Alias: alias, - Host: host, - IdentityFile: privateKeyPath, - User: "brev", - Port: "22", - } - - tmpl, err := template.New(host).Parse(SSHConfigEntryTemplateServiceMesh) - if err != nil { - return "", breverrors.WrapAndTrace(err) - } - buf := &bytes.Buffer{} - err = tmpl.Execute(buf, entry) - if err != nil { - return "", breverrors.WrapAndTrace(err) - } - - return buf.String(), nil -} - type SSHConfigurerJetBrains struct { store SSHConfigurerV2Store } diff --git a/pkg/ssh/sshconfigurer_test.go b/pkg/ssh/sshconfigurer_test.go index 67d11b06..d164369a 100644 --- a/pkg/ssh/sshconfigurer_test.go +++ b/pkg/ssh/sshconfigurer_test.go @@ -40,10 +40,6 @@ var somePlainWorkspaces = []entity.Workspace{ type DummyStore struct{} -func (d DummyStore) GetWorkspaces() ([]entity.Workspace, error) { - return []entity.Workspace{}, nil -} - type DummySSHConfigurerV2Store struct{} func (d DummySSHConfigurerV2Store) GetWSLHostUserSSHConfigPath() (string, error) { diff --git a/pkg/store/authtoken.go b/pkg/store/authtoken.go index 6221487b..2b7e1ede 100644 --- a/pkg/store/authtoken.go +++ b/pkg/store/authtoken.go @@ -18,10 +18,6 @@ const ( brevDirectory = ".brev" ) -func GetBrevDirectory() string { - return brevDirectory -} - func (f FileStore) SaveAuthTokens(token entity.AuthTokens) error { if token.AccessToken == "" { return fmt.Errorf("access token is empty") diff --git a/pkg/store/http.go b/pkg/store/http.go index a0a8fc58..0e26e85a 100644 --- a/pkg/store/http.go +++ b/pkg/store/http.go @@ -36,14 +36,6 @@ type OllamaHTTPClient struct { restyClient *resty.Client } -func NewOllamaHTTPClient(ollamaAPIURL string) *OllamaHTTPClient { - restyClient := resty.New().SetBaseURL(ollamaAPIURL) - - return &OllamaHTTPClient{ - restyClient: restyClient, - } -} - func NewNoAuthHTTPClient(brevAPIURL string) *NoAuthHTTPClient { restyClient := NewRestyClient(brevAPIURL) return &NoAuthHTTPClient{restyClient} diff --git a/pkg/terminal/display.go b/pkg/terminal/display.go index e127caa2..398802eb 100644 --- a/pkg/terminal/display.go +++ b/pkg/terminal/display.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "os/exec" breverrors "github.com/brevdev/brev-cli/pkg/errors" "github.com/manifoldco/promptui" @@ -98,43 +97,6 @@ func DisplayBrevLogo(t *Terminal) { //nolint:funlen // logo t.Vprint(" ###@@@@@##") } -func DisplayVSCodeInstructions(t *Terminal) { - t.Print("Run the following steps") - t.Print("") - t.Print("\t1) Install the following VSCode extension: " + t.Yellow("ms-vscode-remote.remote-ssh") + ".") - t.Print("\t2) In VS Code, open the Command Palette and type in " + t.Yellow("Remote-SSH: Connect to Host...") + "to begin.") -} - -func DisplayGatewayAlreadyInstalledInstructions(t *Terminal) { - t.Print(t.Yellow("You already have JetBrains Gateway installed!")) - t.Print("Run " + t.Green("brev jetbrains") + " and then open Gateway to begin.") -} - -func DisplayToolboxInstalledInstructions(t *Terminal) { - t.Print(t.Yellow("You already have JetBrains Toolbox installed!")) - t.Print("") - t.Print("\t1) Install JetBrains Gateway from Toolbox.") - t.Print("\t2) Run " + t.Green("brev jetbrains") + " and then open Gateway to begin.") -} - -func InstallVSCodeExtension(t *Terminal) { - cmdd := exec.Command("which code") - output, _ := cmdd.Output() - t.Vprintf("%b", output) - _, err := cmdd.Output() - - if err != nil { - t.Vprintf(t.Yellow("Please install the following VS Code extension: ms-vscode-remote.remote-ssh\n")) - } else { - install := exec.Command("code --install-extension ms-vscode-remote.remote-ssh\n") - _, err := install.Output() - if err != nil { - t.Vprintf("Please install the following VS Code extension: ms-vscode-remote.remote-ssh\n") - } - - } -} - type PromptContent struct { ErrorMsg string Label string diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index a492fb56..9d3a1a5f 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -50,22 +50,10 @@ func New() (t *Terminal) { } } -func (t *Terminal) SetVerbose(verbose bool) { - if verbose { - t.out = os.Stdout - } else { - t.out = silentWriter{} - } -} - func (t *Terminal) Print(a string) { fmt.Fprintln(t.out, a) } -func (t *Terminal) Printf(format string, a ...interface{}) { - fmt.Fprintf(t.out, format, a...) -} - func (t *Terminal) Vprint(a string) { fmt.Fprintln(t.verbose, a) } @@ -92,22 +80,8 @@ func (t *Terminal) Errprint(err error, a string) { } } -func (t *Terminal) Errprintf(err error, format string, a ...interface{}) { - t.Eprint(t.Red("Error: " + err.Error())) - if a != nil { - t.Eprint(t.Red(format, a)) - } - if brevErr, ok := err.(breverrors.BrevError); ok { - t.Eprint(t.Red(brevErr.Directive())) - } -} - type silentWriter struct{} -func (w silentWriter) Write(_ []byte) (n int, err error) { - return 0, nil -} - func (t *Terminal) NewSpinner() *spinner.Spinner { spinner := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) err := spinner.Color("cyan", "bold") @@ -118,39 +92,3 @@ func (t *Terminal) NewSpinner() *spinner.Spinner { return spinner } - -func (t *Terminal) NewProgressBar(description string, onComplete func()) *ProgressBar { - bar := progressbar.NewOptions(ProgressBarMax, - progressbar.OptionOnCompletion(onComplete), - progressbar.OptionEnableColorCodes(true), - progressbar.OptionShowBytes(false), - progressbar.OptionSetWidth(15), - progressbar.OptionSetDescription(description), - progressbar.OptionSetTheme(progressbar.Theme{ - Saucer: "[green]=[reset]", - SaucerHead: "[green]🤙[reset]", - SaucerPadding: " ", - BarStart: "[", - BarEnd: "]", - })) - - return &ProgressBar{ - Bar: bar, - CurrPercentage: 0, - } -} - -func (bar *ProgressBar) AdvanceTo(percentage int) { - for bar.CurrPercentage < percentage && bar.CurrPercentage <= 100 { - bar.CurrPercentage++ - err := bar.Bar.Add(1) - if err != nil { - panic(err) - } - time.Sleep(5 * time.Millisecond) - } -} - -func (bar *ProgressBar) Describe(text string) { - bar.Bar.Describe(text) -} diff --git a/pkg/uri/uri.go b/pkg/uri/uri.go index af4de490..77757147 100644 --- a/pkg/uri/uri.go +++ b/pkg/uri/uri.go @@ -12,13 +12,6 @@ type ( URL string ) -func NewHostFromString(host string) (Host, error) { - if strings.HasPrefix(host, "http") { - return "", fmt.Errorf("host can not start with 'http'") - } - return Host(host), nil -} - func (h Host) AddPrefix(prefix string) Host { return Host(fmt.Sprintf("%s%s", prefix, h)) } diff --git a/pkg/workspacemanagerv2/volumes.go b/pkg/workspacemanagerv2/volumes.go index dbfe9e22..14ce1294 100644 --- a/pkg/workspacemanagerv2/volumes.go +++ b/pkg/workspacemanagerv2/volumes.go @@ -103,39 +103,7 @@ type SymLinkVolume struct { MountToPath string } -var _ Volume = SymLinkVolume{} - -func NewSymLinkVolume(fromSymLinkPath string, localVolumePath string, mountToPath string) *SymLinkVolume { - return &SymLinkVolume{ - FromSymLinkPath: fromSymLinkPath, - LocalVolumePath: localVolumePath, - MountToPath: mountToPath, - } -} - -func (s SymLinkVolume) GetIdentifier() string { - return s.LocalVolumePath -} - -func (s SymLinkVolume) GetMountToPath() string { - return s.MountToPath -} - -func (s SymLinkVolume) Setup(_ context.Context) error { - err := os.Symlink(s.FromSymLinkPath, s.LocalVolumePath) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (s SymLinkVolume) Teardown(_ context.Context) error { - err := os.RemoveAll(s.LocalVolumePath) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} +// Deprecated: var _ Volume = SymLinkVolume{} type DynamicVolume struct { FromMountPathPrefix string @@ -143,38 +111,4 @@ type DynamicVolume struct { FileMap map[string]func(string) } -// can be push or polled based? - -var _ Volume = DynamicVolume{} - -func NewDynamicVolume(path string, fileMap map[string]func(string)) *DynamicVolume { - return &DynamicVolume{ToMountPath: path, FileMap: fileMap} -} - -func (s DynamicVolume) WithPathPrefix(prefix string) DynamicVolume { - s.FromMountPathPrefix = prefix - return s -} - -func (s DynamicVolume) GetIdentifier() string { - return "" -} - -func (s DynamicVolume) GetMountToPath() string { - return "" -} - -func (s DynamicVolume) GetMountFromPath() string { - return s.FromMountPathPrefix -} - -func (s DynamicVolume) Setup(_ context.Context) error { - for f, d := range s.FileMap { - d(filepath.Join(s.GetMountFromPath(), f)) - } - return nil -} - -func (s DynamicVolume) Teardown(_ context.Context) error { - return nil -} +// Deprecated: var _ Volume = DynamicVolume{}