From 729585f66ff4c778c22a86c88d0230f0a7641b02 Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Thu, 20 Jun 2024 10:42:29 +0200 Subject: Print hints if deploy is unauthorized --- client/go/internal/cli/cmd/deploy.go | 7 +++++++ client/go/internal/cli/cmd/deploy_test.go | 31 +++++++++++++++++++++++++++---- client/go/internal/vespa/deploy.go | 10 +++++++--- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go index d3bda1089a7..fac779c8241 100644 --- a/client/go/internal/cli/cmd/deploy.go +++ b/client/go/internal/cli/cmd/deploy.go @@ -5,6 +5,7 @@ package cmd import ( + "errors" "fmt" "io" "log" @@ -85,6 +86,12 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, return err }) if err != nil { + if target.IsCloud() && errors.Is(err, vespa.ErrUnauthorized) { + return errHint(err, + "You do not have access to the tenant "+color.CyanString(target.Deployment().Application.Tenant), + "You may need to create the tenant at "+color.CyanString(target.Deployment().System.ConsoleURL+"/tenant"), + "If the tenant already exists you may need to run 'vespa auth login' to gain access to it") + } return err } log.Println() diff --git a/client/go/internal/cli/cmd/deploy_test.go b/client/go/internal/cli/cmd/deploy_test.go index 6becef5b11a..3f71a59e682 100644 --- a/client/go/internal/cli/cmd/deploy_test.go +++ b/client/go/internal/cli/cmd/deploy_test.go @@ -120,6 +120,29 @@ func TestDeployCloudFastWait(t *testing.T) { assert.True(t, httpClient.Consumed()) } +func TestDeployCloudUnauthorized(t *testing.T) { + pkgDir := filepath.Join(t.TempDir(), "app") + createApplication(t, pkgDir, false, false) + + cli, _, stderr := newTestCLI(t, "CI=true") + httpClient := &mock.HTTPClient{} + cli.httpClient = httpClient + + app := vespa.ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"} + assert.Nil(t, cli.Run("config", "set", "application", app.String())) + assert.Nil(t, cli.Run("config", "set", "target", "cloud")) + assert.Nil(t, cli.Run("auth", "api-key")) + assert.Nil(t, cli.Run("auth", "cert", pkgDir)) + httpClient.NextResponseString(403, "bugger off") + require.NotNil(t, cli.Run("deploy", pkgDir)) + assert.Equal(t, `Error: deployment failed: unauthorized (status 403) +bugger off +Hint: You do not have access to the tenant t1 +Hint: You may need to create the tenant at https://console.vespa-cloud.com/tenant +Hint: If the tenant already exists you may need to run 'vespa auth login' to gain access to it +`, stderr.String()) +} + func TestDeployWait(t *testing.T) { cli, stdout, _ := newTestCLI(t) client := &mock.HTTPClient{} @@ -229,13 +252,13 @@ func TestDeployIncludesExpectedFiles(t *testing.T) { } func TestDeployApplicationPackageErrorWithUnexpectedNonJson(t *testing.T) { - assertApplicationPackageError(t, "deploy", 401, + assertApplicationPackageError(t, "deploy", 400, "Raw text error", "Raw text error") } func TestDeployApplicationPackageErrorWithUnexpectedJson(t *testing.T) { - assertApplicationPackageError(t, "deploy", 401, + assertApplicationPackageError(t, "deploy", 400, `{ "some-unexpected-json": "Invalid XML, error in services.xml: element \"nosuch\" not allowed here" }`, @@ -347,7 +370,7 @@ func assertApplicationPackageError(t *testing.T, cmd string, status int, expecte args = append(args, "testdata/applications/withTarget/target/application.zip") assert.NotNil(t, cli.Run(args...)) assert.Equal(t, - "Error: invalid application package (Status "+strconv.Itoa(status)+")\n"+expectedMessage+"\n", + "Error: invalid application package (status "+strconv.Itoa(status)+")\n"+expectedMessage+"\n", stderr.String()) } @@ -359,6 +382,6 @@ func assertDeployServerError(t *testing.T, status int, errorMessage string) { cli.httpClient = client assert.NotNil(t, cli.Run("deploy", "--wait=0", "testdata/applications/withTarget/target/application.zip")) assert.Equal(t, - "Error: error from deploy API at 127.0.0.1:19071 (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", + "Error: error from deploy API at 127.0.0.1:19071 (status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", stderr.String()) } diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go index ca03c82f6d9..2c96b8b0935 100644 --- a/client/go/internal/vespa/deploy.go +++ b/client/go/internal/vespa/deploy.go @@ -7,6 +7,7 @@ package vespa import ( "bytes" "encoding/json" + "errors" "fmt" "io" "mime/multipart" @@ -27,6 +28,7 @@ var ( DefaultApplication = ApplicationID{Tenant: "default", Application: "application", Instance: "default"} DefaultZone = ZoneID{Environment: "prod", Region: "default"} DefaultDeployment = Deployment{Application: DefaultApplication, Zone: DefaultZone} + ErrUnauthorized = errors.New("unauthorized") ) type ApplicationID struct { @@ -538,10 +540,12 @@ func uploadApplicationPackage(url *url.URL, opts DeploymentOptions) (PrepareResu } func checkResponse(req *http.Request, response *http.Response) error { - if response.StatusCode/100 == 4 { - return fmt.Errorf("invalid application package (%s)\n%s", response.Status, extractError(response.Body)) + if response.StatusCode == 401 || response.StatusCode == 403 { + return fmt.Errorf("deployment failed: %w (status %d)\n%s", ErrUnauthorized, response.StatusCode, ioutil.ReaderToJSON(response.Body)) + } else if response.StatusCode/100 == 4 { + return fmt.Errorf("invalid application package (status %d)\n%s", response.StatusCode, extractError(response.Body)) } else if response.StatusCode != 200 { - return fmt.Errorf("error from deploy API at %s (%s):\n%s", req.URL.Host, response.Status, ioutil.ReaderToJSON(response.Body)) + return fmt.Errorf("error from deploy API at %s (status %d):\n%s", req.URL.Host, response.StatusCode, ioutil.ReaderToJSON(response.Body)) } return nil } -- cgit v1.2.3