aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2024-06-20 10:42:29 +0200
committerMartin Polden <mpolden@mpolden.no>2024-06-20 10:43:31 +0200
commit729585f66ff4c778c22a86c88d0230f0a7641b02 (patch)
treebbaa18e3c20d0d09a63241fd2cf172079dd76611
parent9d6acbdd52dd4f5e044c706ee2f8077c08bf5a5d (diff)
Print hints if deploy is unauthorized
-rw-r--r--client/go/internal/cli/cmd/deploy.go7
-rw-r--r--client/go/internal/cli/cmd/deploy_test.go31
-rw-r--r--client/go/internal/vespa/deploy.go10
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
}