From 785d4f670863090259073b1267cca20274a14213 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 20:43:07 +0000 Subject: [PATCH] Add user step to setup and add prompt user with lookup and validation --- README.md | 2 +- cmd/setup.go | 21 ++++++++++++++++++--- internal/operatingsystem/detect.go | 9 +++++++++ internal/operatingsystem/prompt.go | 29 +++++++++++++++++++++++------ internal/operatingsystem/user.go | 29 +++++++++++++++++++++++++++++ internal/workbench/verify.go | 7 ++++--- 6 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 internal/operatingsystem/user.go diff --git a/README.md b/README.md index 7b8eff0..114ef61 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ You can also pass the `--step` flag to begin at a certain spot in the interactiv sudo wbi setup --step workbench ``` -The following steps are valid options: start, prereqs, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, packagemanager, connect, restart, status, verify. +The following steps are valid options: start, prereqs, user, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, packagemanager, connect, restart, status, verify. ## Assumptions - Single server diff --git a/cmd/setup.go b/cmd/setup.go index 6cb0a2f..073f36a 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -70,6 +70,15 @@ func newSetup(setupOpts setupOpts) error { if err != nil { return err } + step = "user" + } + + var username string + if step == "user" { + username, err = operatingsystem.PromptAndVerifyUser() + if err != nil { + return err + } step = "firewall" } @@ -273,7 +282,13 @@ func newSetup(setupOpts setupOpts) error { return fmt.Errorf("issue selecting if verification is to be run: %w", err) } if verifyChoice { - err = workbench.VerifyInstallation() + if username == "" { + username, err = operatingsystem.PromptAndVerifyUser() + if err != nil { + return err + } + } + err = workbench.VerifyInstallation(username) if err != nil { return fmt.Errorf("issue running verification: %w", err) } @@ -311,7 +326,7 @@ func (opts *setupOpts) Validate(args []string) error { return fmt.Errorf("no arguments are supported for this command") } // ensure step is valid - validSteps := []string{"start", "prereqs", "firewall", "security", "languages", "r", "python", "workbench", "license", "jupyter", "prodrivers", "ssl", "packagemanager", "connect", "restart", "status", "verify"} + validSteps := []string{"start", "prereqs", "user", "firewall", "security", "languages", "r", "python", "workbench", "license", "jupyter", "prodrivers", "ssl", "packagemanager", "connect", "restart", "status", "verify"} if opts.step != "" && !lo.Contains(validSteps, opts.step) { return fmt.Errorf("invalid step: %s", opts.step) } @@ -353,7 +368,7 @@ func newSetupCmd() *setupCmd { SilenceUsage: true, } - stepHelp := `The step to start at. Valid steps are: start, prereqs, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, packagemanager, connect, restart, status, verify.` + stepHelp := `The step to start at. Valid steps are: start, prereqs, user, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, packagemanager, connect, restart, status, verify.` cmd.Flags().StringP("step", "s", "", stepHelp) viper.BindPFlag("step", cmd.Flags().Lookup("step")) diff --git a/internal/operatingsystem/detect.go b/internal/operatingsystem/detect.go index e4701a1..c129573 100644 --- a/internal/operatingsystem/detect.go +++ b/internal/operatingsystem/detect.go @@ -3,6 +3,7 @@ package operatingsystem import ( "errors" "os" + "os/user" "runtime" "strings" @@ -49,3 +50,11 @@ func DetectOS() (config.OperatingSystem, error) { return config.Unknown, errors.New("unsupported operating system") } } + +func UserLookup(username string) (*user.User, error) { + user, err := user.Lookup(username) + if err != nil { + return nil, err + } + return user, nil +} diff --git a/internal/operatingsystem/prompt.go b/internal/operatingsystem/prompt.go index 140bcdc..7ace486 100644 --- a/internal/operatingsystem/prompt.go +++ b/internal/operatingsystem/prompt.go @@ -61,12 +61,13 @@ func PromptInstallPrereqs() (bool, error) { var name bool messageText := "In order to install Workbench from start to finish, you will need the following things\n" + "1. Internet access for this server\n" + - "2. The versions of R and Python you would like to install\n" + - "3. The version of R and Python you would like to set as defaults\n" + - "4. Your Workbench license key string in this form: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX\n" + - "5. The location on this server of your SSL key and certificate files (optional)\n" + - "6. The URL and repo name for your instance of Posit Package Manager (optional)\n" + - "7. The URL for your instance of Posit Connect (optional)\n\n" + + "2. At least one non-root local Linux user account with a home directory\n" + + "3. The versions of R and Python you would like to install\n" + + "4. The version of R and Python you would like to set as defaults\n" + + "5. Your Workbench license key string in this form: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX\n" + + "6. The location on this server of your SSL key and certificate files (optional)\n" + + "7. The URL and repo name for your instance of Posit Package Manager (optional)\n" + + "8. The URL for your instance of Posit Connect (optional)\n\n" + "Please confirm that you're ready to install Workbench" prompt := &survey.Confirm{ Message: messageText, @@ -79,3 +80,19 @@ func PromptInstallPrereqs() (bool, error) { log.Info(fmt.Sprintf("%v", name)) return name, nil } + +// PromptUserAccount prompts the user for the name of a local Linux user account to use for verifying the installation +func PromptUserAccount() (string, error) { + target := "" + messageText := "Enter a non-root local Linux account username to use for testing the Workbench installation:" + prompt := &survey.Input{ + Message: messageText, + } + err := survey.AskOne(prompt, &target) + if err != nil { + return "", fmt.Errorf("issue prompting for a local user account: %w", err) + } + log.Info(messageText) + log.Info(target) + return target, nil +} diff --git a/internal/operatingsystem/user.go b/internal/operatingsystem/user.go new file mode 100644 index 0000000..906206d --- /dev/null +++ b/internal/operatingsystem/user.go @@ -0,0 +1,29 @@ +package operatingsystem + +import ( + "fmt" + + "github.com/sol-eng/wbi/internal/system" +) + +func PromptAndVerifyUser() (string, error) { + userAccount, err := PromptUserAccount() + if err != nil { + return "", err + } + // lookup user account details + user, err := UserLookup(userAccount) + if err != nil { + return "", fmt.Errorf("user %s account not found", userAccount) + } + // verify non-root and a home directory exists + if user.Uid == "0" { + return "", fmt.Errorf("user %s account is root. A non-root user is required", userAccount) + } + if user.HomeDir == "" { + return "", fmt.Errorf("user %s account does not have a home directory. A home directory is required", userAccount) + } + system.PrintAndLogInfo(fmt.Sprintf("user %s account found and validated", userAccount)) + + return user.Username, nil +} diff --git a/internal/workbench/verify.go b/internal/workbench/verify.go index c5897bc..fa9c3d5 100644 --- a/internal/workbench/verify.go +++ b/internal/workbench/verify.go @@ -21,16 +21,17 @@ func VerifyWorkbench() bool { } // Runs verify-installation command -func VerifyInstallation() error { +func VerifyInstallation(username string) error { // stop rstudio-server err := StopRStudioServer() if err != nil { return fmt.Errorf("issue stopping rstudio-server: %w", err) } // run verify-installation - err = system.RunCommand("rstudio-server verify-installation", true, 1) + verifyCommand := "rstudio-server verify-installation --verify-user=" + username + err = system.RunCommand(verifyCommand, true, 1) if err != nil { - return fmt.Errorf("issue running verify-installation command 'rstudio-server verify-installation': %w", err) + return fmt.Errorf("issue running verify-installation command '%s': %w", verifyCommand, err) } // start rstudio-server err = StartRStudioServer()