diff --git a/cli/cmd/install.go b/cli/cmd/install.go index 43990c8..e48bd33 100644 --- a/cli/cmd/install.go +++ b/cli/cmd/install.go @@ -5,7 +5,10 @@ import ( "crypto/sha1" "encoding/base64" "encoding/binary" + "encoding/json" + "errors" "fmt" + "io" "io/ioutil" "math/rand" "net/http" @@ -16,6 +19,10 @@ import ( "github.com/spf13/cobra" ) +type jsonError struct { + Error string `json:"error"` +} + // openURL allows mocking the browser.OpenURL function, so our tests do not open // a browser window. type openURL func(url string) error @@ -141,7 +148,7 @@ func newAgentURL(cfg *config, openURL openURL) (string, error) { if err != nil { return "", err } - resp.Body.Close() + defer resp.Body.Close() if resp.StatusCode == http.StatusAccepted { // still polling @@ -155,8 +162,13 @@ func newAgentURL(cfg *config, openURL openURL) (string, error) { if retries < maxPollingRetries { continue } + err := fmt.Errorf("setup failed, unexpected HTTP status code %d for URL %s", resp.StatusCode, connectAgentURL) + bcloudErr := extractErrorFromResponse(resp) + if bcloudErr != nil { + err = fmt.Errorf("setup failed, %s", bcloudErr) + } - return "", fmt.Errorf("setup failed, unexpected HTTP status code %d for URL %s", resp.StatusCode, connectAgentURL) + return "", err } // successful 308, get the agent YAML URL @@ -186,3 +198,17 @@ func genUniqueID() string { return base64.URLEncoding.EncodeToString(hasher.Sum(nil))[0:16] } + +func extractErrorFromResponse(resp *http.Response) error { + // we will try and parse the json error object here + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil + } + jsonErr := &jsonError{} + if err := json.Unmarshal(data, jsonErr); err != nil { + return nil + } + + return errors.New(jsonErr.Error) +} diff --git a/cli/cmd/install_test.go b/cli/cmd/install_test.go index 025ba16..84ed7c5 100644 --- a/cli/cmd/install_test.go +++ b/cli/cmd/install_test.go @@ -3,6 +3,8 @@ package cmd import ( "bytes" "context" + "encoding/json" + "errors" "fmt" "net/http" "net/http/httptest" @@ -14,6 +16,8 @@ import ( "github.com/buoyantio/linkerd-buoyant/cli/pkg/version" ) +const connectAgentPath = "/connect-agent" + func TestInstallNewAgent(t *testing.T) { totalRequests := 0 connectRequests := 0 @@ -23,7 +27,7 @@ func TestInstallNewAgent(t *testing.T) { func(w http.ResponseWriter, r *http.Request) { totalRequests++ switch r.URL.Path { - case "/connect-agent": + case connectAgentPath: connectRequests++ agentUID = r.URL.Query().Get(version.LinkerdBuoyant) if connectRequests == 1 { @@ -86,7 +90,7 @@ func TestInstalWithPollingFailures(t *testing.T) { func(w http.ResponseWriter, r *http.Request) { totalRequests++ switch r.URL.Path { - case "/connect-agent": + case connectAgentPath: agentUID = r.URL.Query().Get(version.LinkerdBuoyant) connectRequests++ w.WriteHeader(http.StatusBadGateway) @@ -118,6 +122,47 @@ func TestInstalWithPollingFailures(t *testing.T) { } } +func TestInstallWithPollingApiErrors(t *testing.T) { + totalRequests := 0 + connectRequests := 0 + ts := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + totalRequests++ + switch r.URL.Path { + case connectAgentPath: + connectRequests++ + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + rsp, _ := json.Marshal(jsonError{Error: "fatal API error"}) + w.Write(rsp) + } + }, + )) + defer ts.Close() + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + cfg := &config{ + stdout: stdout, + stderr: stderr, + bcloudServer: ts.URL, + } + + client := &k8s.MockClient{} + err := install(context.TODO(), cfg, client, mockOpenURL) + expErr := errors.New("setup failed, fatal API error") + if !reflect.DeepEqual(err, expErr) { + t.Errorf("Expected error: %s, Got: %s", expErr, err) + } + + if totalRequests != maxPollingRetries { + t.Errorf("Expected %d total requests, called %d times", maxPollingRetries, totalRequests) + } + if connectRequests != maxPollingRetries { + t.Errorf("Expected %d /connect-agent requests, called %d times", maxPollingRetries, connectRequests) + } +} + func TestInstallExistingAgent(t *testing.T) { totalRequests := 0 connectRequests := 0