From 0069cd39021322c5f0522e299717bdf11578b65f Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Fri, 17 Feb 2023 20:11:51 +0000 Subject: [PATCH 1/4] Add a prompt for a Connect URL with validation --- cmd/setup.go | 17 ++++++++++ internal/config/config.go | 1 + internal/connect/prompt.go | 34 ++++++++++++++++++++ internal/connect/verify.go | 63 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 internal/connect/prompt.go create mode 100644 internal/connect/verify.go diff --git a/cmd/setup.go b/cmd/setup.go index 73bedab..6fad7df 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -5,6 +5,7 @@ import ( "github.com/dpastoor/wbi/internal/authentication" "github.com/dpastoor/wbi/internal/config" + "github.com/dpastoor/wbi/internal/connect" "github.com/dpastoor/wbi/internal/jupyter" "github.com/dpastoor/wbi/internal/languages" "github.com/dpastoor/wbi/internal/license" @@ -149,6 +150,22 @@ func newSetup(setupOpts setupOpts) error { return fmt.Errorf("issue handling authentication: %w", AuthErr) } + // Connect URL + connectChoice, err := connect.PromptConnectChoice() + if err != nil { + return fmt.Errorf("issue in prompt for Connect URL choice: %w", err) + } + if connectChoice { + WBConfig.ConnectURL, err = connect.PromptConnectURL() + if err != nil { + return fmt.Errorf("issue entering Connect URL: %w", err) + } + _, err = connect.VerifyConnectURL(WBConfig.ConnectURL) + if err != nil { + return fmt.Errorf("issue with checking the Connect URL: %w", err) + } + } + // Write config to console WBConfig.ConfigStructToText() diff --git a/internal/config/config.go b/internal/config/config.go index 69e3a09..92f5edd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,6 +31,7 @@ type WBConfig struct { RConfig RConfig PythonConfig PythonConfig AuthConfig AuthConfig + ConnectURL string } type AuthConfig struct { diff --git a/internal/connect/prompt.go b/internal/connect/prompt.go new file mode 100644 index 0000000..315de81 --- /dev/null +++ b/internal/connect/prompt.go @@ -0,0 +1,34 @@ +package connect + +import ( + "errors" + "fmt" + + "github.com/AlecAivazis/survey/v2" +) + +// Prompt users if they wish to add a default Connect URL to Workbench +func PromptConnectChoice() (bool, error) { + name := true + prompt := &survey.Confirm{ + Message: "Would you like to provide a default Connect URL for Workbench?", + } + err := survey.AskOne(prompt, &name) + if err != nil { + return false, errors.New("there was an issue with the Connect URL prompt") + } + return name, nil +} + +// Prompt users for a default Connect URL +func PromptConnectURL() (string, error) { + target := "" + prompt := &survey.Input{ + Message: "Connect URL:", + } + err := survey.AskOne(prompt, &target) + if err != nil { + return "", fmt.Errorf("issue prompting for a Connect URL: %w", err) + } + return target, nil +} diff --git a/internal/connect/verify.go b/internal/connect/verify.go new file mode 100644 index 0000000..0f237c2 --- /dev/null +++ b/internal/connect/verify.go @@ -0,0 +1,63 @@ +package connect + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" +) + +func cleanConnectURL(connectURL string) string { + // remove trailing slash if present + if connectURL[len(connectURL)-1] == '/' { + connectURL = connectURL[:len(connectURL)-1] + } + // if the url does not contain :// then add it, either http if port 3939 or https in other cases (most installs) + if !strings.Contains(connectURL, "://") { + if strings.Contains(connectURL, ":3939") { + connectURL = "http://" + connectURL + } else { + connectURL = "https://" + connectURL + } + } + return connectURL +} + +// Information is only needed to check if the URL is valid +type ProhibitedUsernames struct { + ProhibitedUsernames []string `json:"prohibited_usernames"` +} + +// VerifyConnectURL checks if the Connect URL is valid +func VerifyConnectURL(connectURL string) (ProhibitedUsernames, error) { + + fullTestURL := cleanConnectURL(connectURL) + "/__api__/server_settings" + + client := &http.Client{ + Timeout: 30 * time.Second, + } + req, err := http.NewRequestWithContext(context.Background(), + http.MethodGet, fullTestURL, nil) + if err != nil { + return ProhibitedUsernames{}, errors.New("error creating request") + } + res, err := client.Do(req) + if err != nil { + return ProhibitedUsernames{}, errors.New("error retrieving JSON data") + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return ProhibitedUsernames{}, errors.New("error in HTTP status code") + } + var prohibitedUsernames ProhibitedUsernames + err = json.NewDecoder(res.Body).Decode(&prohibitedUsernames) + if err != nil { + return ProhibitedUsernames{}, errors.New("error unmarshalling JSON data") + } + + fmt.Println("\nConnect URL has been successfull validated.\n") + return prohibitedUsernames, nil +} From e6415e7e347529b01e380c5d46ee219b87dcf967 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Fri, 17 Feb 2023 20:19:59 +0000 Subject: [PATCH 2/4] Add the resulting clean Connect URL to config print --- cmd/setup.go | 4 ++-- internal/config/print.go | 7 +++++++ internal/connect/verify.go | 15 ++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 6fad7df..3a62664 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -156,11 +156,11 @@ func newSetup(setupOpts setupOpts) error { return fmt.Errorf("issue in prompt for Connect URL choice: %w", err) } if connectChoice { - WBConfig.ConnectURL, err = connect.PromptConnectURL() + rawConnectURL, err := connect.PromptConnectURL() if err != nil { return fmt.Errorf("issue entering Connect URL: %w", err) } - _, err = connect.VerifyConnectURL(WBConfig.ConnectURL) + WBConfig.ConnectURL, err = connect.VerifyConnectURL(rawConnectURL) if err != nil { return fmt.Errorf("issue with checking the Connect URL: %w", err) } diff --git a/internal/config/print.go b/internal/config/print.go index 8d9b26d..ffea16e 100644 --- a/internal/config/print.go +++ b/internal/config/print.go @@ -7,6 +7,7 @@ func (WBConfig *WBConfig) ConfigStructToText() { WBConfig.PythonConfig.PythonStructToText() WBConfig.SSLConfig.SSLStructToText() WBConfig.AuthConfig.AuthStructToText() + WBConfig.ConnectStringToText() fmt.Println("\n=== Please restart Workbench after making these changes") } @@ -55,3 +56,9 @@ func (OIDCConfig *OIDCConfig) AuthOIDCStructToText() { fmt.Println("client-id=" + OIDCConfig.ClientID) fmt.Println("client-secret=" + OIDCConfig.ClientSecret) } + +// Prints the ConnectURL configuration string information to the console +func (WBConfig *WBConfig) ConnectStringToText() { + fmt.Println("\n=== Add to config file: /etc/rstudio/rsession.conf:") + fmt.Println("default-rsconnect-server=" + WBConfig.ConnectURL) +} diff --git a/internal/connect/verify.go b/internal/connect/verify.go index 0f237c2..9fb3b47 100644 --- a/internal/connect/verify.go +++ b/internal/connect/verify.go @@ -32,9 +32,10 @@ type ProhibitedUsernames struct { } // VerifyConnectURL checks if the Connect URL is valid -func VerifyConnectURL(connectURL string) (ProhibitedUsernames, error) { +func VerifyConnectURL(connectURL string) (string, error) { - fullTestURL := cleanConnectURL(connectURL) + "/__api__/server_settings" + cleanConnectURL := cleanConnectURL(connectURL) + fullTestURL := cleanConnectURL + "/__api__/server_settings" client := &http.Client{ Timeout: 30 * time.Second, @@ -42,22 +43,22 @@ func VerifyConnectURL(connectURL string) (ProhibitedUsernames, error) { req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fullTestURL, nil) if err != nil { - return ProhibitedUsernames{}, errors.New("error creating request") + return "", errors.New("error creating request") } res, err := client.Do(req) if err != nil { - return ProhibitedUsernames{}, errors.New("error retrieving JSON data") + return "", errors.New("error retrieving JSON data") } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return ProhibitedUsernames{}, errors.New("error in HTTP status code") + return "", errors.New("error in HTTP status code") } var prohibitedUsernames ProhibitedUsernames err = json.NewDecoder(res.Body).Decode(&prohibitedUsernames) if err != nil { - return ProhibitedUsernames{}, errors.New("error unmarshalling JSON data") + return "", errors.New("error unmarshalling JSON data") } fmt.Println("\nConnect URL has been successfull validated.\n") - return prohibitedUsernames, nil + return cleanConnectURL, nil } From d9e3d1d4adb031cd3e695db6213af8e240ef4927 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Fri, 17 Feb 2023 20:29:15 +0000 Subject: [PATCH 3/4] Simplify the server validation by using the health check endpoint --- internal/connect/verify.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/internal/connect/verify.go b/internal/connect/verify.go index 9fb3b47..7291dd6 100644 --- a/internal/connect/verify.go +++ b/internal/connect/verify.go @@ -2,7 +2,6 @@ package connect import ( "context" - "encoding/json" "errors" "fmt" "net/http" @@ -26,16 +25,11 @@ func cleanConnectURL(connectURL string) string { return connectURL } -// Information is only needed to check if the URL is valid -type ProhibitedUsernames struct { - ProhibitedUsernames []string `json:"prohibited_usernames"` -} - // VerifyConnectURL checks if the Connect URL is valid func VerifyConnectURL(connectURL string) (string, error) { cleanConnectURL := cleanConnectURL(connectURL) - fullTestURL := cleanConnectURL + "/__api__/server_settings" + fullTestURL := cleanConnectURL + "/__ping__" client := &http.Client{ Timeout: 30 * time.Second, @@ -53,11 +47,6 @@ func VerifyConnectURL(connectURL string) (string, error) { if res.StatusCode != http.StatusOK { return "", errors.New("error in HTTP status code") } - var prohibitedUsernames ProhibitedUsernames - err = json.NewDecoder(res.Body).Decode(&prohibitedUsernames) - if err != nil { - return "", errors.New("error unmarshalling JSON data") - } fmt.Println("\nConnect URL has been successfull validated.\n") return cleanConnectURL, nil From 4399cf5c4be240f479ca1e7ade3bbf0108e4e5f6 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Fri, 17 Feb 2023 22:48:16 +0000 Subject: [PATCH 4/4] Remove modification of http and https --- internal/connect/verify.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/internal/connect/verify.go b/internal/connect/verify.go index 7291dd6..322d588 100644 --- a/internal/connect/verify.go +++ b/internal/connect/verify.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "strings" "time" ) @@ -14,14 +13,6 @@ func cleanConnectURL(connectURL string) string { if connectURL[len(connectURL)-1] == '/' { connectURL = connectURL[:len(connectURL)-1] } - // if the url does not contain :// then add it, either http if port 3939 or https in other cases (most installs) - if !strings.Contains(connectURL, "://") { - if strings.Contains(connectURL, ":3939") { - connectURL = "http://" + connectURL - } else { - connectURL = "https://" + connectURL - } - } return connectURL }