From 738b0b027df75d1f1b0e8eb59e51bd2f73617bf2 Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Thu, 16 Feb 2023 11:30:10 -0700 Subject: [PATCH 1/7] Use build constraints for platform-specific GetRDPath Signed-off-by: Adam Pickering --- src/go/rdctl/cmd/start.go | 22 +------- .../rdctl/pkg/autostart/autostart_windows.go | 2 +- src/go/rdctl/pkg/utils/utils.go | 56 ------------------- src/go/rdctl/pkg/utils/utils_darwin.go | 21 +++++++ src/go/rdctl/pkg/utils/utils_linux.go | 18 ++++++ src/go/rdctl/pkg/utils/utils_windows.go | 48 ++++++++++++++++ 6 files changed, 89 insertions(+), 78 deletions(-) create mode 100644 src/go/rdctl/pkg/utils/utils_darwin.go create mode 100644 src/go/rdctl/pkg/utils/utils_linux.go create mode 100644 src/go/rdctl/pkg/utils/utils_windows.go diff --git a/src/go/rdctl/cmd/start.go b/src/go/rdctl/cmd/start.go index e09d04c7291..87aabef75d5 100644 --- a/src/go/rdctl/cmd/start.go +++ b/src/go/rdctl/cmd/start.go @@ -20,7 +20,6 @@ import ( "fmt" "os" "os/exec" - "path/filepath" "runtime" "strings" @@ -84,20 +83,11 @@ func doStartCommand(cmd *cobra.Command) error { return err } if applicationPath == "" { - pathLookupFuncs := map[string]func(rdctlPath string) string{ - "windows": utils.GetWindowsRDPath, - "linux": getLinuxRDPath, - "darwin": utils.GetDarwinRDPath, - } - getPathFunc, ok := pathLookupFuncs[runtime.GOOS] - if !ok { - return fmt.Errorf("don't know how to find the path to Rancher Desktop on OS %s", runtime.GOOS) - } rdctlPath, err := os.Executable() if err != nil { rdctlPath = "" } - applicationPath = getPathFunc(rdctlPath) + applicationPath = utils.GetRDPath(rdctlPath) if applicationPath == "" { return fmt.Errorf("could not locate main Rancher Desktop executable; please retry with the --path option") } @@ -128,13 +118,3 @@ func launchApp(applicationPath string, commandLineArgs []string) error { cmd.Stderr = os.Stderr return cmd.Start() } -func getLinuxRDPath(rdctlPath string) string { - if rdctlPath != "" { - normalParentPath := utils.MoveToParent(rdctlPath, 5) - candidatePath := utils.CheckExistence(filepath.Join(normalParentPath, "rancher-desktop"), 0o111) - if candidatePath != "" { - return candidatePath - } - } - return utils.CheckExistence("/opt/rancher-desktop/rancher-desktop", 0o111) -} diff --git a/src/go/rdctl/pkg/autostart/autostart_windows.go b/src/go/rdctl/pkg/autostart/autostart_windows.go index 6c1e2a4afa6..d8c99408c85 100644 --- a/src/go/rdctl/pkg/autostart/autostart_windows.go +++ b/src/go/rdctl/pkg/autostart/autostart_windows.go @@ -30,7 +30,7 @@ func EnsureAutostart(autostartDesired bool) error { if err != nil { return fmt.Errorf("failed to get path to rdctl: %w", err) } - rancherDesktopPath := utils.GetWindowsRDPath(rdctlPath) + rancherDesktopPath := utils.GetRDPath(rdctlPath) if rancherDesktopPath == "" { return errors.New("failed to get path to Rancher Desktop.exe") } diff --git a/src/go/rdctl/pkg/utils/utils.go b/src/go/rdctl/pkg/utils/utils.go index 0e9cb01b263..31d7d6a507f 100644 --- a/src/go/rdctl/pkg/utils/utils.go +++ b/src/go/rdctl/pkg/utils/utils.go @@ -4,8 +4,6 @@ import ( "io/fs" "os" "path/filepath" - - "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/directories" ) // Get the parent (or grandparent, or great-grandparent...) directory of fullPath. @@ -35,57 +33,3 @@ func CheckExistence(candidatePath string, modeBits fs.FileMode) string { } return candidatePath } - -// Returns the absolute path to the Rancher Desktop executable. -// Returns an empty string if the executable was not found. -func GetWindowsRDPath(rdctlPath string) string { - if rdctlPath != "" { - normalParentPath := MoveToParent(rdctlPath, 5) - candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) - if candidatePath != "" { - return candidatePath - } - } - homedir, err := os.UserHomeDir() - if err != nil { - homedir = "" - } - dataPaths := []string{} - // %LOCALAPPDATA% - dir, err := directories.GetLocalAppDataDirectory() - if err == nil { - dataPaths = append(dataPaths, dir) - } - // %APPDATA% - dir, err = directories.GetRoamingAppDataDirectory() - if err == nil { - dataPaths = append(dataPaths, dir) - } - // Add these two paths if the above two fail to find where the program was installed - dataPaths = append( - dataPaths, - filepath.Join(homedir, "AppData", "Local"), - filepath.Join(homedir, "AppData", "Roaming"), - ) - for _, dataDir := range dataPaths { - candidatePath := CheckExistence(filepath.Join(dataDir, "Programs", "Rancher Desktop", "Rancher Desktop.exe"), 0) - if candidatePath != "" { - return candidatePath - } - } - return "" -} - -func GetDarwinRDPath(rdctlPath string) string { - if rdctlPath != "" { - // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin - // and want to move to the "R D.app" part - RDAppParentPath := MoveToParent(rdctlPath, 6) - if CheckExistence(filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop"), 0o111) != "" { - return RDAppParentPath - } - } - // This fallback is mostly for running `npm run dev` and using the installed app because there is no app - // that rdctl would launch directly in dev mode. - return CheckExistence(filepath.Join("/Applications", "Rancher Desktop.app"), 0) -} diff --git a/src/go/rdctl/pkg/utils/utils_darwin.go b/src/go/rdctl/pkg/utils/utils_darwin.go new file mode 100644 index 00000000000..dcb20f69691 --- /dev/null +++ b/src/go/rdctl/pkg/utils/utils_darwin.go @@ -0,0 +1,21 @@ +package utils + +import ( + "path/filepath" +) + +// Returns the absolute path to the Rancher Desktop executable. +// Returns an empty string if the executable was not found. +func GetRDPath(rdctlPath string) string { + if rdctlPath != "" { + // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin + // and want to move to the "R D.app" part + RDAppParentPath := MoveToParent(rdctlPath, 6) + if CheckExistence(filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop"), 0o111) != "" { + return RDAppParentPath + } + } + // This fallback is mostly for running `npm run dev` and using the installed app because there is no app + // that rdctl would launch directly in dev mode. + return CheckExistence(filepath.Join("/Applications", "Rancher Desktop.app"), 0) +} diff --git a/src/go/rdctl/pkg/utils/utils_linux.go b/src/go/rdctl/pkg/utils/utils_linux.go new file mode 100644 index 00000000000..4aa501258cb --- /dev/null +++ b/src/go/rdctl/pkg/utils/utils_linux.go @@ -0,0 +1,18 @@ +package utils + +import ( + "path/filepath" +) + +// Returns the absolute path to the Rancher Desktop executable. +// Returns an empty string if the executable was not found. +func GetRDPath(rdctlPath string) string { + if rdctlPath != "" { + normalParentPath := MoveToParent(rdctlPath, 5) + candidatePath := CheckExistence(filepath.Join(normalParentPath, "rancher-desktop"), 0o111) + if candidatePath != "" { + return candidatePath + } + } + return CheckExistence("/opt/rancher-desktop/rancher-desktop", 0o111) +} diff --git a/src/go/rdctl/pkg/utils/utils_windows.go b/src/go/rdctl/pkg/utils/utils_windows.go new file mode 100644 index 00000000000..8e361a9b388 --- /dev/null +++ b/src/go/rdctl/pkg/utils/utils_windows.go @@ -0,0 +1,48 @@ +package utils + +import ( + "os" + "path/filepath" + + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/directories" +) + +// Returns the absolute path to the Rancher Desktop executable. +// Returns an empty string if the executable was not found. +func GetRDPath(rdctlPath string) string { + if rdctlPath != "" { + normalParentPath := MoveToParent(rdctlPath, 5) + candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) + if candidatePath != "" { + return candidatePath + } + } + homedir, err := os.UserHomeDir() + if err != nil { + homedir = "" + } + dataPaths := []string{} + // %LOCALAPPDATA% + dir, err := directories.GetLocalAppDataDirectory() + if err == nil { + dataPaths = append(dataPaths, dir) + } + // %APPDATA% + dir, err = directories.GetRoamingAppDataDirectory() + if err == nil { + dataPaths = append(dataPaths, dir) + } + // Add these two paths if the above two fail to find where the program was installed + dataPaths = append( + dataPaths, + filepath.Join(homedir, "AppData", "Local"), + filepath.Join(homedir, "AppData", "Roaming"), + ) + for _, dataDir := range dataPaths { + candidatePath := CheckExistence(filepath.Join(dataDir, "Programs", "Rancher Desktop", "Rancher Desktop.exe"), 0) + if candidatePath != "" { + return candidatePath + } + } + return "" +} From b2b1773031f365fb9fbe7998859c17eb9237dc80 Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Thu, 16 Feb 2023 11:37:37 -0700 Subject: [PATCH 2/7] Rename MoveToParent to getParentDir Signed-off-by: Adam Pickering --- src/go/rdctl/pkg/utils/utils.go | 2 +- src/go/rdctl/pkg/utils/utils_darwin.go | 2 +- src/go/rdctl/pkg/utils/utils_linux.go | 2 +- src/go/rdctl/pkg/utils/utils_windows.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/go/rdctl/pkg/utils/utils.go b/src/go/rdctl/pkg/utils/utils.go index 31d7d6a507f..cf77b39200e 100644 --- a/src/go/rdctl/pkg/utils/utils.go +++ b/src/go/rdctl/pkg/utils/utils.go @@ -8,7 +8,7 @@ import ( // Get the parent (or grandparent, or great-grandparent...) directory of fullPath. // numberTimes is the number of steps to ascend in the directory hierarchy. -func MoveToParent(fullPath string, numberTimes int) string { +func getParentDir(fullPath string, numberTimes int) string { fullPath = filepath.Clean(fullPath) for ; numberTimes > 0; numberTimes-- { fullPath = filepath.Dir(fullPath) diff --git a/src/go/rdctl/pkg/utils/utils_darwin.go b/src/go/rdctl/pkg/utils/utils_darwin.go index dcb20f69691..97ce9be0087 100644 --- a/src/go/rdctl/pkg/utils/utils_darwin.go +++ b/src/go/rdctl/pkg/utils/utils_darwin.go @@ -10,7 +10,7 @@ func GetRDPath(rdctlPath string) string { if rdctlPath != "" { // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin // and want to move to the "R D.app" part - RDAppParentPath := MoveToParent(rdctlPath, 6) + RDAppParentPath := getParentDir(rdctlPath, 6) if CheckExistence(filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop"), 0o111) != "" { return RDAppParentPath } diff --git a/src/go/rdctl/pkg/utils/utils_linux.go b/src/go/rdctl/pkg/utils/utils_linux.go index 4aa501258cb..92f05cd9b81 100644 --- a/src/go/rdctl/pkg/utils/utils_linux.go +++ b/src/go/rdctl/pkg/utils/utils_linux.go @@ -8,7 +8,7 @@ import ( // Returns an empty string if the executable was not found. func GetRDPath(rdctlPath string) string { if rdctlPath != "" { - normalParentPath := MoveToParent(rdctlPath, 5) + normalParentPath := getParentDir(rdctlPath, 5) candidatePath := CheckExistence(filepath.Join(normalParentPath, "rancher-desktop"), 0o111) if candidatePath != "" { return candidatePath diff --git a/src/go/rdctl/pkg/utils/utils_windows.go b/src/go/rdctl/pkg/utils/utils_windows.go index 8e361a9b388..4650152c50c 100644 --- a/src/go/rdctl/pkg/utils/utils_windows.go +++ b/src/go/rdctl/pkg/utils/utils_windows.go @@ -11,7 +11,7 @@ import ( // Returns an empty string if the executable was not found. func GetRDPath(rdctlPath string) string { if rdctlPath != "" { - normalParentPath := MoveToParent(rdctlPath, 5) + normalParentPath := getParentDir(rdctlPath, 5) candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) if candidatePath != "" { return candidatePath From 03c5db70ca094c54037c9de84b94b77036831423 Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Thu, 16 Feb 2023 15:25:09 -0700 Subject: [PATCH 3/7] Move getting path to rdctl into GetRDPath Signed-off-by: Adam Pickering --- src/go/rdctl/cmd/start.go | 6 +----- src/go/rdctl/pkg/autostart/autostart_windows.go | 7 +------ src/go/rdctl/pkg/utils/utils_darwin.go | 6 ++++-- src/go/rdctl/pkg/utils/utils_linux.go | 6 ++++-- src/go/rdctl/pkg/utils/utils_windows.go | 5 +++-- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/go/rdctl/cmd/start.go b/src/go/rdctl/cmd/start.go index 87aabef75d5..d22ea260007 100644 --- a/src/go/rdctl/cmd/start.go +++ b/src/go/rdctl/cmd/start.go @@ -83,11 +83,7 @@ func doStartCommand(cmd *cobra.Command) error { return err } if applicationPath == "" { - rdctlPath, err := os.Executable() - if err != nil { - rdctlPath = "" - } - applicationPath = utils.GetRDPath(rdctlPath) + applicationPath = utils.GetRDPath() if applicationPath == "" { return fmt.Errorf("could not locate main Rancher Desktop executable; please retry with the --path option") } diff --git a/src/go/rdctl/pkg/autostart/autostart_windows.go b/src/go/rdctl/pkg/autostart/autostart_windows.go index d8c99408c85..f619ef4e079 100644 --- a/src/go/rdctl/pkg/autostart/autostart_windows.go +++ b/src/go/rdctl/pkg/autostart/autostart_windows.go @@ -3,7 +3,6 @@ package autostart import ( "errors" "fmt" - "os" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/utils" "golang.org/x/sys/windows/registry" @@ -26,11 +25,7 @@ func EnsureAutostart(autostartDesired bool) error { defer autostartKey.Close() if autostartDesired { - rdctlPath, err := os.Executable() - if err != nil { - return fmt.Errorf("failed to get path to rdctl: %w", err) - } - rancherDesktopPath := utils.GetRDPath(rdctlPath) + rancherDesktopPath := utils.GetRDPath() if rancherDesktopPath == "" { return errors.New("failed to get path to Rancher Desktop.exe") } diff --git a/src/go/rdctl/pkg/utils/utils_darwin.go b/src/go/rdctl/pkg/utils/utils_darwin.go index 97ce9be0087..3b09b798dff 100644 --- a/src/go/rdctl/pkg/utils/utils_darwin.go +++ b/src/go/rdctl/pkg/utils/utils_darwin.go @@ -1,13 +1,15 @@ package utils import ( + "os" "path/filepath" ) // Returns the absolute path to the Rancher Desktop executable. // Returns an empty string if the executable was not found. -func GetRDPath(rdctlPath string) string { - if rdctlPath != "" { +func GetRDPath() string { + rdctlPath, err := os.Executable() + if err == nil { // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin // and want to move to the "R D.app" part RDAppParentPath := getParentDir(rdctlPath, 6) diff --git a/src/go/rdctl/pkg/utils/utils_linux.go b/src/go/rdctl/pkg/utils/utils_linux.go index 92f05cd9b81..f6786f46f95 100644 --- a/src/go/rdctl/pkg/utils/utils_linux.go +++ b/src/go/rdctl/pkg/utils/utils_linux.go @@ -1,13 +1,15 @@ package utils import ( + "os" "path/filepath" ) // Returns the absolute path to the Rancher Desktop executable. // Returns an empty string if the executable was not found. -func GetRDPath(rdctlPath string) string { - if rdctlPath != "" { +func GetRDPath() string { + rdctlPath, err := os.Executable() + if err == nil { normalParentPath := getParentDir(rdctlPath, 5) candidatePath := CheckExistence(filepath.Join(normalParentPath, "rancher-desktop"), 0o111) if candidatePath != "" { diff --git a/src/go/rdctl/pkg/utils/utils_windows.go b/src/go/rdctl/pkg/utils/utils_windows.go index 4650152c50c..6b0596e032f 100644 --- a/src/go/rdctl/pkg/utils/utils_windows.go +++ b/src/go/rdctl/pkg/utils/utils_windows.go @@ -9,8 +9,9 @@ import ( // Returns the absolute path to the Rancher Desktop executable. // Returns an empty string if the executable was not found. -func GetRDPath(rdctlPath string) string { - if rdctlPath != "" { +func GetRDPath() string { + rdctlPath, err := os.Executable() + if err == nil { normalParentPath := getParentDir(rdctlPath, 5) candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) if candidatePath != "" { From 93ff6ccad95b21f431b77f1161b775f00a68d8aa Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Thu, 16 Feb 2023 16:53:38 -0700 Subject: [PATCH 4/7] Return (string, error) from GetRDPath Signed-off-by: Adam Pickering --- src/go/rdctl/cmd/start.go | 8 +++--- src/go/rdctl/pkg/utils/utils_darwin.go | 33 +++++++++++++++++-------- src/go/rdctl/pkg/utils/utils_linux.go | 32 ++++++++++++++++-------- src/go/rdctl/pkg/utils/utils_windows.go | 23 +++++++++++------ 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/go/rdctl/cmd/start.go b/src/go/rdctl/cmd/start.go index d22ea260007..5b2bfff8a91 100644 --- a/src/go/rdctl/cmd/start.go +++ b/src/go/rdctl/cmd/start.go @@ -82,10 +82,10 @@ func doStartCommand(cmd *cobra.Command) error { if err != nil { return err } - if applicationPath == "" { - applicationPath = utils.GetRDPath() - if applicationPath == "" { - return fmt.Errorf("could not locate main Rancher Desktop executable; please retry with the --path option") + if !cmd.Flags().Changed("path") { + applicationPath, err = utils.GetRDPath() + if err != nil { + return fmt.Errorf("failed to locate main Rancher Desktop executable: %w\nplease retry with the --path option", err) } } return launchApp(applicationPath, commandLineArgs) diff --git a/src/go/rdctl/pkg/utils/utils_darwin.go b/src/go/rdctl/pkg/utils/utils_darwin.go index 3b09b798dff..102c710664e 100644 --- a/src/go/rdctl/pkg/utils/utils_darwin.go +++ b/src/go/rdctl/pkg/utils/utils_darwin.go @@ -1,23 +1,36 @@ package utils import ( + "fmt" "os" "path/filepath" ) // Returns the absolute path to the Rancher Desktop executable. // Returns an empty string if the executable was not found. -func GetRDPath() string { - rdctlPath, err := os.Executable() - if err == nil { - // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin - // and want to move to the "R D.app" part - RDAppParentPath := getParentDir(rdctlPath, 6) - if CheckExistence(filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop"), 0o111) != "" { - return RDAppParentPath - } +func GetRDPath() (string, error) { + rdctlSymlinkPath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("failed to get path to rdctl: %w", err) } + rdctlPath, err := filepath.EvalSymlinks(rdctlSymlinkPath) + if err != nil { + return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) + } + + // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin + // and want to move to the "R D.app" part + RDAppParentPath := getParentDir(rdctlPath, 6) + if CheckExistence(filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop"), 0o111) != "" { + return RDAppParentPath, nil + } + // This fallback is mostly for running `npm run dev` and using the installed app because there is no app // that rdctl would launch directly in dev mode. - return CheckExistence(filepath.Join("/Applications", "Rancher Desktop.app"), 0) + candidatePath := filepath.Join("/Applications", "Rancher Desktop.app") + if len(CheckExistence(candidatePath, 0)) { + return candidatePath + } + + return "", errors.New("search locations exhausted") } diff --git a/src/go/rdctl/pkg/utils/utils_linux.go b/src/go/rdctl/pkg/utils/utils_linux.go index f6786f46f95..f55aa91d2c9 100644 --- a/src/go/rdctl/pkg/utils/utils_linux.go +++ b/src/go/rdctl/pkg/utils/utils_linux.go @@ -1,20 +1,32 @@ package utils import ( + "errors" + "fmt" "os" "path/filepath" ) -// Returns the absolute path to the Rancher Desktop executable. -// Returns an empty string if the executable was not found. -func GetRDPath() string { - rdctlPath, err := os.Executable() - if err == nil { - normalParentPath := getParentDir(rdctlPath, 5) - candidatePath := CheckExistence(filepath.Join(normalParentPath, "rancher-desktop"), 0o111) - if candidatePath != "" { - return candidatePath +// Returns the absolute path to the Rancher Desktop executable, +// or an error if it was unable to find Rancher Desktop. +func GetRDPath() (string, error) { + rdctlSymlinkPath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("failed to get path to rdctl: %w", err) + } + rdctlPath, err := filepath.EvalSymlinks(rdctlSymlinkPath) + if err != nil { + return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) + } + normalParentPath := getParentDir(rdctlPath, 5) + candidatePaths := []string{ + filepath.Join(normalParentPath, "rancher-desktop"), + "/opt/rancher-desktop/rancher-desktop", + } + for _, candidatePath := range candidatePaths { + if len(CheckExistence(candidatePath, 0o111)) > 0 { + return candidatePath, nil } } - return CheckExistence("/opt/rancher-desktop/rancher-desktop", 0o111) + return "", errors.New("search locations exhausted") } diff --git a/src/go/rdctl/pkg/utils/utils_windows.go b/src/go/rdctl/pkg/utils/utils_windows.go index 6b0596e032f..db46e1deb60 100644 --- a/src/go/rdctl/pkg/utils/utils_windows.go +++ b/src/go/rdctl/pkg/utils/utils_windows.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "os" "path/filepath" @@ -9,15 +10,21 @@ import ( // Returns the absolute path to the Rancher Desktop executable. // Returns an empty string if the executable was not found. -func GetRDPath() string { - rdctlPath, err := os.Executable() - if err == nil { - normalParentPath := getParentDir(rdctlPath, 5) - candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) - if candidatePath != "" { - return candidatePath - } +func GetRDPath() (string, error) { + rdctlSymlinkPath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("failed to get path to rdctl: %w", err) } + rdctlPath, err := filepath.EvalSymlinks(rdctlSymlinkPath) + if err != nil { + return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) + } + normalParentPath := getParentDir(rdctlPath, 5) + candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) + if candidatePath != "" { + return candidatePath + } + homedir, err := os.UserHomeDir() if err != nil { homedir = "" From 84c41bfaf24a02a767b5d0affeab020e598e6712 Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Fri, 17 Feb 2023 11:01:19 -0700 Subject: [PATCH 5/7] Refactor/rename CheckExistence Signed-off-by: Adam Pickering --- .../rdctl/pkg/autostart/autostart_darwin.go | 6 +-- .../rdctl/pkg/autostart/autostart_windows.go | 6 +-- src/go/rdctl/pkg/utils/utils.go | 48 ++++++++++++------- src/go/rdctl/pkg/utils/utils_darwin.go | 18 +++++-- src/go/rdctl/pkg/utils/utils_linux.go | 6 ++- src/go/rdctl/pkg/utils/utils_windows.go | 25 ++++++---- 6 files changed, 73 insertions(+), 36 deletions(-) diff --git a/src/go/rdctl/pkg/autostart/autostart_darwin.go b/src/go/rdctl/pkg/autostart/autostart_darwin.go index 33fbb020c8f..32c82353f99 100644 --- a/src/go/rdctl/pkg/autostart/autostart_darwin.go +++ b/src/go/rdctl/pkg/autostart/autostart_darwin.go @@ -89,9 +89,9 @@ func getDesiredLaunchAgentFileContents() ([]byte, error) { if err != nil { return []byte{}, fmt.Errorf("failed to get path to rdctl: %w", err) } - rancherDesktopPath := utils.GetDarwinRDPath(rdctlPath) - if rancherDesktopPath == "" { - return []byte{}, errors.New("failed to get path to main Rancher Desktop executable") + rancherDesktopPath, err := utils.GetRDPath() + if err != nil { + return []byte{}, errors.New("failed to get path to main Rancher Desktop executable: %w", err) } // get desired contents of LaunchAgent file diff --git a/src/go/rdctl/pkg/autostart/autostart_windows.go b/src/go/rdctl/pkg/autostart/autostart_windows.go index f619ef4e079..259b0f91aea 100644 --- a/src/go/rdctl/pkg/autostart/autostart_windows.go +++ b/src/go/rdctl/pkg/autostart/autostart_windows.go @@ -25,9 +25,9 @@ func EnsureAutostart(autostartDesired bool) error { defer autostartKey.Close() if autostartDesired { - rancherDesktopPath := utils.GetRDPath() - if rancherDesktopPath == "" { - return errors.New("failed to get path to Rancher Desktop.exe") + rancherDesktopPath, err := utils.GetRDPath() + if err != nil { + return fmt.Errorf("failed to get path to Rancher Desktop.exe: %w", err) } err = autostartKey.SetStringValue(nameValue, rancherDesktopPath) if err != nil { diff --git a/src/go/rdctl/pkg/utils/utils.go b/src/go/rdctl/pkg/utils/utils.go index cf77b39200e..d61a7d1c150 100644 --- a/src/go/rdctl/pkg/utils/utils.go +++ b/src/go/rdctl/pkg/utils/utils.go @@ -1,35 +1,49 @@ package utils import ( + "errors" + "fmt" "io/fs" "os" "path/filepath" ) -// Get the parent (or grandparent, or great-grandparent...) directory of fullPath. -// numberTimes is the number of steps to ascend in the directory hierarchy. -func getParentDir(fullPath string, numberTimes int) string { +// Get the steps-th parent directory of fullPath. +func getParentDir(fullPath string, steps int) string { fullPath = filepath.Clean(fullPath) - for ; numberTimes > 0; numberTimes-- { + for ; steps > 0; steps-- { fullPath = filepath.Dir(fullPath) } return fullPath } -/** - * Verify the path exists. For Linux pass in mode bits to guarantee the file is executable (for at least one - * category of user). Note that on macOS the candidate is a directory, so never pass in mode bits. - * And mode bits don't make sense on Windows. - */ -func CheckExistence(candidatePath string, modeBits fs.FileMode) string { - stat, err := os.Stat(candidatePath) +// Verify that the candidatePath is usable as a Rancher Desktop "executable". This means: +// - check that candidatePath exists +// - if checkExecutability is true, check that candidatePath is a regular file, +// and that it is executable +// +// Note that candidatePath may not always be a file; in macOS, it may be a +// .app directory. +func checkUsability(candidatePath string, checkExecutability bool) (bool, error) { + statResult, err := os.Stat(candidatePath) + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } if err != nil { - return "" + return false, fmt.Errorf("failed to get info on %q: %w", candidatePath, err) + } + + if !checkExecutability { + return true, nil } - if modeBits != 0 && (!stat.Mode().IsRegular() || stat.Mode().Perm()&modeBits == 0) { - // The modeBits check is only for executability -- we only care if at least one of the three - // `x` mode bits is on. So this check isn't used for a general permission-mode-bit check. - return "" + + if !statResult.Mode().IsRegular() { + return false, nil } - return candidatePath + + if statResult.Mode().Perm()&0o111 == 0 { + return false, nil + } + + return true, nil } diff --git a/src/go/rdctl/pkg/utils/utils_darwin.go b/src/go/rdctl/pkg/utils/utils_darwin.go index 102c710664e..12c883de920 100644 --- a/src/go/rdctl/pkg/utils/utils_darwin.go +++ b/src/go/rdctl/pkg/utils/utils_darwin.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "os" "path/filepath" @@ -18,18 +19,27 @@ func GetRDPath() (string, error) { return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) } - // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin + // we're at .../Applications/R D.app (could have a different name)/Contents/Resources/resources/darwin/bin/rdctl // and want to move to the "R D.app" part RDAppParentPath := getParentDir(rdctlPath, 6) - if CheckExistence(filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop"), 0o111) != "" { + executablePath := filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop") + usable, err := checkUsability(executablePath, true) + if err != nil { + return "", fmt.Errorf("failed to check usability of %q: %w", executablePath, err) + } + if usable { return RDAppParentPath, nil } // This fallback is mostly for running `npm run dev` and using the installed app because there is no app // that rdctl would launch directly in dev mode. candidatePath := filepath.Join("/Applications", "Rancher Desktop.app") - if len(CheckExistence(candidatePath, 0)) { - return candidatePath + usable, err = checkUsability(candidatePath, false) + if err != nil { + return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) + } + if usable { + return RDAppParentPath, nil } return "", errors.New("search locations exhausted") diff --git a/src/go/rdctl/pkg/utils/utils_linux.go b/src/go/rdctl/pkg/utils/utils_linux.go index f55aa91d2c9..d11e3dccfe3 100644 --- a/src/go/rdctl/pkg/utils/utils_linux.go +++ b/src/go/rdctl/pkg/utils/utils_linux.go @@ -24,7 +24,11 @@ func GetRDPath() (string, error) { "/opt/rancher-desktop/rancher-desktop", } for _, candidatePath := range candidatePaths { - if len(CheckExistence(candidatePath, 0o111)) > 0 { + usable, err := checkUsability(candidatePath, true) + if err != nil { + return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) + } + if usable { return candidatePath, nil } } diff --git a/src/go/rdctl/pkg/utils/utils_windows.go b/src/go/rdctl/pkg/utils/utils_windows.go index db46e1deb60..8e754c75c35 100644 --- a/src/go/rdctl/pkg/utils/utils_windows.go +++ b/src/go/rdctl/pkg/utils/utils_windows.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "os" "path/filepath" @@ -20,9 +21,13 @@ func GetRDPath() (string, error) { return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) } normalParentPath := getParentDir(rdctlPath, 5) - candidatePath := CheckExistence(filepath.Join(normalParentPath, "Rancher Desktop.exe"), 0) - if candidatePath != "" { - return candidatePath + candidatePath := filepath.Join(normalParentPath, "Rancher Desktop.exe") + usable, err := checkUsability(candidatePath, false) + if err != nil { + return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) + } + if usable { + return candidatePath, nil } homedir, err := os.UserHomeDir() @@ -40,17 +45,21 @@ func GetRDPath() (string, error) { if err == nil { dataPaths = append(dataPaths, dir) } - // Add these two paths if the above two fail to find where the program was installed dataPaths = append( dataPaths, filepath.Join(homedir, "AppData", "Local"), filepath.Join(homedir, "AppData", "Roaming"), ) for _, dataDir := range dataPaths { - candidatePath := CheckExistence(filepath.Join(dataDir, "Programs", "Rancher Desktop", "Rancher Desktop.exe"), 0) - if candidatePath != "" { - return candidatePath + candidatePath := filepath.Join(dataDir, "Programs", "Rancher Desktop", "Rancher Desktop.exe") + usable, err := checkUsability(candidatePath, false) + if err != nil { + return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) + } + if usable { + return candidatePath, nil } } - return "" + + return "", errors.New("search locations exhausted") } From 3aad632b9f5768f03c41e2c52eb50880fc7751a5 Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Wed, 1 Mar 2023 10:29:04 -0700 Subject: [PATCH 6/7] Fix issue with darwin Signed-off-by: Adam Pickering --- src/go/rdctl/pkg/autostart/autostart_darwin.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/go/rdctl/pkg/autostart/autostart_darwin.go b/src/go/rdctl/pkg/autostart/autostart_darwin.go index 32c82353f99..e686382a5f8 100644 --- a/src/go/rdctl/pkg/autostart/autostart_darwin.go +++ b/src/go/rdctl/pkg/autostart/autostart_darwin.go @@ -84,14 +84,9 @@ func EnsureAutostart(autostartDesired bool) error { } func getDesiredLaunchAgentFileContents() ([]byte, error) { - // get path to main Rancher Desktop executable - rdctlPath, err := os.Executable() - if err != nil { - return []byte{}, fmt.Errorf("failed to get path to rdctl: %w", err) - } rancherDesktopPath, err := utils.GetRDPath() if err != nil { - return []byte{}, errors.New("failed to get path to main Rancher Desktop executable: %w", err) + return []byte{}, fmt.Errorf("failed to get path to main Rancher Desktop executable: %w", err) } // get desired contents of LaunchAgent file From 4092dc1f5700411d86d946f1c15bf565135e0871 Mon Sep 17 00:00:00 2001 From: Adam Pickering Date: Wed, 1 Mar 2023 16:32:41 -0700 Subject: [PATCH 7/7] Make checkUsability Unix-only Signed-off-by: Adam Pickering --- src/go/rdctl/pkg/utils/utils.go | 35 ---------------------- src/go/rdctl/pkg/utils/utils_darwin.go | 4 +-- src/go/rdctl/pkg/utils/utils_linux.go | 4 ++- src/go/rdctl/pkg/utils/utils_unix.go | 39 +++++++++++++++++++++++++ src/go/rdctl/pkg/utils/utils_windows.go | 19 +++++++----- 5 files changed, 55 insertions(+), 46 deletions(-) create mode 100644 src/go/rdctl/pkg/utils/utils_unix.go diff --git a/src/go/rdctl/pkg/utils/utils.go b/src/go/rdctl/pkg/utils/utils.go index d61a7d1c150..40a9979c9f6 100644 --- a/src/go/rdctl/pkg/utils/utils.go +++ b/src/go/rdctl/pkg/utils/utils.go @@ -1,10 +1,6 @@ package utils import ( - "errors" - "fmt" - "io/fs" - "os" "path/filepath" ) @@ -16,34 +12,3 @@ func getParentDir(fullPath string, steps int) string { } return fullPath } - -// Verify that the candidatePath is usable as a Rancher Desktop "executable". This means: -// - check that candidatePath exists -// - if checkExecutability is true, check that candidatePath is a regular file, -// and that it is executable -// -// Note that candidatePath may not always be a file; in macOS, it may be a -// .app directory. -func checkUsability(candidatePath string, checkExecutability bool) (bool, error) { - statResult, err := os.Stat(candidatePath) - if errors.Is(err, fs.ErrNotExist) { - return false, nil - } - if err != nil { - return false, fmt.Errorf("failed to get info on %q: %w", candidatePath, err) - } - - if !checkExecutability { - return true, nil - } - - if !statResult.Mode().IsRegular() { - return false, nil - } - - if statResult.Mode().Perm()&0o111 == 0 { - return false, nil - } - - return true, nil -} diff --git a/src/go/rdctl/pkg/utils/utils_darwin.go b/src/go/rdctl/pkg/utils/utils_darwin.go index 12c883de920..e1784e38b0a 100644 --- a/src/go/rdctl/pkg/utils/utils_darwin.go +++ b/src/go/rdctl/pkg/utils/utils_darwin.go @@ -23,7 +23,7 @@ func GetRDPath() (string, error) { // and want to move to the "R D.app" part RDAppParentPath := getParentDir(rdctlPath, 6) executablePath := filepath.Join(RDAppParentPath, "Contents", "MacOS", "Rancher Desktop") - usable, err := checkUsability(executablePath, true) + usable, err := checkUsableApplication(executablePath, true) if err != nil { return "", fmt.Errorf("failed to check usability of %q: %w", executablePath, err) } @@ -34,7 +34,7 @@ func GetRDPath() (string, error) { // This fallback is mostly for running `npm run dev` and using the installed app because there is no app // that rdctl would launch directly in dev mode. candidatePath := filepath.Join("/Applications", "Rancher Desktop.app") - usable, err = checkUsability(candidatePath, false) + usable, err = checkUsableApplication(candidatePath, false) if err != nil { return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) } diff --git a/src/go/rdctl/pkg/utils/utils_linux.go b/src/go/rdctl/pkg/utils/utils_linux.go index d11e3dccfe3..d2b3b215ee5 100644 --- a/src/go/rdctl/pkg/utils/utils_linux.go +++ b/src/go/rdctl/pkg/utils/utils_linux.go @@ -18,13 +18,15 @@ func GetRDPath() (string, error) { if err != nil { return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) } + // rdctl should be at /resources/resources/linux/bin/rdctl. + // rancher-desktop should be 5 directories up from that, at /rancher-desktop. normalParentPath := getParentDir(rdctlPath, 5) candidatePaths := []string{ filepath.Join(normalParentPath, "rancher-desktop"), "/opt/rancher-desktop/rancher-desktop", } for _, candidatePath := range candidatePaths { - usable, err := checkUsability(candidatePath, true) + usable, err := checkUsableApplication(candidatePath, true) if err != nil { return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) } diff --git a/src/go/rdctl/pkg/utils/utils_unix.go b/src/go/rdctl/pkg/utils/utils_unix.go new file mode 100644 index 00000000000..47627d83eea --- /dev/null +++ b/src/go/rdctl/pkg/utils/utils_unix.go @@ -0,0 +1,39 @@ +//go:build linux || darwin + +package utils + +import ( + "errors" + "fmt" + "golang.org/x/sys/unix" + "io/fs" + "os" +) + +// Verify that the candidatePath is usable as a Rancher Desktop "executable". This means: +// - check that candidatePath exists +// - if checkExecutability is true, check that candidatePath is a regular file, +// and that it is executable +// +// Note that candidatePath may not always be a file; in macOS, it may be a +// .app directory. +func checkUsableApplication(candidatePath string, checkExecutability bool) (bool, error) { + statResult, err := os.Stat(candidatePath) + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + if err != nil { + return false, fmt.Errorf("failed to get info on %q: %w", candidatePath, err) + } + + if !checkExecutability { + return true, nil + } + + if !statResult.Mode().IsRegular() { + return false, nil + } + + err = unix.Access(candidatePath, unix.X_OK) + return err == nil, nil +} diff --git a/src/go/rdctl/pkg/utils/utils_windows.go b/src/go/rdctl/pkg/utils/utils_windows.go index 8e754c75c35..b5dc2defd39 100644 --- a/src/go/rdctl/pkg/utils/utils_windows.go +++ b/src/go/rdctl/pkg/utils/utils_windows.go @@ -3,6 +3,7 @@ package utils import ( "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -20,13 +21,15 @@ func GetRDPath() (string, error) { if err != nil { return "", fmt.Errorf("failed to resolve %q: %w", rdctlSymlinkPath, err) } + // rdctl should be at /resources/resources/win32/bin/rdctl.exe. + // rancher-desktop should be 5 directories up from that, at /Rancher Desktop.exe. normalParentPath := getParentDir(rdctlPath, 5) candidatePath := filepath.Join(normalParentPath, "Rancher Desktop.exe") - usable, err := checkUsability(candidatePath, false) - if err != nil { - return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) + _, err = os.Stat(candidatePath) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return "", fmt.Errorf("failed to check existence of %q: %w", candidatePath, err) } - if usable { + if err == nil { return candidatePath, nil } @@ -52,11 +55,11 @@ func GetRDPath() (string, error) { ) for _, dataDir := range dataPaths { candidatePath := filepath.Join(dataDir, "Programs", "Rancher Desktop", "Rancher Desktop.exe") - usable, err := checkUsability(candidatePath, false) - if err != nil { - return "", fmt.Errorf("failed to check usability of %q: %w", candidatePath, err) + _, err := os.Stat(candidatePath) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return "", fmt.Errorf("failed to check existence of %q: %w", candidatePath, err) } - if usable { + if err == nil { return candidatePath, nil } }